Php 在Symfony 2控制器中抽象通用功能的正确方法是什么

Php 在Symfony 2控制器中抽象通用功能的正确方法是什么,php,symfony,Php,Symfony,我们有一个相当大的symfony2代码库。通常,我们的控制器动作看起来像 public function landingPageAction(Request $request) { //do stuff return $this->render("view_to_render", $template_data); } 我们所有控制器之间有两个非常通用的功能: 我们倾向于将控制器级模板参数传递给特定控制器中的所有操作——我们称这些为“默认参数” 我们在每个操作的末尾设置HT

我们有一个相当大的symfony2代码库。通常,我们的控制器动作看起来像

public function landingPageAction(Request $request) {
 //do stuff      
 return $this->render("view_to_render", $template_data);
}
我们所有控制器之间有两个非常通用的功能:

  • 我们倾向于将控制器级模板参数传递给特定控制器中的所有操作——我们称这些为“默认参数”
  • 我们在每个操作的末尾设置HTTP缓存头
  • 可以理解,我们想把这种逻辑抽象出来。为此,我们提出了两种方法。我们不确定哪种方法更好,无论是在一般的OO和坚实的原则方面,还是在性能方面,以及SF2推荐的工作方式方面

    这两种方法都依赖于让控制器扩展一个接口,该接口指示控制器是否具有“默认参数”(稍后我们还考虑添加可缓存接口)

    方法1 这种方法基于事件。我们定义了一个对象来存储模板变量,以及(将来)缓存指示符

    class TemplateVariables {
    
    protected $template_name;
    
    protected $template_data;
    
    public function __construct($template_name, $template_data) {
        $this->template_name = $template_name;
        $this->template_data = $template_data;
    }
    
    /**
     * @param mixed $template_data
     * @return $this
     */
    public function setTemplateData($template_data) {
        $this->template_data = $template_data;
    
        return $this;
    }
    
    /**
     * @return mixed
     */
    public function getTemplateData() {
        return $this->template_data;
    }
    
    /**
     * @param mixed $template_name
     * @return $this
     */
    public function setTemplateName($template_name) {
        $this->template_name = $template_name;
    
        return $this;
    }
    
    /**
     * @return mixed
     */
    public function getTemplateName() {
        return $this->template_name;
    }
    
    }
    
    我们还定义了在渲染时触发的事件以及调用视图的事件

    class InjectDefaultTemplateVariablesControllerEventListener {
    
    /** @var DelegatingEngine */
    private $templating;
    
    private $default_template_variables;
    
    public function __construct($templating) {
        $this->templating = $templating;
    }
    
    public function onKernelController(FilterControllerEvent $event) {
        $controller = $event->getController();
    
        if (!is_array($controller)) {
            return;
        }
    
        if ($controller[0] instanceof InjectDefaultTemplateVariablesController) {
            $this->default_template_variables = $controller[0]->getDefaultTemplateVariables($event->getRequest());
        }
    }
    
    public function onKernelView(GetResponseForControllerResultEvent $event) {
        $controller_data = $event->getControllerResult();
    
        if ($controller_data instanceof TemplateVariables) {
            $template_data = (array)$controller_data->getTemplateData();
    
            $template_data = array_merge($this->default_template_variables, $template_data);
    
            $event->setResponse($this->templating->renderResponse($controller_data->getTemplateName(), $template_data));
        }
    
    }
    
    } 
    
    现在我们的行动终于变成了现实

    public function landingPageAction(Request $request) {
     //do stuff      
     return new TemplateVariables("view_to_render", $template_data);
    }
    
    public function landingPageAction(Request $request) {
     //do stuff      
     return $this->renderWithDefaultsAndCache("view_to_render", $template_data);
    }
    
    方法2 这种方法基于将公共逻辑放入BaseController中,其他所有控制器都从中继承。我们仍然保留让子控制器也扩展接口的方法,以防它们想要使用“默认参数”

    以下是基本控制器中的新方法,用于确定是否需要将默认参数与特定模板参数合并。稍后,此方法还将使用ttl参数处理缓存头

    public function renderWithDefaultsAndCache($view, array $parameters = array(), Response $response = null, $ttl = null)
    {
      $default_template_variables = array(); 
      if ($this instanceof InjectDefaultTemplateVariablesController ) {
        $default_template_variables = $this->getDefaultTemplateVariables();
      }
      $template_data = array_merge($default_template_variables, $parameters);
      return $this->render($view, $template_data, $response);
     }
    
    现在行动变得更加重要

    public function landingPageAction(Request $request) {
     //do stuff      
     return new TemplateVariables("view_to_render", $template_data);
    }
    
    public function landingPageAction(Request $request) {
     //do stuff      
     return $this->renderWithDefaultsAndCache("view_to_render", $template_data);
    }
    
    讨论 到目前为止,第一种方法的主要论点是它遵循坚实的原则,并且更容易扩展-如果要添加更多的通用逻辑,它可以直接放入事件侦听器,而不会影响控制器

    第二种方法的主要论点是,我们试图抽象的逻辑实际上属于控制器,而不是外部事件。此外,有人担心以这种方式使用事件会导致性能不佳

    我们非常希望听取专家们关于哪种方法更好,或者可能建议我们错过的第三种方法的意见


    谢谢大家!

    首先,我绝不会自称是Symfony 2架构专家

    我有一个比赛时间表程序,它输出许多不同类型的时间表(公众、团队、裁判等)。不同的时间表都是相似的,它们处理一组游戏,但在细节上有所不同。时间表需要以各种格式显示(html、pdf、xls等)。我还希望能够为个人锦标赛做进一步的调整

    我最初使用您的第二种方法,创建ScheduleBaseController,然后从中派生各种单独的日程控制器。效果不太好。我试图抽象出常见的功能,但日程安排不同,以至于常见的功能变得复杂且难以更新

    因此,我采用了一种与您非常相似的事件驱动方法。为了回答您的一个问题,添加一些事件侦听器不会对性能产生任何明显的影响

    我没有专注于模板数据,而是创建了我称之为动作模型的东西。动作模型负责根据请求参数加载游戏,并(在某些情况下)根据发布的数据更新游戏本身

    动作模型在控制器事件侦听器中创建,存储在请求对象中,然后作为参数传递给控制器的动作方法

    // KernelEvents::CONTROLLER listener
    $modelFactoryServiceId = $request->attributes->get('_model');    
    $modelFactory = $this->container->get($modelFactoryServiceId);
    $model = $modelFactory->create($request);
    $request->attributes->set('model',$model);
    
    // Controller action
    public function action($request,$model)
    {
        // do stuff
    
        // No template processing at all, just return null
        return null;
    }
    // KernelEvents::VIEW listener
    $model = $request->attributes->get('model')
    $response = $view->renderResponse($model);
    
    因此,控制器主要负责表单内容。如果需要,它可以从模型中获取数据,但是让我们让模型来处理大部分与数据相关的内容。控制器根本不进行模板处理。它只返回null,这将启动视图事件进行渲染

    很多东西?当然。关键是在路线定义中将其连接起来:

    // Referee Schedule Route
    cerad_game__project__schedule_referee__show:
    path:  /project/{_project}/schedule-referee.{_format}
    defaults: 
        _controller: cerad_game__project__schedule_referee__show_controller:action
        _model:      cerad_game__project__schedule_referee__show_model_factory
        _form:       cerad_game__project__schedule_referee__show_form_factory
        _template: '@CeradGame\Project\Schedule\Referee\Show\ScheduleRefereeShowTwigPage.html.twig'
        _format:     html
        _views:
            csv:   cerad_game__project__schedule_referee__show_view_csv
            xls:   cerad_game__project__schedule_referee__show_view_xls
            html:  cerad_game__project__schedule_referee__show_view_html
    requirements:
        _format:  html|csv|xls|pdf
    

    每个部分都被分解成单独的服务,至少对我来说,这使得定制单独的部分和查看正在发生的事情变得更容易。这是一个好方法吗?我真的不知道,但对我来说效果很好。

    Cerad,谢谢你,这是非常有用的信息!一个澄清:看起来您有一个创建模型的服务,并且模型也知道如何呈现自己。控制器中到底发生了什么,因为模板变量似乎是由模型计算出来的。非常感谢。实际上,视图渲染模型。模型主要是一个数据库,只提供数据。在控制器中实际完成的工作很少。在某些情况下,action方法不起任何作用。更常见的是会涉及到一个表单。action方法将执行isValid操作,并根据需要重定向。