Symfony遵循“轻控制器,重模型”的理念,这意味着控制器在项目中所扮演的角色是协调应用各个部分的轻型胶水代码。
在控制器中,你应该遵循5-10-20法则,即在一个控制器中,尽量创建少于5个变量,10个action,每个action不超过20行代码。这样做不是科学道理,不过在项目日渐庞大需要你将控制器的代码重构为服务的时候,你就明白了。
最佳实践
让你的controller继承FrameworkBundle base controller,尽可能使用annotation来配置路由,缓存和安全等。
以框架为基准耦合你的控制器能让它发挥全部的功能,并且提高生产效率。
因为除了包含几行胶水代码外,你的控制器要尽可能轻盈,而花上好几个小时来让他从框架中独立出来显然没什么好处,受益远小于付出。
另外,使用注释来配置路由,缓存和安全可以简化项目配置,不要使用多种文件格式来配置项目(YAML,XML,PHP),所有的配置应该存在你需要的地方,而且统一格式。
总之,你需要尽可能让你的业务逻辑代码脱离框架的同时,让你的控制器和路由跟框架整合在一起,从而最大受益。(译者认为这样做能让你专注实现功能,而且又不失开发效率)
为了让路由按照控制器中注释的规则去加载,你需要在总路由配置中添加如下代码:
# app/config/routing.yml
app:
resource: "@AppBundle/Controller/"
type: annotation
以上配置会让应用程序加载所有在src/AppBundle/Controller/目录下的注释,甚至是它的子目录。如果你定义了很多控制器,你完全可以将他们组织在子目录里:
<your-project>/
├─ ...
└─ src/
└─ AppBundle/
├─ ...
└─ Controller/
├─ DefaultController.php
├─ ...
├─ Api/
│ ├─ ...
│ └─ ...
└─ Backend/
├─ ...
└─ ...
最佳实践
不要使用@Template()注释啦!
@Template()注释很有用,但其中包含了一些小戏法,我们现在觉得这么做并不值得。
大多数情况下,使用@Template()时是没有参数的,这样就很难知道到底是哪个模板被绘制了。对于新手而言,这样做会很难让他们知道一个事实:控制器应该总是返回一个响应对象。(除非你使用的是一个视图层)
综上所述,如下是我们demo应用的主页所对应的控制器:
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
*/
public function indexAction()
{
$posts = $this->getDoctrine()
->getRepository('AppBundle:Post')
->findLatest();
return $this->render('default/index.html.twig', array(
'posts' => $posts
));
}
}
如果你使用Doctrine,你可以使用ParamConverter来自动查询目标entity当作参数传入控制器里。
最佳实践
如果方便,使用ParamConverter来自动查询entity。
例如:
use AppBundle\Entity\Post;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* @Route("/{id}", name="admin_post_show")
*/
public function showAction(Post $post)
{
$deleteForm = $this->createDeleteForm($post);
return $this->render('admin/post/show.html.twig', array(
'post' => $post,
'delete_form' => $deleteForm->createView(),
));
}
在以前,你肯定会传入一个$id然后查询这个条目,现在呢,通过传入新的$post参数和Post类(Doctrine entity),ParamConverter会自动查询$id和{/id}相符的对象,若找不到则会返回一个404页面。
上面的例子没做任何的配置,因为路由中的{id}和entity的id相符。当不相符或者逻辑更为复杂的时候,最简单的方法就是自己写查询语句来查找对象。在我们的应用中,有个CommentController就符合这种情形:
/**
* @Route("/comment/{postSlug}/new", name = "comment_new")
*/
public function newAction(Request $request, $postSlug)
{
$post = $this->getDoctrine()
->getRepository('AppBundle:Post')
->findOneBy(array('slug' => $postSlug));
if (!$post) {
throw $this->createNotFoundException();
}
// ...
}
当然你可以使用@ParamConverter配置来做,也很灵活:
use AppBundle\Entity\Post;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
/**
* @Route("/comment/{postSlug}/new", name = "comment_new")
* @ParamConverter("post", options={"mapping": {"postSlug": "slug"}})
*/
public function newAction(Request $request, Post $post)
{
// ...
}
记住一点:使用ParamConverter能完美应对一般情况,但是别忘了,直接查询entity也不难。
如果你希望在执行控制器之前或者之后做点什么,你可以使用EventDispatcher组建来建立前后过滤。