Skip to content

Remove container ArrayAccess and dynamic service properties - #22

Closed
binaryfire wants to merge 78 commits into
0.4from
remove-container-array-access
Closed

Remove container ArrayAccess and dynamic service properties#22
binaryfire wants to merge 78 commits into
0.4from
remove-container-array-access

Conversation

@binaryfire

@binaryfire binaryfire commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR removes ArrayAccess and dynamic service-property access from Hypervel's container. Container registration, lookup, existence checks, and temporary overrides now use named methods throughout the framework and test suite.

This is an intentional Laravel compatibility break for Hypervel 0.4. Laravel still supports these forms on its concrete container, but they hide behavior that Hypervel should make explicit:

  • $app['service'] performs service resolution.
  • $app['service'] = $value silently selects transient binding behavior.
  • unset($app['service']) removes only part of the container's lifecycle state.
  • $app->service makes a resolved service look like a declared property.

ArrayAccess remains appropriate for map-like objects elsewhere in the framework. The problem is specifically using array and dynamic-property syntax for a dependency injection container.

Container API

The container contract now extends only PSR-11's ContainerInterface, and the concrete container no longer implements ArrayAccess. The four offset methods and the __get / __set service accessors have been removed.

The supported replacements are:

Previous form Named API
$app['events'] or $app->events $app->make('events')
isset($app['events']) $app->bound('events')
PSR-11 lookup and existence checks $container->get() and $container->has()
$app['service'] = fn ($app) => ... $app->bind('service', fn ($app) => ...)
$app['service'] = $service $app->instance('service', $service)
Remove a temporary instance override $app->forgetInstance('service')

No compatibility shim or custom error layer is included. Unsupported access fails at the call site through native PHP behavior.

This PR also does not add arbitrary binding removal. The old offset-unset operation cleared selected binding, resolution, instance, scoped, and alias state while leaving other related container state behind. forgetInstance() is the correct operation for temporary overrides because it removes the override and allows the original registration and lifecycle to resolve again.

Framework migration

All active framework and test consumers now use named APIs. Each conversion keeps the intended lifetime explicit:

  • Service reads use make().
  • Binding checks use bound(), or has() for deliberately PSR-11-shaped callers.
  • Fixed objects and scalar values use instance().
  • Factories that must produce a fresh value use bind().
  • Temporary instance overrides use forgetInstance().

The facade application slot is now typed as the nullable Hypervel container contract instead of accepting an untyped ArrayAccess value. Facade roots resolve through make(), while the existing fail-fast behavior when no application is configured is preserved.

Lifecycle and configuration fixes

The migration exposed several places where the old syntax obscured the real behavior:

  • Re-enabling middleware in tests now forgets only the temporary fake instance and restores the original binding.
  • Temporary console output mocks are installed and removed as instances without deleting their underlying lifecycle registration.
  • The detected application environment is registered as the shared env instance instead of an implicit transient binding.
  • The concurrency manager now uses concurrency.default exclusively for the selected driver. It no longer reads or writes a legacy scalar concurrency.driver value that conflicts with the per-driver configuration map.
  • Sentry's guaranteed package configuration is resolved through the typed array getter, so invalid or missing configuration fails at the configuration boundary.
  • View path loading keeps its existing tolerant behavior for missing or non-array view.paths values.
  • Filesystem disk configuration keeps its existing tolerant behavior for optional nested disk settings.

Documentation and porting

The container package README and framework documentation now describe the named-only API and the intentional difference from Laravel. The Laravel porting guide includes concise conversion mappings and keeps the existing lifecycle guidance as the source of truth.

The contribution guide now treats container ArrayAccess and dynamic service properties as unsupported porting surfaces. It also clarifies that code should not duplicate defaults already supplied by framework or package configuration, so missing and misspelled keys fail loudly.

Verification

The implementation was verified with focused container, facade, foundation testing, concurrency, command, queue, Sentry, and Testbench coverage. The full formatter, static analysis, parallel framework suite, Testbench package suite, and dogfood package checks pass. The latest 0.4 request-binding coverage also passes after the branch update.

Define a provider-owned configuration refresh lifecycle for replacement event and task workers. The plan preserves retained service identities, resets lazily rebuilt manager state, and keeps per-worker startup work after configuration is current.

Specify the injectable ServerReloader API, Laravel-style reset and mutation methods, derived configuration replay, restart-owned topology, documentation updates, and complete verification strategy.

Also capture the Swoole cache-table sealing correction so newly configured tables fail explicitly after fork instead of creating private state in each worker.
Add a small provider lifecycle contract for refreshing configuration-derived worker state after dotenv and configuration are rebuilt.

Keep refresh ownership with each provider and require synchronous completion so invalid replacement-worker configuration fails before the worker becomes ready.
Canonicalize container aliases before clearing singleton, scoped, and auto-singleton caches. Forgetting a service through any public alias now removes the same cached object that normal resolution returns.

Add regression coverage proving the next resolution builds a fresh canonical instance.
Introduce an injectable ServerReloader that validates the configured PID and signals event workers plus configured task workers with explicit failures.

Reduce server:reload to a console adapter over the service. This gives applications one strict reload implementation without adding a facade, alias, retry loop, or readiness protocol.
Add a fluent forgetInstances operation to the shared named-instance manager while preserving registered creators and other worker configuration.

Use it from Concurrency and Rate Limiter configuration hooks, with coverage for cache replacement, creator preservation, and facade metadata.
Clear resolved guards and password brokers after worker configuration changes while retaining their manager construction rules and registered callbacks.

Keep auth cache validation in the later worker-start phase, after cache providers have finished refreshing, and cover both provider hooks and broker reset behavior.
Clear resolved broadcast connections and the cached default broadcaster when replacement workers load new configuration.

Preserve the manager and its registered drivers while proving unresolved services are not constructed during refresh.
Forget both the batch repository contract and its separately cached database implementation so refreshed batching configuration cannot leave either container path stale.

Cover both cache keys and avoid replacing the dispatcher or other bus registrations.
Add a fluent cache-driver reset that preserves custom creators and the shared serialization policy, then use it when worker configuration changes.

Seal Swoole cache tables after pre-fork initialization. Existing tables remain shared across reloads, while a newly configured table fails clearly instead of being created privately inside one worker.
Forget the database resolver contract and its auto-singletoned concrete after replacement workers load new configuration.

Keep connection cleanup in its existing pre-refresh lifecycle and verify the next resolver observes the refreshed default connection.
Clear cached hash drivers and rebuild the encrypter after worker configuration changes. Reset the Serializable Closure secret alongside the encrypter so both consumers use the same refreshed key.

Add coverage for refreshed algorithms, keys, and lazily rebuilt service instances without adding request-path work.
Add a fluent disk reset that clears resolved adapters while preserving custom filesystem creators.

Refresh both named disks and the cached default disk in replacement workers, with coverage for lazy reconstruction and facade metadata.
Record Fortify, Horizon, and provider-owned derived configuration as operations that are evaluated again after replacement workers rebuild their environment.

Keep provider lists and gRPC server definitions as explicit master snapshots because those values describe topology already installed before workers are forked.
Add a fluent channel reset that preserves custom creators and shared logging context.

Make the Log provider own worker configuration refresh, clearing application channels and updating the framework stdout logger in place so infrastructure logs use the latest format and levels.
Clear cached mailers, Markdown rendering state, notification channels, and the retained mail channel after worker configuration reload.

Preserve manager registrations and rebuild only services that copied configuration, with unresolved-service coverage for both providers.
Flush an already resolved PoolManager immediately before Swoole forks workers so no master-created pool resources leak into child processes.

Keep recycler startup on AfterWorkerStart and avoid a second worker-start flush that could close pools created by earlier listeners.
Add a fluent connection reset and make the Queue provider restore the configured background and deferred exception callbacks on refreshed eager connections.

Preserve connectors and manager callbacks, clear the default connection and failed-job provider, and keep eager work limited to connections the provider already initialized.
Update the shared CookieJar in place, clear resolved session drivers, and rebuild the default session store after worker configuration changes.

When the Redirector already exists, point that retained object at the refreshed store so ResponseFactory and middleware references remain valid.
Update the URL generator's fallback request, asset root, and HTTPS policy from replacement-worker configuration without replacing the object retained by routing and response services.

Cover both HTTPS directions, identity preservation, current application URLs, and the new boot-only asset-root mutator.
Clear config-derived Inertia view finding, Permission cache setup, Scout engines and clients, and Socialite drivers only when their owning managers were already in use.

Preserve custom creators and request-scoped state, and avoid constructing optional services merely to refresh them.
Clear resolved Reverb application drivers and the webhook batch buffer when replacement workers load new configuration.

Keep the WebSocket listener definition as an explicit pre-fork snapshot because ports, callbacks, and server topology require a full restart.
Make JWT claim and manager configuration reloadable, clear stale parsers and blacklist instances, and rebuild enabled blacklist validation from current worker settings.

Preserve custom JWT creators while covering refresh order, issuer and subject settings, blacklist enablement, validation resets, and facade metadata.
Recompute derived Sentry log-channel defaults from replacement-worker configuration and extract one client construction path for bootstrap and reload.

