symfony入门学习资料之四:第一个Symfony页面
创建一个新页面——无论是HTML还是JSON输出——都是一个简单的“两步”操作:
-
创建一个路由:路由(route)是一个指向你的页面URL(比如
/about
),同时它映射到一个控制器。 -
创建一个控制器:控制器(controller)是你为了构造页面而写的功能。你要拿到发送来的request请求信息,用它创建一个Symfony的
Response
对象,令其包含HTML内容,JSON字符串或是其他。
如同在web上每一次互动都是从HTTP请求开始,任务单纯而简单:request->response。
1. 创建一个页面:路由和控制器
假设你要新建一个/lucky/number
页面,用于生成一个随机的幸运数字并且输出它。那么,要先创建一个类并添加方法,用于在某人访问/lucky/number
时被执行:
// src/AppBundle/Controller/LuckyController.php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;
class LuckyController
{
/**
* @Route("/lucky/number")
*/
public function numberAction()
{
$number = rand(0, 100);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>
);
}
}
在开始之前测试一下!
如果你在Apache或Nginx上正确设置了虚拟主机,就可以把http://localhost:8000
部分替换为主机名的方式,比如http://symfony.dev/app_dev.php/lucky/number
。
在实例中如果看到一个幸运数字被输出,那么说明成功了!接下来就是要了解它是如何工作的。
numberAction
上面的@Route
被称为annotation,它定义了URL匹配。也可以在YAML(或其他格式)里写路由:请参考路由的相关章节介绍。实际上,文档中的多数路由例程都有“标签”,以显示每种格式的配置。
annotation下面的方法——numberAction:
被称为控制器,这是控制页面的地方。此处唯一的原则是:一个控制器必须返回一个Symfony的Response对象(最好要达到活用此原则)。
2. 创建一个JSON响应
在控制器中返回的Response
对象可以包含HTML,JSON甚至二进制文件比如图片或PDF。可以轻松设置HTTP头信息或HTTP状态码。
假设要创建一个JSON返回值,则只需为LuckyController
再添加一个方法:
// src/AppBundle/Controller/LuckyController.php
// ...
class LuckyController
{
// ...
/**
* @Route("/api/lucky/number")
*/
public function apiNumberAction()
{
$data = array(
'lucky_number' => rand(0, 100),
);
return new Response(
json_encode($data),
200,
array('Content-Type' => 'application/json')
);
}
}
在浏览器中测一下:http://localhost:8000/api/lucky/number
更可将代码精简为超好用的JsonResponse
:
// src/AppBundle/Controller/LuckyController.php
// ...
// --> don't forget this new use statement 别忘了这行新的use声明
use Symfony\Component\HttpFoundation\JsonResponse;
class LuckyController
{
// ...
/**
* @Route("/api/lucky/number")
*/
public function apiNumberAction()
{
$data = array(
'lucky_number' => rand(0, 100),
);
// calls json_encode and sets the Content-Type header
// 自动调用json_encode并设置Content-Type头
return new JsonResponse($data);
}
}
3. 动态URL匹配:/lucky/number/{count}
效果不错。假设希望用户可以到/lucky/number/5
来立即生成幸运数字5。更新路由,使其在尾部拥有一个{wildcard}
通配符:
ANNOTATIONS页:
// src/AppBundle/Controller/LuckyController.php
// ...
class LuckyController
{
/**
* @Route("/lucky/number/{count}")
*/
public function numberAction()
{
// ...
}
// ...
}
YAML页:
# app/config/routing.yml lucky_number: path: /lucky/number/{count} defaults: { _controller: AppBundle:Lucky:number }
XML页:
<!-- app/config/routing.xml --> <?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> <route id="lucky_number" path="/lucky/number/{count}"> <default key="_controller">AppBundle:Lucky:number</default> </route> </routes>
PHP页:
// app/config/routing.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add('lucky_number', new Route('/lucky/number/{count}', array( '_controller' => 'AppBundle:Lucky:number', ))); return $collection;
因为{count}
占位符,页面URL变了。现在它要求URLs匹配/lucky/number/*
,比如/lucky/number/5
。这样一来你可以在控制器中收到并使用这个值:
// src/AppBundle/Controller/LuckyController.php
// ...
class LuckyController
{
/**
* @Route("/lucky/number/{count}")
*/
public function numberAction($count)
{
$numbers = array();
for ($i = 0; $i < $count; $i++) {
$numbers[] = rand(0, 100);
}
$numbersList = implode(', ', $numbers);
return new Response(
'<html><body>Lucky numbers: '.$numbersList.'</body></html>
);
}
// ...
}去/lucky/number/xx
测试一下,把xx换为任意数字:http://localhost:8000/api/lucky/number/7
应该看到幸运数字7被输出!在控制器中增加一个$placeholder
参数,就可以得到路由中任何一个{placeholder}
占位符的值。只要确保它们的名字相同即可。
路由系统还可以做更多,像是支持多个占位符(比如/blog/{category}/{page}
)使占位符可选以及强制占位符匹配一个正则表达式(比如{count}
必须为数字)。
5. 渲染模板(利用容器)
如果在控制器中返回HTML,还需要用到渲染模板。Symfony拥有Twig:一种易学的模板语言,强大而且有趣。
目前,LuckyController
没有继承任何基类。此时引用Twig(或其他Symfony工具)最简单的方式,就是继承Symfony的Controller
基类:
// src/AppBundle/Controller/LuckyController.php
// ...
// --> add this new use statement
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class LuckyController extends Controller
{
// ...
}
6. 使用templating服务
这并不改变任何东西,但却把带入Symfony的container/容器:这是一个数组模样的对象,允许从中取出系统级的每一个有用对象。这些有用的对象被称为services(服务),其中Symfony送出了一个专门用于渲染模板的service对象,还有一个用来记录日志的,以及许多许多。
渲染Twig模板,要用到的服务名为templating
:
// src/AppBundle/Controller/LuckyController.php
// ...
class LuckyController extends Controller
{
/**
* @Route("/lucky/number/{count}")
*/
public function numberAction($count)
{
// ...
$numbersList = implode(', ', $numbers);
$html = $this->container->get('templating')->render(
'lucky/number.html.twig',
array('luckyNumberList' => $numbersList)
);
return new Response($html);
}
// ...
}
“服务容器”十分重要,学习过程中会掌握大量相关知识。现在,只需了解它拥有很多对象,然后你可以通过“昵称”(比如templating
或logger
)利用get()
来得到其中的任何一个。templating
服务,是TwigEngine的实例,里面有个render()
方法。
但这还可以再简单些!通过继承Controller
基类,你可以使用很多快捷方法,比如render()
:
// src/AppBundle/Controller/LuckyController.php
// ...
/**
* @Route("/lucky/number/{count}")
*/
public function numberAction($count)
{
// ...
/*
$html = $this->container->get('templating')->render(
'lucky/number.html.twig',
array('luckyNumberList' => $numbersList)
);
return new Response($html);
*/
// render: a shortcut that does the same as above 快捷方法
return $this->render(
'lucky/number.html.twig',
array('luckyNumberList' => $numbersList)
);
}
7.创建模板
如果你现在刷新页面,会得到一个报错:
Unable to find template "lucky/number.html.twig"
修复它要靠创建一个新的app/Resources/views/lucky
目录,再将number.html.twig
置于其中:
- TWIG
{# app/Resources/views/lucky/number.html.twig #} {% extends 'base.html.twig' %} {% block body %} <h1>Lucky Numbers: {{ luckyNumberList }}</h1> {% endblock %}
- PHP
<!-- app/Resources/views/lucky/number.html.php --> <?php $view->extend('base.html.php') ?> <?php $view['slots']->start('body') ?> <h1>Lucky Numbers: <?php echo $view->escape($luckyNumberList) ?> <?php $view['slots']->stop() ?>
单的文件已经展示了基本功能:像是{{ variableName }}
语法被用于print一些东西。而luckyNumberList
这个变量是你在控制器中的render()
方法中将其传递进来。
% extends 'base.html.twig' %
对应的是一个布局文件,它位于app/Resources/views/base.html.twig,这文件随Symfony项目的建立而存在。它相当地单纯(只是一个未经样式处理的HTML结构),供你定制。{% block body %}部分则是使用了Twig模板的inheritance system/继承系统,来将其间的内容置于父模板base.html.twig布局中的相应位置。
http://localhost:8000/lucky/number/9
如果查看页面源代码,会看到完整的HTML骨架,多亏了base.html.twig
。这只是Twig威力的极小部分。若你希望精通Twig的语法、数组循环、输出其他模板乃至更多超酷功能,应当参阅创建并使用模板。
8. 浏览整个项目
你已经创建了一个弹性的URL,渲染了一个“使用了继承功能”的模板,并且输出了JSON响应。真太好!
是时候浏览你的project中的文件并且去掉它们的神秘光环了。之前你已经在两个极为重要的文件夹中进行作业:
app/
:内含配置文件和模板。大体上,只要不是PHP代码的材料都放在这里。
src/:
PHP程序之所在。99%的时间你都会工作在src/
(PHP文件)或app/
(其他东东)之下。随着你的技术实力高大上起来,你会学习到每个文件夹下都发生了什么。
app/:
目录也存有其他内容,像是app/AppKernel.php
,你要用它来开启新bundles(它是app/
下面很少的PHP文件之一)。
src/:目录下暂时只有一个目录 - src/AppBundle - 所有的东西都在这里面。一个bundle,像一个“plugin”,你可以找到开源bundles,然后把它们安装到你的项目中。但就算是你自己的代码,也是处于bundle之中 - 典型的就是AppBundle(尽管这bundle毫无特殊之处)。为了深入了解bundles,以及为何你应当创建多个bundles(提示:在项目之间共享代码),请参阅Bundle系统章节。
web/:
它是整个项目的文档根目录,存放可公开访问的文件,比如CSS、图片以及用来执行app(app_dev.php
和app.php
)的Symfony的前端控制器(front controller)。
tests/:
程序的自动测试(如Unit tests/单元测试)被存放在这里。
bin/:
用于存放二进制(binary)文件。最重要的是console
文件,它被用来在console中执行Symfony命令。
var/:
这是那些自动生成的文件被存放的地方,比如缓存文件(var/cache/
)和日志文件(var/logs/
)。
vendor/:
通过依赖管理器Composer,第三方类库、包、bundles被下载到这里。你应该不去编辑这个目录下的东西。
Symfony是弹性化的。如果需要,你可以覆写默认的目录结构。参考How to Override Symfony's default Directory Structure。
9. 程序级别的配置
Symfony内置了几个原生bundle(打开你的app/AppKernel.php
文件查看),你可以安装更多。bundles的主要配置文件是app/config/config.yml
:
- YAML
# app/config/config.yml
# ...
framework:
secret: '%secret%'
router:
resource: '%kernel.root_dir%/config/routing.yml'
# ...
twig:
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
# ...
- XML
<!-- app/config/config.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xmlns:twig="http://symfony.com/schema/dic/twig" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd http://symfony.com/schema/dic/twig http://symfony.com/schema/dic/twig/twig-1.0.xsd"> <!-- ... --> <framework:config secret="%secret%"> <framework:router resource="%kernel.root_dir%/config/routing.xml" /> <!-- ... --> </framework:config> <!-- Twig Configuration --> <twig:config debug="%kernel.debug%" strict-variables="%kernel.debug%" /> <!-- ... --> </container>
- PHP
// app/config/config.php // ... $container->loadFromExtension('framework', array( 'secret' => '%secret%', 'router' => array( 'resource' => '%kernel.root_dir%/config/routing.php', ), // ... )); // Twig Configuration $container->loadFromExtension('twig', array( 'debug' => '%kernel.debug%', 'strict_variables' => '%kernel.debug%', )); // ...
framework根键配置的是FrameworkBundle,twig根键配置的是TwigBundle,诸如此类。Symfony中的许多行为都可以被控制,只需改变配置文件的一些选项即可。要搞清原因,请阅读配置参考。或者,通过超好用的bin/console命令,取得一个根键下被剥离出的完整配置样板:
$ php bin/console config:dump-reference framework
Symfony配置系统的威力绝大,包括环境、导入和参数等。为了掌握所有这些,参考Configuration章节。
10、下一步学习内容
通过本节的学习,已经开始掌握Symfony,并将学到以全新方式来打造美丽的、功能性的、快速的、可维护的程序。
后面还需要熟悉控制器、路由、创建和使用模板这些章节,才能完成对基础架构的掌握。