Skip to content

Update service_container.rst #12977

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 24, 2020
Merged

Update service_container.rst #12977

merged 1 commit into from
Jan 24, 2020

Conversation

l-vo
Copy link
Contributor

@l-vo l-vo commented Jan 23, 2020

Controller base class doesn't exist anymore in SF 5.0

Controller base class doesn't exist anymore in SF 5.0
@javiereguiluz
Copy link
Member

Good catch, thanks Laurent.

javiereguiluz added a commit that referenced this pull request Jan 24, 2020
This PR was merged into the 5.0 branch.

Discussion
----------

Update service_container.rst

Controller base class doesn't exist anymore in SF 5.0

Commits
-------

3d75d9a Update service_container.rst
@javiereguiluz javiereguiluz merged commit 3d75d9a into symfony:5.0 Jan 24, 2020
OskarStark added a commit that referenced this pull request Feb 19, 2020
…hDude)

This PR was squashed before being merged into the 4.4 branch.

Discussion
----------

[DependencyInjection] Fixed public service use case

Supersedes #12977 which was merged in 5.0, this time using a different approach and targeting 4.4.

Fixes #12978 and complements #12985.

Commits
-------

3694980 [DependencyInjection] Fixed public service use case
@debo
Copy link

debo commented Feb 20, 2020

@l-vo, @javiereguiluz, @HeahDude, @OskarStark People I'm sorry but neither of these two PRs addresses the real problem. Yes you removed the term Controller in favour of AbstractController which is a good start but the issue here is that the behaviour described in the public vs private mode section is not working. The service container exposed via AbstractController contains only a subset of the registered services which basically means that even if you make a service public you can't access it with the $this->container->get('mypublicservice') notation.

As I reported here the issue is deeper that a simple class name. If you check one of the two issues I linked in mine you'll see the user mentioning that they see something like the following:

"Service "mypublicservice" not found: even though it exists in the app's container, the container inside "MyController extending AbstractController" is a smaller service locator that only knows about the "http_kernel", "parameter_bag", "request_stack", "router", "session" and "twig" services. Try using dependency injection instead.

Does this clarify the problem better? In short making a service public as the documentation suggest doesn't work anymore using the AbstractController if it does, please show us how because the currently documented method doesn't.

Thanks a lot for you patience, help and understanding and also for the great work you all do.

@l-vo
Copy link
Contributor Author

l-vo commented Feb 20, 2020

@debo Indeed 🙂 But #13057 fix this problem, isn't it ?

@debo
Copy link

debo commented Feb 23, 2020

Hey @l-vo I'm not sure it does, or maybe it's just me being genuinely thick and not getting it. Even #13057 doesn't say: "no you can't do it it will never work" it still says that services are private by default but that you can override the visibility selectively and have it work but that's not the case. Maybe it's me missing something fundamental to get this to work... I don't know. I'm happy to show you some code if that helps.

The only obvious difference I'm seeing here is that in this PR you don't use the AbstractController and you are injecting the container via constructor, is that what does the trick?

Because this is what I'm trying to do at the moment:

<?php

namespace App\MyNameSpace\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class AboutController extends AbstractController
{
    public function indexAction(): Response
    {
        var_dump($this->container->get('mypublicservice'));die;
    }
}

Given this config:

# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
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: true

  # 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/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

  # controllers are imported separately to make sure services can be injected
  # as action arguments even if you don't extend any base controller class
  App\Controller\:
    resource: '../src/Controller'
    tags: ['controller.service_arguments']

  # add more service definitions when explicit configuration is needed
  # please note that last definitions always *replace* previous ones
  mypublicservice:
    class: App\MyNameSpace\Model\MyPublicService
    public: true

And I get the aforementioned issue.

As I said I'm happy to be wrong and put on the right direction. Also I do know it's better to use dependency injection rather than a service container but because I'm going through a big refactoring activity, being able to temporarily use the latter option will make my life much easier in the transition, if possible of course.

Thanks a lot for your time and help.

@HeahDude
Copy link
Contributor

Hello @debo, you have two options actually:

<?php

namespace App\MyNameSpace\Controller;

use Psr\Container\ContainerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class AboutController extends AbstractController
{
    // inject the "real" container into the action instead of using the locator
    public function indexAction(ContainerInterface $container): Response
    {
        var_dump($container->get('mypublicservice'));die;
    }
}

or

//...
use App\MyNameSpace\Model\MyPrivateService;
// ...

class AboutController extends AbstractController
{
    // override the locator subscribed services
    public static function getSubscribedServices()
    {
        return array_merge(parent::getSubscribedServices(), [
            MyPrivateService::class,
        ]);
    }

    public function indexAction(): Response
    {
        // access to subscribed private services without specific public config needed
        var_dump($this->get(MyPrivateService::class));die;
    }
}

I hope this helps. Thanks you for taking the time to explain your problem.

@debo
Copy link

debo commented Feb 23, 2020

Ha! There you go. Actually NO, I am the one who has to thank YOU for taking the time to listen and explain further, really appreciated.

If I may suggest, I think it should be made clear in the documentation that for the to work, and with this I mean the public vs private visibility, those two example must be adopted for it to work. Maybe having a section that says something along the lines of: "If you are coming from an earlier version of Symfony this is how you do it". Because this is exactly what I was missing. Thoughts?

@debo
Copy link

debo commented Feb 25, 2020

Hey @l-vo I have one more question for you if you don't mind. So the first option is the one that works better for me however I think I need a little further clarification on it.

<?php

namespace App\MyNameSpace\Controller;

use Psr\Container\ContainerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class AboutController extends AbstractController
{
    // inject the "real" container into the constructor.
    public function __construct(ContainerInterface $container)
    {
        // Just and example ideally I'd use a property to be then used in the action of course.
        var_dump($container->get('mypublicservice'));die;
    }

    // inject the "real" container into the action instead of using the locator
    public function indexAction(ContainerInterface $container): Response
    {
        var_dump($container->get('mypublicservice'));die;
    }
}

Given the above example, why if I inject the container in the action whilst the controller extends AbstractController everything works fine, but, if I instead inject it into the constructor it doesn't?

To be more specific and precise this is the error I get:

[critical] Uncaught PHP Exception InvalidArgumentException: "The controller for URI "/about" is not callable. Service "mypublicservice" not found: even though it exists in the app's container, the container inside "App\MyNameSpace\Controller\AboutController" is a smaller service locator that only knows about the "http_kernel", "parameter_bag", "request_stack", "router", "session" and "twig" services. Try using dependency injection instead." at [...]/vendor/symfony/http-kernel/Controller/ControllerResolver.php line 88

I'm confused because there is no contractor in the AbstractController and it's not extending anything else. What's going on behind the scenes that causes this behaviour? Any pointer that can help me understand better would be really appreciated.

@l-vo
Copy link
Contributor Author

l-vo commented Feb 25, 2020

@debo it's because the container injected in the constructor is a service locator that contains only services defined in Abstract controller::getSubscribedServices (see https://symfony.com/doc/current/service_container/service_subscribers_locators.html). To use your public service from the container injected in the constructor, you can add your service to getSuscribedEvents as suggested by HeahDude. Disabling service subscriber behaviour for this controller should work too:

# services.yaml
App\Controller\AboutController:
  autoconfigure: false
  tags: ['controller.service_arguments']

Hope this helps.

@debo
Copy link

debo commented Feb 25, 2020

Hey @l-vo apologies for summoning you instead of @HeahDude my bad. Thanks a lot for the further explanation/clarification and for pointing to the right part of the documentation, really really appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants