Pull to refresh

Comments 35

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

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

Вопрос — инициализаторы вызываются для всех сервисов?
да.

 public function doCreate($rName, $cName) {
// ..
        foreach ($this->initializers as $initializer) {
            if ($initializer instanceof InitializerInterface) {
                $initializer->initialize($instance, $this);
            } else {
                call_user_func($initializer, $instance, $this);
            }
        }
}
еще вопрос, что именно за скобками? это нужно учесть и исправить в следующем посте ;)
Может быть не хватает чуть более подробного введения и описания общего алгоритма, по которому создаются сервисы. «АФ — это последняя попытка СМ создать запрашиваемый сервис.» Вот на этой фразе закрадывается мысль, что наверное Service Locator проходит по всем сервисам в каком-то определенном порядке. Но об этом вы наверное собирались написать во второй части.
А еще во второй части можно было написать, для чего вообще СМ нужен, про IoC, про вынесение абстракции «наверх» и удобство тестирования, ну и про минусы, разумеется, т.к. Service Locator активно критикуют и в ZF3 вроде намечены какие-то изменения на этот счет.
Еще вопрос: Как решить вопрос с автодополнеием имен сервисов при обращении к ним? Даже в относительно небольшом проекте сервисов набирается не один десяток, держать каждый из них в голове не получается, приходится каждый раз сверяться с конфигом.
По моему — это вопрос правильного наименования, в статье не зря есть «best practices».
Хорошей практикой является добавления неймспейса модуля к названию сервиса. Либо использовать абсолютное название класса сервиса.


Я делаю через второй способ:
// В конфиге:
namespace User;
....
    'service_manager' => [
        'factories'  => [
            Service\RoleService::class       => Factory\Service\RoleServiceFactory::class,
        ],
    ],

//В контроллере:
use User\Service\RoleService;
......
public function indexAction() {
    //При вводе Ro -> выскакивает автодополнение IDE для класа RoleService
    // А в PhpStorm даже без указания use вверху (потом после ввода класа шторм сам добавит нужный use)

    $roleService = $this->getServiceLocator()->get(RoleService::class);

    // А для автодополнения $roleService приходиться указывать PhpDoc @var
}


Ну естественно конструкция "::class" ограничивает совместимость — только от PHP 5.5.
Зато удобно, привыкаешь в мгновение ока… )
Удобно, но нужно ли autoloader'у подгружать каждый класс в конфиге?
Это извечная борьба между производительностью и удобностью разработки. )

Ну в конфиге можно не пользоваться "::class" и ручками полное имя вписывать «User\Service\RoleService» (сначала сделать вид, что пишешь имя класса — IDE дополнит его, а потом обернуть в кавычки). И волки сыты, и овцы целы )
Все будет хорошо, ::class не дергает автолоадер, это скорее такой макрос, чисто технически просто разворачивает в строку с полным неймспейсом.

$ php -r 'namespace bla; var_dump(foo\bar::class);'
string(11) «bla\foo\bar»
wow. Вот это новость, спасибо. Оказывается даже `use Namespace\Class` не дергает автозагрузчик :)
use Library\Mvc\Controller;

/**
 * @property \Library\Acl         $acl
 * @property \Library\Session     $session
 * @property \Library\Mvc\Request $request
 */
abstract class ControllerAbstract extends YourFrameworkControllerAbstract {

}


или

/**
 * @property \Library\Acl         $acl
 * @property \Library\Session     $session
 * @property \Library\Mvc\Request $request
 */
interface ServicesDummy {

}

abstract class ControllerAbstract extends YourFrameworkControllerAbstract implements ServicesDummy {

}
При условии, что запрос к сервис-менеджеру пробрасывается через «магический» __get()
можно все в трейт запихнуть
/**
 * @return \Library\Acl
 */
protected function getAclService(){}
//...
таким образом и метод можно определить (выдержка из зенда):
/**
 * Abstract controller
 *
 * Convenience methods for pre-built plugins (@see __call):
 *
 * [....]
 * @method mixed|null identity()
 * @method \Zend\Http\Response|array prg(string $redirect = null, bool $redirectToUrl = false)
 * @method \Zend\Http\Response|array postRedirectGet(string $redirect = null, bool $redirectToUrl = false)
 * @method \Zend\Mvc\Controller\Plugin\Redirect redirect()
 * @method \Zend\Mvc\Controller\Plugin\Url url()
 */
abstract class AbstractController
у меня все просто:
/* @var $user User */
$user = $sm->get('User');
ну вот я про то, как узнать, что он именно User, а не какой-нибудь doctrine.orm_default.bla-bla-bla
Вариант с именем класса сервиса мне показался элегантным ) Ну и именовать надо конечно по общему правилу.
так все же элементарно. заведомо известно, что придет этот тип. как вариант, можно брать интерфейс, абстрактный класс или общий родитель. но я придерживаюсь того, что бы сервис возвращал один единственный тип. считаю, это хорошим тоном и тогда описанная вами ситуация не может существовать ;)
То есть в обьект сервиса будет создан даже, при отсутствии его вызовов? Или в случае с Invokable и Factory СМ все же дождется вызова и тогда создаст обьект?
ждет вызова, тогда создает. но только не в случае, когда сервис установлен вручную:
$sm->setService('a', new A);
Странно как то. Вызывать при каждом запросе кучу кода вида if $service instanceof InterfaceName в случае с инициализаторами.
Вообще спасибо вам за статью, до неё я немного переживал что не пробовал Zend Framework толком, всё на Symfony2 сижу. Сейчас я вижу что как минимум компонент Symfony Dependency Injection спроектирован лучше чем аналогичный в Zend.
Не совсем правильный вывод. Здесь речь идет о сервис менеджере, который по-сути реестр с «ручным» DI. Для DI есть более харкорный компонент — framework.zend.com/manual/2.3/en/modules/zend.di.introduction.html который работает через Reflection API или конфиг и там уже настоящая магия. Он более похож на Symfony DI, чем Service Manager.
Тоже хотел заметить, что Service Locator лишь один из паттернов, решающих задачу DI, есть в ZF2 и другой вариант, про который вы упомянули. Но комментатор видимо имел в виду, что те сервисы, которые есть в Symfony более легковесны: symfony.com/doc/current/components/dependency_injection/configurators.html
В Symfony DI контейнер компилируемый — то есть многие из операций выполняются один раз при прогреве кеша (или компиляции контейнера). И в рантайме, например, не используется обход всех инициализаторов, дабы понять какой из них может проинициализировать этот сервис. Контейнер компилируется в очень оптимальный с точки зрения производительности код.
Причём ручное DI также можно использовать — но оно будет не быстрое.

В Zend DI (ServiceLocator) есть что-то похожее?
Конечно:
if (!file_exists(__DIR__ . '/di-definition.php')) {
   $compiler = new Zend\Di\Definition\Compiler();
   $compiler->addCodeScannerDirectory(
       new Zend\Code\Scanner\ScannerDirectory('path/to/library/My/')
   );
   $definition = $compiler->compile();
   file_put_contents(
       __DIR__ . '/di-definition.php',
       '<?php return ' . var_export($definition->toArray(), true) . '?>;'
   );
} else {
   $definition = new Zend\Di\Definition\ArrayDefinition(
       include __DIR__
       span style= . '/di-definition.php'
   );
}


просканирует классы в заданой папке и сгенерирует массив, который уже можно сохранить.

Больше информации про Di.
При старте любого PHP-фреймворка вообще происходит каждый раз много всего лишнего, чего не хотелось бы делать при каждом запросе. И уж проверка incanceof едва ли окажется узким местом приложения. На эту тему была тут статья «PHP должен умирать» (кто-то дал неверный перевод «умереть»), в том смысле, что при каждом запросе весь цикл инициализации приходится проходить заново. Кстати, была интересная статья с экспериментом, решающим эту проблему: habrahabr.ru/post/220393/
Многие все же выбирают удобство и скорость разработки, а оптимизацией занимаются, когда того требует ситуация.
У таких экспериментов большая проблема — придется отказаться от стандартных средств работы с сетевыми демонами и протоколами (скажем PDO) и писать свои неблокирующие аналоги (начиная с самого низкого уровня — mysql-протокола и т.п.), которые умеют работать в event loop-е. Да, такие наработки частично есть в ReactPHP и PHPDaemon — но тем не менее, это требует отказа от готового кода, стандартных фреймворков и библиотек.

Написать что-то специализированное (демон для работы с вебсокетами, скажем) — да, так можно. Портировать крупное приложение — нереально практически.
Напишите еще о последовательности, в которой загружаются сервисы определенные в module.config.php и Module.php.

Так, например, если в сервисе используется фабрика для построения навигации, внутри которой хочется проверить авторизацию, а авторизация это тоже сервис, то, в зависимоти от того, где какой сервис определен, зависит — потратите ли вы лишние полчаса на WTF или нет.
оно, в принципе, тут упомянуто. все сервисы, не важно где объявлены, в конфиге или в модуле, сперва будут смержены. т.е. когда нужно будет дернуть авторизацию из навигации, оба сервиса уже будут доступны в СМ.
Я сталкивался с проблемой, которую описал неоднократно.

При чем, по моим наблюдениям, так же отличается приоритет сервисов объявленных через фабрику и через замыкание. У замыкания приоритет ниже, они идут после фабрик. Привожу пример

Вот рабочий вариант фабрики для навигации, внутри фабрики проверяется авторизация из сервиса Auth.
Так работает в Module.php.

'index_navigation'                      => function (\Zend\ServiceManager\ServiceManager $sm) {

                    $navigationF = new Navigation\Service\IndexNavidationFactory();
                    $navigation  = $navigationF->createService($sm);

                    return $navigation;
                },


А вот так уже не работает.
'index_navigation' => new Navigation\Service\IndexNavidationFactory(),


Выдается ошибка внутри фабрики о том, что не найден Auth сервис.
Та же ошибка, если переместить в module.config.php

'index_navigation' => 'Application\Navigation\Service\IndexNavigationFactory'


Да, вы правы, оно мержится в итоге, но важно в каком порядке объявлены модули, где объявлен сервис и (тут я могу ошибаться, но пример выше показателен) как он объявлен.

Где-то на немецком ресурсе я видел схему того в каком порядке все мержится. Вот тут.

Именно из-за таких нюансов, раз уж вы взялись писать, я прошу вас написать и о порядке загрузки по русски.
1. покажите код фабрики;
2. код регистрации в Module.php и module.config.php
3. как регистрируется Auth в приложении и его код тоже.
разберемся, думаю.
У вас ошибка.

СМ реалирует


Наверное имелось в виду «реализует».
Sign up to leave a comment.

Articles