Как внедрить интерфейсы (не класс) в symfony3?

У меня проблема с компонентом Symfony DependencyInjection. Я хочу внедрить интерфейсы в контроллеры, чтобы я мог использовать только методы интерфейса. Но я заметил, что могу использовать любой открытый метод из класса, который реализует интерфейс, и это неправильно. Я следую за замечательной статьей: http://php-and-symfony.matthiasnoback.nl/2014/05/inject-a-repository-instead-of-an-entity-manager/

Напишите тестовый класс обслуживания и интерфейс

interface ITestService
{
    public function interfaceFunction();
}

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

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

Настройте мой класс обслуживания приложения как службу (test_service)

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

Настройте мой контроллер в качестве службы:

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

Использование сервиса в контроллере

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();
    }

1 ответ

Решение

Как говорит @Timurib, это потому, что, несмотря на наличие подсказок типов, PHP не оценивает методы, вызываемые до времени выполнения. Это может рассматриваться как нечто нежелательное, но это позволяет использовать некоторые приемы, такие как Duck Typing.

Здесь у вас есть упрощенный пример, основанный на том, который вы предоставляете (он не помещает Symfony Container в смесь, потому что это что-то чисто связанное с PHP). Вы можете запустить его на 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();

Выход:

ServiceWithOtherFunction interfaceFunction
ServiceWithOtherFunction otherFunction

Но когда мы внедряем другую реализацию, которая не содержит otherFunctionкод бросает Error во время выполнения:

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

$controllerWithoutOtherFunction = new Controller(new ServiceWithoutOtherFunction);

$controllerWithoutOtherFunction->indexAction();

Выход:

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.

Если вы собираетесь использовать интерфейсы, DI и DIC, вам не следует вызывать какие-либо открытые методы, а не предоставляемые интерфейсом. Это единственный способ по-настоящему воспользоваться преимуществами наличия интерфейса: отделение от деталей реализации и возможность изменения класса для внедрения без каких-либо изменений внутри вашего Controller,

Другие вопросы по тегам