What's your opinion on a factory provider?

I personally got tired of the multiple files that builds up when creating action factory files so I wrote a class called FactoryProvider.

namespace Core;

/**
 * Class FactoryProvider
 *
 * @package Core
 */
abstract class FactoryProvider
{
    /**
     * @var array
     */
    private $factories = [];

    /**
     * @return array
     */
    public function __invoke()
    {
        $this->init();

        return ['factories' => $this->factories];
    }

    /**
     * @param string $class
     * @param \Closure $closure
     */
    protected function register(string $class, \Closure $closure)
    {
        $this->factories[$class] = $closure;
    }

    abstract public function init();
}

I use pimple as a container, but I’m sure this could work for practically any other container. Purpose of this just makes maintaining factories simpler for instance:

class FactoryProvider extends \Core\FactoryProvider
{
    public function init()
    {
        $this->register(LoginAction::class, function(Container $container) {
            $input = $container->has(Input::class) ? $container->get(Input::class) : null;

            $router = $container->has(RouterInterface::class)
                ? $container->get(RouterInterface::class) : null;

            $template = $container->has(TemplateRendererInterface::class)
                ? $container->get(TemplateRendererInterface::class) : null;

            $entity_manager = $container->has(EntityManager::class)
                ? $container->get(EntityManager::class) : null;

            $session = $container->has(Session::class)
                ? $container->get(Session::class) : null;

            $user_login_service = new UserLoginService($input, $entity_manager, $session);

            return new LoginAction($input, $router, $template, $user_login_service, $session);
        });
    }
}

Now not only in my user module can I keep track of all my user action factories in one file, but there aren’t 4 individual files that clogs up my folder.

Similar to ConfigProvider I just added it to the ConfigAggregator, but I had to add my own check in the container.php file for it to actually register.

How do you feel if expressive implemented something like this in the core framework?

Hey @exts,

Nice, I agree with the fact that factory boilerplate can become quickly huge and It’s good to think about it.

But I’m less sure with your proposal. Personally, it’s gonna move the problem from ‘multiple files’ to ‘one big file’…

I guess you know about it, but if you use zend-servicemanager : "^3.2", one solution could be to use the ReflectionBasedAbstractFactory (or eventually ConfigAbstractFactory)

When registering your dependencies, you’ll end up doing:

use Zend\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory;

return [
  'abstract_factories' => [
    ReflectionBasedAbstractFactory::class,
  ],
  'factories' => [
    LoginAction::class => ReflectionBasedAbstractFactory::class
  ],
]

(*) the same idea can be written for pimple as well

Of course, there is a some drawbacks for this approach:

  • Not all classes can be instanciated based on reflection (types, circular deps).
  • While reflection is good for prototyping it is not ideal for production (of course depend what you do)

Basically the performance problem could be tackled by tooling… (Naïve) examples could be, generating the factories when zend-config-aggregator creates the cache, or a specific command line tool that parse all references to ReflectionBasedAbstractFactory and generate the factories in a dedicated directory… (just some free thoughts, they might be harmful also…).

For my part I’ll continue writing factories, sometimes grouping pretty same actions under a common interface, i.e: TheAlwaysSameActionConstructorInterface::__construct(Template, Router) and load them with a unique factory.

But thank you for popping out with this issue. I had similar questions.

I would love to hear what others think, there can be other solutions I’m not aware of ?

Yeah I know about it, but I don’t use that as my service locator (I use Pimple\Container). So that’s a problem solved w/ zend’s service locator, but even then it’s not recommended outside of rapid prototyping.

Maybe even implement something like Auryn

Thanks I didn’t know about Auryn… not sure if it does not rely on reflection though.

BTW: Would be nice to have a benchmark with reflection…

One huge drawback to this: the factories key becomes part of configuration, which, when using ConfigAggregator, we attempt to cache when in production. The issue that arises is that closures cannot be cached safely, due to how PHP serializes them. As such, the syntax you have above could potentially break the application.

One additional thing for you to research are service providers. These are being proposed by the container-interop group, the same folks who brought us PSR-11, and is an attempt to help address this sort of thing… to an extent. You still end up writing classes or closures, but it provides an easy way to register factories for your PSR-11 container.

If the main problem you face is that you do not want to write classes, try looking into abstract factories or using the second argument passed to factories (the $requestedName); these can be used to re-use logic when the construction of multiple classes follows the same pattern, using the same arguments.