symfony 2_使用Symfony 2构建Web应用程序:完成

symfony 2

In Part 1 and Part 2 of this series, I have covered the basics of using Symfony 2 to develop a functioning web site.

在本系列的第1部分第2部分中,我介绍了使用Symfony 2开发可正常运行的网站的基础知识。

In this part of the tutorial, I will cover some more advanced techniques and finish the project with pagination, image watermarks and NativeQuery.

在本教程的这一部分中,我将介绍一些更高级的技术,并使用分页,图像水印和NativeQuery完成该项目。

The code we'll be using is identical to the code from Part 2 – the features are already there, they just weren't discussed.

我们将使用的代码与第2部分中的代码相同–这些功能已经存在,只是没有进行讨论。

图像水印 (Image watermarks)

Seeing as there's already plenty of image manipulation and watermarking through Imagick tutorials available on SitePoint, and owing to the fact that we don't do any advanced image manipulation in this particular project, we'll stick to PHP's default image mainpulation library – GD.

看到通过SitePoint上的Imagick教程已经存在大量的图像处理和水印处理,并且由于在这个特定项目中我们不进行任何高级图像处理,我们将坚持使用PHP的默认图像维护库GD。

In general, when there is "image processing", we are talking about an action or actions applied to an image before we eventually show it using the <img> tag. In my application, this is done in two steps:

通常,当进行“图像处理”时,在最终使用<img>标签显示图像之前,我们要先讨论一个或多个应用于图像的动作。 在我的应用程序中,这分两个步骤完成:

  • Create a route to capture the "image display request" and map it to an action in a controller.

    创建一条路线来捕获“图像显示请求”,并将其映射到控制器中的操作。
  • Implement the processing in the action.

    在行动中实施处理。
路由配置 (Route configuration)

The route for displaying an image after processing is simple:

处理后显示图像的方法很简单:

cover: 
        pattern: /books/cover/{id}_{title}_{author}_{width}.png
        defaults: {_controller: trrsywxBundle:Default:cover, width: 300}

In a template, we can invoke the call to display the processed image by:

在模板中,我们可以通过以下方式调用调用以显示处理后的图像:

<img src="{{path("cover", {'id':book.id, 'author':author, 'title':book.title}) }}" alt="{{book.title}}'s cover" title="{{book.title}}'s cover"/>

So every time this <img> tag is encountered, a processed image will be displayed.

因此,每次遇到此<img>标记时,都会显示经过处理的图像。

图像处理 (Image processing)

In this application, we do two things before displaying the image:

在此应用程序中,我们在显示图像之前要做两件事:

  1. Depending on whether or not a book cover image exists, display it, or display the default cover image;

    根据书的封面图像是否存在,显示它或显示默认的封面图像;
  2. Add a watermark and adjust the size (300px wide in book detail view and 200px wide in reading list view).

    添加水印并调整大小(书本详细信息视图中为300px宽,阅读列表视图中为200px宽)。

The complete code is in src/tr/rsywxBundle/Controller/DefaultController.php in the function coverAction. The code is simple and straightforward and I'll just show you the output in Detail View for both a real book cover and a default cover:

完整的代码位于功能coverAction中的src/tr/rsywxBundle/Controller/DefaultController.php中。 该代码非常简单明了,我将在详细视图中为您展示实际书籍封面和默认封面的输出:

alt
alt

Note that I have created a "cover" folder under "web" to hold the default cover and the scanned book covers. I also used a Chinese TTF to display the watermark texts. Please feel free to use your own font (and copy that font to the "cover" folder).

请注意,我已经在“ web”下创建了一个“ cover”文件夹来保存默认封面和扫描的书籍封面。 我还使用中文TTF来显示水印文本。 请随时使用您自己的字体(并将该字体复制到“ cover”文件夹中)。

On a higher-traffic site, the correct course of action would be to cache the autogenerated images much like Lukas White did in his article, but I'll leave that up to you to play around with.

在流量较高的网站上,正确的做法是像Lukas White在他的文章中所做的那样,缓存自动生成的图像,但我将留给您自己尝试。

分页 (Pagination)

There are also a lot of articles on paginating a big dataset. In this tutorial, I will show you how I did it in this app, and we'll test it.

关于分页大数据集也有很多文章。 在本教程中,我将向您展示如何在此应用程序中进行操作,我们将对其进行测试。

The source code for the class is in src/tr/rsywxBundle/Utility/Paginator.php.

该类的源代码在src/tr/rsywxBundle/Utility/Paginator.php

The code itself is easy to read and does not involve anything particularly advanced, so I will just discuss the process.

该代码本身易于阅读,并且不涉及任何特别高级的内容,因此我将仅讨论该过程。

There are two fundamental values in a Pagination class:

分页类有两个基本值:

  1. How many records in total and how many pages in total?

    总共多少条记录,总共多少页?
  2. What is the current page and how to construct an easily accessible page list for further processing ?

    当前页面是什么?如何构建易于访问的页面列表以进行进一步处理?

Many pagination classes often deal with data retrieval too, but this is not good practice: pagination should only deal with things related to pagination, not the data itself. The data is the domain of the Entity/Repository.

许多分页类也经常处理数据检索,但这不是一个好习惯:分页只应处理与分页有关的事物,而不是数据本身。 数据是实体/存储库的域。

If we go back to the implementation of getting the books matching certain criteria, you will notice how the two steps (get data and do pagination) are separated in the controller:

如果我们回到使书籍符合特定条件的实现的方式,您将注意到控制器中两个步骤(获取数据和分页)是如何分开的:

File location: src/tr/rsywxBundle/Controller/BookController.php

File location: src/tr/rsywxBundle/Controller/BookController.php

public function listAction($page, $key)
    {
        $em = $this->getDoctrine()->getManager();
        $rpp = $this->container->getParameter('books_per_page');

        $repo = $em->getRepository('trrsywxBundle:BookBook');

        list($res, $totalcount) = $repo->getResultAndCount($page, $rpp, $key);
        //Above to retrieve data and counts
        //Below to instantiate the paginator

        $paginator = new \tr\rsywxBundle\Utility\Paginator($page, $totalcount, $rpp);
        $pagelist = $paginator->getPagesList();

        return $this->render('trrsywxBundle:Books:List.html.twig', array('res' => $res, 'paginator' => $pagelist, 'cur' => $page, 'total' => $paginator->getTotalPages(), 'key'=>$key));
    }

The constructor of my paginator takes 3 parameters:

我的分页器的构造函数采用3个参数:

  • page: to tell what is the current page. This will be used to return a list for pages to show as clickable links in the template;

    页面:告诉当前页面是什么。 这将用于返回页面列表,以在模板中显示为可点击链接。
  • totalcount: to tell the count of the results. This will be used to calculate the total pages together with the rpp parameter;

    totalcount:告诉结果计数。 这将与rpp参数一起用于计算总页数;
  • rpp: short for records per page.

    rpp:每页记录的缩写。

In my current implementation, I used a simple version of pagination showing only "First", "Previous", "Next", and "Last" page links, but you can try out different types by using the getPagesList function.

在当前的实现中,我使用了分页的简单版本,仅显示“第一”,“上一个”,“下一个”和“最后”页面链接,但是您可以使用getPagesList函数尝试不同的类型。

Take for example a page list like this:

以这样的页面列表为例:

1 2 3 4 5

The key here is in the getPagesList function which makes sure that the current page is always in the middle, or if there aren't enough pages, it makes sure it's in the correct position.

此处的键位于getPagesList函数中,该函数确保当前页面始终位于中间,或者如果没有足够的页面,则确保其位置正确。

public function getPagesList()
    {
        $pageCount = 5;
        if ($this->totalPages <= $pageCount) //Less than total 5 pages
            return array(1, 2, 3, 4, 5);

        if($this->page <=3)
            return array(1,2,3,4,5);

        $i = $pageCount;
        $r=array();
        $half = floor($pageCount / 2);
        if ($this->page + $half > $this->totalPages) // Close to end
        {
            while ($i >= 1)
            {
                $r[] = $this->totalPages - $i + 1;
                $i--;
            }
            return $r;
        } else
        {
            while ($i >= 1)
            {
                $r[] = $this->page - $i + $half + 1;
                $i--;
            }
            return $r;
        }
    }

To make sure this function really works, we'll have to test it before using it. We'll use PHPUnit as the test bench. Please refer to the official site for detailed instructions on how to install it. I used the phpunit.phar way to download the package and place it in my project root folder.

