[RFC] Removal of container-interop from laminas-servicemanager

Background

Currently, laminas-servicemanager depends on container-interop/container-interop. This is because we were part of the working group for PSR-11, and chose to adapt our code to it before the specification stabilized and was accepted. Once it was, container-interop modified its interfaces such that they now extend the PSR-11 interfaces, and thus can be used anywhere PSR-11 is.

However, ultimately this means that we cannot update directly to PSR-11 without a backwards compatilibity break, as a number of our interfaces and implementations directly typehint against the container-interop interfaces still, and doing so would break userland implementations and extensions.

Plan for removal

We will create a new package, something along the line of “laminas/laminas-container-contracts”, which would ship the various factory interfaces (and some utility classes implementing them) currently in laminas-servicemanager, but targeting PSR-11 instead. These include the following interfaces:

  • Laminas\ServiceManager\Factory\AbstractFactoryInterface
  • Laminas\ServiceManager\Factory\DelegatorFactoryInterface
  • Laminas\ServiceManager\Factory\FactoryInterface
  • Laminas\ServiceManager\Initializer\InitializerInterface (this one is up for debate, as we are largely directing users towards delegators instead)

and the following implementations:

  • Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory (uses reflection to create an instance of the class requested).
  • Laminas\ServiceManager\Factory\InvokableFactory (creates an instance of a class using new and no constructor arguments)

laminas-servicemanager would be updated such that the ServiceManager could consume both implementations of its own interfaces, as well as those from the new package. This allows users and other packages to adopt the PSR-11 variants immediately in order to remain forwards-compatible with version 4. Additionally, since such checks generally happen during configuration, and not runtime, they should have little to any performance impact.

Existing interfaces and implementations with equivalents in the new package would be marked @deprecated.

For implementations we provide within laminas-servicemanager, if the class is marked final, we would update it to use the PSR-11 interfaces; this affects the ConfigAbstractFactory and InvokableFactory only at this time. Otherwise, in the documentation, we would point users to the new interfaces package.

Finally, we will provide laminas-cli commands to allow users to:

  • Identify implementations of the legacy interfaces in their own code.
  • Identify usage of legacy implementations in configuration, and update them.
  • Potentially update legacy implementations to implement the new interfaces (this might not be viable, however).

In version 4, we would remove the legacy definitions and implementations entirely, and update the ServiceManager to only typehint against the new versions.

Packages implementing or consuming laminas-servicemanager interfaces

When it comes to packages that implement or consume laminas-servicemanager interfaces, modifying non-final classes that implement the current interfaces would clearly be a backwards-compatibility break. As such, the plan would be:

  • Deprecate the existing implementations.
  • Ship parallel implementations targeting the new interfaces.
  • Document the new implementations.
  • If the package provides autowiring via a ConfigProvider or Module class, only reference the new implementations.

Since user extensions would have to be wired manually anyways, such extensions would continue to work.

Components with implementations

What follows is a list of components that have implementations of laminas-servicemanager interfaces. With each, I have listed whether or not autowiring is present in the package, and then each implementation, including information on:

  • its purpose
  • what it implements
  • whether or not it is autoregistered

In the majority of cases, we can do the following:

  • Add a dependency on the new contracts package.
  • Create parallel implementations targeting the contracts package.
  • Deprecate the original implementations.
  • Use the new implementations in any autowiring present.
  • Make laminas-servicemanager and/or container-interop suggested packages instead of require packages.
  • Document the new classes, and note that users of the legacy implementations will need to migrate to the new implementations (either by changing configuration or changing inheritance trees).

Affected Components

