I currently have the use-case that I want to pass options into a factory from within another factory.
I thought I would use the build method of the ServiceManager.
But now I’m facing the problem, that the ContainerInterface passed into my factories does not expose that method.
Well technically the $container object I’m receiving exposes it, because it’s the ServiceManager. But the contract does not guarantee it.
The __invoke method signature of the current FactoryInterface looks like this:
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null);
I want to pass in the $options from another factory using the build() method of the ServiceManager.
So I would basically have two factories:
// ValidatorFactory
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null) {
return new Validator($options['length'] ?? 0);
}
// FormFactory
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null) {
$validator1 = $container->build(Validator::class, ['length' => 12]); // ContainerInterface only exposes get and has
$validator2 = $container->build(Validator::class, ['length' => 20]); // ContainerInterface only exposes get and has
$form = new Form();
$form->addValidators([$validator1, $validator2]);
return $form;
}
So essentially I want to use this build() method to pass $options to the factory like described in the documentation, but I seem to struggle with how this is supposed to work, since the contract of the factory does not guarantee, that I get a $container which implements the ServiceLocatorInterface I would need.
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$serviceManager = $container->get(ServiceManager::class);
return new FileSystem($serviceManager->build(Validator::class, $options));
}
You were right that the $container does not expose build, the ServiceManager does.
This leads to me getting the ServiceManager first, and then calling the build on it instead of the ContainerInterface.
thank you for your input. In fact I’m doing something similar currently:
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) {
// THIS HERE
assert($container instanceof ServiceLocatorInterface, 'Only usable in ServiceLocator context.');
$validator1 = $container->build(Validator::class, ['length' => 12]);
// ...
}
While I find it reasonable that the ServiceManager implements the PSR-11 container interface, I find it strange, that the FactoryInterface bundled with the ServiceManager doesn’t expose all of its features or rather doesn’t make it intuitive as to what type of container you’ll most likely get in a Laminas environment.
I thought there is something that I missed.
I feel like the default FactoryInterface shipped with the component and also mentioned in the documentation should look more like this:
public function __invoke(ServiceLocatorInterface $container, $requestedName, array $options = null);
and maybe, for convenience reasons, should also ship with a Psr11FactoryInterface which then would just be:
public function __invoke(ContainerInterface $container, $requestedName, array $options = null);
I think I will leave this topic open for a while and hope that some core developer might also chime in.
I feel like I should emphasize that there’s no build method in ContainerInterfacebut in ServiceManager, which implements ServiceLocatorInterface. How about you use the ContainerInterface to get a ServiceManager and call its build method like I did?
All of those interfaces and classes are within the laminas-namespace, therefore are available to you too.
I’m very aware of that. It’s the sole reason I asked about the underlying concepts, and what the idiomatic way, a laminas user is “supposed to” use the features of the ServiceManager is.
Are we “supposed to” fetch it explicitly in every factory we would need the build method?
Or are we “supposed to” assert, that the $container passed into the factory posseses the feature we are actually interested in?
The ServiceManager is currently the concept used in the Laminas world to obtain services. I’m wondering why it is not passed into my Laminas factories, so that I can use all of its features without prior having to fetch it.
Maybe for context: In ZF2 the ServiceLocatorInterface was in fact passed into the factory. I’m worried that there was an oversight during the refactor.
Long story short: I’m interested in the idiomatic way. The way the designer intended this to be used.