Приветствую всех! А давайте сделаем топик-шпаргалку по Symfony и Doctrine.
Введение
Я обычный сибирский разработчик, основная задача в жизни захватить мир и сделать людей счастливее. На Symfony сделал несколько проектов. При разработке иногда встречаются задачи, на которые совсем нет информации в интернете, либо очень мало и не совсем понятная. Поэтому и захотелось создать данное обсуждение, где я покажу как решал некоторые свои задачи, а вы, надеюсь, направите меня на путь истинный или присоединитесь и тоже поделитесь своими хаками. Данный топик предназначен для тех людей, которые используют выше упомянутый фреймворк ну или его компоненты. А так же для тех, кто хочет пообщаться по поводу решения тех или иных проблем, поделиться своим опытом с другими разработчиками.
Ближе к делу
Функции работы с датами и Doctrine
Представим себе небольшой личный блог. Нужно вывести навигацию для постов с группировкой по году и месяцу. Очень простая задача. Вы создаете метод в классе BlogRepository, и называете его например getArchiveByMonths(). На автомате пишите код похожий на:
$qb = $this->createQueryBuilder('p');
return $qb
->addSelect('MONTHNAME(p.created) as month')
->addSelect('YEAR(p.created) as year')
->addSelect('COUNT(p) as cnt')
->groupBy('month, year')
->orderBy('p.created', 'DESC')
->getQuery()
->getArrayResult();
И поллучаете ошибку: Expected known function, got 'MONTHNAME'
Дело в том что Doctrine знает не все функции для работы с датой. Значит нужно их добавить. В официальной документации есть пару строчек о том как добавить эти функции(Тыц)
Решение: есть замечательный репозиторий: github.com/simukti/DoctrineExtensions, там нам интересны 2 класса:
DoctrineExtensionsQueryMysqlMonth;
DoctrineExtensionsQueryMysqlYear;
Почему бы ими не воспользоваться? Копируем их к себе в бандл, например в папку Dql, меняем namespace на AcmeBlogBundleDql
и радуемся тому, что есть такой замечательный репозиторий. Функцию Monthname
делаем по примеру Month
. И согласно ссылки, нам осталось сказать doctrine об этих функциях.
В config.yml добавляем:
# Doctrine Configuration
doctrine:
orm:
dql:
datetime_functions:
month: AcmeBlogBundleDqlMonth
monthname: AcmeBlogBundleDqlMonthname
year: AcmeBlogBundleDqlYear
После этого наши функции будут работать.
Для меня было неожиданно что Doctrine не знает о этих функциях. Но можно ей это простить в виду других плюсов.
Навигация и хлебные крошки
Для создания навигации я использую KnpMenuBundle. Как ей пользоваться читайте на гитхабе. Итак, мы прочитали, сделали свое первое меню, например такое:
namespace AcmeBlogBundleMenu;
use KnpMenuFactoryInterface;
use KnpMenuMenuItem;
use SymfonyComponentDependencyInjectionContainerAware;
class Builder extends ContainerAware {
public function mainMenu (FactoryInterface $factory, array $options) {
$menu = $factory->createItem('root');
$request = $this->container->get('request');
$menu
->addChild('Homepage', array(
'route' => 'homepage',
));
$blog = $menu->addChild('Blog', array(
'route' => 'blog'
));
$blog->addChild('BlogView',array(
'route' => 'blog_post_view',
'routeParameters' => array('id' => $request->get('id', 1)),
'display' => false
));
return $menu;
}
Вывели это меню где-нибудь в левой/правой части сайта. И теперь нам надо вывести хлебные крошки. И вот тут у меня нет красивого и замечательного решения, надеюсь кто-нибудь мне подскажет как это можно сделать. А пока я приведу свое решение.
Для начала нам нужно найти активный пункт меню. Стандартного метода больше в бандле от Knp нет. Расширим наш MenuBuilder:
namespace AcmeBlogBundleMenu;
use KnpMenuFactoryInterface;
use KnpMenuIteratorCurrentItemFilterIterator;
use KnpMenuIteratorRecursiveItemIterator;
use KnpMenuMenuItem;
use SymfonyComponentDependencyInjectionContainerAware;
class Builder extends ContainerAware {
public function mainMenu (FactoryInterface $factory, array $options) {
$menu = $factory->createItem('root');
$request = $this->container->get('request');
$menu
->addChild('Homepage', array(
'route' => 'homepage',
));
$blog = $menu->addChild('Blog', array(
'route' => 'blog'
));
$blog->addChild('BlogView',array(
'route' => 'blog_view',
'routeParameters' => array('id' => $request->get('id', 1)),
'display' => false
));
return $menu;
}
public function getCurrentItem (FactoryInterface $factory, array $options) {
$menu = $this->mainMenu($factory, $options);
$matcher = $this->container->get('knp_menu.matcher');
$voter = $this->container->get('knp_menu.voter.router');
$matcher->addVoter($voter);
$treeIterator = new RecursiveIteratorIterator(
new RecursiveItemIterator(
new ArrayIterator(array($menu))
),
RecursiveIteratorIterator::SELF_FIRST
);
$iterator = new CurrentItemFilterIterator($treeIterator, $matcher);
// Set Current as an empty Item in order to avoid exceptions on knp_menu_get
$current = new MenuItem('', $factory);
foreach ($iterator as $item) {
$current = $item;
break;
}
return $current;
}
Мы добавили метод для нахождения текущего пункта меню. Теперь нам нужно вывести его. Для этого в нашем представлении пишем:
{% set breadcrumbs = knp_menu_get('AcmeBlogBundle:Builder:getCurrentItem').getBreadcrumbsArray() %}
<ul class="breadcrumb">
<li>
<i class="icon-home"></i>
<a href="{{ path('homepage') }}">Home</a>
<span class="icon-angle-right"></span>
</li>
{% for link in breadcrumbs %}
{% if link.label != 'root' %}
<li>
<a href="{{ link.uri }}">{{ link.label|trans }}</a>
{% if not loop.last %}
<span class="icon-angle-right"></span>
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul>
Здесь мы используем метод getBreadCrumbsArray()
, который и вернет то что нам нужно.
На этом пожалуй и все. Надеюсь был полезен. О ошибках просьба в личку, ведь чукча не писатель, чукча разработчик. Добавляйте свои решения, чтобы сделать топик еще полезнее. Спасибо за внимание.
Автор: leviothan