Symfony - 在哪里为多个/所有控制器放置公共代码?

时间:2017-11-09 20:04:11

标签: php symfony controller

让我们说我的应用有多个页面,如

  • /家
  • /设置
  • /博客

对于所有这些,我需要一些通用代码。示例:从登录用户获取电子邮件地址,以便在每个页面的右上角显示它,在注销按钮旁边。

所以在每个控制器(HomeController,SettingsController,BlogController)上我都要注册一个twig变量:

例如:AppBundle / Controller / HomeController.php

public function indexAction()
{
    $user_email = Something::getUserEmail();
    ...
    return $this->render('home/index.html.twig', array('user_email' => $user_email));
}

所以getUserEmail()(可能还有很多业务逻辑)在我的应用程序的所有前端控制器方法上都是一样的。将代码复制到呈现页面的每个控制器方法中将是非常多余的。我可以注册一个服务,但我也必须在每个控制器上调用它。我也可以写一个BaseController - 让我们说BaseController->init() - 但是,我必须一遍又一遍地在每个控制器方法上调用这个方法。

那么为多个/所有控制器放置通用业务逻辑的最佳做法是什么?

奖金:我可以预先注册"像smarty中的twig变量或我是否必须通过render提交它们?将$ user_email注册一次而不是每个控制器方法都会很好,因为这会再次导致大量重复的代码。

要求Symfony 3 +,4。

3 个答案:

答案 0 :(得分:2)

TL; DR:不要让你的控制器'胖'而是使用依赖注入。

我使用Symfony多年了,我有很长一段时间都有同样的问题。 This article值得一读。通过使用许多第三方库和包,我看到了放置业务逻辑的常用方法。

以下是我的一个软件包的目录结构示例:

Appbundle\
    Command
    Controller
    DataFixtures
    Datatables
    Entity
    EventListener
    Exception
    Extension
    Form
    Menu
    Resources\
         views
    Security\
         Authorization\
             Voter
    Template
    Util
    Validator

(并非所有目录都提到)

业务逻辑的一个重要部分是Entity,而安全规则(主要是SecurityVoters)位于Security\Authorization\Voter

对于未与Symfony Components或Doctrine(如Forms,SecurityVoters和我的实体模型)紧密耦合的业务逻辑,我为特定目标创建类(记住单一责任原则)并使用Services / Dependency Injection在我的控制器中使用业务逻辑:

use AppBundle\Mailer\Something;

// ...
public function indexAction()
{
    $mailer = $this->get(Something::class);

    $user_email = $mailer->getUserEmail();

    ...
    return $this->render('home/index.html.twig', array('user_email' => $user_email));
}

一条重要规则:keep your controller slim。它只有一个目的:获取请求并发回响应。保持控制器尽可能的纤薄有很多优点:

  • 它使单元测试更容易。测试服务比测试控制器容易得多。
  • 您也可以在命令中使用相同的业务逻辑。在我的应用程序变得更复杂之后,我了解到并非我的应用程序所做的所有事情都必须由前端控制器启动。例如后台任务:它们由cron作业启动。
  • 如果您想拥有第二个前端控制器(用于本机应用/第三方访问等的RESTfull API),您可以重用这些服务。如果您想切换到另一个框架,您也可以轻松切换和使用Symfony组件。

关于Twig变量的问题,请检查How to Inject Variables into all Templates (i.e. global Variables)

答案 1 :(得分:1)

您是否尝试过Traits?根据文件

  

Trait旨在减少单一继承的一些限制   通过使开发人员能够在几个方面自由地重用方法集   独立班级

     

特质与类相似,但仅用于分组   功能细化和一致的方式

如果您不想要注射或服务,这将是一个好主意。 Symfony正在使用这种方法。

abstract class Controller implements ContainerAwareInterface
{
    use ContainerAwareTrait;
    use ControllerTrait;
....
}

你可以检查两个特征并尝试一下。

希望有所帮助

答案 2 :(得分:0)

如果我理解正确,您可以使用标准控制器和树枝来实现这一目标。您可以为常见操作创建一个控制器,例如呈现登录用户的电子邮件,然后 - 在“base.html.twig”中 - 呈现操作的结果。

// CommonController.php

/**
 * @Route("/common/get-email", name="get_email")
 */
public function getEmail()
{
    // ... fetch email in some way ...
    return $this->render('home/index.html.twig', array('user_email' => $user_email));
}

// base.html.twig

{{ render(url('get_email')) }}

请考虑 Embedding Controllers 部分。