samedi 20 février 2021

from scratch dependency injection container get error in php8

I made a small dependency injection container, inspired from Symfony covered by unit testing which works perfectly under php 7 but when switching to php8 get an error regarding the testIfServiceWithInjectionfound test which tells:

1) Tests\Container\ContainerTest::testIfServiceWithInjectionIsFound
App\Container\NotFoundException: 

C:\wamp\www\lockpassword\app\Container\Container.php:108
C:\wamp\www\lockpassword\app\Container\Definition.php:101
C:\wamp\www\lockpassword\app\Container\Definition.php:98
C:\wamp\www\lockpassword\app\Container\Container.php:126
C:\wamp\www\lockpassword\tests\Container\ContainerTest.php:57

think it is related to the update of ReflectionParameter :: getClass () because there is only that I change from then php7, give you the compet code, I've been stuck on it for a week now:

update:

The problem concerns certainly the ReflectionParameter in method make and register but don't know how to do it, can you show me?


     class ContainerTest extends TestCase 
    {
      public function testIfSharedServiceIsFound()
      {
          $container = new Container();
          $response1 = $container->get(ResponseContext::class);
          $this->assertInstanceOf(ResponseContext::class, $response1);
          $response2 = $container->get(ResponseContext::class);
          $this->assertEquals(spl_object_id($response1), spl_object_id($response2));
      }
    
      public function testIfNoSharedServiceIsFound()
        {
            $container = new Container();
            $container->getDefinition(ResponseContext::class)->setShared(false);
            $response1 = $container->get(ResponseContext::class);
            $this->assertInstanceOf(ResponseContext::class, $response1);
            $response2 = $container->get(ResponseContext::class);
            $this->assertNotEquals(spl_object_id($response1), spl_object_id($response2));
        }
    
        public function testIfServiceWithInjectionIsFound()
        {
          $container = new Container();
          $bar = $container->get(Bar::class);
          $this->assertInstanceOf(Bar::class, $bar);
          $this->assertInstanceOf(Foo::class, $bar->foo);
        }
    
        public function testIfServiceWithParametersIsFound()
        {
            $container = new Container();
            $container
                ->addParameter("path", "path")
                ->addParameter("action", "action")
            ;
            $route = $container->get(Route::class);
            $this->assertInstanceOf(Route::class, $route);
        }
    
        public function testIfServiceAliasIsFound()
        {
            $container = new Container();
            $container->addAlias(RequestContextInterface::class, RequestContext::class);
            $request = $container->get(RequestContextInterface::class);
            $this->assertInstanceOf(RequestContextInterface::class, $request);
            $this->assertInstanceOf(RequestContext::class, $request);
        }
    
        public function testIfServiceParameterIsFound()
        {
            $container = new Container();
            $container->addParameter("key", "value");
            $this->assertEquals("value", $container->getParameter("key"));
        }
    
      public function testIfParameterNotFound()
      {
          $container = new Container();
          $this->expectException(NotFoundExceptionInterface::class);
          $container->getParameter("fail");
      }
    
      public function testIfClassNotFound()
      {
          $container = new Container();
          $this->expectException(NotFoundExceptionInterface::class);
          $container->get(Fail::class);
      }
    } 


    class Container implements ContainerInterface
    { 
        /**
         * @var array
         */
        private array $parameters = [];
    
        /**
         * @var Definition[]
         */
        private array $definitions = [];
    
        /**
         * @var array
         */
        private array $instances = [];
    
        /**
         * @var array
         */
        private array $aliases = [];
    
        /**
         * Retrieves the requested reflection class and registers it.
         * 
         * @param $id The service identifier.
         * @return $this This registered service.
         */
        public function register(string $id): self
        {
            $reflectionClass = new ReflectionClass($id);
            if ($reflectionClass->isInterface()) {
                $this->register($this->aliases[$id]);
                $this->definitions[$id] = &$this->definitions[$this->aliases[$id]];
                return $this;
            }
            $dependencies = [];
            if (null !== $reflectionClass->getConstructor()) {
                $dependencies = array_map(
                    fn (ReflectionParameter $parameter) => $this->getDefinition($parameter->getType()->getName()),
                    array_filter(
                        $reflectionClass->getConstructor()->getParameters(),
                        fn (ReflectionParameter $parameter) => $parameter->getType() && !$parameter->getType()->isBuiltin() 
                        ? new ReflectionClass($parameter->getType()->getName()) : null
                    )
                );
            }
            
            $aliases = array_filter($this->aliases, fn (string $alias) => $id === $alias);
            $this->definitions[$id] = new Definition($id, true, $aliases, $dependencies);
            var_dump($this);
            return $this;
        }
    
        /**
         * Definition instance to gets for this service.
         * 
         * @param $id The id of Definition instance.
         * @return Definition Returns the definition value.
         */ 
        public function getDefinition($id): Definition
        {
            if (!isset($this->definitions[$id])) {
                $this->register($id);
            }
            return $this->definitions[$id];
        }
    
        /**
         * Adds an parameter to pass to the service constructor method.
         * 
         * @param $id The parameter name.
         * @param $value The parameter value.
         * @return $this Returns this parameter.
         */
        public function addParameter($id, $value): self
        {
            $this->parameters[$id] = $value;
            return $this;
        }
        /**
         * Gets an parameter to pass to the service constructor method.
         * 
         * @param $id The parameter name.
         * @return array|bool|float|int|string|null Returns the parameter value.
         * @throws NotFoundException When the parameter does not exist.
         */
        public function getParameter($id)
        {
            if (!isset($this->parameters[$id])) {
                throw new NotFoundException();
            }
            return $this->parameters[$id];
        }
    
        /**
         * Gets a service.
         * 
         * @param string $id The service identifier.
         * @return mixed|object Returns the associated service.
         * @throws NotFoundException When the service does not exist.
         */
        public function get($id)
        {
            if (!$this->has($id)) {
                if (!class_exists($id) && !interface_exists($id)) {
                    throw new NotFoundException();
                }
                $instance = $this->getDefinition($id)->make($this);
                if (!$this->getDefinition($id)->isShared()) {
                    return $instance;
                }
                $this->instances[$id] = $instance;
            }
            return $this->instances[$id];
        }
    
        /**
         * Determine if the given service is defined.
         * 
         * @param string $id The service identifier.
         * @return bool Returns true if the service is defined, false otherwise.
         */
        public function has($id): bool
        {
            return isset($this->instances[$id]);
        }
    
        /**
         * Adds a specific alias for this service.
         * 
         * @param string $id The service identifier.
         * @param string $class The associated class name.
         * @return $this Returns this alias.
         */
        public function addAlias(string $id, string $class): self
        {
            $this->aliases[$id] = $class;
            return $this;
        }
    
    
    }


    class Definition
    {
        /**
         * @var string
         */
        private string $id;
    
        /**
         * @var bool
         */
        private bool $shared = true;
    
        /**
         * @var array
         */
        private array $aliases = [];
    
    
    
        /**
         * @var Definition[]
         */
        private array $dependencies = [];
    
        /**
         * @var ReflectionClass
         */
        private ReflectionClass $class;
    
        /**
         * Definition constructor.
         * 
         * @param string $id The service identifier.
         * @param bool $shared the shared service.
         * @param array $aliases this alias
         * @param array $dependencies
         */
        public function __construct(string $id, bool $shared = true, array $aliases = [], array $dependencies = [])
        {
            $this->id = $id;
            $this->shared = $shared;
            $this->aliases = $aliases;
            $this->dependencies = $dependencies;
            $this->class = new ReflectionClass($id);
        }
    
        /**
         * Sets if the service must be shared or not.
         * 
         * @param bool $shared True if the service is shared. False else.
         * @return self Returns the shared service.
         */
        public function setShared(bool $shared): self
        {
            $this->shared = $shared;
            return $this;
        }
    
        /**
         * Whether this service is shared. 
         * 
         * @return bool Returns true if the container is shared. False else.
         */
        public function isShared(): bool
        {
            return $this->shared;
        }
    
        /**
         * Creates a service.
         *  
         * @param ContainerInterface $container Interface ContainerInterface allows to use it's methods
         * @return object Returns a new instance for the current service.
         */
        public function make(ContainerInterface $container): object
        {
            $constructor = $this->class->getConstructor();
            if (null === $constructor) {
                return $this->class->newInstance();
            }
            $parameters = $constructor->getParameters();
            return $this->class->newInstanceArgs(
                array_map(function (ReflectionParameter $param) use ($container) {
                    if ($param->getType() && !$param->getType()->isBuiltin() 
                    ? new ReflectionClass($param->getType()->getName()) : null === null) {
                        return $container->getParameter($param->getName());
                    }
                    return $container->get($param->getType()->getName());
                }, $parameters)
            );
        }
    
    }

interface ContainerInterface extends PsrContainerInterface
{
    /**
     * @param string $id
     * @return $this
     */
    public function register(string $id): self;

    /**
     * @param $id
     * @return Definition
     */
    public function getDefinition($id): Definition;

    /**
     * @param $id
     * @param $value
     * @return $this
     */
    public function addParameter($id, $value): self;

    /**
     * @param $id
     * @return mixed
     */
    public function getParameter($id);

    /**
     * @param string $id
     * @param string $class
     * @return $this
     */
    public function addAlias(string $id, string $class): self;
}




Aucun commentaire:

Enregistrer un commentaire