Статьи

Построение собственного фильтра веток TDD Way

Twig — мощный, но простой в освоении шаблонизатор. Это также мой личный фаворит, поскольку вся моя веб-разработка основана на Symfony или Silex .

Twig Header

Помимо основного синтаксиса ( {{ ... }} и {% ... %} ), Twig имеет встроенную поддержку различных фильтров. Фильтр похож на «конвертер». Он получает определенные исходные данные (строку, число, дату и т. Д.) И, применяя преобразование, выводит данные в новой форме (в виде строки, числа, даты и т. Д.). Например, фильтр number_format может преобразовать число в более читаемое:

 {{price|number_format(2, '.', ',')}} 

Предполагая, что price — это переменная со значением 1234567.12345 , после операции фильтрации на странице будет отображаться 1 1,234,567.12 : 2 десятичных знака, «.» В качестве десятичной точки и «,» в качестве разделителя тысяч. Это делает его намного более читабельным.

В качестве другого примера, capitalize будут делать каждую первую букву слова в верхнем регистре предложения, а другие строчные:

 {{title|capitalize}} 

Если предположить, что title — это переменная со значением, this tutorial is nice , то после операции фильтрации вывод будет « This Tutorial Is Nice this tutorial is nice .

Twig имеет ряд встроенных фильтров. Полный список можно найти в его официальной документации .

Зачем выбирать фильтр?

Некоторые могут утверждать, что вышеуказанная функциональность также выполнима в PHP; это правда. Мы также часто находим функции, предоставляемые встроенными фильтрами, довольно ограниченными и недостаточными. Итак, зачем использовать фильтр?

В среде MVC уровень модели отвечает за предоставление данных (например, цена книги или название статьи). Слой представления отвечает за отображение данных. Выполнение преобразования данных в стиле фильтра в контроллере не рекомендуется, поскольку это противоречит роли конструктора контроллера, и выполнение этого в модели эффективно изменяет данные, что не очень хорошо. На мой взгляд, мнение является единственным жизнеспособным вариантом.

Кроме того, поскольку конкретное преобразование данных может запрашиваться во многих местах в шаблоне (а также в различных шаблонах) для одних и тех же данных из различных источников, лучше вызывать этот фильтр в шаблоне каждый раз, когда требуется такое преобразование. , чем вызывать функцию в контроллере. Код будет намного аккуратнее.

Рассмотрим сравнение следующих сегментов кода с использованием фильтра и вызова функции PHP (с использованием Symfony 2 + Doctrine). Мы можем легко увидеть различия в элегантности и удобстве использования.

Версия фильтра:

 ... <em>{{ book.title|capitalize }}</em> has {{book.pages|number_format(2, ".", ",")}} and costs ${{ book.price|number_format(2, ".", ",")}}. ... 

И для этого подхода то, что мы делаем в контроллере, будет:

 $book=$repo->findById('00666'); ... return $this->render('A_Template', ['book'=>$book]); 

Найдите книгу (данные) и передайте ее для просмотра.

Но если мы используем вызовы функций PHP, код может выглядеть так:

 //Using PHP function within a Symfony framework and Doctrine $book=$repo->findById('00666'); $book['tmpTitle'] = ucwords($book->getTitle); $book['tmpPage'] = number_format($book->getPages(), 2, ".", ","); $book['tmpPrice'] = number_format($book->getPrice(), 2, ".", ","); ... return $this->render('A_Template', ['book'=>$book]); 

.. а потом в шаблоне

 <em>{{ book.tmpTitle }}</em> has {{book.tmpPages}} and costs ${{ book.tmpPrice}}. 

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

Давайте построим фильтр

Мы создадим фильтр для более удобного отображения даты / времени публикации. То есть вместо того, чтобы говорить что-то вроде «Опубликовано в 2015-03-14 13:34 », эта метка времени будет преобразована в нечто вроде « Только сейчас », « Несколько часов назад », « Несколько дней назад », « Довольно некоторое время назад »,« Давным-давно »и т. д.

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

Сначала установите PHPUnit , выполнив следующую команду Composer:

 composer global require phpunit/phpunit 

Это установит самую последнюю версию PHPUnit во всем мире, делая ее доступной на всей вашей машине из любой папки. Мы будем использовать PHPUnit для запуска тестов и утверждать, что все ожидания оправданы.

Установить ожидания

Вариант использования понятен: мы хотим преобразовать объект даты / времени ( 2014-03-19 12:34 ) в нечто вроде « Только сейчас », « Несколько часов назад », « Несколько дней назад », « Довольно время назад »,« давно, давно »и т. д., в зависимости от того, как далеко дата / время от текущего момента.

Не существует установленного правила, определяющего, насколько далеко должна быть дата / время, чтобы она могла отображаться как « Довольно давно ». Это субъективный вопрос, поэтому мы определим настроенный набор правил для нашего приложения, и эти правила будут отражены в наших ожиданиях:

Как давно Быть расцененным как
< 1 minute Прямо сейчас
< 10 minutes Несколько минут назад
< 1 hour В течение часа
< 16 hours Несколько часов назад
< 24 hours В течение одного дня
< 3 days Некоторое время назад
< 10 days Давным-давно
> 10 days С Марса

Давайте переведем эти ожидания в тестовый скрипт, чтобы мы могли протестировать его с помощью PHPUnit. Этот скрипт сохраняется в src/AppBundle/Tests/Twig/timeUtilTest.php :

 <?php namespace AppBundle\Tests\Twig; use AppBundle\Twig\AppExtension; class timeUtilTest extends \PHPUnit_Framework_TestCase { /** * @dataProvider tsProvider */ public function testtssFilter($testTS, $expect) { $tu=new AppExtension(); $output=$tu->tssFilter($testTS); $this->assertEquals($output, $expect); } public static function tsProvider() { return [ [date_sub(new \DateTime(), new \DateInterval("PT50S")), "Just now"], [date_sub(new \DateTime(), new \DateInterval("PT2M")), "Minutes ago"], [date_sub(new \DateTime(), new \DateInterval("PT57M")), "Within an hour"], [date_sub(new \DateTime(), new \DateInterval("PT13H1M")), "A few hours ago"], [date_sub(new \DateTime(), new \DateInterval("PT21H2M")), "Within one day"], [date_sub(new \DateTime(), new \DateInterval("P2DT2H2M")), "Some time back"], [date_sub(new \DateTime(), new \DateInterval("P6DT2H2M")), "Ages ago"], [date_sub(new \DateTime(), new \DateInterval("P13DT2H2M")), "From Mars"], ]; } } 

Если мы запустим этот тест сейчас:

 phpunit -c app/ 

Тест не будет запущен, потому что мы еще не определили AppBundle\Twig\AppExtension . Мы можем быстро создать файл скелета: src/AppBundle/Twig/AppExtension.php . Это может быть так просто, как это:

 namespace AppBundle\Twig; class AppExtension extends \Twig_Extension { public function getFilters() { return [ new \Twig_SimpleFilter('tss', [$this, 'tssFilter']), ]; } public function getName() { return 'app_extension'; } public function tssFilter(\DateTime $timestamp) { // to be implemented } } 

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

ПРИМЕЧАНИЕ: Symfony2 очень хорошо работает с PHPUnit. При стандартной настройке Symfony2 в папке app проекта есть файл phpunit.xml.dist . Приведенная выше команда автоматически использует этот файл в качестве файла конфигурации для PHPUnit. Как правило, дальнейшая настройка не требуется.

Полный код функции tssFilter приведен ниже:

 public function tssFilter(\DateTime $timestamp) { $TSS=['Just now','Minutes ago','Within an hour','A few hours ago','Within one day','Some time back','Ages ago', 'From Mars']; $i=-1; $compared = new \DateTime(); $ts1=$timestamp->getTimestamp(); $co1=$compared->getTimestamp(); $diff=$ts1-$co1; if($diff<0 ) // Diff is always <0, so always start from index 0 { $i++; } if($diff<-1*60 ) //within one minute { $i++; } if($diff<-10*60) // within ten minues { $i++; } if($diff<-60*60) { $i++; } if($diff<-16*60*60) { $i++; } if($diff<-24*60*60) { $i++; } if($diff<-3*24*60*60) { $i++; } if($diff<-10*24*60*60) { $i++; } return $TSS[$i]; } 

Код будет находиться в tssFilter . Он принимает объект DateTime чтобы программа могла определить, какая строка в $TSS должна быть возвращена, основываясь на далекой timestamp с этого момента.

Это оно! Запустите тест, и все должно пройти!

Интегрировать его в Symfony

tssFilter по-прежнему изолирован от платформы Symfony. Чтобы использовать его в нашем шаблоне, нам нужно зарегистрировать его в файле services.yml :

 services: app.twig_extension: class: AppBundle\Twig\AppExtension tags: - { name: twig.extension } 

Мы должны предоставить полное имя класса фильтра: AppBundle\Twig\AppExtension .

Наконец, мы можем использовать это так в нашем шаблоне Twig:

 {{post.author|capitalize}} posted "{{post.title|capitalize}}" (posted {{post.creation|tss}}) 

Имя фильтра ( tss ) является производным от имени функции tssFilter() файла src/AppBundle/Twig/AppExtension.php и, как и для других компонентов Symfony, «Фильтр» удаляется.

Завершение

В этом быстром уроке мы рассмотрели несколько вещей:

  1. Фильтры Twig и почему их лучше использовать, чем чистые вызовы PHP.
  2. Как создать собственный фильтр TDD с помощью PHPUnit.
  3. Как интегрировать фильтр в платформу Symfony.

Оставьте свои комментарии и мысли ниже и поделитесь своими достижениями!