zend-frameworkzend-framework3abstract-factoryzend-servicemanager

ZF3: ServiceNotFoundException while creating a class with Abstract Factory registered in ServiceManager


I got problem with the Abstract Factories example.

I get ServiceNotFoundException while creating a class with Abstract Factory registered in the ServiceManager.

First I download zend-servicemanager with composer

composer require zendframework/zend-servicemanager

Then I run the ServiceManager example in the single file (for simplicity).

<?php
require_once 'vendor/autoload.php';

use Interop\Container\ContainerInterface;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\Factory\InvokableFactory;
use Zend\ServiceManager\Factory\FactoryInterface;
use Zend\ServiceManager\Factory\AbstractFactoryInterface;

Class that should be obtained with ServiceManager.

class A
{
    public $text;

    public function __construct() 
    {
        $this->text = "Default text";
    }
}

I use MyAbstractFactory from documentation.

class MyAbstractFactory implements AbstractFactoryInterface
{
  public function canCreate(ContainerInterface $container, $requestedName)
  {
    return in_array('Traversable', class_implements($requestedName), true);
  }

  public function __invoke(ContainerInterface $container, 
                           $requestedName,
                           array $options = null)
  {
    return $requestedName();
  }
}

I create ServiceManager with registered Abstract Factory.

$serviceManager = new ServiceManager([
    // Neither works    
  //'abstract_factories' => array('MyAbstractFactory')
  'abstract_factories' => array( new MyAbstractFactory() )
  //'abstract_factories' => array( MyAbstractFactory::class )
  /*  
    'abstract_factories' => [
        MyAbstractFactory::class => new MyAbstractFactory()
    ]
  */  
]);

Finally I try to obtain the instance of class A.

$a = $serviceManager->get(A::class);
var_dump($a);

I get Fatal error: Uncaught Zend\ServiceManager\Exception\ServiceNotFoundException: Unable to resolve service "A" to a factory; are you certain you provided it during configuration? with the stack trace

.\vendor\zendframework\zend-servicemanager\src\ServiceManager.php(763): Zend\ServiceManager\ServiceManager->getFactory('A') #1
.\vendor\zendframework\zend-servicemanager\src\ServiceManager.php(200): Zend\ServiceManager\ServiceManager->doCreate('A') #2
.\script.php(53): Zend\ServiceManager\ServiceManager->get('A') #3

Solution

  • My formulation of the problem was wrong. @rkeet's comment made it clear.

    As my objective was the working example of the Abstract Factory registration within ServiceManager, I post the single-file script where canCreate() is simplified just to check if the class exists.

    <?php
    // composer require zendframework/zend-servicemanager
    
    require_once 'vendor/autoload.php';
    
    use Interop\Container\ContainerInterface;
    use Zend\ServiceManager\ServiceManager;
    use Zend\ServiceManager\Factory\AbstractFactoryInterface;
    
    
    class A
    {
        public $text;
    
        public function __construct() 
        {
            $this->text = "Default text";
        }
    }
    
    
    class MyAbstractFactory implements AbstractFactoryInterface
    {
        public function canCreate(ContainerInterface $container, $requestedName)
        {
            return class_exists($requestedName);
        }
    
        public function __invoke(ContainerInterface $container, 
                                 $requestedName,
                                 array $options = null)
        {
            return new $requestedName();
        }
    }
    
    
    $serviceManager = new ServiceManager([
      //'abstract_factories' => array('MyAbstractFactory') // works
      //'abstract_factories' => array( new MyAbstractFactory() ) // works
      'abstract_factories' => array( MyAbstractFactory::class ) // also works
    ]);
    
    
    try {
        $a = $serviceManager->get(A::class);
        var_dump($a);
        echo "<br/>\n";
        $b = $serviceManager->get(B::class);
        var_dump($b);
    } catch (Exception $e) {
        echo $e->getMessage() . "<br/>\n";
        echo "{$e->getFile()}  ({$e->getLine()})";
    }
    ?>
    

    As @rkeet pointed, if someone needs the original example working, class A should implement Traversable.