Создание блога на Symfony 2.8 lts [ Часть 5]

641e2f00c3f347999dfe9779cedd63d7.jpg

Навигация по частям руководства

Проект на Github

Узнать как установить нужную вам часть руководства, можно в описании к репозиторию по ссылке. (Например, если вы хотите начать с это урока не проходя предыдущий)

Домашняя страница. Записи и комментарии.

Теперь на главной странице выводится список последних записей, но нет никакой информации относительно комментариев к этим записям. У нас есть Сущность Comment благодаря которой мы можем вернуться к главной странице, чтобы предоставить эту информацию. Так как мы установили связь между сущностями Blog и Comment мы знаем, что Doctrine 2 будет иметь возможность получать комментарии к записи (помните, мы добавили объект $comments к сущности Blog). Давайте обновим шаблон главной страницы

src/Blogger/BlogBundle/Resources/views/Page/index.html.twig
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}

{# .. #}

Comments: {{ blog.comments|length }}

Posted by {{ blog.author }} at {{ blog.created|date('h:iA') }}

Tags: {{ blog.tags }}

{# .. #}

Мы использовали comments getter для получения комментариев, а затем передали коллекцию через Twig length фильтр. Если вы посмотрите на домашнюю страницу сейчас, перейдя по адресу http://localhost:8000/ вы увидите число комментариев к каждой записи.

Как объяснялось выше, мы уже сообщили Doctrine 2 что объект $comments сущности Blog будет иметь связь с сущностью Comment. Мы добились этого в предыдущей части с помощью метаданных в сущности Blog.

src/Blogger/BlogBundle/Entity/Blog.php
// src/Blogger/BlogBundle/Entity/Blog.php

/**
 * @ORM\OneToMany(targetEntity="Comment", mappedBy="blog")
 */
protected $comments;



Итак, мы знаем, что Doctrine 2 известно о взаимосвязи между записями и комментариями, но как это заполняет объект $comments соответствующими сущностями Comment? Если вы помните метод в BlogRepository, который мы создали (показан ниже) получающий на домашней странице записи, мы не делали выборку для извлечения связанных Comment сущностей.

src/Blogger/BlogBundle/Repository/BlogRepository.php
// src/Blogger/BlogBundle/Repository/BlogRepository.php

public function getLatestBlogs($limit = null)
{
    $qb = $this->createQueryBuilder('b')
               ->select('b')
               ->addOrderBy('b.created', 'DESC');

    if (false === is_null($limit))
        $qb->setMaxResults($limit);

    return $qb->getQuery()
              ->getResult();
}


Однако, Doctrine 2 использует процесс, называемый отложенной загрузкой, где сущности Comment извлекаются из базы данных в случае необходимости, в нашем случае, когда вызывается {{blog.comments | length}}. Мы можем продемонстрировать этот процесс, с помощью панели инструментов разработчика. Мы уже начали изучать основы панели инструментов разработчика и настало время, чтобы представить одну из самых полезных её функций, Doctrine 2 профайлера. Doctrine 2 профайлер можно открыть, щелкнув значок на панели инструментов разработчика… Число рядом с этой пиктограммой показывает количество запросов, выполненных к базе данных для текущего HTTP-запроса.

e244ff0fd2394a83af2c967ecf4d0075.jpg

Если щелкнуть значок Doctrine 2 вам будет представлена информация о запросах, которые были выполнены Doctrine 2 к базе данных для текущего запроса HTTP.

e0da3721a90a4ff184e77d2ab4077919.png

Как вы можете видеть в приведенном выше скриншоте, там есть целый ряд выполняемых запросов для вывода на главную страницу. Второй запрос, извлекает сущности записей из базы данных и выполняется в результате метода getLatestBlogs () класса BlogRepository. После этого запроса вы заметите ряд запросов, которые получают комментарии из базы данных, одну запись за один раз. Мы можем увидеть это здесь WHERE t0.blog_id = ? в каждом из запросов, где ? заменяется на значение параметра (id блога). Каждый из этих запросов является результатом вызова {{blog.comments}} в шаблоне домашней страницы. Каждый раз, когда эта функция выполняется, Doctrine 2 должен лениво (lazily) загрузить сущности Comment, которые относятся к сущности Blog.

В то время как отложенная загрузка очень эффективна при извлечении связанных сущностей из базы данных, это не всегда является наиболее эффективным способом для выполнения этой задачи. Doctrine2 предоставляет возможность объединить связанные объекты вместе, когда выполняются запросы к базе данных. Таким образом, мы можем вернуть сущность Blog и связанные с ней сущности Comment из базы данных в одном запросе. Обновите код QueryBuilder в BlogRepository.

src/Blogger/BlogBundle/Repository/BlogRepository.php
// src/Blogger/BlogBundle/Repository/BlogRepository.php

public function getLatestBlogs($limit = null)
{
    $qb = $this->createQueryBuilder('b')
               ->select('b, c')
               ->leftJoin('b.comments', 'c')
               ->addOrderBy('b.created', 'DESC');

    if (false === is_null($limit))
        $qb->setMaxResults($limit);

    return $qb->getQuery()
              ->getResult();
}


Теперь, если вы обновите главную страницу и посмотрите на вывод Doctrine 2 в панели инструментов разработчика вы заметите, что количество запросов сократилось. Вы также увидите, что таблица comment была присоединена к таблице blog.

Ленивая загрузка и объединение связанных объектов являются очень мощными концепциями, но они должны быть использованы правильно. Правильный баланс между 2 концепциями должен быть найден для того, чтобы обеспечить вашему приложению эффективность, настолько насколько это возможно. На первый взгляд может показаться хорошей идеей объединять каждые связанные сущности, так что вам никогда не придется пользоваться ленивой нагрузкой и количество запросов к базе данных всегда будет оставаться низким. Однако важно помнить, что чем больше информации вы извлекаете из базы данных, тем больше придётся обрабатывать Doctrine 2. Больше данных означает также больший объем памяти, используемый сервером для хранения объектов сущностей.

Прежде чем двигаться дальше давайте сделаем одно небольшое дополнение к шаблону домашней страницы, для количества комментариев, которые мы только что добавили. Обновите шаблон домашней страницы

src/Blogger/BlogBundle/Resources/views/Page/index.html.twig
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}

{# .. #}



{# .. #}



чтобы при нажатии на количество комментариев мы переходили к комментариям соответствующей записи.

Боковая панель (sidebar)


В настоящее время боковая панель symblog выглядит пустой. Мы обновим её добавив распространённые элементы присущие большинству блогов Облако тегов и Последние комментарии.Облако тегов
Облако тегов показывает теги для каждой записи обращая внимание на более популярные теги, путем выделения их более жирным шрифтом. Для достижения этой цели нам нужен способ для получения всех тегов для всех записей. Давайте создадим несколько новых методов в классе BlogRepository для этого. Обновите класс BlogRepository

src/Blogger/BlogBundle/Repository/BlogRepository.php
// src/Blogger/BlogBundle/Repository/BlogRepository.php

public function getTags()
{
    $blogTags = $this->createQueryBuilder('b')
                     ->select('b.tags')
                     ->getQuery()
                     ->getResult();

    $tags = array();
    foreach ($blogTags as $blogTag)
    {
        $tags = array_merge(explode(",", $blogTag['tags']), $tags);
    }

    foreach ($tags as &$tag)
    {
        $tag = trim($tag);
    }

    return $tags;
}

public function getTagWeights($tags)
{
    $tagWeights = array();
    if (empty($tags))
        return $tagWeights;

    foreach ($tags as $tag)
    {
        $tagWeights[$tag] = (isset($tagWeights[$tag])) ? $tagWeights[$tag] + 1 : 1;
    }
    // Shuffle the tags
    uksort($tagWeights, function() {
        return rand() > rand();
    });

    $max = max($tagWeights);

    // Max of 5 weights
    $multiplier = ($max > 5) ? 5 / $max : 1;
    foreach ($tagWeights as &$tag)
    {
        $tag = ceil($tag * $multiplier);
    }

    return $tagWeights;
}



Так как теги хранятся в базе данных в виде значений, разделенных запятыми (CSV) нам нужен способ, чтобы разделить их и вернуть в виде массива. Это достигается с помощью метода getTags (). Метод getTagWeights () может использовать массив тегов, чтобы вычислить «вес» каждого тега на основе его популярности, в массиве. Тэги перемешиваются, чтобы отображаться в случайном порядке на странице.

У нас есть возможность сгенерировать облако тегов, теперь нам надо его отобразить. Создайте новый метод в контроллере

src/Blogger/BlogBundle/Controller/PageController.php
// src/Blogger/BlogBundle/Controller/PageController.php

public function sidebarAction()
{
    $em = $this->getDoctrine()
               ->getManager();

    $tags = $em->getRepository('BloggerBlogBundle:Blog')
               ->getTags();

    $tagWeights = $em->getRepository('BloggerBlogBundle:Blog')
                     ->getTagWeights($tags);

    return $this->render('BloggerBlogBundle:Page:sidebar.html.twig', array(
        'tags' => $tagWeights
    ));
}



Метод очень прост, он использует 2 новых метода BlogRepository для создания облака тегов, и передает это в отображение. Теперь давайте создадим шаблон

src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #}

Tag Cloud

{% for tag, weight in tags %} {{ tag }} {% else %}

There are no tags

{% endfor %}


Шаблон также очень прост. Он просто перебирает различные теги и устанавливает класс в соответствии с «весом» тега. Цикл for показывает, как получить доступ к ключам и значениям пар массива, где tag является ключом, а weight значением. Существует несколько вариантов использования цикла for, приведенных в документации Twig.

Если вы посмотрите на основной шаблон макета BloggerBlogBundle, расположенного src/Blogger/BlogBundle/Resources/views/layout.html.twig вы заметите, что мы помещаем заполнитель (placeholder) для блока боковой панели. Давайте теперь заменим это выводом нового метода для боковой панели. Вспомните из предыдущей части, что метод Twig render будет выводить содержимое метода контроллера, в этом случае метода sidebar контроллера Page.

{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}

{# .. #}

{% block sidebar %}
    {{ render(controller('BloggerBlogBundle:Page:sidebar' ))}} 
{% endblock %}


Наконец давайте добавим стили для Облака тегов. Добавьте новые стили в файл

src/Blogger/BlogBundle/Resources/public/css/sidebar.css
.sidebar .section { margin-bottom: 20px; }
.sidebar h3 { line-height: 1.2em; font-size: 20px; margin-bottom: 10px; font-weight: normal; background: #eee; padding: 5px;  }
.sidebar p { line-height: 1.5em; margin-bottom: 20px; }
.sidebar ul { list-style: none }
.sidebar ul li { line-height: 1.5em }
.sidebar .small { font-size: 12px; }
.sidebar .comment p { margin-bottom: 5px; }
.sidebar .comment { margin-bottom: 10px; padding-bottom: 10px; }
.sidebar .tags { font-weight: bold; }
.sidebar .tags span { color: #000; font-size: 12px; }
.sidebar .tags .weight-1 { font-size: 12px; }
.sidebar .tags .weight-2 { font-size: 15px; }
.sidebar .tags .weight-3 { font-size: 18px; }
.sidebar .tags .weight-4 { font-size: 21px; }
.sidebar .tags .weight-5 { font-size: 24px; }



Так как мы добавили новые стили, давайте подключим их. Обновите BloggerBlogBundle main шаблон

src/Blogger/BlogBundle/Resources/views/layout.html.twig
{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}

{# .. #}

{% block stylesheets %}
    {{ parent() }}
    
    
{% endblock %}

{# .. #}


Заметка

Если вы не используете метод символических ссылок для обращения к assets бандла в папке web, вы должны повторно запустить команду установки assets.

$ php app/console assets:install web


Если теперь вы обновите главную страницу блога вы увидите Облако тегов в боковой панели. Для того, чтобы получить теги с различными «весами» (популярностью), вам может понадобиться обновить фикстуры блога.

Последние Комментарии

Теперь у нас есть Облако тегов, давайте добавим компонент Последние комментарии на боковую панель.

Во-первых, нам нужен способ для получения последних комментариев к записям. Для этого мы добавим новый метод в CommentRepository

src/Blogger/BlogBundle/Repository/CommentRepository.php
createQueryBuilder('c')
                ->select('c')
                ->addOrderBy('c.id', 'DESC');

    if (false === is_null($limit))
        $qb->setMaxResults($limit);

    return $qb->getQuery()
              ->getResult();
}


Далее обновим метод sidebar для получения последних комментариев и передачи их в шаблон

src/Blogger/BlogBundle/Controller/PageController.php
// src/Blogger/BlogBundle/Controller/PageController.php

public function sidebarAction()
{
    // ..

    $commentLimit   = $this->container
                           ->getParameter('blogger_blog.comments.latest_comment_limit');
    $latestComments = $em->getRepository('BloggerBlogBundle:Comment')
                         ->getLatestComments($commentLimit);

    return $this->render('BloggerBlogBundle:Page:sidebar.html.twig', array(
        'latestComments'    => $latestComments,
        'tags'              => $tagWeights
    ));
}


Вы заметите что мы использовали новый параметр blogger_blog.comments.latest_comment_limit для лимита количества полученных комментариев. Чтобы создать этот параметр, обновите файл конфигурации

src/Blogger/BlogBundle/Resources/config/config.yml

# src/Blogger/BlogBundle/Resources/config/config.yml

parameters:
    # ..

    # Blogger max latest comments
    blogger_blog.comments.latest_comment_limit: 10



Наконец, мы должны отобразить последние комментарии в шаблоне боковой панели. Обновите шаблон

src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #}

{# .. #}

Latest Comments

{% for comment in latestComments %} {% else %}

There are no recent comments

{% endfor %}

Если теперь вы обновите страницу вы увидите Последние комментарии, которые выводятся под блоком Облако тегов.

fe17c00312ee427b8beacb45815b637f.png

Расширения Twig

До сих пор мы, отображали дату добавления комментария блога в стандартном формате даты, 2011–04–21. Гораздо лучший подход будет заключаться в том, чтобы отображать не дату, а время, которое прошло с момента публикации комментария, например, Опубликовано 3 часа назад. Мы могли бы добавить метод в сущность Comment для достижения этой цели и изменить шаблоны, чтобы использовать этот метод вместо метода {{comment.created|date (' h: iA Y-m-d')}}.

Но так как нам может понадобиться использовать эту функциональность в другом месте было бы лучше, вынести метод из сущности Comment. Так как преобразование даты задача именно шаблона, мы должны осуществить это используя Twig. Twig дает нам эту возможность, предоставляя интерфейс Extension.

Мы можем использовать интерфейс extension в Twig для расширения функциональных возможностей, которые он предоставляет по умолчанию. Мы собираемся создать новое Twig расширение фильтр, который можно будет использовать следующим образом.
{{ comment.created|created_ago }}. Это вернет дату создания комментария в формате, Опубликовано 2 дня назад.

Расширение

Создайте файл для расширения Twig со следующим содержимым

src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogExtension.php
getTimestamp();
        if ($delta < 0)
            throw new \InvalidArgumentException("createdAgo is unable to handle dates in the future");

        $duration = "";
        if ($delta < 60)
        {
            // Seconds
            $time = $delta;
            $duration = $time . " second" . (($time > 1) ? "s" : "") . " ago";
        }
        else if ($delta <= 3600)
        {
            // Mins
            $time = floor($delta / 60);
            $duration = $time . " minute" . (($time > 1) ? "s" : "") . " ago";
        }
        else if ($delta <= 86400)
        {
            // Hours
            $time = floor($delta / 3600);
            $duration = $time . " hour" . (($time > 1) ? "s" : "") . " ago";
        }
        else
        {
            // Days
            $time = floor($delta / 86400);
            $duration = $time . " day" . (($time > 1) ? "s" : "") . " ago";
        }

        return $duration;
    }

    public function getName()
    {
        return 'blogger_blog_extension';
    }
}


Создание расширения является простой задачей. Мы перезапишем метод getFilters () и вернем любое количество фильтров, которое мы хотим. В данном случае мы создаем фильтр created_ago. Этот фильтр затем зарегистрирует использование метода createdAgo, который просто преобразует объект DateTime в строку, представляющую продолжительность времени прошедшее с момента сохранения значения в объект DateTime.

Регистрация Расширения

Для того, чтобы расширение Twig стало нам доступно, необходимо обновить файл служб

src/Blogger/BlogBundle/Resources/config/services.yml
services:
    blogger_blog.twig.extension:
        class: Blogger\BlogBundle\Twig\Extensions\BloggerBlogExtension
        tags:
            - { name: twig.extension }



Как видите это регистрирует новый сервис используя класс расширения BloggerBlogExtension Twig который мы только что создали.

Отображение

Теперь новый Twig фильтр готов к использованию. Давайте обновим список Последних комментариев в боковой панели используя created_at фильтр. Обновите шаблон боковой панели

src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #}

{# .. #}

Latest Comments

{% for comment in latestComments %} {# .. #} {# .. #} {% endfor %}

Если теперь вы введете в ваш браузер http://localhost:8000/ вы увидите даты последних комментариев, с использованием фильтра Twig.

Давайте также обновим комментарии на странице блога с использованием нового фильтра. Замените контент в шаблоне

src/Blogger/BlogBundle/Resources/views/Comment/index.html.twig
{# src/Blogger/BlogBundle/Resources/views/Comment/index.html.twig #}

{% for comment in comments %}
    

{{ comment.user }} commented

{{ comment.comment }}

{% else %}

There are no comments for this post. Be the first to comment...

{% endfor %}

Совет

Есть целый ряд полезных расширений Twig, доступных через библиотеку Twig-Extensions на GitHub. Если вы создали полезное расширение вы можете создать pull request для этого репозитория, и расширение может быть добавлено, чтобы другие люди могли его использовать.

Url


В настоящее время URL для каждой записи в блоге отображает только id записи. Хотя это вполне приемлемо с функциональной точки зрения, это не является хорошим решением для SEO. Например, URL http://localhost:8000/1 не дает никакой информации о содержании записи, что-то вроде http://localhost:8000/1/a-day-with-symfony2 было бы гораздо лучше. Для достижения этой цели мы добавим slug в название блога и используем его как часть этого URL. Добавление slug удалит все символы, не являющиеся ASCII и заменит их на дефис .Обновите маршрут

Давайте модифицируем правило маршрута для страницы записи и добавим компонент slug. Обновите правило маршрута

src/Blogger/BlogBundle/Resources/config/routing.yml
# src/Blogger/BlogBundle/Resources/config/routing.yml

BloggerBlogBundle_blog_show:
    path:  /{id}/{slug}
    defaults: { _controller: "BloggerBlogBundle:Blog:show" }
    requirements:
        methods:  GET
        id: \d+



Контроллер
Как и в случае существующего компонента id, новый компонент slug будет передан в действие контроллера в качестве аргумента, так что давайте обновим контроллер

src/Blogger/BlogBundle/Controller/BlogController.php
// src/Blogger/BlogBundle/Controller/BlogController.php

public function showAction($id, $slug)
{
    // ..
}




чтобы это отразить.

Заметка

Порядок, в котором аргументы передаются в действие контроллера не имеет значения, имеют значение только имена. Symfony2 способен сопоставлять аргументы маршрутизации со списком параметров. Так как мы еще не использовали значения компонентов по умолчанию, стоит упомянуть их здесь. Если мы добавляем еще один компонент для правила маршрутизации мы можем указать значение по умолчанию с помощью опции defaults.

BloggerBlogBundle_blog_show:
    path:  /{id}/{slug}
    defaults: { _controller: "BloggerBlogBundle:Blog:show", comments: true }
    requirements:
        methods:  GET
        id: \d+

public function showAction($id, $slug, $comments)
{
    // ..
}


Используя этот метод, запрос к http://localhost:8000/1/symfony2-blog приведет к тому что в $comments будет установлено значение true в методе showAction

Slug


Поскольку мы хотим, создавать slug из названия записи, мы будем автоматически генерировать значение slug. Мы могли бы просто выполнить это действие во время вывода поля заголовка, но вместо этого мы будем хранить slug в сущности Blog и сохранять в базу данных.Обновление сущности Blog
Давайте добавим новое свойство в сущности Blog для хранения slug. Обновите сущность

src/Blogger/BlogBundle/Entity/Blog.php
// src/Blogger/BlogBundle/Entity/Blog.php

class Blog
{
    // ..

    /**
     * @ORM\Column(type="string")
     */
    protected $slug;

    // ..
}


Теперь создайте методы доступа для нового свойства $slug. Как и раньше выполним команду.

$ php app/console doctrine:generate:entities Blogger

Далее обновим схему базы данных.

$ php app/console doctrine:migrations:diff

$ php app/console doctrine:migrations:migrate

Для генерации значения slug мы будем использовать метод slugify из symfony1 Jobeet руководства. Добавьте метод slugify для сущности Blog

src/Blogger/BlogBundle/Entity/Blog.php
// src/Blogger/BlogBundle/Entity/Blog.php

public function slugify($text)
{
    // replace non letter or digits by -
    $text = preg_replace('#[^\\pL\d]+#u', '-', $text);

    // trim
    $text = trim($text, '-');

    // transliterate
    if (function_exists('iconv'))
    {
        $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
    }

    // lowercase
    $text = strtolower($text);

    // remove unwanted characters
    $text = preg_replace('#[^-\w]+#', '', $text);

    if (empty($text))
    {
        return 'n-a';
    }

    return $text;
}


Поскольку мы хотим, чтобы slug генерировался автоматически из заголовка мы можем генерировать slug, когда устанавливается значение заголовка. Для этого мы можем обновить метод setTitle чтобы он устанавливал значение slug. Обновите сущность Blog

src/Blogger/BlogBundle/Entity/Blog.php
// src/Blogger/BlogBundle/Entity/Blog.php

public function setTitle($title)
{
    $this->title = $title;

    $this->setSlug($this->title);
}


Далее обновите метод setSlug.

src/Blogger/BlogBundle/Entity/Blog.php
// src/Blogger/BlogBundle/Entity/Blog.php

public function setSlug($slug)
{
    $this->slug = $this->slugify($slug);
}



Далее перезагрузим данные фикстур чтобы сгенерировать slug.

$ php app/console doctrine:fixtures:load

Обновление маршрутов

Наконец, нам нужно обновить существующие вызовы для создания маршрутов к странице блога. Есть несколько мест где нам нужно это сделать.
Откройте шаблон домашней страницы и добавьте следующее

src/Blogger/BlogBundle/Resources/views/Page/index.html.twig
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}

{% extends 'BloggerBlogBundle::layout.html.twig' %}

{% block body %}
    {% for blog in blogs %}
        
    {% else %}
        

There are no blog entries for symblog

{% endfor %} {% endblock %}


Кроме того, одно обновление должно быть сделано в блоке Последние комментарии шаблона боковой панели

src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #}

{# .. #}


    {{ comment.blog.title }}


{# .. #}



Наконец должен быть обновлен метод createAction из Comment контроллера при перенаправлении на страницу блога при успешной публикации комментария. Обновите контроллер Comment

src/Blogger/BlogBundle/Controller/CommentController.php
// src/Blogger/BlogBundle/Controller/CommentController.php

public function createAction($blog_id)
{
    // ..

    if ($form->isValid()) {
        // ..

        return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show', array(
            'id'    => $comment->getBlog()->getId(),
            'slug'  => $comment->getBlog()->getSlug())) .
            '#comment-' . $comment->getId()
        );
    }

    // ..
}



Теперь, если вы перейдете на домашнюю страницу http://localhost:8000/ и нажмёте на один из заголовков записей вы увидите, что slug записи был добавлен к концу URL.

Окружения


Окружения являются очень мощными и в то же время простыми функциями Symfony2. С помощью окружений мы можем настроить различные аспекты Symfony2 для запуска в зависимости от конкретных потребностей по-разному в течение всего жизненного цикла приложения. По умолчанию Symfony2 настроен работать с 3-мя Окружениями:

dev — Разработка
test — Тестирование
prod — Рабочее

Цель этих окружений не требует пояснений, но что, если эти окружения должны быть настроены по-разному в зависимости от индивидуальных потребностей. При разработке приложения полезно иметь панель инструментов для разработчика с отображаемыми ошибками, в то время как в рабочем окружении вы не хотите этого отображать. На самом деле, если это информация будет отображаться она будет представлять угрозу безопасности так как будет доступно много деталей относительно внутренности приложения и сервера. В рабочем окружении было бы лучше отображать пользовательские страницы ошибок с помощью упрощенных сообщений, в то время как информация будет записываться в текстовые файлы. Было бы также полезно включить кэширование, чтобы обеспечить приложению хорошую работу. Если кэширование будет включено в окружении разработки — это будет доставлять массу неудобств так как вам придётся очищать кэш каждый раз, когда вы изменяете файлы конфигураций и т.д.
Окружение test является тестовой средой. Оно используется при выполнении тестов, таких как unit или функциональное тестирование. Мы ещё не рассматривали тестирование, оно будет рассмотрено в следующей части…

Front контроллер

До этого момента мы работали в окружении разработчика. Если мы взглянем на фронт-контроллер расположенный в web/app_dev.php вы увидите следующую строку:

$kernel = new AppKernel('dev', true);


В противоположность этому, если мы посмотрим на фронт-контроллер для рабочего окружения, расположенного в web/app.php мы увидим следующее:

$kernel = new AppKernel('prod', false);


Вы можете видеть, что в данном случае в AppKernel передаётся рабочее окружение.
Тестовое окружение не должно быть запущено в браузере и поэтому отсутствует фронт-контроллер app_test.php.Параметры конфигурации
Выше мы видели, как фронт-контроллеры используются для изменения окружения в котором, работает приложение. Теперь мы рассмотрим, как различные настройки изменяются во время выполнения в каждом окружении. Если вы посмотрите на файлы в app/config вы увидите несколько файлов config.yml. В частности, есть один основной config.yml и 3 других все суффиксом окружения; config_dev.yml, config_test.yml и config_prod.yml. Каждый из этих файлов загружается в зависимости от текущего окружения. Если мы исследуем файл config_dev.yml вы увидите следующие строки в верхней части.

imports:
    - { resource: config.yml }

Директива imports заставит включить config.yml, в этот файл Та же директива imports может быть найдена в верхней части 2 других конфигурационных файлов окружений, config_test.yml и config_prod.yml. Включив общий набор параметров конфигурации, определенных в config.yml мы имеем возможность переопределять конкретные параметры для каждой среды. Мы можем увидеть в файле конфигурации development, расположенного app/config/config_dev.yml следующие строки, конфигурации использования панели инструментов разработчика.

# app/config/config_dev.yml
web_profiler:
    toolbar: true


Эта настройка отсутствует в конфигурационном файле рабочего окружения так как мы не хотим, чтобы отображалась панель инструментов разработчика.Запуск рабочего окружения
Для тех из вас кто хотел увидеть, как сайт работает в рабочем окружении сейчас самое время.
Во-первых, мы должны очистить кэш с помощью одной из команд Symfony2.

$ php app/console cache:clear --env=prod

Теперь введите в ваш браузер http://localhost:8000/app.php.

Вы заметите, что сайт выглядит так же, но есть несколько важных различий. Панель инструментов разработчика и подробное сообщение об ошибке больше не отображаются, попытайтесь перейти http://localhost:8000/app.php/999.

50be8525cbbd44cdb6e5336d4f3db1e9.png

Подробное сообщение об ошибке было заменено упрощенным сообщением, информирующим пользователя о проблеме. Эти страницы могут быть настроены в соответствие с внешним видом вашего приложения.

Кроме того, вы заметите что файл app/logs/prod.log заполняется данными относительно выполнения. Это бывает полезным, когда у вас есть проблемы с приложением в рабочем окружении, так как ошибки не могут быть больше отображены на экране.

Заметка

Как запрос на http://localhost:8000/app.php в конечном итоге направляется через файл app.php? Я уверен, все вы создавали файлы, такие как index.html и index.php, которые действуют как индекс сайта, но как app.php может стать таким? Это стало возможным благодаря RewriteRule в файле web/.htaccess

RewriteRule ^(.*)$ app.php [QSA, L]
Мы можем видеть, что эта строка имеет регулярное выражение, которое соответствует любому тексту, ^(.*)$ и передано в app.php.

Если работаете на сервере Apache, у которого не включен mod_rewrite.c, вы можете просто добавить app.php к URL, http://localhost:8000/app.php/.

Создание нового окружения

Создание собственного окружения в Symfony2 может понадобится, например, когда вы захотите создать промежуточное окружение, которое будет работать на рабочем сервере, но будет выводить некоторую информацию для отладки. Это позволило бы тестировать платформу вручную на фактическом рабочем сервере, так как конфигурации рабочего сервера и сервера разработчика могут отличаться.
Так как создание нового окружения является простой задачей, она выходит за рамки этого руководства. Существует отличная статья в cookbook Symfony2, в которой это описано.

Assetic

Так как начиная с версии Symfony2.8 библиотека Assetiс не поставляется по умолчанию, мы должны сами установить бандл assetic. Добавьте строчку в файл composer.json в корне проекта

"require": {
      //...
       "symfony/assetic-bundle": "dev-master"
    },


и введите команду

composer update 


в консоли.

Далее зарегистрируем бандл в файле

app/AppKernel.php
//app/AppKernel.php

  public function registerBundles()
    {
        $bundles = array(
            // ...
            new Symfony\Bundle\AsseticBundle\AsseticBundle(),
        );

        // ...
    }


И в конце добавим минимально необходимые настройки в файл

app/config/config.yml
# app/config/config.yml
assetic:
    debug:          '%kernel.debug%'
    use_controller: '%kernel.debug%'
    filters:
        cssrewrite: ~

# ...


Библиотека была разработана Kris Wallsmith под вдохновением Python библиотеки webassets.

Assetic имеет дело с 2-мя частями управления assets, assets такие как изображения, таблицы стилей, JavaScript и фильтры, которые могут быть применены к ним. Эти фильтры могут выполнять полезные задачи, такие как минификация ваших CSS и JavaScript, передача файлов CoffeeScript через компилятор CoffeeScript и объединения assets файлов вместе, чтобы уменьшить количество запросов HTTP сделанных на сервере.

В настоящее время мы используем Twig asset функцию для включения assets в шаблон следующим образом.



Assets


Assetic библиотека описывает asset следующим образом:
Assetic asset это что-то с фильтруемым контентом, который может быть загружен. Asset также включает в себя метаданные, некоторыми из которых можно манипулировать.
Проще говоря, assets являются ресурсами которые использует ваше приложение, например, таблицы стилей и изображения.
Чтобы включить Assetic для BloggerBlogBundle мы должны изменить

app/config/config.yml
# ..
assetic:
    bundles:    [BloggerBlogBundle]
    # ..



Это позволит включить Assetic только для BloggerBlogBundle и потребует корректировки всякий раз, когда новый бандл должен использовать Assetic. Мы можем полностью удалить строку bundles и включить его для всех будущих бандлов.

Таблицы стилей


Давайте начнем с замены текущих вызовов asset для таблиц стилей в основном шаблоне BloggerBlogBundle. Обновите контент в шаблоне

src/Blogger/BlogBundle/Resources/views/layout.html.twig
{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}

{# .. #}

{% block stylesheets %}
    {{ parent () }}

    {% stylesheets
        '@BloggerBlogBundle/Resources/public/css/*'
    %}
        
    {% endstylesheets %}
{% endblock %}

{# .. #}


Мы заменили предыдущие 2 ссылки на CSS-файлы, некоторой Assetic функциональностью. Использованием таблиц стилей из Assetic мы определили, что все CSS файлы в src/Blogger/BlogBundle/Resources/public/css должны быть объединены в 1 файл, а затем выведены. Объединение файлов является очень простым, но эффективным способом оптимизации вашего сайта за счет уменьшения количества необходимых файлов. Меньшее количество файлов означает меньшее количество HTTP-запросов к серверу. В то время как мы использовали * чтобы указать все файлы в каталоге css мы могли бы просто перечислить каждый файл по отдельности.

следующим образом
{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}

{# .. #}

{% block stylesheets %}
    {{ parent () }}

    {% stylesheets
        '@BloggerBlogBundle/Resources/public/css/blog.css'
        '@BloggerBlogBundle/Resources/public/css/sidebar.css'
    %}
        
    {% endstylesheets %}
{% endblock %}

{# .. #}


Конечный результат в обоих случаях одинаков. Первый вариант с использованием * гарантирует, что, когда новые CSS файлы добавятся в каталог, они всегда будут включены в объединенный файл CSS Assetic. Это может быть не тем что вам требуется, так что используйте любой метод описанный выше для удовлетворения ваших потребностей.

Если вы посмотрите на код HTML http://localhost:8000/ вы увидите, что CSS файлы были включены примерно таким образом (Заметьте, мы находимся в окружении разработчика).




Вы можете быть удивлены, почему здесь 2 файла. Ведь выше было указано, что Assetic должен объединять файлы в 1 CSS файл. Это происходит потому, что мы запустили symblog в окружении разработчика. Мы можем попросить Assetic работать в режиме без отладки, установив флаг отладки в false

следующим образом
{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}

{# .. #}

{% stylesheets
    '@BloggerBlogBundle/Resources/public/css/*'
    debug=false
%}
    
{% endstylesheets %}

{# .. #}


Теперь, если вы посмотрите на HTML код вы увидите что-то вроде этого.




Если просмотреть содержимое этого файла вы увидите, что 2 CSS файла, blog.css и sidebar.css были объединены в 1 файл. Имя созданного файла CSS генерируется случайным образом. Если вы хотите контролировать имя, данное сгенерированному файлу используйте output опцию

следующим образом
{% stylesheets
    '@BloggerBlogBundle/Resources/public/css/*'
    output='css/blogger.css'
%}
    
{% endstylesheets %}


Перед тем, как продолжить удалите флаг отладки из предыдущего фрагмента, так как мы хотим, возобновить поведение по умолчанию

© Habrahabr.ru