我们有相当大的 symfony2 代码库。通常我们的 Controller Action 看起来像
public function landingPageAction(Request $request) {
//do stuff
return $this->render("view_to_render", $template_data);
}
我们有两个在所有 Controller 之间非常通用的功能:
- 我们倾向于将 Controller 级别的模板参数传递给特定 Controller 中的所有操作 - 我们称这些为“默认参数”
- 我们在每个 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/