Работа с датами Doctrine 2. Навигация в Symfony 2. И, надеюсь, прочие полезности

    Приветствую всех! А давайте сделаем топик-шпаргалку по 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 класса:
    DoctrineExtensions\Query\Mysql\Month;
    DoctrineExtensions\Query\Mysql\Year;

    Почему бы ими не воспользоваться? Копируем их к себе в бандл, например в папку Dql, меняем namespace на Acme\BlogBundle\Dql и радуемся тому, что есть такой замечательный репозиторий. Функцию Monthname делаем по примеру Month. И согласно ссылки, нам осталось сказать doctrine об этих функциях.

    В config.yml добавляем:
    # Doctrine Configuration
    doctrine:
        orm:
            dql:
                datetime_functions:
                    month: Acme\BlogBundle\Dql\Month
                    monthname: Acme\BlogBundle\Dql\Monthname
                    year: Acme\BlogBundle\Dql\Year
    


    После этого наши функции будут работать.

    Для меня было неожиданно что Doctrine не знает о этих функциях. Но можно ей это простить в виду других плюсов.

    Навигация и хлебные крошки

    Для создания навигации я использую KnpMenuBundle. Как ей пользоваться читайте на гитхабе. Итак, мы прочитали, сделали свое первое меню, например такое:
    namespace Acme\BlogBundle\Menu;
    
    use Knp\Menu\FactoryInterface;
    use Knp\Menu\MenuItem;
    use Symfony\Component\DependencyInjection\ContainerAware;
    
    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 Acme\BlogBundle\Menu;
    
    use Knp\Menu\FactoryInterface;
    use Knp\Menu\Iterator\CurrentItemFilterIterator;
    use Knp\Menu\Iterator\RecursiveItemIterator;
    use Knp\Menu\MenuItem;
    use Symfony\Component\DependencyInjection\ContainerAware;
    
    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(), который и вернет то что нам нужно.

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

    Подробнее
    Реклама
    Комментарии 4

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