Skip to content
Advertisement

Correct way to extend classes with Symfony autowiring

I’m wondering if this is the correct way to extend and use classes with Symfonies autowiring.

For example, I have a BaseClass that instantiates and auto wires the entity manager.

class BaseClass
{
  protected $entityManager;

  public function __construct(EntityManagerInterface $entityManager)
  {
    $this->entityManager = $entityManager;
  }

  protected function someMethodIWantToUse(Entity $something)
  {
    // Do something there
    $this->entityManager->persist($something);
    $this->entityManager->flush();
  }
}

Then I have a subclass that extends the BaseClass and needs access that method. So I let it autowire again and pass it to the parent constructor.

class SubClass extends BaseClass
{
  private $handler;

  public function __construct(EntityManagerInterface $em, SomeHandler $handler)
  {
    parent::__construct($em);
    $this->handler = $handler;
  }

  public function SubClassMethod()
  {
    // Get some data or do something
    $entity = SomeEntityIGot();
    $this->someMethodIWantToUse($entity);
  }
}

Now I’m wondering if this is actually the correct way to do this or there’s something I’m missing and the parent class should be able to autowire the entitymanager by itself?

Advertisement

Answer

To summarize the comments, yes your way is correct. Depending on your use case there are alternatives.

This are the ways you can go about it:

1. Extending Class and using Constructor Injection (what you do)

    class BaseClass {
        protected $some;

        public function __construct(SomeInterface $some)
        {
            $this->some = $some;
        }
    }

    class SubClass extends BaseClass {
        private $other;

        public function __construct(SomeInterface $some, OtherInterface $other)
        {
            parent::__construct($some);
            $this->other = $other;
        }
    }

2. Setter Injection

    class BaseClass {
        protected $some;

        public function __construct(SomeInterface $some)
        {
            $this->some = $some;
        }
    }

    class SubClass extends BaseClass {
        private $other;

        public function setOther(OtherInterface $other)
        {
            $this->other = $other;
        }
    }

Now setOther won’t automatically be called, you have to “manually” call it by either specifying a calls property in your services.yaml file, as described here: https://symfony.com/doc/current/service_container/calls.html. This would then look something like this:

// services.yaml
AppSubClass:
    calls:
        - [setOther, ['@other']]

Or

// services.yaml
app.sub_class:
    class: AppSubClass
    calls:
        - [setOther, ['@other']]

assuming, an implementation of OtherInterface is available as @other in the service container.

A more elegant solution if you’re using autowiring, simply add a @required annotation to the function as described here: https://symfony.com/doc/current/service_container/autowiring.html#autowiring-calls, which would look like this:

/**
 * @required
 */
public function setOther(OtherInterface $other)
{
    $this->other = $other;
}

3. Property Injection

    class BaseClass {
        protected $some;

        public function __construct(SomeInterface $some)
        {
            $this->some = $some;
        }
    }

    class SubClass extends BaseClass {
        public $other;
    }

As with the Setter Injection, you’ll need to tell Symfony to populate this property, by specifying it in your services.yaml file like this:

// services.yaml
AppSubClass:
    properties:
        other: '@other'

Or

// services.yaml
app.sub_class:
    class: AppSubClass
    properties:
        other: '@other'

assuming, an implementation of OtherInterface is available as @other in the service container.


Conclusion:
Since there are different ways to solve this, it’s up to you to determine the correct way for your use case. I personally go with either option 1 (Constructor Injection) or option 2 (Setter Injection) using the annotation. Both of them allow you to use typehints and thus allowing your IDE to help you write clean code.
In 90% of cases, I’d go with option 1, as then it’s clear for every one reading your code, what services are available with one glance at the __constructor function.
One use case for Setter Injection would be a base class offering all the setXXX functions but then sub classes not needing all of them. You could have a constructor in each sub class, requesting the needed services and then calling the setXXX methods of the base class.
Note: this is kind of an edge case and you probably won’t run into this.

You can find a list of advantages and disadvantages of each method directly in the Symfony documentation about the Service Container -> Types of Injection

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement