Documentation

Оглавление

Предыдущий раздел

< Список примеров

Следующий раздел

Архитектура MVC >

Эта страница

Использование внедрения зависимостей

Phalcon\Di — это компонент, реализующий паттерны Dependency Injection и Service Locator, и являющийся контейнером для зависимостей.

Поскольку Phalcon обладает низкой связанностью, Phalcon\Di необходимо обеспечить интеграцию различных компонентов фреймворка. Разработчики также могут использовать этот компонент для внедрения зависимостей и использования глобальных экземпляров различных классов, используемых в приложении.

В основе своей, компонент реализует паттерн Инверсии управления. Применяя его, объекты получают их зависимости не с использованием сеттеров или конструкторов, а с помощью сервиса внедрения зависимостей. Это снижает общую сложность, поскольку остаётся только один способ получения зависимостей в компоненте.

К тому же, этот паттерн увеличивает тестируемость в коде, что позволяет снизить “ошибочность” кода.

Регистрация сервисов в контейнере сервисов

Регистрация сервисов возможна как разработчиком, так и самим фреймворком. Когда компоненту A требуется компонент B (или экземпляр его класса) для работы, он может запросить его из контейнера, а не создавать новый экземпляр.

Такой способ работы даёт нам много преимуществ:

  • Мы можем легко заменять компонент на созданный нами или кем-то другим.
  • Мы обладаем полным контролем над инициализацией объекта, что позволяет нам настраивать эти объекты так, как нам необходимо, прежде, чем передать их компонентам.
  • Мы можем получать глобальный экземпляр компонента структурированным и унифицированным образом.

Зарегистрировать сервисы можно несколькими различными способами:

Простая регистрация

Как было показано выше, есть несколько способов регистрации сервисов. Следующие из них мы называем “простыми”:

Строка

Этот способ ожидает в качестве параметра имя существующего класса, возвращает его объект, если класс не был загружен автозагрузчиком. Такой способ не позволяет передавать аргументы для конструктора класса или настраивать параметры:

<?php

// Возвращает новый Phalcon\Http\Request();
$di->set(
    "request",
    "Phalcon\\Http\\Request"
);

Объект

Этот способ в качестве параметра принимает объект. Объект не нуждается в создании, потому как объект уже является объектом сам по себе. Вообще говоря, в данном случае это не является настоящим внедрением зависимости, однако такой способ вполне используем, если вы хотите быть уверены в том, что возвращаемая зависимость всегда будет одним и тем же объектом/значением:

<?php

use Phalcon\Http\Request;

// Возвращает новый Phalcon\Http\Request();
$di->set(
    "request",
    new Request()
);

Замыкания/Анонимные функции

Этот метод дает больше свободы для построения зависимости, если этого захотеть, тем не менее, он весьма сложен в плане изменения некоторых параметров извне без полного замещения определения зависимости:

<?php

use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;

$di->set(
    "db",
    function () {
        return new PdoMysql(
            [
                "host"     => "localhost",
                "username" => "root",
                "password" => "secret",
                "dbname"   => "blog",
            ]
        );
    }
);

Некоторые ограничения можно преодолеть путём передачи дополнительных переменных в область видимости замыкания:

<?php

use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;

$config = new Config(
    [
        "host"     => "127.0.0.1",
        "username" => "user",
        "password" => "pass",
        "dbname"   => "my_database",
    ]
);

// Использование переменной $config в текущей области видимости
$di->set(
    "db",
    function () use ($config) {
        return new PdoMysql(
            [
                "host"     => $config->host,
                "username" => $config->username,
                "password" => $config->password,
                "dbname"   => $config->name,
            ]
        );
    }
);

Вы также можете получить доступ к другим сервисам с помощью get() method:

<?php

use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;

$di->set(
    "config",
    function () {
        return new Config(
            [
                "host"     => "127.0.0.1",
                "username" => "user",
                "password" => "pass",
                "dbname"   => "my_database",
            ]
        );
    }
);

// Использование сервиса 'config' из DI
$di->set(
    "db",
    function () {
        $config = $this->get("config");

        return new PdoMysql(
            [
                "host"     => $config->host,
                "username" => $config->username,
                "password" => $config->password,
                "dbname"   => $config->name,
            ]
        );
    }
);

Сложная регистрация

Если потребуется изменить определение сервиса без создания экземпляра, тогда нам придётся определять его с использованием синтаксиса массивов. Такое определение может оказаться чуть более длинным:

<?php

use Phalcon\Logger\Adapter\File as LoggerFile;

// Регистрируем сервис 'logger' с помощью имени класса и его параметров
$di->set(
    "logger",
    [
        "className" => "Phalcon\\Logger\\Adapter\\File",
        "arguments" => [
            [
                "type"  => "parameter",
                "value" => "../apps/logs/error.log",
            ]
        ]
    ]
);

