欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Symfony2学习笔记之控制器用法详解

程序员文章站 2024-04-01 21:32:52
本文实例讲述了symfony2控制器用法。分享给大家供大家参考,具体如下: 一个controller是你创建的一个php函数,它接收http请求(request)并创建和...

本文实例讲述了symfony2控制器用法。分享给大家供大家参考,具体如下:

一个controller是你创建的一个php函数,它接收http请求(request)并创建和返回一个http回复(response)。回复对象(response)可以是一个html页面,一个xml文档,一个序列化的json数组,一个图片,一个重定向,一个404错误或者任何你想要的内容。controller中可以包含任何渲染你页面内容的所需要的逻辑。

下面是一个controller最简单的例子,仅仅打印一个hello world!

use symfony\component\httpfoundation\response;
public function helloaction()
{
 return new response('hello world!');
}

controller的终极目标都是相同的那就是创建并返回一个response对象。按照这个思路,你可以从request对象读取信息,加载数据库资源,发送email,或者在用户的session中写入信息。但是所有情况下,controller将最终都会返回一个response对象并被分发会客户端。

比如如下情况:

controller a 准备一个response对象来表现网站homepage内容。
controller b 从request中读取slug参数从数据库中加载一个blog内容并创建一个response对象来显示这个blog。如果slug在数据库中不存在,它将创建并返回一个带有404状态码的response对象.

controller c 处理一个从联系表单,它从request对象中读取表单信息,保存联系信息到数据库并发邮件给管理员。最后,它创建一个response对象重定向客户端浏览器到联系表单感谢页面。

requests,controller, response的生命周期

symfony2项目中处理的每一个request都是经过了相同的简单生命周期。框架负责重复的任务,最终执行一个controller,该controller会包含你的应用程序代码:

1.每个request都会被一个统一的前端控制器文件(比如,app.php,或者app_dev.php)处理,它会启动应用程序。
2.router从request中读取uri信息,并找到匹配它的route,从该route中读取_controller参数。
3.匹配成功的route的controller被执行,controller中的代码创建并返回一个response对象。
4.http头和生成的response对象内容将会被发回客户端。

创建一个页面跟创建一个controller一样容易,创建一个路由来映射一个url到该controller。

注意:尽管从名字上来看,前端控制器和controller差不多,其实它们是不同的。
一个前端控制器是一个存放于web目录下的php文件,多有的request都会通过它被重定向。每一个应用程序都会有一个产品前端控制器app.php和一个开发用的前端控制器app_dev.php。你不需要编辑,查看或者担心它们。

看一个简单的controller: 任何的php可调用内容(比如函数,对象方法或者一个closure)都可以成为一个controller。symfongy2中,一个controller通常为controller对象中一个单一的方法。controllers通常也被称为actions。

// src/acme/hellobundle/controller/hellocontroller.php
namespace acme\hellobundle\controller;
use symfony\component\httpfoundation\response;
class hellocontroller
{
 public function indexaction($name)
 {
  return new response('<html><body>hello '.$name.'!</body></html>');
 }
}

注意在这个例子中controller是indexaction方法,它存在于controller类(hellocontroller)中。不要混淆,之所以定义一个controller类(hellocontroller)只是为了方便组织多个controllers/actions在一起。一般情况下,一个controller类会有多个controllers/actions。

上面例子中的controller相当简单:

namespace行是symfony2使用了php5.3的命名空间功能来为整个controller类指定命名空间。
use关键字导入了response类,这是我们的controller必须返回的内容。

controller类名字都是由其名字后面加controller来定义,但是只有前面的部分才是其真正名字,为了统一起见,在后面统一添加controller。 在路由配置时只会取前面部分。

controller类中每个被用于真正controller的方法都会被添加一个统一的后缀action,同样我们在配置其路由时也只会取前面部分而忽略掉action。把它映射到某个url。

每个controller方法的最后必然会创建一个response对象并返回它。

映射一个url到一个controller方法:

上面例子中的controller方法返回一个简单的html页面。如果要在浏览器中访问到该页面,那么你需要为它创建一个route,把它映射到一个特定模式的url上。

# app/config/routing.yml
hello:
 pattern:  /hello/{name}
 defaults:  { _controller: acmehellobundle:hello:index }

xml格式:

<!-- app/config/routing.xml -->
<route id="hello" pattern="/hello/{name}">
 <default key="_controller">acmehellobundle:hello:index</default>
</route>

php代码格式:

// app/config/routing.php
$collection->add('hello', new route('/hello/{name}', array(
 '_controller' => 'acmehellobundle:hello:index',
)));

现在想url /hello/ryan 将被映射到hellocontroller::indexaction() controller并将ryan传递给$name变量。

创建一个所谓的页面,其实就是创建一个controller方法和一个相关的route。

注意我们使用的指向controller方法的表示语法:acmehellobundle:hello:index

symfony2使用了一个非常灵活的字符串声明来指向不同的controller。它告诉symfony2在一个名叫acmehellobundle的bundle中去查找一个叫hellocontroller的类,并执行它的indexaction()方法。在这个例子中,我们的路由配置直接写在了app/config/ 目录下,一个更好的组织方式是把你的路由放到各自的bundle中。

路由参数作为controller方法参变量

你已经了_controller参数 acmehellobundle:hello:index指向一个位于acmehellobundle中名叫hellocontroller::indexaction()的方法。有趣的是路由中参数都会被传递给该方法。

<?php
// src/acme/hellobundle/controller/hellocontroller.php
namespace acme\hellobundle\controller;
use symfony\bundle\frameworkbundle\controller\controller;
class hellocontroller extends controller
{
 public function indexaction($name)
 {
  // ...
 }
}

上例中controller方法有一个唯一参数,$name, 它对应着route中定义的{name}占位符名称。事实上,等你执行你的controller时,symfony2会匹配controller和route中每一个参数。

如果我修改一下hello的路由定义:

yaml格式:

# app/config/routing.yml
hello:
 pattern:  /hello/{first_name}/{last_name}
 defaults:  { _controller: acmehellobundle:hello:index, color: green }

xml格式:

<!-- app/config/routing.xml -->
<route id="hello" pattern="/hello/{first_name}/{last_name}">
 <default key="_controller">acmehellobundle:hello:index</default>
 <default key="color">green</default>
</route>

php代码格式:

// app/config/routing.php
$collection->add('hello', new route('/hello/{first_name}/{last_name}', array(
 '_controller' => 'acmehellobundle:hello:index',
 'color'  => 'green',
)));

这时候controller中可以获取这些参变量了:

public function indexaction($first_name, $last_name, $color)
{
 // ...
}

注意route定义中无论是占位符变量还是默认值变量都会被转化为controller方法的输入变量。当一个route匹配成功时,它会合并占位符和defaults到一个数组传递给controller。映射route参数到controller参数非常简单和灵活。它们从route到controller不匹配顺序。symfony能够把route中参变量的名字映射到controller方法签名中的变量名字。比如{last_name} => $last_name,跟排列顺序无关。

controller方法中的参数必须匹配route中定义的参数下面为hello route定义的controller方法将会抛出异常:

public function indexaction($last_name, $color, $first_name)
{
 // ..
}

如果我们把$foo变量变为可选变量,那么就不会抛异常了。

public function indexaction($first_name, $last_name, $color, $foo)
{
 // ..
}

并不是每一个在route中定义的参数都需要在controller中有与之对应的签名参变量的,比如hello route中定义的{$last_name} 如果对你没什么意义的话可以在controller中省略掉它。

public function indexaction($first_name, $color)
{
 // ..
}

反之,如果你在controller签名中定义了变量,并且不是可选变量,那么必须在route中有与之对应的参数被定义。

在route定义中有一个特殊参数 _route, 它匹配route的名称(如上例中的hello)。虽然不常用,但是它也可以作为controller方法的一个参变量使用。

request作为一个controller方法签名变量

为了方便,你可能会让symfony传递你的request对象作为参数到你的controller方法。这在你处理表单时尤为方便。

use symfony\component\httpfoundation\request;
public function updateaction(request $request)
{
 $form = $this->createform(...);
 $form->bindrequest($request);
 // ...
}

