0

I need to use a service by value in an "Utils" Class method called from a controller which implements AbstractControllerInterface.

I have an entity in my project which have some mappings. Some of them have a Utils Class. Some of them implement myInterface.

I have a loop to check every one of those mapping fields, get each correct utils class name, check for its existence and the implementation of myInterface.

In that case I want to instantiate that class (or use them as a service) and call my method.

Every Util class can have different constructor parameters and I thought Autowiring could help.

I can't pass the container and use container get method because the AbstractControllerInterface implementation and can't just make "new $myClassName" because I don't know how every construct definition in each Utils class can be.

my service definition:

App\Utils\MyUtilsClass:
  class:  App\Utils\MyUtils
  public: true
  arguments:
    - '@doctrine.orm.entity_manager'
    - '@App\Helper\MyHelper'

part of my code:

# src\Utils\MyUtilsClass.php

namespace App\Utils
use Doctrine\ORM\EntityManagerInterface;
class MyUtilsClass{
    protected $em;

    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }
    
    # $name = 'App\\Utils\\MyOtherUtilsClass'
    public function myMethod($name)
    {
        $containerBuilder = new ContainerBuilder();
        $fileLocator      = new FileLocator(__DIR__ . '/../../config');
        $loader           = new YamlFileLoader($containerBuilder, $fileLocator);
        $loader->load('services.yaml');
        $containerBuilder->compile();

        $utils = $containerBuilder->get($name);

This throws me "service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead."

Edit: Trying to make it public throws a non-existent service exception witdDoctrine\ORM\EntityManagerInterface in my container

public function myMethod($name) 
{
   ....
   $containerBuilder->getDefinition($claseUtils)->setPublic(true);

   $utils = $containerBuilder->get($name);

Every help is appreciated and sorry about my bad English.

Thank you.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
  • I can be a bit wrong about your purpose, but it looks like you need to take a look at [compiler passes](https://symfony.com/doc/current/service_container/compiler_passes.html) because they would allow you to prepare all required service definitions at a time of container building and avoid wasting resources on container building in runtime. – Flying Jun 27 '19 at 09:13
  • Thanks Flying! But it seems to change my service status to public, for example... Anyway, i can't access to that service because of the lack of container->get method If i do: $containerBuilder->getDefinition($claseUtils)->setPublic(true); inside my method it throws me a non-existent service exception Maybe i missunderstood your answer – Rodrigo Lozano Jun 27 '19 at 09:53
  • Main point of my comment is that you need to move this services definition logic into compiler pass rather then keeping it in runtime because it brings large overhead. Inside compiler pass you have access to actual `ContainerBuilder` and can do anything you want to do with service definition. Your approach for marking service as `public` should be done **before** compiling container, please make sure that it is done in this way – Flying Jun 27 '19 at 09:57
  • Hello again, after calling getDefinition before the compile it runs a little bit more... but (i think) because i'm setting the containerBuilding in a class and importing services.yaml, i'm getting a service not exists error with Doctrine\ORM\EntityManagerInterface... wich i use in the constructor (edited and added to example) – Rodrigo Lozano Jun 27 '19 at 10:37
  • Once again, please try to follow my advice and consider using compiler passes instead. Container compilation is very intensive process and it should not be done in runtime. Your new issue with "service not found" is obvious - you're creating new `ContainerBuilder` and it, obviously contains no `EntityManager` inside. In other words if you will keep going in this way - you will end up re-adding most of services into your `ContainerBuilder` and compilation time will be huge. – Flying Jun 27 '19 at 10:40
  • Thank you for your help, I'll check it, but i have some questions about this: What is the diference between defining services as public in a yaml file and doing it with a compiler pass? i don't need to change its definitions. Will it use autowire? How do i get that service inside my method without a container object? – Rodrigo Lozano Jun 27 '19 at 10:47
  • 1
    1. It doesn't matter where to define service at all, every definition ends up into instance of `Definition` class. 2. Autowiring process is also a compiler pass that runs at later stage of compilation so it will use every service definition that will be available at `ContainerBuilder` by this time no matter where it came from. 3. You can inject container directly, or, better, you can inject all your services into your `MyUtilsClass` class as `['name'=>$instance]` map. Check `Definition::addArgument()` for details – Flying Jun 27 '19 at 10:57
  • Thank you very much Flying. I don't like the addition of every service "by hand" on MyClass definition, tested it and works, but i don't like that and i'm triying to avoid it. Once again, thank you soooo much ^_^ – Rodrigo Lozano Jun 27 '19 at 11:26
  • You don't need to add every service by hand. Instead you need to create compiler pass that will take definition of your `MyClass` service and calls `addArgument()` with list of service references. It will tell container compiler to create this list for you. You can consider marking required services with tag or use some other query approach to identify services that needs to be passed. In this case you will also not need to mark them as `public` because they will be used and hence will not get removed from container. – Flying Jun 27 '19 at 11:31
  • Not positive but I think you are asking for a [Service Locator](https://symfony.com/doc/current/service_container/service_subscribers_locators.html#defining-a-service-locator). A service locator acts as a container but only provides access to specific services. It is actually what the AbstractController class uses to access it's services. And definitely do not try to build a container at runtime. – Cerad Jun 27 '19 at 12:05
  • Hello everyone, i've finally managed this by overriding `getSubscribedServices` method in my controller, returning an array_merge of both `parent::getSubscribedServices()` and my custom array. ¿how bad is this? (and sorry for this almost a year late response) – Rodrigo Lozano May 07 '20 at 05:29

0 Answers0