Работа с датами 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(), который и вернет то что нам нужно.

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

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