tdd 私有方法_使用TDD方法构建自定义的Twig过滤器

tdd 私有方法

Twig is a powerful, yet easy to master template engine. It is also my personal favorite as all my web development is based on either Symfony or Silex.

Twig是一个功能强大但易于掌握的模板引擎。 这也是我个人的最爱,因为我所有的Web开发都是基于SymfonySilex

Twig Header

Apart from its core syntax ({{ ... }} and {% ... %}), Twig has built-in support for various filters. A filter is like a “converter”. It receives certain original data (a string, a number, a date, etc) and by applying a conversion, outputs the data in a new form (as a string, a number, a date, etc). For example, the number_format filter can convert a number into a more readable one:

除了其核心语法( {{ ... }}{% ... %} )之外,Twig还​​内置了对各种过滤器的支持。 过滤器就像一个“转换器”。 它接收某些原始数据(字符串,数字,日期等),并通过应用转换以新形式(作为字符串,数字,日期等)输出数据。 例如, number_format过滤器可以将数字转换为可读性更高的数字:

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

Assuming price is a variable with value 1234567.12345, after the filter operation, the output in the page will be 1,234,567.12: 2 decimal places, “.” as the decimal point and “,” as the thousands separator. This makes it much more readable.

假设price是一个值为1234567.12345的变量,在进行过滤操作之后,页面中的输出将为1,234,567.12 :小数点后1,234,567.12 “。”。 作为小数点,“,”作为千位分隔符。 这使其更具可读性。

As another example, capitalize will make every first letter of a word in a sentence uppercase and others lowercase:

再举一个例子, capitalize将使句子中单词的每个首字母变为大写,其他字母变为小写:

{{title|capitalize}}

Assuming title is a variable with the value this tutorial is nice, after the filter operation, the output will be This Tutorial Is Nice.

假设title是一个变量,其值是this tutorial is nice ,在进行过滤操作之后,输出将是This Tutorial Is Nice

Twig has a number of built-in filters. The full list can be found in its official documentation.

Twig具有许多内置过滤器。 完整列表可在其官方文档中找到。

为什么选择过滤器? (Why Choose a Filter?)

Some may argue that the above functionality is also doable in PHP; that is true. We also often find the functionality provided by built-in filters quite limited and insufficient. So, why use a filter?

有人可能会说上述功能在PHP中也是可行的。 那是真实的。 我们还经常发现内置过滤器提供的功能十分有限和不足。 那么,为什么要使用过滤器?

In an MVC environment, the model layer is responsible for providing data (a book price or an article title, for example). The view layer is responsible for displaying the data. Doing the data conversion, filter-style, in a controller is not advisable because it is against the design role of a controller, and doing it in a model effectively changes the data, which is not good. In my opinion, the view is the only viable option.

在MVC环境中,模型层负责提供数据(例如,书籍价格或文章标题)。 视图层负责显示数据。 不建议在控制器中执行过滤器式的数据转换,因为这违背了控制器的设计角色,而在模型中进行有效地更改了数据,这是不好的。 我认为,视图是唯一可行的选择。

Besides, as a particular transformation of data may be requested in many places in a template (as well as in various templates) on the same data from various sources, it is better to call that filter in the template every time such a conversion is required, than to call a function in the controller. The code will be much tidier.

此外,由于可能需要在模板(以及各种模板)中的多个位置对来自各种来源的同一数据进行特定的数据转换,因此,每次需要这种转换时,最好在模板中调用该过滤器,而不是在控制器中调用函数。 代码会更加整洁。

Let’s consider the following code segments comparing using a filter and a PHP function call (using Symfony 2 + Doctrine). We can easily see the differences in elegance and usability.

让我们考虑使用过滤器和PHP函数调用(使用Symfony 2 + Doctrine)进行比较的以下代码段。 我们可以轻松地看到优雅和可用性方面的差异。

Filter version:

过滤器版本:

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

And for this approach, what we do in the controller will be:

对于这种方法,我们将在控制器中执行以下操作:

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

Find the book (the data) and pass it to the view to display.

查找书籍(数据)并将其传递给视图以显示。

But if we use PHP function calls, the code may look like this:

但是,如果我们使用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]);

.. and then in the template

..然后在模板中

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

You can see that the filter approach is much cleaner and easier to manage, with no clumsy temp variables in between.

您会看到,过滤器方法更加简洁,易于管理,并且中间没有笨拙的临时变量。

让我们建立一个过滤器 (Let’s build a filter)

We’ll build a filter to display the publication date/time of a post in a fancier way. That is, instead of saying something like “Posted at 2015-03-14 13:34“, this timestamp will be transformed into something like “Just now“, “A few hours earlier“, “A few days back“, “Quite some time ago“, “Long, long ago“, etc.

我们将构建一个过滤器,以一种更奇妙的方式显示帖子的发布日期/时间。 也就是说, 2015-03-14 13:34 “发布于2015-03-14 13:34 ”之类的时间戳, 2015-03-14 13:34说它变成了“ 刚好 ”,“ 几小时前 ”,“ 几天前 ”,“ 相当”前一段时间 ”,“ 很久很久以前 ”等。

We’ll build it in a TDD way. To get introduced to TDD, see this post and the links within it, but the approaches we take in this tutorial should be easy enough to understand even without looking into TDD beforehand.

我们将以TDD方式构建它。 要了解TDD,请参阅本文和其中的链接,但是即使不事先研究TDD,我们在本教程中采用的方法也应该足够容易理解。

First, install PHPUnit by executing the following Composer command:

首先,通过执行以下Composer命令安装PHPUnit

composer global require phpunit/phpunit

This will install the most recent version of PHPUnit globally, making it accessible on your entire machine from any folder. We will use PHPUnit to run the tests and assert that all the expectations are met.

这将在全球范围内安装最新版本PHPUnit,从而可从任何文件夹在您的整个计算机上对其进行访问。 我们将使用PHPUnit来运行测试并断言所有期望均已满足。

设定期望 (Set expectations)

The use case is clear: we want to convert a date/time object (2014-03-19 12:34) into something like “Just now“, “A few hours ago“, “A few days back“, “Quite some time ago“, “Long, long ago“, etc, depending on how far the date/time is from the current moment.

用例是明确的:我们想要的日期/时间对象(转换2014-03-19 12:34 )成类似“ 刚才 “,“ 几个小时前 “,“ 几天后 “,“ 颇有些时间之前 ”,“ 很久很久以前 ”等,具体取决于日期/时间距当前时刻有多远。

There is no set rule to determine how far away a date/time should be so that it can be displayed as “Quite some time ago“. This is a subjective matter so we will define a customized rule set for our app and these rules will be reflected in our expectations:

没有设置规则来确定日期/时间应该多远,以便可以将其显示为“ 相当早 ”。 这是一个主观问题,因此我们将为我们的应用程序定义一个自定义规则集,这些规则将反映在我们的期望中:

How long agoTo be regarded as
< 1 minuteJust now
< 10 minutesMinutes ago
< 1 hourWithin an hour
< 16 hoursA few hours ago
< 24 hoursWithin one day
< 3 daysSome time back
< 10 daysAges ago
> 10 daysFrom Mars
多久以前 被视为
< 1 minute 现在
< 10 minutes 几分钟前
< 1 hour 一小时内
< 16 hours 几个小时前
< 24 hours 一天之内
< 3 days 回来一段时间
< 10 days 很久以前
> 10 days 来自火星

Let’s translate these expectations into a test script so that we can test it with PHPUnit. This script is saved in src/AppBundle/Tests/Twig/timeUtilTest.php:

让我们将这些期望转换为测试脚本,以便我们可以使用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"],
        ];
    }
}

If we run this test now:

如果我们现在运行此测试:

phpunit -c app/

The test won’t run because we have not defined the AppBundle\Twig\AppExtension yet. We can quickly create a skeleton file: src/AppBundle/Twig/AppExtension.php. It can be as simple as this:

由于我们尚未定义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 
    }
}

Now we can run the test script. All tests (expectations) will fail because we have not done anything to implement the tssFilter function.

现在我们可以运行测试脚本了。 所有测试(期望)都将失败,因为我们尚未完成任何实现tssFilter函数的操作。

NOTE: Symfony2 works very well with PHPUnit. With the default Symfony2 setup, there is a phpunit.xml.dist file in the project’s app folder. The above command will automatically use that file as the configuration file for PHPUnit. Normally, no further adjustment is needed.

注意: Symfony2与PHPUnit配合得很好。 使用默认的Symfony2设置,项目的app文件夹中有一个phpunit.xml.dist文件。 上面的命令将自动将该文件用作PHPUnit的配置文件。 通常,不需要进一步的调整。

The full code of the tssFilter function is listed below:

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];
    }

The code will reside in tssFilter. It accepts a DateTime object so that the program can determine which string in $TSS should be returned based on far timestamp is from now.

该代码将驻留在tssFilter 。 它接受一个DateTime对象,以便程序可以根据距现在远的timestamp确定应返回$TSS哪个字符串。

That’s it! Run the test, and everything should pass!

而已! 运行测试,一切都会通过!

将其集成到Symfony中 (Integrate it into Symfony)

The tssFilter is still isolated from the Symfony framework. To use it in our template, we need to register it in the services.yml file:

tssFilter仍与Symfony框架隔离。 要在我们的模板中使用它,我们需要在services.yml文件中注册它:

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

We must provide the fully qualified name of the filter class: AppBundle\Twig\AppExtension.

我们必须提供过滤器类的完全限定名称: AppBundle\Twig\AppExtension

Finally, we can use it like this in our Twig template:

最后,我们可以在Twig模板中像这样使用它:

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

The filter name (tss) is derived from src/AppBundle/Twig/AppExtension.php file’s tssFilter() function name and like with other Symfony components, “Filter” is stripped.

过滤器名称( tss )源自src/AppBundle/Twig/AppExtension.php文件的tssFilter()函数名称,并且与其他Symfony组件一样,“过滤器”被剥离。

结语 (Wrapping up)

In this quick tutorial, we covered a few things:

在本快速教程中,我们介绍了几件事:

  1. Twig filters and why it is better to use them than pure PHP calls.

    Twig筛选器以及为什么比纯PHP调用更好使用它们。
  2. How to build a custom filter the TDD way with PHPUnit.

    如何使用PHPUnit以TDD方式构建自定义过滤器。
  3. How to integrate a filter into the Symfony framework.

    如何将过滤器集成到Symfony框架中。

Leave your comments and thoughts below and share your achievements!

在下面留下您的评论和想法,分享您的成就!

翻译自: https://www.sitepoint.com/building-custom-twig-filter-tdd-way/

tdd 私有方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值