Reputation: 33
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
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