php - 在 Symfony 2 Controller 中抽象通用功能的正确方法是什么

标签 php symfony

我们有相当大的 symfony2 代码库。通常我们的 Controller Action 看起来像

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

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

  1. 我们倾向于将 Controller 级别的模板参数传递给特定 Controller 中的所有操作 - 我们称这些为“默认参数”
  2. 我们在每个 Action 的末尾设置 HTTP 缓存 header

可以理解的是,我们希望将此逻辑抽象掉。为此,我们提出了两种方法。我们不确定哪种方法更好,无论是在一般 OO 和 SOLID 原则方面,还是在性能方面以及 SF2 建议的工作方式方面。

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

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

方法一

此方法基于事件。我们定义一个对象来存储我们的模板变量,以及(将来)缓存指示器

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

}

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

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

}

} 

最后我们的 Action 现在变成了

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

方法二

这种方法的基础是将通用逻辑放入一个 BaseController 中,每个其他 Controller 都从该 BaseController 继承。我们仍然保持让子 Controller 也扩展接口(interface)的方法,以防它们想要使用“默认参数”。

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

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 原则并且更容易扩展 - 如果要添加更常见的逻辑,它可以直接放入事件监听器而不影响 Controller 。

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

如果能听到专家们关于哪种方法更好的意见,或者可能建议我们错过的第三种方法,我们将非常感激。

谢谢!

最佳答案

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

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

我最初通过创建一个 ScheduleBaseController 然后从中派生各种单独的计划 Controller 来使用您的第二种方法。它不能很好地工作。我试图抽象出通用功能,但时间表差异很大,以至于通用功能变得复杂且难以更新。

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

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

Action 模型在 Controller 事件监听器中创建,存储在请求对象中,然后作为参数传递给 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);

所以 Controller 主要负责表单内容。如果需要,它可以从模型中获取数据,但让模型处理大部分与数据相关的内容。 Controller 根本不做模板处理。它只返回 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

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

关于php - 在 Symfony 2 Controller 中抽象通用功能的正确方法是什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25710942/

相关文章:

Symfony2 - 添加新的请求类型代码

php - FB api - 从页面邮箱中提取未读消息数

php - SSL 连接是否提供任何唯一的用户标识符?

php - 对非对象上的成员函数 bind_param() 的 fatal error 调用

symfony - Doctrine2 [语义错误] 找不到常量?

javascript - symfony 2.8 : error when assetic:dump

PHP编译: off_t undefined

php - 从 Windows 打印服务器轮询打印机信息

php - symfony2 中可选的多个 oneToMany 关系

windows - Symfony2 : automatically logging in users from their Windows session