// Или в виде анонимной функции
$di->set(
    "logger",
    function () {
        return new LoggerFile("../apps/logs/error.log");
    }
);

Оба способа приведут к одинаковому результату. Определение же с помощью массива позволяет изменять параметры, если это необходимо:

<?php

// Изменяем названия класса для сервиса
$di->getService("logger")->setClassName("MyCustomLogger");

// Изменяем первый параметр без пересоздания экземпляра сервиса logger
$di->getService("logger")->setParameter(
    0,
    [
        "type"  => "parameter",
        "value" => "../apps/logs/error.log",
    ]
);

В дополнение к этому, используя синтаксис массивов, можно использовать три типа внедрения зависимостей:

Внедрение с помощью конструктора

Этот тип передаёт зависимости/аргументы в конструктор класса. Представим, что у нас есть следующий компонент:

<?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{
    /**
     * @var Response
     */
    protected $_response;

    protected $_someFlag;



    public function __construct(Response $response, $someFlag)
    {
        $this->_response = $response;
        $this->_someFlag = $someFlag;
    }
}

Сервис может быть зарегистрирован следующим образом:

<?php

$di->set(
    "response",
    [
        "className" => "Phalcon\\Http\\Response"
    ]
);

$di->set(
    "someComponent",
    [
        "className" => "SomeApp\\SomeComponent",
        "arguments" => [
            [
                "type" => "service",
                "name" => "response",
            ],
            [
                "type"  => "parameter",
                "value" => true,
            ],
        ]
    ]
);

Сервис “response” (Phalcon\Http\Response) передаётся в конструктор в качестве первого параметра, в то время как вторым параметром передаётся булевое значение (true) без изменений.

Внедрение с помощью сеттера

Классы могут иметь сеттеры для внедрения дополнительных зависимостей. Наш предыдущий класс может быть изменён, чтобы принимать зависимости с помощью сеттеров:

<?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{
    /**
     * @var Response
     */
    protected $_response;

    protected $_someFlag;



    public function setResponse(Response $response)
    {
        $this->_response = $response;
    }

    public function setFlag($someFlag)
    {
        $this->_someFlag = $someFlag;
    }
}

Сервис с сеттерами для зависимостей может быть зарегистрирован следующим образом:

<?php

$di->set(
    "response",
    [
        "className" => "Phalcon\\Http\\Response",
    ]
);

$di->set(
    "someComponent",
    [
        "className" => "SomeApp\\SomeComponent",
        "calls"     => [
            [
                "method"    => "setResponse",
                "arguments" => [
                    [
                        "type" => "service",
                        "name" => "response",
                    ]
                ]
            ],
            [
                "method"    => "setFlag",
                "arguments" => [
                    [
                        "type"  => "parameter",
                        "value" => true,
                    ]
                ]
            ]
        ]
    ]
);

Внедерение через свойства класса

Менее распространённым способом является внедрение зависимостей или полей класса напрямую:

<?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{
    /**
     * @var Response
     */
    public $response;

    public $someFlag;
}

Сервис с прямым внедрением может быть зарегистрирован следующим способом:

<?php

$di->set(
    "response",
    [
        "className" => "Phalcon\\Http\\Response",
    ]
);

$di->set(
    "someComponent",
    [
        "className"  => "SomeApp\\SomeComponent",
        "properties" => [
            [
                "name"  => "response",
                "value" => [
                    "type" => "service",
                    "name" => "response",
                ],
            ],
            [
                "name"  => "someFlag",
                "value" => [
                    "type"  => "parameter",
                    "value" => true,
                ],
            ]
        ]
    ]
);

Поддерживаются параметры следующих типов:

Тип Описание Пример
parameter Буквенное значение, передаваемое в качестве параметра ["type" => "parameter", "value" => 1234]
service Другой сервис в контейнере ["type" => "service", "name" => "request"]
instance Объект, который должен создаваться динамически ["type" => "instance", "className" => "DateTime", "arguments" => ["now"]]

Получение сервисов, определение которых весьма сложно может быть немного медленнее, чем рассмотренные выше определения. Однако, это предоставляет больше возможностей для определения и внедрения сервисов.

Можно совмещать различные типы определения, определяя для себя наиболее подходящий способ регистрации сервиса в соответствии с потребностями приложения.

Array Syntax

Для регистрации сервисов можно также использовать синтаксис массивов:

<?php

use Phalcon\Di;
use Phalcon\Http\Request;

// Создем контейнер DI
$di = new Di();

// По названию класса
$di["request"] = "Phalcon\\Http\\Request";

