В этой статье пойдет речь о том, как можно решить некоторые проблемы в Symfony 2 и Doctrine, используя базовые компоненты из коробки, а именно:
Думаю многие кто работал с Symfony 2 сталкивался с проблемой внедрения сервис контейнера в модель (Entity) или же какого-то другого компонента (сервиса). Прежде чем Вы узнаете как можно это сделать, подумайте, нужно ли Вам это? Можно создать определенный сервис (утилиту) и сделать уже там все то что необходимо. Ну если Вы решили, что это нужно, тогда:
Есть несколько способов внедрения, и зачастую все выбирают самый простой.
Взять и «тупо» вызвать кернел с глобальной области видимости
Как по мне способ очень простой, но и очень дырявый, так как невозможно проверить существование той же переменной…
Здесь нужно будет немного «попотеть» для стой простой функции, но это решение будет намного лучше первого и гарантирует полную работоспособность. Здесь будут использоваться евенты доктрины, которые разрешают нам делать все что угодно во время загрузки/сохранения модели.
Для начала нам необходимо будет создать либо Event либо Subscriber для Doctrine, которые внедрит необходимый компонент во время загрузки модели (postLoad). И все это дело нам нужно будет создать как сервис, чтобы можно было передать любой сервис.
Создаем Subscriber для Doctrine:
Вот как выглядит новая модель:
Регистрируем как сервис
Ну вот и все, при загрузке модели, система проверит, является ли эта модель необходимой, и если да, то «втулит» необходимый компонент в переменную myComponent используя отдельный сеттер.
Заключение: пытайтесь избежать таких дел, так как модели служат только для чтения/записи в БД.
Иногда приходиться сохранить всю историю изменения некоторой модели, ну к примеру — история изменения записи для дальнейшей возможности отката. В данной ситуации очень сильно помогут евенты самой доктрины, а именно onFlush
Предположим у нас есть модель «новость», за которой нам нужно будет следить, а именно за полем title:
Ну и в догонку необходимо будет создать еще одну модель, которая как раз и будет контролировать изменения:
На самом евенте нужно будет проверить, изменялось ли это поле. В этом поможет UnitOfWork, который тщательно следит за всем моделями в определенном менеджере.
Ну и как всегда, нужно зарегистрировать как сервис, указывая, что это subscriber для доктрины:
UnitOfWork — это так называемое Api, при помощи которого Вы можете делать любые махинации с моделями.
Таким же образом, можно контролировать создание новых моделей и удаление старых.
Иногда есть необходимость запустить команды (с консоли) которые делают очень много запросов к БД. Хорошим примером может послужить перенос сайта с какой-то CMS или фраемворка на Symfony 2 при наличии более 100 000 записей в БД. Если запустить какую-то команду на Симфони с консоли, то по умолчанию, система запустится в режиме dev и включенным дебагом, в котором доктрина сохраняет все запросы для последующего дебага. В результате чего постоянно увеличивается память выделенная для скрипта, что в последствии влияет на скорость и может выбросить критическую ошибку memory limit. В таких ситуациях мы можем отключить все логеры, которые использует доктрина (менеджер), или же полностью переопределить, чтобы контролировать только то, что нужно. Сам же логгер висит в конфигурации доктрины.
Пример полного отключения:
Также следует не забывать, что все модели, которые были загружены с БД сохраняются в менеджере до тех пор пока не будет вызван unset или же сброшен «статический» кеш. Это было разработано для того, чтобы не делать постоянно запросы на выборку одной и той же модели. В результате, при таких больших скриптах (которые делают множество операций с моделями) необходимо периодически чистить кеш:
При поднятии новой сборки Симфони, всегда сталкиваемся с проблемой некоторых папок (Permission denied): app/cache и app/logs. Эта проблема возникает тогда, когда Вы уже запустили сайт с браузера и пытаетесь сделать что-то в консоли. Проблема в том, что сам веб сервер работает под другим пользователем.
Те, у кого есть полный доступ к серверу (root), этого делать не нужно, посмотрите лучше: http://symfony.com/doc/current/book/installation.html#configuration-and-setup
Но если Вы не имеете полный доступ к системе (к примеру хостинг) и Вас уже достала эта проблема, то можно разделить систему запуска на dev и console. Делается это следующим образом:
Теперь при запуске app/console система будет работать под совсем другим environment, что и не будет мешать dev-у
Внимание: речь идет только если система находиться в dev режиме!
P.S. Коды писались для примера, а не для работы на прод системах. Использовалась Symfony 2.2.
P.S.S. Прошу сильно не пинать… Мой первый пост. Спасибо за внимание!
- Внедрение сервиса в модель
- Сохранение истории изменения
- Отключение SQLLogger и чистка кеша
- Разделение environment (dev — console)
Думаю многие кто работал с Symfony 2 сталкивался с проблемой внедрения сервис контейнера в модель (Entity) или же какого-то другого компонента (сервиса). Прежде чем Вы узнаете как можно это сделать, подумайте, нужно ли Вам это? Можно создать определенный сервис (утилиту) и сделать уже там все то что необходимо. Ну если Вы решили, что это нужно, тогда:
Плюшка №1 Внедрение сервиса в модель:
Есть несколько способов внедрения, и зачастую все выбирают самый простой.
Вариант №1
Взять и «тупо» вызвать кернел с глобальной области видимости
class MyEntity { // ... properties // ... methods public function someAction() { global $kernel; return $kernel->getContainer() ->..... } }
Как по мне способ очень простой, но и очень дырявый, так как невозможно проверить существование той же переменной…
Вариант №2
Здесь нужно будет немного «попотеть» для стой простой функции, но это решение будет намного лучше первого и гарантирует полную работоспособность. Здесь будут использоваться евенты доктрины, которые разрешают нам делать все что угодно во время загрузки/сохранения модели.
Для начала нам необходимо будет создать либо Event либо Subscriber для Doctrine, которые внедрит необходимый компонент во время загрузки модели (postLoad). И все это дело нам нужно будет создать как сервис, чтобы можно было передать любой сервис.
Создаем Subscriber для Doctrine:
namespace Acme\DemoBundle\EventListener; use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Event\LifecycleEventArgs; use Symfony\Component\DependencyInjection\ContainerInterface; use Acme\DemoBundle\Entity\MyEntity; class MyEntitySubscriber implements EventSubscriber { protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function postLoad(LifecycleEventArgs $event) { $entity = $event->getEntity(); // Ну вот, это наша модель, можем пихать с контейнера все что угодно, // хоть даже сам контейнер if ($entity instanceof MyEntity) { $entity-> setMyComponent($this->container->get('....')); } } /** * Определяем, какие именно эвенты мы будем использовать * Внимание: само название евента - и есть название * функции, которая будет вызываться */ public function getSubscribedEvents() { return array( 'postLoad' ); } }
Вот как выглядит новая модель:
class MyEntity { // … properties protected $myComponent; // … methods public function setMyComponent($myComponent) { $this->myComponent = $myComponent; } public function someAction() { return $this->myComponent ->.... } }
Регистрируем как сервис
<service id="my_entity.doctrine.subscriber" class="Acme\DemoBundle\EventListener\MyEntitySubscriber"> <!-- Тег необходим для того, чтобы доктрина приняла этот subscriber --> <tag name="doctrine.event_subscriber" /> <argument type="service" id="service_container" /> </service>
Ну вот и все, при загрузке модели, система проверит, является ли эта модель необходимой, и если да, то «втулит» необходимый компонент в переменную myComponent используя отдельный сеттер.
Заключение: пытайтесь избежать таких дел, так как модели служат только для чтения/записи в БД.
Плюшка №2 Сохранения истории:
Иногда приходиться сохранить всю историю изменения некоторой модели, ну к примеру — история изменения записи для дальнейшей возможности отката. В данной ситуации очень сильно помогут евенты самой доктрины, а именно onFlush
Предположим у нас есть модель «новость», за которой нам нужно будет следить, а именно за полем title:
class News { protected $title; // ... properties // ... methods }
Ну и в догонку необходимо будет создать еще одну модель, которая как раз и будет контролировать изменения:
class NewsHistory { protected $title; // ... properties // ... methods }
На самом евенте нужно будет проверить, изменялось ли это поле. В этом поможет UnitOfWork, который тщательно следит за всем моделями в определенном менеджере.
namespace Acme\DemoBundle\EventListener; use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Event\OnFlushEventArgs; use Symfony\Component\DependencyInjection\ContainerInterface; use Acme\DemoBundle\Entity\News; use Acme\DemoBundle\Entity\NewsHistory; class NewsSubscriber implements EventSubscriber { public function onFlush(OnFlushEventArgs $event) { $em = $event->getEntityManager(); $uow = $em->getUnitOfWork(); // Проходим по всем ентити, которые должны обновиться foreach ($uow->getScheduledEntityUpdates() as $entity) { // Это наша новость if ($entity instanceof News) { // Достаем массив всех полей, которые изменились $changeSet = $uow->getEntityChangeSet($entity); // Присутствует ли наше поле title? if (isset($changeSet['title'])) { // Достаем старое и новое значение list ($oldTitle, $newTitle) = $changeSet['title']; // Создаем entity для сохранения истории $newsHistory = new NewsHistory; $newsHistory->setTitle($oldTitle); // Тыкаем новую модель в менеджер $em->persist($newsHistory); // ВНИМАНИЕ!!! // Система сразу не распознает эту модель как ту, которая // должна сохраниться, по причине того, что flush менеджера // уже начался, который как раз и определяет, что нужно // обновить, а что вставить. // На помощь нам приходит UnitOfWork, которому можем четко указать, // что вот эту модель необходимо сохранить // Для начала достаем ClassMetadata для объекта // Здесь храниться вся "мета" информация о данной модели $classMetadata = $em->getClassMetadata(get_class($newsHistory)); // Ну и теперь "тыкаем" в UnitOfWork, что эту модель нужно вставить $uow->computeChangeSet($classMetadata, $newsHistory); } } } } public function getSubscribedEvents() { return array( 'onFlush' ); } }
Ну и как всегда, нужно зарегистрировать как сервис, указывая, что это subscriber для доктрины:
<service id="news.doctrine.subscriber" class="Acme\DemoBundle\EventListener\NewsSubscriber"> <tag name="doctrine.event_subscriber" /> </service>
UnitOfWork — это так называемое Api, при помощи которого Вы можете делать любые махинации с моделями.
Таким же образом, можно контролировать создание новых моделей и удаление старых.
Плюшка №3 Отключение SQL логгера, чистка кеша в Doctrine:
Иногда есть необходимость запустить команды (с консоли) которые делают очень много запросов к БД. Хорошим примером может послужить перенос сайта с какой-то CMS или фраемворка на Symfony 2 при наличии более 100 000 записей в БД. Если запустить какую-то команду на Симфони с консоли, то по умолчанию, система запустится в режиме dev и включенным дебагом, в котором доктрина сохраняет все запросы для последующего дебага. В результате чего постоянно увеличивается память выделенная для скрипта, что в последствии влияет на скорость и может выбросить критическую ошибку memory limit. В таких ситуациях мы можем отключить все логеры, которые использует доктрина (менеджер), или же полностью переопределить, чтобы контролировать только то, что нужно. Сам же логгер висит в конфигурации доктрины.
Пример полного отключения:
PHP
$container->get('doctrine') ->getManager() ->getConnection() ->getConfiguration() ->setSQLLogger(null); // Здесь можно внедрить свой логгер
Configuration
doctrine:
dbal:
connections:
default:
logging: falseТакже следует не забывать, что все модели, которые были загружены с БД сохраняются в менеджере до тех пор пока не будет вызван unset или же сброшен «статический» кеш. Это было разработано для того, чтобы не делать постоянно запросы на выборку одной и той же модели. В результате, при таких больших скриптах (которые делают множество операций с моделями) необходимо периодически чистить кеш:
$container->get('doctrine')->getManager()->clear();
Плюшка №4 Разделение environments для консоли:
При поднятии новой сборки Симфони, всегда сталкиваемся с проблемой некоторых папок (Permission denied): app/cache и app/logs. Эта проблема возникает тогда, когда Вы уже запустили сайт с браузера и пытаетесь сделать что-то в консоли. Проблема в том, что сам веб сервер работает под другим пользователем.
Те, у кого есть полный доступ к серверу (root), этого делать не нужно, посмотрите лучше: http://symfony.com/doc/current/book/installation.html#configuration-and-setup
Но если Вы не имеете полный доступ к системе (к примеру хостинг) и Вас уже достала эта проблема, то можно разделить систему запуска на dev и console. Делается это следующим образом:
- Создаем файлик app/config/config_console.yml, в который импортируем конфиг config_dev.yml
- В файле app/console по дефоулту выставляем не dev а console
- Корректируем файл app/AppKernel.php в том месте, где подключаются дополнительные бандлы (идет проверка environment) и добавляем еще console
Теперь при запуске app/console система будет работать под совсем другим environment, что и не будет мешать dev-у
Внимание: речь идет только если система находиться в dev режиме!
P.S. Коды писались для примера, а не для работы на прод системах. Использовалась Symfony 2.2.
P.S.S. Прошу сильно не пинать… Мой первый пост. Спасибо за внимание!
