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

时间:2014-09-07 13:55:51

标签: php symfony

我们有一个相当大的symfony2代码库。通常我们的Controller操作看起来像

public function landingPageAction(Request $request) {
 //do stuff      
 return $this->render("view_to_render", $template_data);
}

我们在所有控制器之间有两个非常通用的功能:

  1. 我们倾向于将控制器级模板参数传递给特定控制器中的所有操作 - 让我们调用这些参数"默认参数"
  2. 我们在每个Action
  3. 的末尾设置HTTP缓存标头

    可以理解,我们希望将这种逻辑抽象出来。在这样做时,我们提出了两种方法。我们不确定哪种方法更好,无论是在一般OO和SOLID原则方面,还是在性能方面以及SF2如何建议完成任务。

    这两种方法都依赖于让控制器扩展一个界面,该界面指示控制器是否具有"默认参数" (后来我们也在考虑添加Cacheable接口)

    use Symfony\Component\HttpFoundation\Request;
    interface InjectDefaultTemplateVariablesController {
    public function getDefaultTemplateVariables(Request $request);
    }
    

    方法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);
    }
    

    方法2

    这种方法基于将公共逻辑放入BaseController,其他控制器都从该BaseController继承。我们仍然采用让Child控制器扩展接口的方法,以防他们想要使用"默认参数"。

    以下是基本控制器中的新方法,用于确定是否需要将默认参数与特定模板参数合并。稍后此方法还将使用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 $this->renderWithDefaultsAndCache("view_to_render", $template_data);
    }
    

    讨论

    到目前为止,第一种方法的主要论点是它遵循SOLID原则并且更容易扩展 - 如果要添加更常见的逻辑,它可以直接放入事件监听器而不会影响控制器。

    第二种方法的主要论据是,我们试图抽象出来的逻辑确实属于控制器,而不是外部事件。此外,人们担心以这种方式使用事件会导致表现不佳。

    我们非常感谢专家们了解哪种方法更好或可能建议我们错过了第三种方法。

    谢谢!

1 个答案:

答案 0 :(得分:2)

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

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

我最初使用你的第二种方法是创建一个ScheduleBaseController,然后从中导出各种各样的计划控制器。它运作不佳。我试图抽象出常见的功能,但是时间表的不同之处在于常见功能变得复杂且难以更新。

所以我选择了与你非常相似的事件驱动方法。要回答您的一个问题,添加一些事件监听器不会对性能产生任何明显的影响。

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

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

// 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,然后启动VIEW事件进行渲染。

很多物品?你打赌。关键是在路线定义中对此进行布线:

// 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

每个部分都分解为单独的服务,至少对我来说,这样可以更容易地定制各个部分并查看正在发生的事情。这是一个好方法吗?我真的不知道,但对我来说效果很好。