Bind a fresh client onto the retained framework Hub, preserve the global SDK Hub and Telescope's dump handler wrapper, and clear backtrace configuration that depends on the old client.
Add boot-time setters for database connection and chunk size, then update each repository contract view that Telescope and its watchers retain.

Avoid constructing a separate concrete repository during refresh and keep every retained contract pointing at the same updated storage object.
Separate the worker base locale from coroutine-local overrides and validate base, request, and fallback locales through one path.

Record addLines operations in call order, replay them after fresh loader results, and clear only loaded groups during worker refresh. This preserves package registrations without suppressing new language files or changing parent-child overwrite semantics.
Update the existing View Factory's finder paths and Blade compiler settings in place so engines, directives, namespaces, components, and decorators keep their identities.

Clear only stale lookup and compile-check caches, document boot-only finder mutations, and cover refreshed behavior through retained factory, finder, compiler, resolver, and engine objects.
Rebuild dotenv and configuration, replay tracked mutations, and invoke registered configuration-reload providers in their existing order before replacement workers become ready. Hooks remain synchronous and fail fast.

Move stdout refresh into its provider, update Foundation-owned timezone, maintenance, and dump-source state, and retain installed dumper and Telescope wrapper identities. Separate editor-link behavior from dump-source state so exception frames no longer inherit unrelated worker-global APIs.

Add lifecycle ordering, retained-logger composition, invalid-config, dumper-format, maintenance, static-cleanup, and provider filtering coverage. Declare Foundation's direct POSIX signal dependency.
Document programmatic and command-driven reloads, provider-owned ReloadsConfiguration hooks, and the difference between worker configuration refresh and pre-fork server topology.

Explain restart-only changes, Swoole table behavior, invalid replacement-worker recovery, and separate process lifecycles in Laravel-style prose. Remove the completed framework reload item from the todo list.
Capture the final ServerReloader and provider-owned worker configuration refresh design, including verified lifecycle facts, object identity rules, provider behavior, restart boundaries, and test requirements.

Record the anti-overengineering constraints and completed corrections so future maintenance can preserve the intended worker-start boundary without rebuilding speculative orchestration machinery.
Clear temporary middleware instances with forgetInstance() when middleware is re-enabled. This restores any original binding and lifecycle instead of deleting the registration through container offset unsetting.

Add coverage for a middleware binding that cannot be reconstructed without its original factory, while retaining the existing global and formerly-unbound cases.
Resolve auth, session, database, event, and console services through explicit container methods in the foundation testing concerns. Register fixed test doubles as instances so their intended shared lifetime is clear.

Update the matching authentication and database truncation coverage to use the same named registration and resolution surface.
Resolve configuration, connection, migration, filesystem, and event services through explicit container methods across the database manager, provider, migrator, and console commands.

Migrate database integration setup to explicit instance and make calls, preserving fixed-value lifetimes and existing driver coverage while removing reliance on container offset syntax.
Resolve queue, cache, event, and command services through make() and express queue test fixtures with explicit instance lifetimes. This removes implicit resolution and registration behavior from queue managers, sync dispatch, and worker commands.

Clear one-time payload fixtures with forgetInstance() so the tests continue to exercise stale callback behavior without depending on destructive offset unsetting.
Replace the facade layer's ArrayAccess-shaped application slot with the nullable Hypervel container contract. Resolve facade roots through make() and use PSR has()/get() only where the caller is intentionally contract-shaped.

Keep existing fail-fast behavior at unset application boundaries, retain tolerant nested filesystem configuration, and update the facade test containers to count real make() resolutions.
Resolve service-provider configuration through make() while preserving the existing tolerant behavior for missing or non-array view paths. Replace support test setup and maintenance-mode config access with explicit instance registration and repository resolution.

This keeps configuration semantics unchanged while removing the support package's dependency on container offsets and dynamic service properties.
Resolve vendor-link, event, filesystem, and cloned-application services through make(), removing the unreachable fallback for the guaranteed vendor symlink flag.

Convert Testbench fixtures and application setup to explicit instance registration and named resolution. Clear one-time payload values with forgetInstance() so cleanup restores the container lifecycle without deleting registrations.
Resolve cache and view services through make() in the shared testing concerns. Register PendingCommand's console output mock as the exact temporary instance and remove only that instance during cleanup.

Update the matching utility and parallel-test coverage to use explicit binding checks, resolutions, and fixed-value registrations.
Resolve validator and application path services through the command application's make() method. Update console integration setup to register fixed dependencies explicitly and resolve services through named methods.