controller基类

为了方便,symfony2定义了一个controller基类,包含了一些常用的controller任务并给了你的controller类访问任何你需要的资源的途径。通过继承该类,你可以获得许多帮助方法。

// src/acme/hellobundle/controller/hellocontroller.php
namespace acme\hellobundle\controller;
use symfony\bundle\frameworkbundle\controller\controller;
use symfony\component\httpfoundation\response;
class hellocontroller extends controller
{
 public function indexaction($name)
 {
  return new response('<html><body>hello '.$name.'!</body></html>');
 }
}

在symfony中controller并不一定非得继承controller基类,因为它内部的帮助方法等都不是必须的。你也可以继承 symfony\component\dependencyinjection\containeraware 服务容器对象可以通过container属性来访问。同时你也可以把controller定义成service。

通用的controller任务:

尽管controller可以干任何事情,但是大部分的controller还是要重复的干一些基础的任务。比如 重定向,跳转,渲染模板和访问核心服务等。

重定向(redirecting)

如果你想重定向你的用户到另一个页面,可以使用redirect()方法。

public function indexaction()
{
 return $this->redirect($this->generateurl('homepage'));
}

这里generateurl()方法是一个帮助函数,用于根据给定的route生成相应的url。默认情况下,redirect()方法执行一个302重定向。如果要执行301重定向,那么需要修改第二个参数如下:

public function indexaction()
{
 return $this->redirect($this->generateurl('homepage'), 301);
}

redirect()方法其实是一个简化写法,真正的代码如下:

use symfony\component\httpfoundation\redirectresponse;
return new redirectresponse($this->generateurl('homepage'));

跳转(forwarding)

你可以使用forward()方法很容易从一个controller到另一个controller内部。它执行的是一个内部子请求,来调用指定的controller,所以不会产生用户客户端浏览器的重定向。forward()方法返回的response对象还将从原controller返回。

public function indexaction($name)
{
 $response = $this->forward('acmehellobundle:hello:fancy', array(
  'name' => $name,
  'color' => 'green'
 ));
 // further modify the response or return it directly
 return $response;
}

这里forward()方法使用了跟route配置中相同的字符串参数。这里传入数组参数会作为目标调用controller的参数。当将controller嵌入到模板时,也会使用同样的接口。目标调用的controller方法应该是如下定义:

public function fancyaction($name, $color)
{
 // ... create and return a response object
}

就像为一个route创建一个controller一样,跟参数的顺序没关系。symfony2 会匹配索引键名称name到方法参数名称$name,即使顺序打乱也没关系。跟其它controller基类方法一样,forward方法也仅仅是一个symfony2核心函数的快捷写法。一个跳转可以直接通过http_kernel服务来完成,返回一个response对象。

$httpkernel = $this->container->get('http_kernel');
$response = $httpkernel->forward('acmehellobundle:hello:fancy', array(
 'name' => $name,
 'color' => 'green',
));

渲染模板:

虽然不是必须的,但是大部分controller将最终渲染一个负责生成为controller负责生成html的模板。renderview()方法会渲染一个模板并返回它的内容。这个返回内容可以用作创建response对象,以供controller返回使用。

$content = $this->renderview('acmehellobundle:hello:index.html.twig', array('name' => $name));
return new response($content);

上面的代码完全可以更进一步的使用下面的代码形式来写:

复制代码 代码如下:
return $this->render('acmehellobundle:hello:index.html.twig', array('name' => $name));

这两种情况下,acmehellobundle中的模板resources/views/hello/index.html.twig都会被渲染。

renderview()方法是如下代码的快捷写法:

$templating = $this->get('templating');
$content = $templating->render('acmehellobundle:hello:index.html.twig', array('name' => $name));

当然也可以在子目录中渲染模板

$templating->render('acmehellobundle:hello/greetings:index.html.twig', array('name' => $name));
// index.html.twig 存放于 resources/views/hello/greetings 目录.

访问其它服务

只要是继承了controller基类,你就可以通过get()方法访问symfony2的服务了。比如:

$request = $this->getrequest();
$templating = $this->get('templating');
$router = $this->get('router');
$mailer = $this->get('mailer');

