Comments 35
Спасибо, полезно. Отдельно порадовало упоминанию нюанса с замыканиями и кэшированием. Первый раз с этим сталкиваешься, когда уже все написано и дошло дело до оптимизации. И тут начинается утомительный процесс вынесения всех замыканий в отдельные классы.
Правда для новичков, мне кажется, вы погорячились, слишком многое остается за скобками :)
Вопрос — инициализаторы вызываются для всех сервисов?
Правда для новичков, мне кажется, вы погорячились, слишком многое остается за скобками :)
Вопрос — инициализаторы вызываются для всех сервисов?
0
да.
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);
}
}
}
0
еще вопрос, что именно за скобками? это нужно учесть и исправить в следующем посте ;)
0
Может быть не хватает чуть более подробного введения и описания общего алгоритма, по которому создаются сервисы. «АФ — это последняя попытка СМ создать запрашиваемый сервис.» Вот на этой фразе закрадывается мысль, что наверное Service Locator проходит по всем сервисам в каком-то определенном порядке. Но об этом вы наверное собирались написать во второй части.
А еще во второй части можно было написать, для чего вообще СМ нужен, про IoC, про вынесение абстракции «наверх» и удобство тестирования, ну и про минусы, разумеется, т.к. Service Locator активно критикуют и в ZF3 вроде намечены какие-то изменения на этот счет.
А еще во второй части можно было написать, для чего вообще СМ нужен, про IoC, про вынесение абстракции «наверх» и удобство тестирования, ну и про минусы, разумеется, т.к. Service Locator активно критикуют и в ZF3 вроде намечены какие-то изменения на этот счет.
0
Еще вопрос: Как решить вопрос с автодополнеием имен сервисов при обращении к ним? Даже в относительно небольшом проекте сервисов набирается не один десяток, держать каждый из них в голове не получается, приходится каждый раз сверяться с конфигом.
0
По моему — это вопрос правильного наименования, в статье не зря есть «best practices».
Я делаю через второй способ:
Ну естественно конструкция "::class" ограничивает совместимость — только от PHP 5.5.
Зато удобно, привыкаешь в мгновение ока… )
Хорошей практикой является добавления неймспейса модуля к названию сервиса. Либо использовать абсолютное название класса сервиса.
Я делаю через второй способ:
// В конфиге:
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.
Зато удобно, привыкаешь в мгновение ока… )
+1
Удобно, но нужно ли autoloader'у подгружать каждый класс в конфиге?
0
Это извечная борьба между производительностью и удобностью разработки. )
Ну в конфиге можно не пользоваться "::class" и ручками полное имя вписывать «User\Service\RoleService» (сначала сделать вид, что пишешь имя класса — IDE дополнит его, а потом обернуть в кавычки). И волки сыты, и овцы целы )
Ну в конфиге можно не пользоваться "::class" и ручками полное имя вписывать «User\Service\RoleService» (сначала сделать вид, что пишешь имя класса — IDE дополнит его, а потом обернуть в кавычки). И волки сыты, и овцы целы )
0
Все будет хорошо, ::class не дергает автолоадер, это скорее такой макрос, чисто технически просто разворачивает в строку с полным неймспейсом.
$ php -r 'namespace bla; var_dump(foo\bar::class);'
string(11) «bla\foo\bar»
$ php -r 'namespace bla; var_dump(foo\bar::class);'
string(11) «bla\foo\bar»
+2
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 {
}
0
При условии, что запрос к сервис-менеджеру пробрасывается через «магический» __get()
0
можно все в трейт запихнуть
/**
* @return \Library\Acl
*/
protected function getAclService(){}
//...
0
таким образом и метод можно определить (выдержка из зенда):
/**
* 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
0
у меня все просто:
/* @var $user User */
$user = $sm->get('User');
0
ну вот я про то, как узнать, что он именно User, а не какой-нибудь doctrine.orm_default.bla-bla-bla
Вариант с именем класса сервиса мне показался элегантным ) Ну и именовать надо конечно по общему правилу.
Вариант с именем класса сервиса мне показался элегантным ) Ну и именовать надо конечно по общему правилу.
0
В phpstorm-е есть на этот случай специальный хак для описания метаданных на псевдо-php:
confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata
Но я не уверен, что можно его заставить работать для вызовов вида $this->getServiceLocator().
confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata
Но я не уверен, что можно его заставить работать для вызовов вида $this->getServiceLocator().
0
То есть в обьект сервиса будет создан даже, при отсутствии его вызовов? Или в случае с Invokable и Factory СМ все же дождется вызова и тогда создаст обьект?
0
Странно как то. Вызывать при каждом запросе кучу кода вида if $service instanceof InterfaceName в случае с инициализаторами.
Вообще спасибо вам за статью, до неё я немного переживал что не пробовал Zend Framework толком, всё на Symfony2 сижу. Сейчас я вижу что как минимум компонент Symfony Dependency Injection спроектирован лучше чем аналогичный в Zend.
Вообще спасибо вам за статью, до неё я немного переживал что не пробовал Zend Framework толком, всё на Symfony2 сижу. Сейчас я вижу что как минимум компонент Symfony Dependency Injection спроектирован лучше чем аналогичный в Zend.
0
Не совсем правильный вывод. Здесь речь идет о сервис менеджере, который по-сути реестр с «ручным» DI. Для DI есть более харкорный компонент — framework.zend.com/manual/2.3/en/modules/zend.di.introduction.html который работает через Reflection API или конфиг и там уже настоящая магия. Он более похож на Symfony DI, чем Service Manager.
0
Тоже хотел заметить, что Service Locator лишь один из паттернов, решающих задачу DI, есть в ZF2 и другой вариант, про который вы упомянули. Но комментатор видимо имел в виду, что те сервисы, которые есть в Symfony более легковесны: symfony.com/doc/current/components/dependency_injection/configurators.html
0
В Symfony DI контейнер компилируемый — то есть многие из операций выполняются один раз при прогреве кеша (или компиляции контейнера). И в рантайме, например, не используется обход всех инициализаторов, дабы понять какой из них может проинициализировать этот сервис. Контейнер компилируется в очень оптимальный с точки зрения производительности код.
Причём ручное DI также можно использовать — но оно будет не быстрое.
В Zend DI (ServiceLocator) есть что-то похожее?
Причём ручное DI также можно использовать — но оно будет не быстрое.
В Zend DI (ServiceLocator) есть что-то похожее?
0
Конечно:
просканирует классы в заданой папке и сгенерирует массив, который уже можно сохранить.
Больше информации про Di.
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.
0
При старте любого PHP-фреймворка вообще происходит каждый раз много всего лишнего, чего не хотелось бы делать при каждом запросе. И уж проверка incanceof едва ли окажется узким местом приложения. На эту тему была тут статья «PHP должен умирать» (кто-то дал неверный перевод «умереть»), в том смысле, что при каждом запросе весь цикл инициализации приходится проходить заново. Кстати, была интересная статья с экспериментом, решающим эту проблему: habrahabr.ru/post/220393/
Многие все же выбирают удобство и скорость разработки, а оптимизацией занимаются, когда того требует ситуация.
Многие все же выбирают удобство и скорость разработки, а оптимизацией занимаются, когда того требует ситуация.
0
У таких экспериментов большая проблема — придется отказаться от стандартных средств работы с сетевыми демонами и протоколами (скажем PDO) и писать свои неблокирующие аналоги (начиная с самого низкого уровня — mysql-протокола и т.п.), которые умеют работать в event loop-е. Да, такие наработки частично есть в ReactPHP и PHPDaemon — но тем не менее, это требует отказа от готового кода, стандартных фреймворков и библиотек.
Написать что-то специализированное (демон для работы с вебсокетами, скажем) — да, так можно. Портировать крупное приложение — нереально практически.
Написать что-то специализированное (демон для работы с вебсокетами, скажем) — да, так можно. Портировать крупное приложение — нереально практически.
0
Напишите еще о последовательности, в которой загружаются сервисы определенные в module.config.php и Module.php.
Так, например, если в сервисе используется фабрика для построения навигации, внутри которой хочется проверить авторизацию, а авторизация это тоже сервис, то, в зависимоти от того, где какой сервис определен, зависит — потратите ли вы лишние полчаса на WTF или нет.
Так, например, если в сервисе используется фабрика для построения навигации, внутри которой хочется проверить авторизацию, а авторизация это тоже сервис, то, в зависимоти от того, где какой сервис определен, зависит — потратите ли вы лишние полчаса на WTF или нет.
0
оно, в принципе, тут упомянуто. все сервисы, не важно где объявлены, в конфиге или в модуле, сперва будут смержены. т.е. когда нужно будет дернуть авторизацию из навигации, оба сервиса уже будут доступны в СМ.
0
Я сталкивался с проблемой, которую описал неоднократно.
При чем, по моим наблюдениям, так же отличается приоритет сервисов объявленных через фабрику и через замыкание. У замыкания приоритет ниже, они идут после фабрик. Привожу пример
Вот рабочий вариант фабрики для навигации, внутри фабрики проверяется авторизация из сервиса Auth.
Так работает в Module.php.
А вот так уже не работает.
Выдается ошибка внутри фабрики о том, что не найден Auth сервис.
Та же ошибка, если переместить в module.config.php
Да, вы правы, оно мержится в итоге, но важно в каком порядке объявлены модули, где объявлен сервис и (тут я могу ошибаться, но пример выше показателен) как он объявлен.
Где-то на немецком ресурсе я видел схему того в каком порядке все мержится. Вот тут.
Именно из-за таких нюансов, раз уж вы взялись писать, я прошу вас написать и о порядке загрузки по русски.
При чем, по моим наблюдениям, так же отличается приоритет сервисов объявленных через фабрику и через замыкание. У замыкания приоритет ниже, они идут после фабрик. Привожу пример
Вот рабочий вариант фабрики для навигации, внутри фабрики проверяется авторизация из сервиса 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'
Да, вы правы, оно мержится в итоге, но важно в каком порядке объявлены модули, где объявлен сервис и (тут я могу ошибаться, но пример выше показателен) как он объявлен.
Где-то на немецком ресурсе я видел схему того в каком порядке все мержится. Вот тут.
Именно из-за таких нюансов, раз уж вы взялись писать, я прошу вас написать и о порядке загрузки по русски.
0
Вот на английском схема загрузки.
Из архива, т.к. она не доступна сейчас.
Вот еще кое-что. Может поможет.
Из архива, т.к. она не доступна сейчас.
Вот еще кое-что. Может поможет.
0
1. покажите код фабрики;
2. код регистрации в Module.php и module.config.php
3. как регистрируется Auth в приложении и его код тоже.
разберемся, думаю.
2. код регистрации в Module.php и module.config.php
3. как регистрируется Auth в приложении и его код тоже.
разберемся, думаю.
0
У вас ошибка.
Наверное имелось в виду «реализует».
СМ реалирует
Наверное имелось в виду «реализует».
0
Sign up to leave a comment.
Zend Framework 2: Service Manager