The command lifecycle remains unchanged while the console package no longer depends on array-shaped application access.
Resolve the event dispatcher through the command application's make() method and register cache test doubles through explicit instances.

Update Redis cache lock and funnel coverage to use named container resolution without changing the tested locking or throttling behavior.
Resolve event dispatchers through make() in the context provider and log manager. Convert logging and queued-context tests to explicit service resolution and fixed-value instance registration.

This preserves logger construction, event handling, and context propagation while removing implicit container offset behavior.
Resolve the configuration repository through make() and read Sentry's guaranteed package config with the typed array getter. Invalid or missing root configuration now fails at the configuration boundary instead of being hidden by an empty fallback.

Update Sentry providers, integrations, and feature tests to register application services explicitly and use named resolution throughout.
Use the container's make() method when the filesystem manager resolves the URL generator. This preserves lazy URL generation while removing the manager's last dependency on container offset access.
Check optional validation dependencies with bound() and resolve translator and presence-verifier services through make(). This keeps the provider's conditional behavior intact without relying on offset existence or reads.
Replace foundation test setup, service reads, and fixed application values with explicit bind(), instance(), make(), and bound() calls. Add native void return types to the touched test methods while retaining their existing bootstrap, console, helper, Vite, and static-state assertions.

These conversions make each test's intended container lifecycle visible before array access is removed.
Migrate route caching, exception handling, provider registration, and health-route fixtures to explicit container registration and resolution. Fixed objects and flags are installed with instance(), while services under test are resolved through make().

Keep the integration behavior and failure assertions unchanged while removing implicit array-shaped application access.
Register environment values, configuration repositories, and command dependencies through explicit container instances in the key-generation and source-generator suites. Resolve application services with make() and add native void return types to touched tests.

The generated output and command behavior remain the same; only the container interaction is made explicit.
Convert authentication, Fortify, and Inertia test setup from container offsets to explicit instance registration and named resolution. This makes fixed request and service fixtures shared by intent while preserving the existing authentication and component assertions.

Add native void return types to the test methods touched by the migration.
Register request, middleware, route, and URL dependencies explicitly and resolve application services through make() across the HTTP middleware and routing integration suites.

Preserve request-forgery, CORS, compiled-route, binding, precognition, and signed-URL behavior while removing implicit container access from their fixtures.
Convert notification, session, translation, and view integration fixtures to explicit application registrations and named service resolution. Fixed config and service values now use instance(), while tested services are obtained through make().

Retain all locale, persistence, rendering, and delivery assertions and add native void return types to the methods touched by the conversion.
Replace Horizon controller and supervisor fixture offsets with explicit instance registration and named application resolution. Memory monitors, clear commands, dashboard statistics, and batch endpoints keep their existing behavior while their service lifetimes become visible in setup.

Add native void return types to the touched Horizon test methods.
Migrate Reverb application setup, event dispatch, protocol handlers, channel fixtures, and server lifecycle tests to explicit bindings, instances, and named resolution.

Preserve the existing websocket, shutdown, and protocol behavior while removing container offset syntax and adding native void return types to the methods touched by the migration.
Register Telescope test services as explicit instances and resolve the Reverb watcher dependencies through named container methods. The feature base and watcher assertions remain unchanged while no longer depending on array-shaped application access.
Drop ArrayAccess from the container contract and implementation, and remove offset and dynamic service-property methods from the concrete container. Named methods now define the complete registration, lookup, existence, and temporary-instance lifecycle surface.

Keep a concise source marker for future Laravel ports, migrate extender coverage to bind() and forgetExtenders(), and consolidate lifecycle assertions around bound() and forgetInstance() without adding an incomplete arbitrary binding-removal API.
Describe the intentional Laravel difference in the container package and Laravel porting guide, including direct mappings for resolution, existence checks, factories, fixed instances, and temporary override cleanup.

Narrow broader compatibility claims in the container and release documentation so they acknowledge deliberate public API omissions without duplicating the detailed migration guidance.
Record container ArrayAccess and dynamic service properties as unsupported porting surfaces, and direct Laravel source and tests toward named container methods.

Clarify that typed configuration reads must not duplicate defaults already defined by framework or package config, so missing and misspelled keys fail at the configuration boundary instead of silently falling back.
Record the final public contract, migration rules, lifecycle decisions, facade typing, configuration cleanup, documentation requirements, and verification gates for the container API change.

Keep the plan as the authoritative design reference for the completed refactor and future review of intentional Laravel differences.
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown

Too many files changed for review (412 files, 300 file limit).

Bypass the limit by tagging @greptile-apps to review.

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c53e427-0755-4777-9952-55f5d111bf9b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@binaryfire binaryfire closed this Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant