4

I'm trying to implement the DependencyInjection Component from Symfony (version 4.0.0) into my project. For this I followed the following simple steps, in order to understand the autowiring process:

  • In composer.json: Assign namespace App to the src folder.
  • In services.yaml: Assign namespace App to the src folder.
  • Define MyController class in src folder, under the namespace App\Controller.
  • In bootstrap.php: Create a ContainerBuilder instance ($container).
  • In bootstrap.php: Create a YamlFileLoader object and load the config file services.yaml into it.
  • In bootstrap.php: Get an instance of MyController from the $container and display it on screen.

But I keep receiving the following error:

( ! ) Fatal error: Uncaught ReflectionException: Class does not exist in <path-to-my-project-root>/vendor/symfony/dependency-injection/ContainerBuilder.php on line 1051
( ! ) ReflectionException: Class does not exist in <path-to-my-project-root>/vendor/symfony/dependency-injection/ContainerBuilder.php on line 1051
Call Stack
#   Time    Memory  Function    Location
1   0.0160  354632  {main}( )   .../index.php:0
2   0.0166  357928  require_once( '<path-to-my-project-root>/bootstrap.php' ) .../index.php:7
3   0.0872  1752040 Symfony\Component\DependencyInjection\ContainerBuilder->get( )  .../bootstrap.php:16
4   0.0872  1752040 Symfony\Component\DependencyInjection\ContainerBuilder->doGet( )    .../ContainerBuilder.php:522
5   0.0872  1752816 Symfony\Component\DependencyInjection\ContainerBuilder->createService( )    .../ContainerBuilder.php:555
6   0.0873  1752928 __construct ( ) .../ContainerBuilder.php:1051

The error occurs at this line (1051) in ContainerBulder::createService method, because $definition->getClass() returns NULL:

private function createService(Definition $definition, array &$inlineServices, $id = null, $tryProxy = true) {
    // Line 1051:    
    $r = new \ReflectionClass($class = $parameterBag->resolveValue($definition->getClass()));

    //..
}

In the chapter Automatic Service Loading in services.yaml, as I understood it, by using only those settings without any other settings in services.yaml, the DI container will know how to create an instance of MyController. Maybe I'm wrong?...

Could you please take a second to help me? Thank you very much.


My project consists of the following structure and files:

Project structure

bootstrap.php
composer.json
config/
    services.yaml
src/
    Controller/
        MyController
vendor/
    symfony/
        config/
        dependency-injection/
        filesystem/
        polyfill-mbstring/
        yaml/

composer.json

"require": {
    "php": ">=5.5.0",
    "symfony/dependency-injection": "^4.0",
    "symfony/config": "^4.0",
    "symfony/yaml": "^4.0",
},
"autoload": {
    "psr-4": {
        "App\\": "src/"
    }
}

bootstrap.php

<?php

use App\Controller\MyController;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

require '../vendor/autoload.php';

$container = new ContainerBuilder();

$fileLocator = new FileLocator(__DIR__ . '/config');
$loader = new YamlFileLoader($container, $fileLocator);
$loader->load('services.yaml');

$myController = $container->get(MyController::class);

var_dump($myController);

config/services.yaml

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
        public: false       # Allows optimizing the container by removing unused services; this also means
                            # fetching services directly from the container via $container->get() won't work.
                            # The best practice is to be explicit about your dependencies anyway.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Tests}'

src/Controller/MyController

<?php

namespace App\Controller;

class MyController {

    public function myAction() {
        echo 'Hello from MyController.';
    }

}

Update 1:

After container compilation with compile()

$loader->load('services.yaml');
$container->compile();

I received the following error:

( ! ) Fatal error: Uncaught Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: You have requested a non-existent service "App\Controller\MyController". in <my-project-path>/vendor/symfony/dependency-injection/ContainerBuilder.php on line 950
( ! ) Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: You have requested a non-existent service "App\Controller\MyController". in <my-project-path>/vendor/symfony/dependency-injection/ContainerBuilder.php on line 950
Call Stack
#   Time    Memory  Function    Location
1   0.1512  357648  {main}( )   .../index.php:0
2   0.1675  361056  require_once( '<my-project-path>/bootstrap.php' )   .../index.php:7
3   3.5621  2582624 Symfony\Component\DependencyInjection\ContainerBuilder->get( ???, ??? ) .../bootstrap.php:18
4   3.5621  2582624 Symfony\Component\DependencyInjection\ContainerBuilder->doGet( ???, ???, ??? )  .../ContainerBuilder.php:522

I checked the $container->definitions array following the compile() call. I realised that all services (including App\Controller\MyController) saved in definitions list prior to compile() where removed from the array by the compilation process.

More of it: In $compiler->passConfig->log I found these entries (after compilation step):

[0] string  "Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Psr\Container\ContainerInterface"; reason: private alias."
[1] string  "Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\DependencyInjection\ContainerInterface"; reason: private alias."
[2] string  "Symfony\Component\DependencyInjection\Compiler\InlineServiceDefinitionsPass: Inlined service "App\Service\MyService" to "App\Controller\MyController"."
[3] string  "Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "App\Controller\MyController"; reason: unused."
[4] string  "Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass: Removed service "App\Service\MyService"; reason: unused."
8
  • See if you can add a die statement and determine what $definition->getClass() is returning. Might be a simple autoload issue though your psr-4 value seems correct. Commented Dec 2, 2017 at 1:59
  • Thank you, @Cedar. The $definition->getClass() returns NULL. For me that seems odd, because I would have expected a class name, or instance, or similar of MyController. My composer and autoload are working ok. I can't find the missing piece, if any. Or maybe there's a bug... Commented Dec 2, 2017 at 2:04
  • Sure you don't need a $container->compile() before you use it? I use the framework all the time but have never tried to use the container standalone. But I'm pretty sure it needs to be compiled. Commented Dec 2, 2017 at 3:00
  • @Cerad Oh, that would be a good idea!!! :-) I didn't pay much attention to the compilation part, since I didn't thought it would be necessary outside the framework. I am new to Symfony. Unfortunately is too late now. But I will check it tomorrow and I will certainly give you a feedback. Thank you again! Until tomorrow. Commented Dec 2, 2017 at 3:10
  • @Cerad Hi. I tested and debugged. When the container is compiled, and YamlFileLoader::load is called, then the BadMethodCallException('Adding definition to a compiled container is not allowed') is thrown in ContainerBuilder::setDefinition. In general, it seems that a class property must be defined in the yaml file. Otherwise the ReflectionException (on line 1051) is thrown over and over again. I also looked into the Changelog. Not even that simple code example is running. I had to define class: ... too. Commented Dec 2, 2017 at 11:01

1 Answer 1

13
+200

I went ahead and setup a fresh project.

There are two issues with your setup.

First you do need to compile the container before using it:

// bootstrap.php
$loader->load('services.yaml');

$container->compile();

$myController = $container->get(MyController::class);

And then you need to make your controller service public before pulling it from the container:

// services.yaml
App\:
    resource: 'src/*'
    exclude: 'src/{Entity,Migrations,Tests}'

App\Controller\:
    resource: 'src/Controller/*'
    public: true

This is basically what the framework does.

Sign up to request clarification or add additional context in comments.

3 Comments

It's unbelievable, you really did it !!! :-))) It works indeed !!!!.. I must say, I am very impressed by your perseverance, Cedar! In finding an answer I spent hours and hours... And probably you too spent a lot of time with this task, in order to help me. You made my day and I THANK YOU for your patience! In the next 3 days please accept a 150+ present from me :-) Have a very nice day and I hope to read your answers again. With gratefulness. P.S: Sorry to be late, but I write this message since 25 minute ago :-)) Bye
Not a problem but thanks. I was actually waiting for college football to start. The Reflection error was new to me so I wanted to track it down especially since S4 was just released. The public/private issue I should have picked up just by reading your question more carefully.
Oh, trust me, I'll do it with pleasure. And, well, score a few goals in the honour of the new S4! :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.