// С использованием анонимной функции для отложенной загрузки
$di["request"] = function () {
    return new Request();
};

// Регистрация экземпляра напрямую
$di["request"] = new Request();

// Определение с помощью массива
$di["request"] = [
    "className" => "Phalcon\\Http\\Request",
];

В примере, данном выше, когда фреймворк нуждается в доступе к запрашиваемым данным, он будет запрашивать в контейнере сервис, названный ‘request’. Контейнер, в свою очередь, возвращает экземпляр требуемого сервиса. Разработчик, в конечном итоге, может заменить компонент, когда захочет.

Каждый из методов регистрации сервисов имеет свои достоинства и недостатки. Какой из них использовать — зависит только от разработчика и от конкретных требований.

Назначить сервис строкой просто, но это лишает гибкости. В качестве массива — предоставляет большую гибкость, но делает код более сложным. Анонимные функции неплохо балансируют между этими двумя способами, но им может потребоваться больше обслуживания, чем это ожидается.

Phalcon\Di предоставляет отложенную загрузку для каждого хранимого им сервиса. Если разработчик не решит создавать экземпляр объекта напрямую и хранить его в контейнере, любой объект сохранённый в нём (через массив, строку и т.д.) будет загружен отложенно (lazy load), т.е. создастся только тогда, когда будет востребован.

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

Получение сервиса из контейнера очень просто производится вызовом метода “get”. Будет возвращен новый экземпляр сервиса:

<?php $request = $di->get("request");

Также можно вызвать магический метод:

<?php

$request = $di->getRequest();

Или использовать доступ как к массиву:

<?php

$request = $di["request"];

Аргументы могут быть переданы в конструктор добавлением массива параметров в метод “get”:

<?php

// новый MyComponent("some-parameter", "other")
$component = $di->get(
    "MyComponent",
    [
        "some-parameter",
        "other",
    ]
);

События

Phalcon\Di может посылать события в EventsManager, если таковой имеется. События вызываются с типом “di”. Некоторые события, при возвращении значения false, могут прервать текущее действие. Поддерживаются следующие события:

Название события Когда вызывается Можно ли прервать действие? Срабатывает на
beforeServiceResolve Вызывается до разрешения сервиса (service resolve). Слушатели (listeners) получают название сервиса и переданные ему параметры. Нет Слушателях
afterServiceResolve Вызывается после разрешения сервиса. Слушатели получают название сервиса, экземпляр и переданные ему параметры. Нет Слушателях

Совместный доступ к сервисам

Сервисы могут быть зарегистрированы, как предназначенные для совместного (“shared”) доступа. Это означает, что они всегда будут синглтонами. После того, как этот сервис будет один раз создан, всегда будет возвращаться тот же самый его экземпляр:

<?php

use Phalcon\Session\Adapter\Files as SessionFiles;

// Регистрируем сервис сессий для совместного доступа
$di->setShared(
    "session",
    function () {
        $session = new SessionFiles();

        $session->start();

        return $session;
    }
);

// Создает сервис в первый раз
$session = $di->get("session");

// Возвращает первоначальный экзмепляр объекта
$session = $di->getSession();

Также можно зарегистрировать сервис с совместным доступом, передав “true” в качестве третьего параметра метода “set”:

<?php

// Регистрация сервиса сессий для совместного доступа
$di->set(
    "session",
    function () {
        // ...
    },
    true
);

Если сервис не был зарегистрирован для общего доступа, и вы хотите всё же получать один и тот же экземпляр каждый раз, то можно получать его, используя метод DI “getShared”:

<?php

$request = $di->getShared("request");

Ручное управление сервисами

После того, как сервис был зарегистрирован в контейнере, вы можете управлять им вручную:

<?php

use Phalcon\Http\Request;

// Регистрируем сервис "request"
$di->set("request", "Phalcon\\Http\\Request");

// Получем сервис
$requestService = $di->getService("request");

// Изменяем его определение
$requestService->setDefinition(
    function () {
        return new Request();
    }
);

// Делаем его общим
$requestService->setShared(true);

// Разрешаем сервис (возвращает экземпляр Phalcon\Http\Request)
$request = $requestService->resolve();

Создание экземпляров классов через контейнер сервисов

Когда вы запрашиваете какой-то сервис из контейнера, и он не может найти его по такому имени, контейнер пытается загрузить класс с таким же названием. С помощью этого вы можете легко заменить какой-либо класс на любой другой, зарегистрировав сервис с таким же названием:

<?php

// Регистрируем контроллер как сервис
$di->set(
    "IndexController",
    function () {
        $component = new Component();

        return $component;
    },
    true
);

// Регистрируем компонент как сервис
$di->set(
    "MyOtherComponent",
    function () {
        // Actually returns another component
        $component = new AnotherComponent();

        return $component;
    }
);

// Создаем экземпляр объекта с помощью контейнера сервисов
$myComponent = $di->get("MyOtherComponent");

Вы можете пользоваться этим, всегда создавая экземпляры объектов ваших классов с помощью контейнера сервисов (даже если они не регистрировались как сервисы). DI будет запускать правильный автозагрузчик для того, чтобы в итоге загрузить класс. Делая так, вы сможете легко заменить любой класс в будущем, реализовав его определение.

Автоматическое внедрение DI

Если класс или компонент требует DI для нахождения сервисов, DI может автоматически внедрить себя в экземпляры этих компонентов или объектов, чтобы сделать это вам необходимо реализовать Phalcon\Di\InjectionAwareInterface в своём классе:

<?php

use Phalcon\DiInterface;
use Phalcon\Di\InjectionAwareInterface;

class MyClass implements InjectionAwareInterface
{
    /**
     * @var DiInterface
     */
    protected $_di;



    public function setDi(DiInterface $di)
    {
        $this->_di = $di;
    }

    public function getDi()
    {
        return $this->_di;
    }
}

Когда сервис будет запрошен, $di будет передан в setDi() автоматически:

<?php

// Регистрируем сервис
$di->set("myClass", "MyClass");

// Получаем сервис (ВНИМАНИЕ: $myClass->setDi($di) вызовется автоматически)
$myClass = $di->get("myClass");

Размещение сервисов в файлах

Вы можете улучшить организацию вашего приложения переместив регистрацию сервисов в отдельные файлы, которые делают всё, что происходит при старте приложения:

<?php

$di->set(
    "router",
    function () {
        return include "../app/config/routes.php";
    }
);

А файл (”../app/config/routes.php”) вернёт готовый объект:

<?php

$router = new MyRouter();

$router->post("/login");

return $router;

Статический доступ к DI

При необходимости вы можете получить доступ к последнему созданному DI в статической функции следующим образом:

<?php

use Phalcon\Di;

class SomeComponent
{
    public static function someMethod()
    {
        // Получаем сервис сессий
        $session = Di::getDefault()->getSession();
    }
}

Factory Default DI

Несмотря на то, что разрозненный характер Phalcon дарит нам огромную свободу и гибкость, возможно мы захотим легко использовать полноценный фреймворк. Для достижения этой цели фреймворк предоставляет вариант Phalcon\Di, называющийся Phalcon\Di\FactoryDefault. Этот класс автоматически регистрирует такие сервисы, которые обычно определены в полноценном фреймворке.

<?php

use Phalcon\Di\FactoryDefault;

$di = new FactoryDefault();

Соглашение именования сервисов

Хотя, вы и можете регистрировать сервисы с любыми именами, какие вам только понравятся, Phalcon имеет некоторое соглашение именования сервисов, что позволяет ему правильно работать с сервисами, когда они вам необходимы.

Название сервиса Описание По умолчанию Общий доступ
dispatcher Диспетчер контроллеров Phalcon\Mvc\Dispatcher Да
router Маршрутизатор Phalcon\Mvc\Router Да
url Генератор URL’ов Phalcon\Mvc\Url Да
request Окружение HTTP запросов Phalcon\Http\Request Да
response Окружение HTTP ответов Phalcon\Http\Response Да
cookies Сервис управления HTTP Cookies Phalcon\Http\Response\Cookies Да
filter Входной фильтр Phalcon\Filter Да
flash Всплывающие сообщения Phalcon\Flash\Direct Да
flashSession Сессия всплывающих сообщений Phalcon\Flash\Session Да
session Сессия Phalcon\Session\Adapter\Files Да
eventsManager Управление событиями Phalcon\Events\Manager Да
db Низкоуровневый коннектор к базе данных Phalcon\Db Да
security Помощник безопасности Phalcon\Security Да
crypt Шифрование/Дешифрование данных Phalcon\Crypt Да
tag генератор HTML конструкций Phalcon\Tag Да
escaper Контекстное экранирование Phalcon\Escaper Да
annotations Парсер аннотаций Phalcon\Annotations\Adapter\Memory Да
modelsManager Управление моделями Phalcon\Mvc\Model\Manager Да
modelsMetadata Мета-данные моделей Phalcon\Mvc\Model\MetaData\Memory Да
transactionManager Управление транзакциями моделей Phalcon\Mvc\Model\Transaction\Manager Да
modelsCache Кэширование для моделей Нет Нет
viewsCache Кэширование для частичных представлений Нет Нет

Реализация собственного DI

Для создания собственного DI необходимо реализовать интерфейс Phalcon\DiInterface, или использовать наследование и переопределить стандартный компонент Phalcon.

Follow along: