In Part II we configured doctrine to connect to our database, created some managed entities out of plain old PHP objects using annotations and then used those annotations to let doctrine create our database tables for us. In this part, we will set up some routing, use controllers and learn about templating with Twig. We are going to be setting up some static pages to show how routing, controllers and templates work at a high level. In the next part we will start getting to some dynamic content and database interaction.
在第二部分,我们配置Doctrine去连接我们的数据库,利用POPO的注释去创建受管实体类,然后使用这些注释去让Doctrine创建我们的数据表。在本部分,我们将设置一些路由、使用控制器,并学习twig模板。我们打算设置一些静态页来展现路由、控制器和模板是如何高水平工作的。在下一部分,我们将开始动态内容和数据库交互方面的内容。
Before we go further lets talk about testing. It just so happens that symfony2 has some fantastic testing support, so lets go ahead and try to be good, responsible programmers and take a test-driven development (TDD) approach to our blog application. To write tests for symfony2 you need to have PHPUnit 3.5.11 or greater installed on your system. Go ahead and install that if you haven’t already. I won’t go step-by-step through the installation here, but it is easy enough and you can find instructions here.
在我们进一步深入之前先讲讲测试。正巧,Symfony2对测试有着不错的支持,因此就让我们成为一名有责任心的好程序员,使用测试驱动开发模式来构建我们的博客应用。为了为Symfony2编写测试,您需要PHPUnit3.5.11或更高的版本。如果您还没安装,那么就去安装吧。在这里我不会一步步地演示安装,安装非常简单,您可以在这里找到演示。
Now that we have PHPUnit installed we can start writing some simple tests. For now we will only be testing two pages. In this part of the tutorial we are going to add a “home” page and an “about” page. So lets create some new test classes to accomplish this. We need to create two new classes which we will use to test our controllers. First we need to add a Controller directory in the src/Company/BlogBundle/Tests directory. In the Controller directory we just created, add a file called BlogControllerTest.php. Here is the code for the class:
现在我们已经安装了PHPUnit,我们可以开始写一些简单的测试程序。现在我们将只测试两个页面。在教程的这个部分,我们打算添加一个“home”页面和一个“about”页面。因此让我们创建一些新的测试类来实现它。我们需要创建两个新类用来测试我们的控制器。首先我们需要在src/Company/BlogBundle/Tests目录中添加一个Controller目录,在我们刚刚创建的Controller目录中,添加一个名为BlogControlleTest.php的文件。以下是该类的代码:
Lets go through this class. As usual we declare our namespace which matches our directory structure. Then we declare that we are going to use the WebTestCase class. This class is provided by symfony2 to be the base class for your test classes. The method name testIndex indicates that we want to test the index action of our BlogController class, which is what we will map to the home page in the routing shortly. We will be digging deeper into testing in future parts of this tutorial, so I will not get deeply into everything that is being done here. Just know that in this method we are sending a GET request to the route matching the ‘/’ pattern. Then we are taking the response generated and testing it to verify that it was a success, that the title element contains the string “Home” and that there is an h2 element on the page that contains the string “Welcome to the Blog”.
我们过一遍这个类。通常我们声明的命名空间是与我们目录结构相匹配的。然后我们声明我们打算使用WebTestCase类。这个类由symfony2提供,是您测试类的基类。TestIndex方法是我们想要测试BlogController类的index动作,我们将会在路由里将它映射到home页。因为我们打算在这个教程的后续部分对测试做深入讨论,所以我对这里所做的就不一一深入介绍了。您只需要知道在该方法中我们发送了一个GET请求,以便让路由去匹配‘/’条目。然后我们将生成响应并且测试它是否成功,title元素包含“Home”字符串,并且页面上的h2元素也包含了“Welcome to The Blog”字符串。
That is it for the “home” page test. Now lets create a test for the “about” page. I personally prefer to create a controller that handles all of my static pages. This way I just need one route to handle all of them. We will see how this works very shortly. For now lets create another test class file in the src/Company/BlogBundle/Tests/Controller directory named PagesControllerTest.php. This class should look familiar.
这是“home”页面的测试。现在我们再创建一个“about”页面的测试。我个人比较喜欢创建一个控制器去处理所有的静态页面。这样我只需要一个路由就可以处理所有的东西。很快我们就可以知道它是如何实现的。现在让我们在src/Company/BlogBundle/Tests/Controller 目录中创建一个名叫PagesControllerTest.php的文件。该类看起来应该很熟悉。
Here we are running a similar test to the one we just completed. The testShow method will test the show action of the PagesController. We will create this controller in our bundle shortly. The PagesController will handle all of our requests to pages with static content. In this test we are sending a GET request to the route matching ‘/about’ and verifying that the response was a success, that the title element that contains the string “About” and that it also has a h2 element that contains the string “About.” These tests are very simple, but are fine for now. We will be adding more complex tests, once we start to add more complex functionality to our blog.
在这里我们运行了一个与上一个非常相似的测试。testShow方法将测试PagesController的show动作。稍后我们将在我们的Bundle中创建该控制器。PagesController会处理所有到静态内容页面的请求。在这个测试里我们将一个GET请求发送到匹配‘/about’的路由,并确认响应是否成功、title元素是否包含了“About”字符串并且h2元素也包含“About”字符串。这些测试非常简单,但对于现在而言却非常好。当我们向我们的博客中添加更为复杂的功能时,我们将会相应添加更为复杂的测试。
Running the tests is very simple as symfony2 has already provided a default PHPUnit xml config file in the app directory. Just open up a terminal and run the following command from your base project directory:
运行测试非常简单,symfony2在其app目录里已经提供了一个默认的PHPUnit的xml.conf文件。只需打开终端,并在项目根目录里运行以下的命令:
You should see the that we have 2 tests and 6 assertions and they all fail. Now we need to write code to make those tests pass. This is the core concept of TDD. Write tests that fail, then implement the functionality to make them pass. After you have done this you can safely refactor code and implement new features and be confident that your old code has not broken. The initial investment of writing tests up front will pay off the larger your application grows in size.
您将看到我们有两个测试和6个断点,它们都失败了。现在我们需要编写代码来使它们通过。这是TDD的核心理念。编写测试,失败了就实现功能使其通过。在您这样做之后,您可以安全的重构代码、实现新功能,并确保不会破坏您的旧代码。在前期编写测试所花费的投入在您应用程序得到较大增长时显得十分值得。
The first thing we need to do to get our tests passing is to set up the routing. Routing is one of the most importants parts of your application. If your routing is incorrect or missing then the symfony2 framework would never be able to translate a request from a client into a bundle controller action. This is the key functionality that the routing component provides. It parses out a request and matches that against your routing configuration, then it uses the route that it matches and invokes your controller and the corresponding action. Then your action returns a Response object. The symfony2 framework then takes this Response object and outputs it to the client.
要想使测试通过的话,我们要做的第一件事就是设置路由。路由是你程序中最重要的部分之一。如果你的路由不正确或错过的话,那么Symfony2框架将永远不能将你的请求从客户端传递到Bundle的控制器动作中。这是路由组件所提供的关键功能。它解析请求并匹配你的路由配置,然后执行你的控制器中相应的动作。然后该动作会返回一个Response对象。Symfony2框架会将这个Response对象输出到客户端。
Lets get started by opening up the routing.yml file in the app/config directory. This is the main routing configuration file for the application. When you open this file you will see the following:
让我们从app/config目录中的routing.yml文件开始。这是应用程序的主路由配置文件。当你打开这个文件,你将会看到下列语句:
Here we have imported the routing.yml file located in our BlogBundle. The resource simply appends all the routes for the BlogBundle to the app routing configuration. This is a simple way to add routes defined in the various bundles you will use to your application without having to rewrite them or copy and paste anything. It is very simple and efficient. So now that we have learned how to import routes from your bundle, lets take a look at our BlogBundle routing.yml. Open up the routing.yml file located in the src/Company/BlogBundle/Resources/config directory. Remove any existing routes that were auto-generated. Our goal is to get the tests to pass that we wrote earlier. We need to add routes to match the patterns ‘/’ and ‘/about’. Here are those routes:
在这里,我们已经导入了BlogBundle中的routing.yml文件。resource可以很容易地将BlogBundle的所有路由都添加到配置文件里。这是一个简单的方式,可以很方便地添加定义在各个Bundle中的路由,您可以在您的应用程序中使用,而无须去重新编 写或拷贝粘贴它,它非常简单高效。我们已经学会了如何从您的Bundles中导入路由,现在站我们看一下BlogBundle的routing.yml。打开 src/Company/BlogBundle/Resources/config目录中的routing.yml文件。删除所有自动生成的路由。我们的目标是让我们先前写的测试通过。我们需要添加匹配‘/’和‘/about’的路由。以下是这些路由:
We have defined two routes. One named show_page and one named homepage. Each route that you define has a pattern that the symfony2 routing component tries to match. A route also has has the defaults parameter. Here you define the special _controller parameter which tells symfony2 what controller and action to map this route to as well as default values for any custom parameters you put in your route. The show_page route has a _controller value of BlogBundle:Pages:show. The symfony2 framework will take this string and determine that it should look in the BlogBundle for the PagesController and call the show action. Not so hard, right?
我们有了两个路由。一个名为show_page,另一个名为homepage。每一条你所定义的路由都有一个patten,是Symfony2路由组件尝试匹配的。路由还有一个defaults参数。在这里你定义了一个特定的_controller参数,该参数告诉Symfony2哪个控制器和动作被映射到这个路由以及在您路由中您自定义参数的缺省值。Show_page路由有一个_controller值是BlogBundle:Pages:show。Symfony2框架将使用该字符串在BlogBundle中查找PagesController控制器,并调用show动作。这不是很难,对吧?
In the show_page route we have defined a custom parameter named page. Any word wrapped in brackets will be transformed into a variable for your controller to use. In our example, our controller will have access to a variable named $page which holds the value of the string after the ‘/’ character. One important thing to remember is that the order in which your routes are defined matters. The symfony2 routing framework will choose the first route that it finds which matches the requirements. So that means that if we had a route with the pattern ‘/contact’ declared before the route with pattern ‘/{page}’ then the request would map to the ‘/contact’ route. If the ‘/contact’ route was declared after the ‘/{page}’ route, the a request with ‘/contact’ would match the ‘/{page}’ route. Easy enough right? There are other parameters and requirements that you can put on routes, such as the HTTP verb, but we have covered what you need to get started. We will be diving much deeper into the routing system in future parts when we start to work with the database and our entities. Hopefully you can see how the show_page route as we have defined it is very helpful in rendering static pages in a generic way. If you don’t, then don’t worry it will be made very clear in just a moment.
在show_page路由中,我们定义了一个自定义参数page。在大括号中的任何单词将被转换成您控制器中的变量来使用。在我们的例子中,控制器将会访问一个名叫$page的变量,该变量有着‘/’后面的字符串值。一个重要的事情就是必须记住在您定义路由的顺序问题。Symfony2路由框架将会选择它所找到的第一条满足要求的路由。这就意味着如果我们有一条‘/contact’路由声明在‘/{page}’路由之前,那么请求将映射‘/contact’路由。如果‘/contact’路由声明在‘/{page}’路由之后,那么先前匹配‘/contact’的请求将会匹配‘/{page}’路由。非常简单,不是吗?这里您还可以添加其他的参数和要求到路由里,比如HTTP动词,但我们已经介绍了您入门所需的内容。当我们开始使用数据库和我们的实体工作时,我们将会在后面部分深入学习路由系统。幸运的是你可以看到在一般情况下,我们所定义的show_page路由对渲染静态页面是非常有帮助的。如果您还不太清楚,别担心,稍后它将会变得非常清晰。
Now that we have our routing setup we are on our way to making our tests go from red to green. Next we need to create our controllers and actions. Navigate to the src/Company/BlogBundle/Controller directory and open the BlogController.php file. Make your file look like this:
现在我们已经有了我们的路由配置,这使得我们的测试道路上的红灯变成绿灯。接下来我们需要创建控制器和动作。打开src/Company/BlogBundle/Controller 目录中的BlogController.php文件。让您的文件看上去如下所示:
In our BlogController class we are defining an indexAction method. If you take a look back at the homepage route that we defined we declared that the route should map to the indexAction in the BlogController of the BlogBundle. This is exactly what we have implemented here. The action in the controller should return a Response object and that is exactly what the render method inherited from Symfony\Bundle\FrameworkBundle\Controller\Controller does. The render method takes as a parameter the name of the template to render. Lets translate the string BlogBundle:Blog:index.html.twig. Here we are telling the controller to render the template in the src/Company/BlogBundle/Resources/views/Blog folder named index.html.twig.
在BlogController类中我们定义了一个indexAction方法。如果您回顾一下我们定义的homepage路由,您将会发现我们声明该路由是映射到BlogBundle中BlogController控制器的indexAction方法的。这正是我们在这里实现的。控制器中的动作会返回一个Response对象,那是从Symfony\Bundle\FrameworkBundle\Controller\Controller 继承过来的render方法。render方法将模板名作为参数去渲染。让我们翻译一下BlogBundle:Blog:index.html.twig字符串 。在这里我们告诉控制器去渲染src/Company/BlogBundle/Resources/views/Blog文件夹中名为index.html.twig的模板。
Next we need to implement our PagesController. Create a new file named PagesController.php in the src/Company/BlogBundle/Controller directory. Here is the source for this file:
接下来我们需要实现我们的PagesController。在src/Company/BlogBundle/Controller 目录中创建一个名为PagesController.php的新文件。下面是该文件的源代码:
This is almost exactly like our BlogController except for two things. Instead of indexAction we have the showAction. Remember that page parameter in our route that we wanted to use in our controller? To use it in our action we simply declare it as a parameter to the action method. The symfony2 framework will transform the string in the route to a variable named $page. This action is set up to render a static page based on the value of the $page parameter. All we do in this action is render the template with the page value inserted into the template name string. Now to create new static pages all we have to do is create a template file with the appropriate name and put it in our src/Company/BlogBundle/Resources/views/Pages folder. That’s it. We don’t even have to modify the routing. Pretty cool, huh? You don’t have to handle your static pages this way, but I find this makes it easy.
除了两点之外,上述代码和我们的BlogController几乎完全一样。我们用showAction替代了indexAction。还记得我们想在控制器中使用的那个路由page参数吗?要在我们的动作中使用它,只需要简单地将它声明为动作方法的参数。设置该动作是为了渲染一个基于$page参数的静态页。我们在该动作中所做的就是将带有page值的模板插入到模板名字符串中。现在要创建新的静态页,我们所有要做的就是创建一个适当 的模板,并将其放在src/Company/BlogBundle/Resources/views/Pages文件夹中。就这样,我们甚至不需要去修改路由。很酷吧?这样您就不必处理去您的静态页,但我发现这样可以很容易。
Now that we have our routing and controllers set up, all that is left is to tackle some templating with Twig. Twig is a new templating language that is packaged with symfony2. You are not required to use Twig. You can use regular old PHP templates if you would like, but I am going to use Twig in this tutorial. Twig is powerful and allows for template inheritance, filters and much more. If you want to read about why Twig is so awesome then check out this page.
现在我们完成了路由和控制器的设置,剩下的就是使用twig来制作模板了。Twig是symfony2自带的一种新的模板语言。您并没有被要求使用Twig。如果您喜欢的话您可以使用常规的PHP模板,但是在本教程里我还是打算使用Twig。Twig很强大并支持模板继承、过滤等诸多功能。如果您想了解为什么Twig这么棒,请查阅这里。
Twig supports template inheritance. This allows templates to inherit from one another much like an object inherits functionality from its parent classes. In this tutorial I am going to use a three-level inheritance structure. We will have a base.html.twig. This file will contain the html, head and body elements. It will also contain any javascripts and style sheets that we want every page in our application to include. We will also have a layout.html.twig file where we will keep things like the main header, navigation and footer for the application. We will use Twig blocks to allow our other templates to include their content into these. If you are coming from symfony1 then you can think of a block as a slot. We will see an example of a block shortly.
Twig支持模板继承。这就允许模板可以象对象继承其父类一样,从其它模板中继承。在本教程中我打算使用三层继承结构。我们有一个base.html.twig。该文件包含html、head和body元素。它还包含了一些在我们应用程序中的每一页都需要包含的JavaScript和样式表。我们还将有一个layout.html.twig文件,该文件保存着应用程序主要的页头、导航和页脚。我们还将使用Twig的区块(Block),让我们其它的模板将其内容包含进来。如果您使用过symfony1,那么您可以将一个区块(Block)看成一个插槽(slot)。稍后我们将看到一个关于区块(Block)的例子。
The base.html.twig file is located in the app/views folder [Update: A change has been made to the framework after I wrote this part. The base.html.twig file should now reside in the app/Resources/views folder]. Open up the base.html.twig file and change your copy to match the following:
Base.html.twig文件放在app/views文件夹中[更新:在我写本部分之后,框架有了改变。base.html.twig文件现在放置在app/Resources/views文件夹中]。打开base.html.twig文件,将您的代码改成下面情况: