Skip to content
This repository has been archived by the owner on Jan 31, 2020. It is now read-only.

DiAbstractServiceFactory Recursion #19

Open
2 tasks done
mirza572 opened this issue Nov 26, 2019 · 3 comments
Open
2 tasks done

DiAbstractServiceFactory Recursion #19

mirza572 opened this issue Nov 26, 2019 · 3 comments

Comments

@mirza572
Copy link

mirza572 commented Nov 26, 2019

  • I was not able to find an open or closed issue matching what I'm seeing.
  • This is not a question. (Questions should be asked on chat (Signup here) or our forums.)

The Zend\ServiceManager\Di\DiAbstractServiceFactory when used as an abstract factory is causing recursion.

The issue is caused by Zend\ServiceManager\ServiceManager::has() function implementation and Zend\ServiceManager\Di\DiAbstractServiceFactoryFactory: :__invoke() function which is using DiAbstractServiceFactory::USE_SL_BEFORE_DI and Zend\ServiceManager\Di\DiServiceFactory: :get() method which is sending the object initialization request back to the Zend\ServiceManager\ServiceManager and as a result creating the recursion.

During object initialization by ServiceManager via DiAbstractServiceFactory the DiServiceFactory::get() method checks whether it should call ServiceManager (Service Locator) first by the following code (line 126) and therefore sends the object initialization request back to the ServiceManager because both conditions for the if statement are correct in this situation.

// Allow this di service to get dependencies from the service locator BEFORE trying DI.
if ($this->useContainer === self::USE_SL_BEFORE_DI && $this->container->has($name)) {    
     return $this->container->get($name);
 }

As the object initialization request is sent or pushed back to the ServiceManager and the ServiceManager will forward the request again to DiAbstractServiceFactory therefore creating the recursion (sending the request back and forth) and the end result is system memory exhausted.

Code to reproduce the issue

Please just use Zend\ServiceManager\Di\DiAbstractServiceFactory as an abstract factory
and then use ServiceManager to get/initialize an object. This object should not have any other factory class defined in the config. The only factory which should initialize this object should be Zend\ServiceManager\Di\DiAbstractServiceFactory.

Expected results

No Recursion.

Actual results

Recursion

@weierophinney
Copy link
Member

I created the following to test this:

// in src/Example.php:
namespace Test;

class Example
{
}

// in test.php:
use Test\Example;
use Zend\ServiceManager\Di\ConfigProvider;
use Zend\ServiceManager\Di\DiAbstractServiceFactory;
use Zend\ServiceManager\ServiceManager;

require './vendor/autoload.php';

$container = new ServiceManager((new ConfigProvider())->getDependencyConfig());
$container->addAbstractFactory($container->get('DiAbstractServiceFactory'));

$instance = $container->get(Example::class);
var_export($instance);

This mimics how zend-servicemanager-di would register its services, and demonstrates how to add its abstract factory to the container after initialization.

This definitely reproduces the issue.

I'm not certain how quickly we will try to address it, particularly as zend-di provides comprehensive support for zend-servicemanager as of version 3. We typically recommend using that support if you are able to to, as it provides a better path from development to production, and more flexibility in how users interact with their container.

Interestingly, I checked to see if this worked in that paradigm as well:

use Test\Example;
use Zend\Di\ConfigProvider;
use Zend\Di\Container\ServiceManager\AutowireFactory;
use Zend\ServiceManager\ServiceManager;

require './vendor/autoload.php';

$container = new ServiceManager(array_merge_recursive(
    (new ConfigProvider())->getDependencyConfig(),
    [
        'abstract_factories' => [
            new AutowireFactory(),
        ],
    ]
));

$instance = $container->get(Example::class);
var_export($instance);

Surprisingly, the behavior is exhibited there as well. (Anecdotally, I'm surprised, as I've used the autowire factory before when prototyping functionality, and it worked fine.)

I'm curious if the issue is due to having an empty constructor. Can you provide some information about the class you're trying to retrieve from the container?

@mirza572
Copy link
Author

mirza572 commented Nov 27, 2019

The issue exists in both cases whether the class has an empty constructor or not. Following is the class constructor

    /**
     * Constructor
     *
     *  \Vendor\Framework\App\ResourceConnection $resourceConnection
     */
    public function __construct(
        \Vendor\Framework\App\ResourceConnection $resourceConnection
    ) {
       $this->connection = $resourceConnection->getConnection();
    }

A quick solution to this issue is changing Zend\ServiceManager\Di\DiAbstractServiceFactoryFactory: :__invoke() method as following i.e. replace DiAbstractServiceFactory::USE_SL_BEFORE_DI by DiAbstractServiceFactory::USE_SL_NONE or DiAbstractServiceFactory::USE_SL_AFTER_DI

    /**
     * Class responsible for instantiating a DiAbstractServiceFactory
     *
     * @param ContainerInterface $container
     * @param string $name
     * @param null|array $options
     * @return DiAbstractServiceFactory
     */
    public function __invoke(ContainerInterface $container, $name, array $options = null)
    {
        $factory = new DiAbstractServiceFactory($container->get('Di'), DiAbstractServiceFactory::USE_SL_NONE);

        if ($container instanceof ServiceManager) {
            $container->addAbstractFactory($factory, false);
        }

        return $factory;
    }

@weierophinney
Copy link
Member

This repository has been closed and moved to laminas/laminas-servicemanager-di; a new issue has been opened at laminas/laminas-servicemanager-di#1.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants