Добрый день, хабражитель.
Не так давно я столкнулся с не очень стандартной проблемой, хотелось бы поделиться ее решением, а также узнать несколько умных мыслей на эту тему.
Кому интересно, добро пожаловать под кат. Несколько слов о конфигурации проекта: используется Symfony 2.3 + Doctor 2.
Постановка задачи
Реализуйте обработчик событий, имя которого заранее неизвестно.Система содержит некий реестр всех событий в базе данных.
Используя документацию, мы можем выделить 3 варианта подписки на события в стандартной реализации диспетчера событий symfony.
- Добавьте сервис с тегом kernel.event_listener в контейнер DI.
- Добавьте в контейнер DI службу с тегом kernel.event_subscriber, реализующую EventSubscriberInterface.
- Добавьте вызовы методов addListener во время отправки приложения.
Казалось бы, второй метод — именно то, что нужно, но метод getSubscribedEvents должен быть статическим, а значит, не может взаимодействовать с внедренными сервисами.
Третий способ показался мне наиболее логичным, но добавлять на каждый запрос еще один запрос в базу данных мне не хотелось, поэтому я начал искать более элегантное решение с кэшированием списка названий событий.
Пришла идея использовать проход компилятора и это решило эту проблему.
При создании экземпляра объекта пакета мы можем зарегистрировать класс, который будет участвовать в компиляции DI-контейнера.
Остается один важный вопрос: как дойти до доктрины, и здесь нам на помощь приходит сортировка проходов компилятора.
Существует 5 этапов компиляции контейнера:
- PassConfig::TYPE_BEFORE_OPTIMIZATION
- PassConfig::TYPE_OPTIMIZE
- PassConfig::TYPE_BEFORE_REMOVING
- PassConfig::TYPE_REMOVE
- PassConfig::TYPE_AFTER_REMOVING
Выполнение
Последний этап нам подходит, так как все сервисы на этом этапе уже готовы к использованию.Давайте объявим наш компилятор:
и сам код компилятора (код, отвечающий за выборку данных из базы, скрыт в классе репозитория)/** * Builds the bundle. * * It is only ever called once when the cache is empty. * * This method can be overridden to register compilation passes, * other extensions, .
* * @param ContainerBuilder $container A ContainerBuilder instance */ public function build(ContainerBuilder $container) { parent::build($container); $container->addCompilerPass(new EventsCompilerPass(), PassConfig::TYPE_AFTER_REMOVING); }
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition(self::SERVICE_KEY)) {
return;
}
$eventClassName = $container->getParameter(self::EVENT_ENTITY_CLASS_PARAM);
$dispatcher = $container->getDefinition(self::DISPATCHER_KEY);
$em = $container->get('doctrine.orm.entity_manager');
$eventNames = array();
if ($this->isSchemaSynced($em, $eventClassName) !== false) {
$eventNames = $em->getRepository($eventClassName)
->getEventNames();
}
foreach ($eventNames as $eventName) {
$dispatcher->addMethodCall(
'addListenerService',
array($eventName['name'], array(self::SERVICE_KEY, 'process'))
);
}
}
Как видите, мы выбираем все имена событий и регистрируем наш сервис в качестве обработчика этих событий.
Единственное, что осталось нераскрытым, — это метод isSchemaSynced. Начнем с его реализации: protected function isSchemaSynced(EntityManager $em, $className)
{
$tables = $em->getConnection()->getSchemaManager()->listTableNames();
$table = $em->getClassMetadata($className)->getTableName();
return array_search($table, $tables);
}
он проверяет, созданы ли таблицы с именами событий.
Дело в том, что при первой компиляции контейнера при вызове сервисной команды Doctor:schema:create он может выдать исключение DBException. Спасибо за внимание, буду рад услышать мнение тех, кто столкнулся с подобной проблемой.
Не судите строго, это мой первый пост о программировании вообще.
Теги: #symfony2 #php #symfony
-
Нетплан И Как Его Правильно Подготовить
19 Oct, 24 -
Манипуляторы С Конвейера Снимают Фильм
19 Oct, 24 -
Mediatek Mt6595: Впечатления От... Чипсета
19 Oct, 24