为了确保此功能确实有效,我们必须在使用前对其进行测试。 我们将使用PHPUnit作为测试平台。 请参阅官方网站以获取有关如何安装它的详细说明 。 我使用phpunit.phar方式下载了软件包并将其放置在项目的根文件夹中。

To test the Paginator class we just created, firstly we need to create a folder Utility under src/tr/rsywxBundle/Tests. All tests in Symfony should go under src/tr/rsywxBundle/Tests. In the Utility folder, create a PHP file named PaginatorTest.php:

为了测试我们刚刚创建的Paginator类,首先我们需要在src/tr/rsywxBundle/Tests下创建一个文件夹Utility 。 Symfony中的所有测试都应该在src/tr/rsywxBundle/Tests 。 在Utility文件夹中,创建一个名为PaginatorTest.phpPHP文件:

namespace tr\rsywxBundle\Tests\Utility;

    use tr\rsywxBundle\Utility\Paginator;

    class PaginatorTest extends \PHPUnit_Framework_TestCase
    {
        public function testgetPageList()
        {
            $paginator=new Paginator(2, 101, 10);
            $pages=$paginator->getTotalPages();
            $this->assertEquals($pages, 11);
            $list=$paginator->getPagesList();
            $this->assertEquals($list, array(1,2,3,4,5));

            $paginator=new Paginator(7, 101, 10);
            $list=$paginator->getPagesList();
            $this->assertEquals($list, array(5,6,7,8,9));

            $paginator=new Paginator(10, 101, 10);
            $list=$paginator->getPagesList();
            $this->assertEquals($list, array(7,8,9,10,11));
        }
    }

This kind of test is called a Unit Test. It tests a particular unit of the program.

这种测试称为单元测试 。 它测试程序的特定单元。

In testgetPageList function, we basically instantiate the object we want to test (a paginator) with virtually any combination of parameters we can think of. We then call some methods of that object and test the validity of the result by using assertions. Here we only use the method assertEquals.

testgetPageList函数中,我们基本上用我们能想到的任何参数组合实例化了要测试的对象(分页器)。 然后,我们调用该对象的某些方法,并使用断言测试结果的有效性。 在这里,我们仅使用assertEquals方法。

In the example $this->assertEquals($list, array(7,8,9,10,11)) from the $paginator object we created, we know there should be a total of 11 pages (with 101 records in total and 10 records per page), and page 10 as the current page will return a page list 7,8,9,10,11 as page 10 is very close to the end. We assert this and if that assertion fails, there must be something wrong in the function logic.

在我们创建的$paginator对象的示例$this->assertEquals($list, array(7,8,9,10,11)) ,我们知道总共应该有11页(总共101条记录,每页10条记录),而当前页的第10页将返回第7,8,9,10,11页列表,因为第10页非常接近末尾。 我们对此进行断言,如果断言失败,则函数逻辑中肯定有问题。

In our command line/terminal, we run the following command:

在命令行/终端中,我们运行以下命令:

php phpunit.phar -c app/

php phpunit.phar -c app/

This reads the configuration file for PHPUnit from the app/ folder (phpunit.xml.dist is generated by the Symfony installation. DON'T CHANGE IT!)

这将从app/文件夹中读取PHPUnit的配置文件( phpunit.xml.dist由Symfony安装生成。请勿更改!)

Note: Please delete all other test files auto-generated by Symfony (like Controller folder under Tests). Otherwise, you will see at least one error.

注意:请删除Symfony自动生成的所有其他测试文件(例如Tests下的Controller文件夹)。 否则,您将至少看到一个错误。

The above command will parse all test files under Tests and make sure all assertions pass. In the above example, you will see a prompt saying something like OK, 1 test, 4 assertions. This means all the tests we created have passed and thus proved the function behaves properly. If not, there must be something wrong in the code (in the implementation or in the test).

上面的命令将分析所有测试文件下Tests ,并确保所有的断言通过。 在上面的示例中,您将看到提示,提示类似OK, 1 test, 4 assertions 。 这意味着我们创建的所有测试均已通过,因此证明了该功能可以正常运行。 如果不是,那么代码中一定有什么问题(在实现中或在测试中)。

Feel free to expand the test file for the Paginator class.

随意扩展Paginator类的测试文件。

It is always a good practice to test a home-made module before it is used in your program.

在程序中使用自制模块之前,始终将其作为一种好习惯。

For a more in-depth look at PHPUnit and testing in PHP, see any of SitePoint's numerous PHPUnit articles.

要更深入地了解PHPUnit和在PHP中进行测试,请参阅SitePoint的众多PHPUnit文章中的任何一篇

本机查询 (NativeQuery)

Our database has a table called book_visit, we use timestamp as the data type to log the time of a visit to the book detail page. We need to do some statistics aggregation on the visits and one of them is to get the total visit count by day (my "day" is in the +8 hours timezone).

我们的数据库有一个名为book_visit的表,我们使用timestamp作为数据类型来记录访问图书详细信息页面的时间。 我们需要对访问进行一些统计汇总,其中之一是按天获取总访问次数(我的“天”在+8小时时区中)。

In SQL, this is easy:

在SQL中,这很容易:

select count(v.bid) vc, date(from_unixtime(v.visitwhen+15*60*60)) vd from book_visit v group by vd order by vd

In the above, 15*60*60 is there to adjust my server time to my timezone.

在上面, 15*60*60可以将服务器时间调整为我的时区。

However, if you try to use similar grammar in Symonfy (changing the table name to its FQN, of course), an error prompt will tell you something like date function is not supported. To solve this, one way is to use pure SQL:

但是,如果您尝试在Symmonfy中使用类似的语法(当然,将表名更改为其FQN),则会出现错误提示,告诉您date function is not supported 。 为了解决这个问题,一种方法是使用纯SQL:

$q = $em->getConnection()->prepare('select count(v.bid) vc, date(from_unixtime(v.visitwhen+8*60*60)) vd from book_visit v group by vd order by vd');
    $q->execute();
    return $q->fetchAll();

Or as recommended by Symfony and Doctrine, we can (and should) use createNativeQuery and ResultSetMapping.

或根据Symfony和Doctrine的建议,我们可以(并且应该)使用createNativeQueryResultSetMapping

public function getVisitCountByDay()
    {
        $em = $this->getEntityManager();

        $rsm=new \Doctrine\ORM\Query\ResultSetMapping;

        $rsm->addScalarResult('vc', 'vc');
        $rsm->addScalarResult('vd', 'vd');

        $q=$em->createNativeQuery('select count(v.bid) vc, date(from_unixtime(v.visitwhen+15*60*60)) vd from book_visit v group by vd order by vd', $rsm);

        $res=$q->getResult();

        return $res;

    }

In the example above, the most critical statements are to create a ResultSetMapping and add results to that mapping.

在上面的示例中,最关键的语句是创建ResultSetMapping并将结果添加到该映射。

vc (visit count) and vd (visit date) both appeared twice in the addScalarResult call. The first is a column name that will be returned from the query and the second is an alias for that column. To prevent the complication of creating more names, we just use the same names.

vc (访问计数)和vd (访问日期)在addScalarResult调用中都出现了两次。 第一个是将从查询中返回的列名,第二个是该列的别名。 为了避免创建更多名称的麻烦,我们只使用相同的名称。

A scalar result describes the mapping of a single column in an SQL result set to a scalar value in the Doctrine result. Scalar results are typically used for aggregate values but any column in the SQL result set can be mapped as a scalar value.

标量结果描述SQL结果集中的单个列到Doctrine结果中的标量值的映射。 标量结果通常用于聚合值,但是SQL结果集中的任何列都可以映射为标量值。

The above functionality is not implemented in the final code. Take it as home work.

以上功能未在最终代码中实现。 把它当作家庭作业。

结论 (Conclusion)

This is far from a complete tutorial for Symfony. There's plenty not covered (forms, security, functional testing, i18n, etc), which could easily take another 10-12 parts. I highly recommend you read the full official documentation provided by Symfony, which can be downloaded here.

这远不是Symfony的完整教程。 有很多未涵盖的内容(表单,安全性,功能测试,i18n等),可以轻松地包含另外10-12个部分。 我强烈建议您阅读Symfony提供的完整正式文档,可以在此处下载。

This being my first time writing a series in PHP and for Sitepoint, I would appreciate any constructive criticism and general feedback you could throw my way.

这是我第一次用PHP和Sitepoint编写系列文章,对于任何建设性的批评和一般性的反馈,我将不胜感激。

翻译自: https://www.sitepoint.com/building-web-app-symfony-2-finalizing/

symfony 2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值