2

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();
    }
5
  • I don't think that such concept exists in OOP. You could try playing around with traits but afaik those are not type-hintable... Commented Dec 5, 2016 at 10:20
  • Why should it be inaccessible? $testService is still an object of class TestService irregardless of that class implementing some interface. Commented Dec 5, 2016 at 11:34
  • 1
    Thanks, @JovanPerovic and yoshi. Well, in my constructor I'm waiting an object with ITestService contract. I should discard any methods that not exists in this contract. I worked with c# and castle windsor IoC framework and I could only use the methods defined in the interface. It make sense to me, but symfony component confuses me. Commented Dec 5, 2016 at 12:18
  • 2
    This is a PHP issue. Just the way interfaces and objects were implemented. Your IDE should at least prevent you from using non-interface methods. Commented Dec 5, 2016 at 13:54
  • 2
    PHP resolves methods to call at runtime. You don't need explicit downcasting of object classes such as in Java or C++. In fact, you don't need to specify type-hints at all: methods will work well. Type-hints are runtime checks, contracts for method signatures. Commented Dec 5, 2016 at 16:17

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.