List of affected components (click to expand)
  • laminas/laminas-cache
    • Package DOES have both a ConfigProvider and a Module class
    • PatternPluginManagerFactory (create pattern plugin instances; FactoryInterface implementation; registered)
    • StorageAdapterPluginManagerFactory (create adapter plugin manager instance; FactoryInterface implementation; registered)
    • StorageCacheAbstractServiceFactory (create named StorageInterface instances; AbstractFactoryInterface implementation; registered)
    • StorageCacheFactory (create default StorageInterface instance; FactoryInterface implementation; NOT registered)
    • StoragePluginManagerFactory (return PluginManager instance; FactoryInterface implementation; registered)
  • laminas/laminas-config
    • Package DOES NOT have a ConfigProvider or Module class
    • AbstractConfigFactory (pluck config segments from the config service; AbstractFactoryInterface implementation)
  • laminas/laminas-db
    • Package DOES have both a ConfigProvider and a Module class
    • AdapterAbstractServiceFactory (provide named DB adapters; AbstractFactoryInterface implementation; registered)
    • AdapterServiceFactory (provide default DB adapter; FactoryInterface implementation; registered)
  • laminas/laminas-di
    • Package DOES have both a ConfigProvider and a Module class
    • AutowireFactory (DI autowiring; AbstractFactoryInterface implementation; registered)
  • laminas/laminas-filter
    • Package DOES have both a ConfigProvider and a Module class
    • FilterPluginManagerFactory (creates FilterPluginManager instance; FactoryInterface implementation; registered)
    • SeparatorToSeparatorFactory (creates SeparatorToSeparator instances based on $options provided; FactoryInterface implementation; registered in FilterPluginManager instance)
  • laminas/laminas-form
    • Package DOES have both a ConfigProvider and a Module class
    • AnnotationBuilderFactory (create and return AnnotationBuilder instance; FactoryInterface implementation; registered)
    • ElementFactory (create and return ElementInterface implementations, using $options; FactoryInterface implementation; registered by FormElementManager; IS FINAL)
    • FormAbstractServiceFactory (create and return FormInterface instances; AbstractFactoryInterface implementation; registered)
    • FormElementManagerFactory (create and return FormElementManager instance; FactoryInterface implementation; registered)
  • laminas/laminas-i18n
    • Package DOES have both a ConfigProvider and a Module class
    • LoaderPluginManagerFactory (create and return LoaderPluginManager instance; FactoryInterface implemenation; registered)
    • TranslatorServiceFactory (create and return Translator instance; FactoryInterface implementation; registered)
  • laminas/laminas-inputfilter
    • Package DOES have both a ConfigProvider and a Module class
    • InputFilterAbstractServiceFactory (produces named InputFilterInterface instances; AbstractFactoryInterface implementation; registered with InputFilterPluginManager and in laminas-api-tools/api-tools-content-validation)
    • InputFilterPluginManagerFactory (produces InputFilterPluginManager instance; FactoryInterface implementation; registered)
  • laminas/laminas-log
    • Package DOES have both a ConfigProvider and a Module class
    • FilterPluginManagerFactory (produces FilterPluginManager instance; FactoryInterface implementation; registered)
    • FormatterPluginManagerFactory (produces FormatterPluginManager instance; FactoryInterface implementation; registered)
    • LoggerAbstractServiceFactory (produces named Logger instances; AbstractFactoryInterface implementation; registered)
    • LoggerServiceFactory (produces default Logger instance; FactoryInterface implementation; registered)
    • ProcessorPluginManagerFactory (produces ProcessorPluginManager instance; FactoryInterface implementation; registered)
    • WriterFactory (produces WriterInterface instances using provided name and options; FactoryInterface implementation; registered in WriterPluginManager)
    • WriterPluginManagerFactory (produces WriterPluginManager instance; FactoryInterface implementation; registered)
  • laminas/laminas-mail
    • Package DOES have both a ConfigProvider and a Module class
    • SmtpPluginManagerFactory (produces SmtpPluginManager instance; FactoryInterface implementation; registered)
  • laminas/laminas-mvc
    • Package DOES NOT have either a ConfigProvider or a Module class (but there are some proposals to add them in a development version)
    • LazyControllerAbstractFactory (reflection-based factory for producing controllers; AbstractFactoryInterface implementation; NOT registered anywhere — completely optional)
    • ForwardFactory (produces a Forward plugin instance; FactoryInterface implementation; registered in ControllerManager)
    • AbstractPluginManagerFactory (abstract class for plugin manager factories; FactoryInterface implementation, providing the __invoke() definition).
    • ApplicationFactory (produces an Application instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ConfigFactory (produces the config service by retrieving the ModuleManager, loading modules, retrieving the configuration listener, and pulling the config it aggregates; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ControllerManagerFactory (produces a ControllerManager instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • DispatchListenerFactory (produces a DispatchListener instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • EventManagerFactory (produces an EventManager instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • HttpDefaultRenderingStrategyFactory (produces a View\Http\DefaultRenderingStrategy instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • HttpExceptionStrategyFactory (produces an Http\ExceptionStrategy instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • HttpMethodListenerFactory (produces an HttpMethodListener instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • HttpRouteNotFoundStrategyFactory (produces a View\Http\RouteNotFoundStrategy instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • HttpViewManagerFactory (produces a View\Http\ViewManager instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • InjectTemplateListenerFactory (produces a View\Http\InjectTemplateListener instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ModuleManagerFactory (produces a ModuleManager instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • RequestFactory (produces a Request instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ResponseFactory (produces a Response instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ServiceListenerFactory (produces a ServiceListener instance; FactoryInterface implementation; registered in skeleton)
    • ViewFactory (produces a View instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ViewFeedStrategyFactory (produces a ViewFeedStrategy instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ViewJsonStrategyFactory (produces a ViewJsonStrategy instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ViewManagerFactory (produces a ViewManager instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ViewPhpRendererFactory (produces a ViewPhpRenderer instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ViewPhpRendererStrategyFactory (produces a ViewPhpRendererStrategy instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ViewPrefixPathStackResolverFactory (produces a ViewPrefixPathStackResolver instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ViewResolverFactory (produces a ViewResolver instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ViewTemplateMapResolverFactory (produces a ViewTemplateMapResolver instance; FactoryInterface implementation; registered in ServiceListenerFactory)
    • ViewTemplatePathStackResolverFactory (produces a ViewTemplatePathStackResolver instance; FactoryInterface implementation; registered in ServiceListenerFactory)
  • laminas/laminas-mvc-console
    • Package DOES have both a ConfigProvider and a Module class
    • ConsoleRouterDelegatorFactory (substitutes console router for HTTP router if console env detected; DelegatorFactoryInterface implementation; registered)
    • ConsoleRouterFactory (produces console router instance; FactoryInterface implementation; registered)
    • SimpleRouteStack (consumes laminas-router RouteInvokableFactory, RoutePluginManager, SimpleRouteStack)
    • ConsoleAdapterFactory (produces console instance; FactoryInterface implementation; registered)
    • ConsoleApplicationDelegatorFactory (attaches an alternate view renderer to the laminas-mvc Application instance; DelegatorFactoryInterface implementation; NOT registered, and, in fact, deprecated)
    • ConsoleExceptionStrategyFactory (produces a console ExceptionStrategy; FactoryInterface implementation, registered)
    • ConsoleRequestDelegatorFactory (substitutes console request for HTTP request if console env detected; DelegatorFactoryInterface implementation; registered)
    • ConsoleResponseDelegatorFactory (substitutes console response for HTTP response if console env detected; DelegatorFactoryInterface implementation; registered)
    • ConsoleResponseSenderDelegatorFactory (injects console response sender in send response listener; DelegatorFactoryInterface implementation; registered)
    • ConsoleRouteNotFoundStrategyFactory (produces RouteNotFoundStrategy instance; FactoryInterface implementation; registered)
    • ConsoleViewHelperManagerDelegatorFactory (injects alternative url, basepath, and server url plugins in view helper manager if console env detected; DelegatorFactoryInterface implementation; registered)
    • ConsoleViewManagerFactory (produces ConsoleViewManager instance; FactoryInterface implementation; registered)
    • ControllerManagerDelegatorFactory (injects initializer into ControllerManager for injecting console instance into controllers; DelegatorFactoryInterface implementation; registered)
    • DefaultRenderingStrategyFactory (produces DefaultRenderingStrategy instance; FactoryInterface implementation; registered)
    • ViewManagerDelegatorFactory (replaces HTTP view manager with Console view manager instance; DelegatorFactoryInterface implementation; registered)
  • laminas/laminas-mvc-i18n
    • Package DOES have both a ConfigProvider and a Module class
    • HttpRouterDelegatorFactory (injects router with Translator instance if it is available and the router supports translation; DelegatorFactoryInterface implementation; registered)
    • TranslatorFactory (produces Translator instance; FactoryInterface implementation; registered)
  • laminas/laminas-mvc-plugin-flashmessenger
    • Package DOES have a Module class
    • FlashMessengerFactory (produces FlashMessenger view helper instance; FactoryInterface implementation; registered)
  • laminas/laminas-mvc-plugin-identity
    • Package DOES have a Module class
    • IdentityFactory (produces Identity instance; FactoryInterface implementation; registered)
  • laminas/laminas-navigation
    • Package DOES have both a ConfigProvider and a Module class
    • AbstractNavigationFactory (abstract class produces a Navigation instance; FactoryInterface implementation)
    • NavigationAbstractServiceFactory (produces named Navigation instances; AbstractFactoryInterface implementation; registered)
    • NavigationHelperFactory (produces Navigation view helper instance; FactoryInterface implementation; registered in HelperConfig
    • ViewHelperManagerDelegatorFactory (injects view helper manager with HelperConfig; DelegatorFactoryInterface implementation; registered)
  • laminas/laminas-paginator
    • Package DOES have both a ConfigProvider and a Module class
    • AdapterPluginManagerFactory (produces AdapterPluginManager instance; FactoryInterface implementation; registered)
    • CallbackFactory (produces Callback adapter instance; FactoryInterface implementation; registered in AdapterPluginManager)
    • DbSelectFactory (produces DbSelect adapter instance; FactoryInterface implementation; registered in AdapterPluginManager)
    • DbTableGatewayFactory (produces DbTableGateway adapter instance; FactoryInterface implementation; registered in AdapterPluginManager)
    • IteratorFactory (produces Iterator adapter instance; FactoryInterface implementation; registered in AdapterPluginManager)
    • ScrollingStylePluginManagerFactory (produces ScrollingStylePluginManager instance; FactoryInterface implementation; registered)
  • laminas/laminas-router
    • Package DOES have both a ConfigProvider and a Module class
    • HttpRouterFactory (produces a TreeRouteStack instance; FactoryInterface implementation; registered)
    • TreeRouteStack (consumes RouteInvokableFactory)
    • RouteInvokableFactory (produces RouteInterface instances; FactoryInterface and AbstractFactoryInterface implementation)
    • RoutePluginManagerFactory (produces RoutePluginManager instance; FactoryInterface implementation; registered)
    • RoutePluginManager (consumes RouteInvokableFactory as an abstract factory; registered)
    • RouterFactory (produces RouteStackInterface instance; FactoryInterface implementation; registered)
  • laminas/laminas-serializer
    • Package DOES have both a ConfigProvider and a Module class
    • AdapterPluginManagerFactory (produces an AdapterPluginManager instance; FactoryInterface implementation; registered)
  • laminas/laminas-servicemanager-di
    • Package DOES have both a ConfigProvider and a Module class
    • DiAbstractServiceFactoryFactory (produces DiAbstractServiceFactory instance; FactoryInterface implementation; registered)
    • DiAbstractServiceFactory (produces arbitrary instances based on reflection; AbstractFactoryInterface implementation, not registered)
    • DiFactory (produces Laminas\Di\Di instance; FactoryInterface implementation; registered)
    • DiServiceFactory (produces arbitrary instances based on reflection; FactoryInterface implementation; not registered)
    • DiServiceInitializerFactory (produces DiServiceInitializer instance; FactoryInterface implementation; registered)
    • DiStrictAbstractServiceFactoryFactory (produces DiStrictAbstractServiceFactory instance; FactoryInterface implementation; registered)
    • DiStrictAbstractServiceFactory (produces arbitrary instances based on reflection; AbstractFactoryInterface implementation, not registered)
  • laminas/laminas-session
    • Package DOES have both a ConfigProvider and a Module class
    • ContainerAbstractServiceFactory (produces session Container instances; AbstractFactoryInterface implementation; registered)
    • SessionConfigFactory (produces appropriate session ConfigInterface instance; FactoryInterfaceimplementation; registered)
    • SessionManagerFactory (produces SessionManagerInterface instance; FactoryInterface implementation; registered)
    • StorageFactory (produces StorageInterface instance; FactoryInterface implementation; registered)
  • laminas/laminas-validator
    • Package DOES have both a ConfigProvider and a Module class
    • ValidatorPluginManagerFactory (produces ValidatorPluginManager instance; FactoryInterface implementation; registered)
  • laminas/laminas-view
    • Package DOES NOT have either a ConfigProvider or a Module class
    • AssetFactory (produces Asset view helper instance; FactoryInterface implementation; registered with `HelperPluginManager)
    • FlashMessengerFactory (produces FlashMessenger view helper instance; FactoryInterface implementation; registered with `HelperPluginManager)
    • IdentityFactory (produces Identity view helper instance; FactoryInterface implementation; registered with `HelperPluginManager)
  • laminas-api-tools/api-tools
    • Package has a Module class backed by a module.config.php file
    • TableGatewayAbstractFactory (produces named TDG instances; AbstractFactoryInterface implementation; registered in module.config.php)
    • DbConnectedResourceAbstractFactory (produces Laminas\ApiTools\Rest\Resource instances; AbstractFactoryInterface implementation; registered in module.config.php)
  • laminas-api-tools/api-tools-admin
    • Package has a Module class backed by a module.config.php file
    • AuthenticationControllerFactory (produces AuthenticationController instance; FactoryInterface implementation; registered in module.config.php)
    • AuthenticationTypeControllerFactory (produces AuthenticationTypeController instance; FactoryInterface implementation; registered in module.config.php)
    • AuthorizationControllerFactory (produces AuthorizationController instance; FactoryInterface implementation; registered in module.config.php)
    • ConfigControllerFactory (produces ConfigController instance; FactoryInterface implementation; registered in module.config.php)
    • DashboardControllerFactory (produces DashboardController instance; FactoryInterface implementation; registered in module.config.php)
    • DbAutodiscoveryControllerFactory (produces DbAutodiscoveryController instance; FactoryInterface implementation; registered in module.config.php)
    • DocumentationControllerFactory (produces DocumentationController instance; FactoryInterface implementation; registered in module.config.php)
    • FiltersControllerFactory (produces FiltersController instance; FactoryInterface implementation; registered in module.config.php)
    • HydratorsControllerFactory (produces HydratorsController instance; FactoryInterface implementation; registered in module.config.php)
    • InputFilterControllerFactory (produces InputFilterController instance; FactoryInterface implementation; registered in module.config.php)
    • ModuleConfigControllerFactory (produces ModuleConfigController instance; FactoryInterface implementation; registered in module.config.php)
    • ModuleCreationControllerFactory (produces ModuleCreationController instance; FactoryInterface implementation; registered in module.config.php)
    • SourceControllerFactory (produces SourceController instance; FactoryInterface implementation; registered in module.config.php)
    • StrategyControllerFactory (produces StrategyController instance; FactoryInterface implementation; registered in module.config.php)
    • ValidatorsControllerFactory (produces ValidatorsController instance; FactoryInterface implementation; registered in module.config.php)
    • VersioningControllerFactory (produces VersioningController instance; FactoryInterface implementation; registered in module.config.php)
    • InputFilterInputFilterFactory (produces InputFilterInputFilter instance; FactoryInterface implementation; registered in module.config.php)
    • AbstractPluginManagerModelFactory (produces AbstractPluginManagerModel instance; FactoryInterface implementation; extended by HydratorsModelFactory, which is registered)
    • DocumentationModelFactory (produces DocumentationModel instance; FactoryInterface implementation; registered in module.config.php)
    • FiltersModelFactory (produces FiltersModel instance; FactoryInterface implementation; registered in module.config.php)
    • InputFilterModelFactory (produces InputFilterModel instance; FactoryInterface implementation; registered in module.config.php)
    • ValidatorMetadataModelFactory (produces ValidatorMetadataModel instance; FactoryInterface implementation; registered in module.config.php)
    • ValidatorsModelFactory (produces ValidatorsModel instance; FactoryInterface implementation; registered in module.config.php)
  • laminas-api-tools/api-tools-content-negotiation
    • Package has a Module class backed by a module.config.php file
    • RenameUploadFilterFactory (produces RenameUpload filter instance; FactoryInterface implementation; registered in module.config.php
    • UploadFileValidatorFactory (produces UploadFile validator instance; FactoryInterface implementation; registered in module.config.php
    • module.config.php (registers laminas-db AdapterAbstractServiceFactory)
  • laminas-api-tools/api-tools-content-validation
    • Package has a Module class backed by a module.config.php file
    • ContentValidationListenerFactory (produces ContentValidationListener instance; FactoryInterface implementation; registered in module.config.php
    • NoRecordExistsFactory (produces NoRecordExists validator; FactoryInterface implementation; registered in module.config.php)
    • RecordExistsFactory (produces RecordExists validator; FactoryInterface implementation; registered in module.config.php)
  • laminas-api-tools/api-tools-doctrine
    • Package has a Module class backed by a server.config.php and an admin.config.php file
    • DoctrineAutodiscoveryControllerFactory (produces DoctrineAutodiscoveryController instance; FactoryInterface implementation; registered in admin.config.php)
    • DoctrineResourceFactory (produces DoctrineResource instances; AbstractFactoryInterface implementation; registered in server.config.php)
    • NoObjectExistsFactory (produces NoObjectExists instance; FactoryInterface implementation; registered in server.config.php)
    • ObjectExistsFactory (produces ObjectExists instance; FactoryInterface implementation; registered in server.config.php)
  • laminas-api-tools/api-tools-documentation
    • Package has a Module class backed by a module.config.php file
    • ControllerFactory (produces Controller instance; FactoryInterface implementation; registered in module.config.php)
  • laminas-api-tools/api-tools-documentation-apiblueprint
    • Package has a Module class backed by a module.config.php file
    • ControllerFactory (produces Controller instance; FactoryInterface implementation; registered in module.config.php)
  • laminas-api-tools/api-tools-documentation-swagger
    • Package has a Module class backed by a module.config.php file
    • SwaggerUiControllerFactory (produces SwaggerUiController instance; FactoryInterface implementation; registered in module.config.php)
  • laminas-api-tools/api-tools-hal
    • Package has a Module class backed by a module.config.php file
    • HalControllerPluginFactory (produces Hal plugin; FactoryInterface implementation; registered in module.config.php)
  • laminas-api-tools/api-tools-mvc-auth
    • Package has a Module class backed by a module.config.php file
    • AclAuthorizationFactory (produces AclAuthorization instance; FactoryInterface implementation; registered in module.config.php)
    • ApacheResolverFactory (produces ApacheResolver instance; FactoryInterface implementation; registered in module.config.php)
    • AuthenticationAdapterDelegatorFactory (injects adapters into DefaultAuthenticationListener; DelegatorFactoryInterface implementation; registered in module.config.php)
    • AuthenticationServiceFactory (produces Laminas\Authentication\AuthenticationService instance; FactoryInterface implementation; registered in module.config.php)
    • DefaultAuthenticationListenerFactory (produces DefaultAuthenticationListener instance; FactoryInterface implementation; registered in module.config.php)
    • DefaultAuthHttpAdapterFactory (produces Laminas\Authentication\Adapter\Http instance; FactoryInterface implementation; registered in module.config.php)
    • DefaultAuthorizationListenerFactory (produces DefaultAuthorizationListener instance; FactoryInterface implementation; registered in module.config.php)
    • DefaultResourceResolverListenerFactory (produces DefaultResourceResolverListener instance; FactoryInterface implementation; registered in module.config.php)
    • FileResolverFactory (produces FileResolver instance; FactoryInterface implementation; registered in module.config.php)
  • laminas-api-tools/api-tools-oauth2
    • Package has a Module class backed by a module.config.php file
    • AuthControllerFactory (produces AuthController instance; FactoryInterface implementation; registered in module.config.php)
  • laminas-api-tools/api-tools-rest
    • Package has a Module class backed by a module.config.php file
    • OptionsListenerFactory (produces OptionsListener instance; FactoryInterface implementation; registered in module.config.php)
    • RestControllerFactory (produces RestController instances; AbstractFactoryInterface implementation; registered in module.config.php)
  • laminas-api-tools/api-tools-rpc
    • Package has a Module class backed by a module.config.php file
    • RpcControllerFactory (produces RpcController instances; AbstractFactoryInterface implementation; registered in module.config.php)
2 Likes

First of all, this is a great addition. As we have the chance to totally re-work the whole ServiceManager ecosystem, I’d like to propose some more changes for the upcoming v4.

What about having new interfaces for PluginManager instances only? Imho, the only reason when I consume options within a factory is, when I am working with Plugins.

When we are splitting interfaces, it can be easier to create new stuff for plugin managers and we can finally have some return typehints (such as object for all plugin factories) aswell as providing generics for those factories as every factory within a specific plugin manager have to return a specific implementation of a pre-defined instance.

PluginManager factory interfaces

PluginFactoryInterface

Provides a method PluginFactoryInterface::__invoke(ContainerInterface $container, array $options): object

AbstractPluginFactoryInterface

Provides the well-known AbstractPluginFactoryInterface:canCreate(ContainerInterface $container, string $requestedName): bool method aswell as AbstractPluginFactoryInterface::__invoke(ContainerInterface $container, string $requestedName, array $options): object

PluginDelegatorFactoryInterface

Provides PluginDelegatorFactoryInterface::__invoke(ContainerInterface $container, callable $factory, array $options): object

I’d omitted the $name parameter from the PluginDelegatorFactoryInterface as I think that most of the time, instanceof is being used on the return value of the $factory-callable anyways.

ServiceManager factory interfaces

FactoryInterface

Imho, this can be dropped at all. There is no need for that interface as 99,99% of factories I’ve seen in the wild are neither using $requestedName nor $options at all.
As the InvokableFactory is a special factory, it could be changed to something like this:

/**
 * @template T of object
 */ 
final class InvokableFactory
{
    /**
     * @psalm-var class-string<T>
     */
    private $invokableClass;

    /**
     * @psalm-param class-string<T> $invokableClass
     */
    public function __construct(string $invokableClass)
    {
        $this->invokableClass = $invokableClass;
    }

    /**
     * @psalm-return T
     */
    public function __invoke(ContainerInterface $container): object
    {
        $invokable = $this->invokableClass;
        return new $invokable;
    }
}

It can be instantiated from within the servicemanager and/or custom implementations
We could also create an invokable factory for plugins to avoid having an all-in-one implementation here aswell

InitializerInterface

I am +1 for removal of anything regarding initializers as they’re executed on every service and thus every initializer has direct performance implications on every service within the service manager.

ReflectionBasedAbstractFactory

I’d prefer not using this factory in any project (due to performance) but as there is no other “handy” way of creating factories for “simple services”, I see why this is still part of the library.
Same suggestion here: we might create one for plugins and one for “services”.

Summary

When we split-up factories between PluginManager and ServiceManager, we can provide better interfaces (especially for plugins) and avoid confusion regarding the meaning of the options parameter which is almost always null.

I’d propose to drop the ServiceLocatorInterface with this aswell, as projects should not rely on the servicemanager implementation but on the PSR-11 interface. All these changes might require a new major version bump and thus, old factory interfaces might be dropped and therefore there is no need to use the ServiceLocatorInterface anymore.

This said, we could remove ServiceManager::build which then means, that there is a 100% chance that options is always null.

Instead, we might want to have a PluginManagerInterface (which extends the ContainerInterface) with the PluginManager::build method instead.

Thus, the AbstractPluginManager will implement that new interface, the build-Method and so on.

Thoughts?

We’re using the $requestedName quite extensively as this allows us to reuse factories, and to retrieve an interface in the lib which is aliased to a particular class via configuration in the app. (If DI would be a better solution for us here to reduce boilerplate, then I’m happy to move to that and accept the deprecation)

As an aside, I’m also curious how this would affect all other laminas/mezzio components based on this. My assumption is that once SM4 does get released, each framework component will also need a BC break for the factories that are not final? Or would these be considered internal?

Either way happy for BC breaks, looking forward to v4.