dvdsantana
dvdsantana

Reputation: 33

How to inject interfaces (not class) in symfony3?

I have a problem with Symfony DependencyInjection Component. I want to inject interfaces into controllers, so I could only use interface methods. But, I notice I can use any public method from class that implement the interface and this is wrong. I follow the great article: http://php-and-symfony.matthiasnoback.nl/2014/05/inject-a-repository-instead-of-an-entity-manager/

Write the test service class and interface

interface ITestService
{
    public function interfaceFunction();
}

class TestService implements ITestService
{
    public function interfaceFunction() {/* do somenthing */}

    public function classFunction() {/*do somenthing*/}
}

Configure my application service class as a service (test_service)

# file: app/config/services.yml
test_service:
    class: MyApp\Application\Services\TestService

Configure my controller as a service:

# file: app/config/services.yml
test_controller:
    class: MyApp\AppBundle\Controller\TestController
    arguments:
        - '@test_service'

Using service in controller

class TestController extends Controller
{
    private testService;

    function _construct(ITestService $testService)
    {
        $this->testService = $testService;
    }

    public function indexAction()
    {
        // This should be inaccesible but it works :(
        $this->testService->classFunction();

        // This is the only function I should use.
        $this->testService->interfaceFunction();
    }

Upvotes: 2

Views: 3010

Answers (1)

JavierCane
JavierCane

Reputation: 2412

As @Timurib says, this is because despite having Type Hintings, PHP doesn't evaluate the methods to call until runtime. This could be seen as something undesirable, but it allows to use some technics such as Duck Typing.

Here you have a simplified example based on the one you're providing (it doesn't put the Symfony Container into the mix, because this is something purely related to PHP). You can run it on 3v4l.org:

interface IService
{
    public function interfaceFunction();
}

final class ServiceWithOtherFunction implements IService
{
    public function interfaceFunction() { echo "ServiceWithOtherFunction interfaceFunction\n"; }

    public function otherFunction() { echo "ServiceWithOtherFunction otherFunction\n"; }
}

final class Controller
{
    private $service;

    public function __construct(IService $service)
    {
        $this->service = $service;
    }

    public function indexAction()
    {
        $this->service->interfaceFunction();

        $this->service->otherFunction();
    }
}

$controllerWithOtherFunction = new Controller(new ServiceWithOtherFunction);

$controllerWithOtherFunction->indexAction();

Output:

ServiceWithOtherFunction interfaceFunction
ServiceWithOtherFunction otherFunction

But when we inject another implementation that does not contains the otherFunction, the code throws an Error at runtime:

final class ServiceWithoutOtherFunction implements IService
{
    public function interfaceFunction() { echo "ServiceWithoutOtherFunction interfaceFunction\n"; }
}

$controllerWithoutOtherFunction = new Controller(new ServiceWithoutOtherFunction);

$controllerWithoutOtherFunction->indexAction();

Output:

ServiceWithoutOtherFunction interfaceFunction

Fatal error: Uncaught Error: Call to undefined method ServiceWithoutOtherFunction::otherFunction() in /in/mZcRq:28
Stack trace:
#0 /in/mZcRq(43): Controller->indexAction()
#1 {main}
  thrown in /in/mZcRq on line 28

Process exited with code 255.

If you're going towards the use of interfaces, DI, and DIC, you should not call any public method rather than the exposed by the interface. This is the only way to really take advantadge of the benefits of having an interface: Decoupling from the implementation details, and be able to change the class to be injected without changing anything inside your Controller.

Upvotes: 3

Related Questions