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

red*_*nax 7 php symfony

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

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

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

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

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

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

use Symfony\Component\HttpFoundation\Request;
interface InjectDefaultTemplateVariablesController {
public function getDefaultTemplateVariables(Request $request);
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

我们还定义了将在渲染时触发并调用视图的事件

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

}

} 
Run Code Online (Sandbox Code Playgroud)

最后我们的行动现在成了

public function landingPageAction(Request $request) {
 //do stuff      
 return new TemplateVariables("view_to_render", $template_data);
}
Run Code Online (Sandbox Code Playgroud)

方法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);
 }
Run Code Online (Sandbox Code Playgroud)

行动现在变成了

public function landingPageAction(Request $request) {
 //do stuff      
 return $this->renderWithDefaultsAndCache("view_to_render", $template_data);
}
Run Code Online (Sandbox Code Playgroud)

讨论

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

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

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

谢谢!

Cer*_*rad 2

首先,我绝不自称是 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);
Run Code Online (Sandbox Code Playgroud)

所以控制器主要负责表单内容。如果需要,它可以从模型获取数据,但让模型处理大部分与数据相关的东西。控制器根本不进行模板处理。它只是返回 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
Run Code Online (Sandbox Code Playgroud)

每个部分都分为单独的服务,至少对我来说,这使得自定义各个部分并查看正在发生的情况变得更容易。这是一个好方法吗?我真的不知道,但它对我来说效果很好。