0

I am trying to follow the Build you own framework tutorial. I get stuck when I want to use DI inside my controller.

I have found this question (Controller (Service) not having dependencies injected on Symfony project) on stack overflow that got me as far as I am now but still cannot resolve the following issue:

This is my controller

<?php

namespace App\Controllers;

use App\Models\LeapYear;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

class LeapYearController
{
    private LeapYear $model;

    public function __construct(LeapYear $model)
    {
        $this->model = $model;
    }

    public function index(int $year): JsonResponse
    {
        if ($this->model->isLeapYear($year)) {
            return new JsonResponse('Yep, this is a leap year!');
        }

        return new JsonResponse('Nope, this is not a leap year.');
    }
}

This is my model:

<?php

namespace App\Models;

class LeapYear
{
    public function isLeapYear(int $year = null): bool
    {
        if (null === $year) {
            $year = date('Y');
        }

        return 0 === $year % 400 || (0 === $year % 4 && 0 !== $year % 100);
    }
}

They are both in ./src/controllers and ./src/models

This is my container.php

<?php

use App\Controllers\LeapYearController;
use App\Models\LeapYear;
use GGPHP\Framework;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\EventListener\ResponseListener;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;

$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.yml');

$container->register('context', RequestContext::class);
$container->register('matcher', UrlMatcher::class)
    ->setArguments([$routes, new Reference('context')]);

$container->register('request_stack', RequestStack::class);
$container->register('controller_resolver', ContainerControllerResolver::class)
    ->setArguments([$container]);
$container->register('argument_resolver', ArgumentResolver::class);

$container->register('listener.router', RouterListener::class)
    ->setArguments([new Reference('matcher'), new Reference('request_stack')]);
$container->register('listener.response', ResponseListener::class)
    ->setArguments(['UTF-8']);

$container->register('dispatcher', EventDispatcher::class)
    ->addMethodCall('addSubscriber', [new Reference('listener.router')])
    ->addMethodCall('addSubscriber', [new Reference('listener.response')]);

$container->register('framework', Framework::class)
    ->setArguments([
        new Reference('dispatcher'),
        new Reference('controller_resolver'),
        new Reference('request_stack'),
        new Reference('argument_resolver'),
    ]);

return $container;

And this is my front controller index.php

<?php

require_once dirname(__DIR__).'/vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;

$request = Request::createFromGlobals();
$routes = include dirname(__DIR__).'/routes.php';
$container = include dirname(__DIR__).'/container.php';

$response = $container->get('framework')->handle($request);
$response->send();

my services.yaml is:

services:
  App\Models\LeapYear:
    arguments:
  App\Controllers\LeapYearController:
    arguments:
      $model: '@App\Models\LeapYear'

The error I get is:

Fatal error: Uncaught ReflectionException: Class "" does not exist in C:\Users\.......\vendor\symfony\dependency-injection\ContainerBuilder.php:1131 Stack

I am not sure what to do anymore. I have removed the model from the services.yaml. Tried to load them manually by registering, but nothing works, always get this error.

g9m29
  • 373
  • 3
  • 16
  • Check your project into github and I'll be glad to take a look. Nothing seems to be obviously wrong but probably a minor detail missing somewhere. – Cerad Aug 06 '23 at 12:29
  • I also sort of want to say that you need to compile the container before using it ([like this](https://stackoverflow.com/questions/58823181/symfony-dependency-injection-component-autowiring-not-working/58827963#58827963)) but I could be wrong. – Cerad Aug 06 '23 at 12:34
  • Hey @Cerad I have created a repository containing the code here: https://github.com/b17Runner/test-framework – g9m29 Aug 06 '23 at 17:57
  • besides that your DI injection has a problem... in your shown specific usecase.. why you need a service for that at all? thats a classic static method call. `LeapYear::isLeapYear($value)` – Rufinus Aug 06 '23 at 19:24
  • @Rufinus I understand, we are talking just conceptually, about how can I achieve this. Because after that I would probably create two separate layers, for services and repositories and I would need to implement my ORM manager there. For example, if I use entity manager I can declare it once in the constructor and use it in the whole class. So what I have provided is the simplest example of my error so the community can help me. After that, I will expand on the logic. – g9m29 Aug 06 '23 at 20:42
  • @g9m29 all good. Unfortunatly the "skill" level on stackoverflows vary so much that sometimes the searched solution is not what the asker really needs. That the reason to give it as comment and not as answer. Glad you got the CI problem solved! – Rufinus Aug 07 '23 at 19:07

1 Answers1

3

The rather obscure Class "" does not exist comes from the fact that you are not specifying the class parameter when defining your services. The Symfony framework has a bit of magic to overcome this but standalone means it is up to you.

services:
  App\Models\LeapYear:
    class: App\Models\LeapYear

  App\Controllers\LeapYearController:
    class: App\Controllers\LeapYearController
    arguments:
      $model: '@App\Models\LeapYear'

This was my original workaround which involves skipping the config file completely. You might still want to consider using it just to streamline your framework even further. No need for symfony/config or symfony/yaml with this approach.

$container = new ContainerBuilder();
//$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
//$loader->load('services.yml');

$container->register(LeapYear::class,LeapYear::class);

$container->register(LeapYearController::class, LeapYearController::class)
    ->setArguments([
        new Reference(LeapYear::class)
    ]);
Cerad
  • 48,157
  • 8
  • 90
  • 92
  • Both ways are working. When I did the second version (without yaml and config) I did the argument with 'App\Models\LeapYear' instead of LeapYear::class. This is awesome, thank you very very much – g9m29 Aug 07 '23 at 18:54
  • LeapYear::class is identical to 'App\Models\LeapYear'. Just a bit easier to type and it lets the IDE know you are dealing with a class. Reduces typos. – Cerad Aug 07 '23 at 19:35