symfony2中还有无数的可用服务,同时也鼓励你定义自己的服务。要查看所有的服务,可以使用container:debug 命令行工具

$ php app/console container:debug

管理错误和404页面

当一些东西没有找到,你应该重置http协议返回一个404 回复。要做到这个,你将抛出一个特殊类型的异常。如果你是继承了controller基类,则:

public function indexaction()
{
 $product = // retrieve the object from database
 if (!$product) {
  throw $this->createnotfoundexception('the product does not exist');
 }
 return $this->render(...);
}

createnotfoundexception()方法创建一个特定的notfoundhttpexception对象,它最终触发404 http回复。当然你从你的controller方法中可以抛出任何类型的exception 类,symfony2会自动返回一个500 http回复代码。

throw new \exception('something went wrong!');

管理session

symfony2 提供了一个非常好的session对象,你可以用它来在请求之间存贮有关用户的信息。默认情况下,symfony2 通过php本身的session保存属性到cookie。在任何controller中存储和获取session信息将非常容易:

$session = $this->getrequest()->getsession();
// 为用户的后一个请求使用存储一个属性
$session->set('foo', 'bar');
// 在另一个controller中为另一个请求获取该属性
$foo = $session->get('foo');
// 设置用户的本地化语言
$session->setlocale('fr');

flash 消息

你可以为特定的请求存储少量的消息到用户的session。这在处理一个表单时非常有用,你想重定向和一个特定的信息显示在下一个请求中。这种类型的消息被称为flash消息。比如,假设你处理一个表单提交:

public function updateaction()
{
 $form = $this->createform(...);
 $form->bindrequest($this->getrequest());
 if ($form->isvalid()) {
  // 做些排序处理
  $this->get('session')->setflash('notice', 'your changes were saved!');
  return $this->redirect($this->generateurl(...));
 }
 return $this->render(...);
}

此例中,在处理完请求后,controller设置了一个notice flash消息并作了重定向。名字notice没什么意义,只是用于标识该消息。在下一个活动的模板中,下面的代码能够渲染这个notic消息:

twig

{% if app.session.hasflash('notice') %}
 <div class="flash-notice">
  {{ app.session.flash('notice') }}
 </div>
{% endif %}

php代码:

<?php if ($view['session']->hasflash('notice')): ?>
 <div class="flash-notice">
  <?php echo $view['session']->getflash('notice') ?>
 </div>
<?php endif; ?>

这样设计,flash消息就能够为准确的某个请求存在了。他们一般被设计出来就是用于重定向的。

response对象

作为一个controller来说,唯一必须做到的是返回一个response对象。

response对象是一个php代码对http response的抽象。
http response是一个基于文本的消息有http headers和 返回给客户端的内容组成。

//创建一个简单的response对象,默认状态码为200
$response = new response('hello ' .$name, 200);
//创建一个基于json的response对象,状态码也为200
$response = new response(json_encode(array('name'=>$name)));
$response->headers->set('content-type','application/json');

其中headers属性是一个headerbag对象,内部包含许多有用的方法来读取和改变response的头信息。头名字被标准化使用content-type 与content-type或者content_type效果等同。

请求对象request

除了路由占位符的值以外,如果继承了controller基类那么该controller还可以访问request对象。

$request = $this->getrequest();
$request->isxmlhttprequest(); // 判断是不是ajax请求
$request->getpreferredlanguage(array('en','fr'));
$request->query->get('page'); // 获取$_get 参数
$request->request->get('page'); //获取$_post参数

跟response对象一样,request对象的头也保存在headerbag对象中,可以很方便的被访问。

总结思考:

无论何时,你创建一个页面,你最终需要为它写一些包含逻辑的代码。在symfony中,这叫一个controller, 它是一个php的函数,它可以为了最后返回一个response对象给用户可以做需要的任何事情。简单的说,你可以选择继承一个controller基类,它包含了许多执行controller通用任务的快捷方法。比如,你不想把html代码写入你的controller, 你可以使用render()方法来渲染并返回一个模板内容。

希望本文所述对大家基于symfony框架的php程序设计有所帮助。