Skip to content

6.x remove league/container - #19559

Open
LordSimal wants to merge 8 commits into
6.xfrom
6.x-remove-league-container
Open

6.x remove league/container#19559
LordSimal wants to merge 8 commits into
6.xfrom
6.x-remove-league-container

Conversation

@LordSimal

Copy link
Copy Markdown
Contributor

Refs: #18090

This is the final step in our process of moving away from league/container.

I tried to move everything container specific to the container package and still keep the things in core which made sense to keep there.

One thing that came to my mind is the fact, that for now we force the $container = new Container(); in our BaseApplication class. Should we allow users to define their own container implementation to be used and just fallback to ours? Or is it expected that users just overwrite that buildContainer() method if they want to do that?

@LordSimal LordSimal added this to the 6.0 milestone Jul 21, 2026
use Psr\Container\ContainerInterface as PsrContainerInterface;

interface DefinitionContainerInterface extends ContainerInterface
interface ContainerInterface extends PsrContainerInterface

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wanted to make this as straight forward as possible, so i renamed this interface

@ADmad

ADmad commented Jul 22, 2026

Copy link
Copy Markdown
Member

Or is it expected that users just overwrite that buildContainer() method if they want to do that?

buildContainer() uses the ContainerFactory to get the container instance. The latter uses the App.container config, so we could just update the factory to allow setting a FQCN for App.container.

@ADmad

ADmad commented Jul 22, 2026

Copy link
Copy Markdown
Member

Hah... The PR deletes the ContainerFactory, I would keep it and implement what I suggested above :)

@LordSimal

Copy link
Copy Markdown
Contributor Author

ContainerFactory logic + config to set a custom class has been restored.

@LordSimal
LordSimal force-pushed the 6.x-remove-league-container branch 2 times, most recently from baeabad to ccb1490 Compare July 22, 2026 10:36
@LordSimal
LordSimal marked this pull request as ready for review August 2, 2026 18:34
CakePHP has carried two DI container implementations since 5.4.0:
league/container (the default) and Cake\Container\Container (opt-in
via Configure::read('App.container') === 'cake'), bridged together by
CakeContainerBridge/CakeDefinitionBridge/CakeInflectorBridge so the
'cake' option could satisfy Cake\Core\ContainerInterface, which itself
extended League\Container\DefinitionContainerInterface. With 6.0 free
to break compatibility, this collapses to a single implementation.

- Cake\Core\ContainerInterface now extends
  Cake\Container\DefinitionContainerInterface instead of League's.
- Cake\Core\Container now extends Cake\Container\Container directly
  (adds an explicit delegate() override since the interface narrows
  its return type and the parent method declares none).
- Cake\Core\ServiceProvider now extends
  Cake\Container\ServiceProvider\AbstractServiceProvider /
  BootableServiceProviderInterface.
- ContainerFactory and the three Cake*Bridge classes are deleted -
  there's no longer a choice to factor, so BaseApplication::
  buildContainer() constructs Container directly.
- ComponentRegistry drops League's ArgumentReflectorTrait/
  ArgumentResolverTrait/LiteralArgument/ResolvableArgument/
  ReflectionContainer for the Cake\Container equivalents, and its
  getMode()/ATTRIBUTE_RESOLUTION shim goes away since Cake\Container
  has no mode-flag concept (auto-wiring is always on).
- league/container dropped from composer.json (root and
  cakephp/core); cakephp/core now requires cakephp/container instead.

Ported the one real feature gap: Cake\Container had no equivalent of
League's attribute-based argument resolution, which
Cake\Core\Attribute\Configure depends on for #[Configure] constructor
injection. Added Cake\Container\Attribute\AttributeInterface and wired
attribute resolution into ArgumentResolverTrait::reflectArguments(),
mirroring League's ArgumentReflectorTrait::resolveArgumentFromAttribute().

Cake\Container\Container enables auto-wiring by default (unlike
League, where it was opt-in), which changes has() to return true for
any autoloadable class rather than only explicitly-registered
services. This broke three call sites that used has() to mean
"explicitly registered": ControllerFactory::create() (controller
construction), ControllerFactory::getActionArgs() (action parameter
DI), and Console\CommandFactory::create() - all switched to
hasDefinition(), which checks only explicit definitions. Also updated
TableContainer's docblock and its tests to call disableAutoWiring()
first, since it's meant to be checked before auto-wiring resolves a
class by bare reflection.

Full test suite passes (10,183 tests); only a pre-existing,
environment-dependent stdin-availability test is unrelated and
unaffected.
The previous commit kept a thin Cake\Core\Container subclass and a
Cake\Core\ContainerInterface that added a delegate() requirement on
top of Cake\Container\DefinitionContainerInterface, purely so the
concrete class had something to implement. With only one container
implementation left, that layer added nothing.

- delegate() moved into Cake\Container\DefinitionContainerInterface
  itself (Cake\Container\Container already had a concrete delegate()
  method, just undeclared in its interface and missing a return type).
- Cake\Core\Container and Cake\Core\ContainerInterface are deleted.
  Every services(ContainerInterface $container)/getContainer(): ... /
  bootstrap(ContainerInterface $container) signature across
  ContainerApplicationInterface, PluginInterface, BasePlugin,
  ServiceProvider, ComponentRegistry, ControllerFactory,
  BaseApplication, MiddlewareQueue, CommandFactory,
  EventListenerRegistrationTrait, and ContainerStubTrait now types
  against Cake\Container\DefinitionContainerInterface directly, and
  BaseApplication/WebExceptionRenderer instantiate Cake\Container\
  Container directly instead of a Core-level wrapper.
- ServiceProvider::getContainer() no longer needs its override - it
  was only asserting the container satisfied Cake\Core\
  ContainerInterface, which no longer exists.

Full test suite passes (10,183 tests, same single pre-existing
stdin-availability failure as before); phpcs and phpstan show the
same pre-existing findings as the unmodified baseline on every
touched file.
Now that Cake\Core\ContainerInterface is gone, "DefinitionContainerInterface"
read as an oddly-qualified name for what is simply the container's public
interface. Renamed to Cake\Container\ContainerInterface across the package
and every consumer (Core, Http, Controller, Console, Event, and their tests).

This introduces a name collision with Psr\Container\ContainerInterface in
the three files within the Container package that reference both: the
interface's own file, Container.php, and ArgumentResolverTrait.php. Each
now aliases the PSR import as `PsrContainerInterface` and keeps bare
`ContainerInterface` referring to the local (same-namespace) interface -
except where a method genuinely operates on any PSR-11 container rather
than Cake's own (delegate()'s parameter, defaultToShared()'s return type,
and the has()/get() check in ArgumentResolverTrait::resolveArguments()
that must also match a bare ReflectionContainer, which only implements
the PSR interface), which explicitly use PsrContainerInterface instead.

Full test suite passes (10,183 tests, same pre-existing stdin-availability
failure as before); phpcs and phpstan show identical findings to the
pre-rename baseline on every touched file.
ServiceProvider only ever depended on Cake\Container classes
(AbstractServiceProvider, BootableServiceProviderInterface,
ContainerInterface) - it's a thin CakePHP-flavored convenience layer
over the container package's own service provider base class, not
something that belongs to Core. Moved to Cake\Container\ServiceProvider
alongside it, dropping the now-redundant ContainerInterface import
since it's the same namespace.

Test file and the two TestApp service provider fixtures moved/updated
to match. No composer.json changes needed - cakephp/core already
requires cakephp/container for its other Container-typed interfaces
(ContainerApplicationInterface, PluginInterface, BasePlugin), and
cakephp/container's PSR-4 autoload already covers the new file.

Full test suite passes (10,183 tests, same pre-existing
stdin-availability failure); phpcs clean, phpstan identical to the
pre-move baseline.
Console (CommandFactory), Event (EventListenerRegistrationTrait), Http
(BaseApplication, MiddlewareQueue), and ORM (TableContainer) all
reference Cake\Container\* classes directly, but none of their
composer.json files declared cakephp/container as a dependency - it
only worked because this monorepo autoloads everything from one flat
src/ regardless of which subpackage composer.json declares what.

Caught by split-package static analysis, which installs each
subpackage's own declared dependencies in isolation: phpstan couldn't
resolve Cake\Container\ContainerInterface at all when analyzing Http
standalone, since cakephp/http never required cakephp/container.
`composer stan` runs phpstan, psalm, and structarmed (an architecture
layer linter) across the whole repo - broader coverage than the
scoped checks used while landing each individual commit in this
series, which is how these slipped through:

- ArgumentResolverInterface::resolveArguments()'s docblock declared
  an overly-narrow param type (array<LiteralArgument|ResolvableArgument>)
  that never matched what the method's own implementation actually
  handles (any ArgumentInterface, checked generically via instanceof).
  Adding attribute-resolution support widened the argument list to
  include LiteralArgumentInterface, which is what finally tripped
  phpstan's stricter cross-class analysis. Corrected the docblock to
  the interface both call sites and the implementation actually use.

- structarmed.php's layer ruleset predates this whole
  league/container removal: it only allowed the `Core` layer to
  depend on `Container`, because previously everything else reached
  the container through Core's own Container/ContainerInterface
  wrapper. Now that Console, Controller, Error, Event, Http, and
  TestSuite depend on Cake\Container directly by design, the ruleset
  needed the same layers added to reflect the now-intentional
  architecture instead of the one it's replacing.

- psalm-baseline.xml had two stale entries for
  ComponentRegistry::getContainer()/getMode() left over from earlier
  cleanup in this series; psalm's own baseline pruning removed them
  since they no longer match current code.

Verified with `source setup/env && composer stan` (phpstan, psalm,
and structarmed all clean, exit 0) and the full test suite (10,183
tests, same single pre-existing stdin-availability failure).
@LordSimal
LordSimal force-pushed the 6.x-remove-league-container branch from ccb1490 to cf85be0 Compare August 2, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants