thephpleague / container

Small but powerful dependency injection container

Home Page:http://container.thephpleague.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Fatal error: Uncaught ArgumentCountError

burzum opened this issue · comments

OK, maybe this is another thing I just don't understand, however, I can't understand why this is not working. I'm getting this error for two classes in my provider.

There are other classes that have similar constructors that are loaded without issues through the reflection container. I have no idea why I'm getting this error.

Fatal error: Uncaught ArgumentCountError: Too few arguments to function Phauthentic\Infrastructure\Http\Dispatcher\Dispatcher::__construct(), 0 passed and exactly 1 expected in \src\Infrastructure\Http\Dispatcher\Dispatcher.php on line 39

When I'm using addArgument() and passing the interfaces as FQCN it works. But why do I have to do this in this case and it works just fine for almost all other classes. The container discovers the args via reflection and loads them properly. Here is the related code minus doc blocks and unrelated things:

$container = new \Phauthentic\Infrastructure\Container\Container();
$container->addServiceProvider(\Phauthentic\Provider\HttpStackProvider::class);

The container class I'm calling is this, extending the league container:

class Container extends LeagueContainer
{
    public function __construct(
        DefinitionAggregateInterface $definitions = null,
        ServiceProviderAggregateInterface $providers = null,
        InflectorAggregateInterface $inflectors = null
    ) {
        parent::__construct($definitions, $providers, $inflectors);

        $this->delegate(
            new ReflectionContainer()
        );

        $this->add(ContainerInterface::class, $this, true);
    }
}

The provider class:

use Lead\Router\Middleware\RouterMiddleware;
use Lead\Router\Route;
use Lead\Router\Router;
use League\Container\ServiceProvider\AbstractServiceProvider;
use Moon\HttpMiddleware\Delegate;
use Nyholm\Psr7\Factory\Psr17Factory;
use Phauthentic\Application\Http\Controller\PagesController;
use Phauthentic\Infrastructure\Http\Dispatcher\ControllerFactory;
use Phauthentic\Infrastructure\Http\Dispatcher\ControllerFactoryInterface;
use Phauthentic\Infrastructure\Http\Dispatcher\Dispatcher;
use Phauthentic\Infrastructure\Http\Dispatcher\DispatcherInterface;
use Phauthentic\Infrastructure\Http\Dispatcher\HandlerExtractorInterface;
use Phauthentic\Infrastructure\Http\Dispatcher\RouteHandlerExtractor;
use Phauthentic\Infrastructure\Http\Emitter\CorrelationIDDecorator;
use Phauthentic\Infrastructure\Http\Emitter\NarrowsparkSapiEmitter;
use Phauthentic\Infrastructure\Http\Emitter\ResponseEmitterInterface;
use Phauthentic\Infrastructure\Http\Middleware\CorrelationIDMiddleware;
use Phauthentic\Infrastructure\Http\Middleware\DispatcherMiddleware;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Server\RequestHandlerInterface;

class HttpStackProvider extends AbstractServiceProvider
{

    protected $provides = [
        RequestHandlerInterface::class,
        ResponseEmitterInterface::class,
        ResponseFactoryInterface::class,
        ServerRequestFactoryInterface::class,
        StreamFactoryInterface::class,
        UriFactoryInterface::class,
        UploadedFileFactoryInterface::class,
        HandlerExtractorInterface::class,
        ControllerFactoryInterface::class,
        DispatcherInterface::class
    ];

    public function register()
    {
        $container = $this->getLeagueContainer();
        $psr17Factory = new Psr17Factory();

        $container->add(ServerRequestFactoryInterface::class, $psr17Factory, true);
        $container->add(UriFactoryInterface::class, $psr17Factory, true);
        $container->add(UploadedFileFactoryInterface::class, $psr17Factory, true);
        $container->add(StreamFactoryInterface::class, $psr17Factory, true);
        $container->add(ResponseFactoryInterface::class, $psr17Factory, true);
        $container->add(HandlerExtractorInterface::class, RouteHandlerExtractor::class, true);
        $container->add(ControllerFactoryInterface::class, ControllerFactory::class, true);
//            ->addArgument(ContainerInterface::class);
        $container->add(DispatcherInterface::class, Dispatcher::class);
//            ->addArgument(ControllerFactoryInterface::class);
        $container->add(ResponseEmitterInterface::class, new CorrelationIDDecorator(
            new NarrowsparkSapiEmitter()
        ));

        dd($container->get(ControllerFactoryInterface::class));

        $router = new Router();

        $routerMiddleware = new RouterMiddleware($router);

        $default = function (ServerRequestInterface $request) use ($psr17Factory) {
            return $psr17Factory->createResponse(404);
        };

        $container->add(
            RequestHandlerInterface::class,
            new Delegate(
                [
                    new CorrelationIdMiddleware(),
                    $routerMiddleware,
                    $container->get(DispatcherMiddleware::class)
                ],
                $default,
                $container
            )
        );
    }
}
class Dispatcher implements DispatcherInterface
{
    public function __construct(
        ControllerFactoryInterface $controllerFactory
    ) {
        $this->controllerFactory = $controllerFactory;
    }
}

It looks like that the ArgumentResolver is not calling reflectArguments() because it doesn't see the container as ReflectionContainer. I think position 14 in the stack should be the reflection container. I have no idea why it switches there.

# | Time | Memory | Function | Location
-- | -- | -- | -- | --
1 | 0.0003 | 417256 | {main}( ) | ...\index.php:0
2 | 0.0049 | 587704 | require( 'C:\xampp\htdocs\phauthentic-base\config\container.php' ) | ...\index.php:18
3 | 0.0103 | 766224 | Phauthentic\Infrastructure\Container\Container->get( ) | ...\container.php:26
4 | 0.0109 | 843920 | League\Container\ReflectionContainer->get( ) | ...\Container.php:181
5 | 0.0109 | 844160 | League\Container\ReflectionContainer->reflectArguments( ) | ...\ReflectionContainer.php:52
6 | 0.0111 | 846088 | League\Container\ReflectionContainer->resolveArguments( ) | ...\ArgumentResolverTrait.php:86
7 | 0.0111 | 846512 | Phauthentic\Infrastructure\Container\Container->get( ) | ...\ArgumentResolverTrait.php:45
8 | 0.0112 | 846512 | League\Container\ServiceProvider\ServiceProviderAggregate->register( ) | ...\Container.php:170
9 | 0.0112 | 847184 | Phauthentic\Provider\HttpStackProvider->register( ) | ...\ServiceProviderAggregate.php:101
10 | 0.0248 | 1514888 | Phauthentic\Infrastructure\Container\Container->get( ) | ...\HttpStackProvider.php:114
11 | 0.0252 | 1519864 | League\Container\ReflectionContainer->get( ) | ...\Container.php:181
12 | 0.0252 | 1520104 | League\Container\ReflectionContainer->reflectArguments( ) | ...\ReflectionContainer.php:52
13 | 0.0254 | 1522096 | League\Container\ReflectionContainer->resolveArguments( ) | ...\ArgumentResolverTrait.php:86
14 | 0.0258 | 1525104 | Phauthentic\Infrastructure\Container\Container->get( ) | ...\ArgumentResolverTrait.php:45
15 | 0.0258 | 1525104 | League\Container\Definition\DefinitionAggregate->resolve( ) | ...\Container.php:155
16 | 0.0258 | 1525104 | League\Container\Definition\Definition->resolve( ) | ...\DefinitionAggregate.php:94
17 | 0.0258 | 1525104 | League\Container\Definition\Definition->resolveClass( ) | ...\Definition.php:212
18 | 0.0258 | 1525272 | newInstanceArgs ( ) | ...\Definition.php:252
19 | 0.0258 | 1525328 | Phauthentic\Infrastructure\Http\Dispatcher\Dispatcher->__construct( ) | ...\Definition.php:252

Changing Definition::resolveClass() to this made it work...

    protected function resolveClass(string $concrete)
    {
        $resolved   = $this->resolveArguments($this->arguments);
        $reflection = new ReflectionClass($concrete);

        if (empty($this->arguments) && $reflection->hasMethod('__construct')) {
            $method = $reflection->getMethod('__construct');
            $resolved = [];
            $unresolved = [];
            $paramters = $method->getParameters();
            $count = count($paramters);

            foreach ($paramters as $parameter) {
                $type = (string)$parameter->getType();
                if ($this->getContainer()->has($type)) {
                    $resolved[] = $this->getContainer()->get($type);
                } else {
                    $unresolved[] = $type;
                }
                $unresolved[] = $type . ' $' . $parameter->getName();
            }

            if ($count > count($resolved)) {
                throw new \RuntimeException(sprintf(
                    'Could not resolve all arguments for %s::__construct(): %s',
                    $reflection->getName(),
                    implode(', ', $unresolved)
                ));
            }
        }

        return $reflection->newInstanceArgs($resolved);
    }

I think there is something wrong with the internals of some other classes as well that it doesn't resolve them properly or something else. $this->arguments is always taking the external arguments, it looks like it never attempts to resolve constructor args properly.

Any better solution is welcome, I can also provide this as PR.

Another issue is that I can't simply switch the default definition that is used in DefinitionAggregate::add():

        if (!$definition instanceof DefinitionInterface) {
            $definition = new Definition($id, $definition);
        }

It would be nice if we could set another default definition.

@philipobenito any guideline on this issue? I don't mind to prepare a PR and tests for this, I just need to know if it's right. I don't want to waste time for something that gets rejected or is not an issue and can be solved otherwise.

For any reflection to happen at all, the ReflectionContainer needs to be added as a delegate to the main Container.

By design, reflection is disabled by default and I discourage its use.

Reflection will also not be attempted if there is a definition that exists for the requested alias/class name as it is then unclear what is actually happening in the internals to the user of the package.

My recommendation is to always fully define your container mappings.

Closing due to inactivity