問題描述
我正在做一個(gè)個(gè)人 HMVC 項(xiàng)目:
I'm working on a personal HMVC project:
- 沒有服務(wù)定位器,沒有全局狀態(tài)(如
static
或global
),沒有單例. - 模型處理封裝在服務(wù)中(服務(wù) = 域?qū)ο?+ 存儲(chǔ)庫(kù) + 數(shù)據(jù)映射器).
- 所有控制器都擴(kuò)展了一個(gè)抽象控制器.
- 所有項(xiàng)目依賴都通過Auryn依賴注入容器注入.
- No service locators, no global state (like
static
orglobal
), no singletons. - The model handling is encapsulated in services (service = domain objects + repositories + data mappers).
- All controllers extend an abstract controller.
- All project dependencies are injected through Auryn dependency injection container.
所有需要的依賴都被注入到抽象控制器的構(gòu)造函數(shù)中.如果我想覆蓋這個(gè)構(gòu)造函數(shù),那么我也必須在子控制器的構(gòu)造函數(shù)中傳遞所有這些依賴項(xiàng).
All needed dependencies are injected in the constructor of the abstract controller. If I want to override this constructor, then I have to pass all these dependencies in the child controller's constructor too.
class UsersController extends AbstractController {
private $authentication;
public function __construct(
Config $config
, Request $request
, Session $session
, View $view
, Response $response
, Logger $logger
, Authentication $authentication // Domain model service
) {
parent::__construct(/* All dependencies except authentication service */);
$this->authentication = $authentication;
}
// Id passed by routing.
public function authenticateUser($id) {
// Use the authentication service...
}
}
依賴項(xiàng)列表將進(jìn)一步增長(zhǎng).這需要改變.所以我在想:
The dependencies list would further grow. This needs to change. So I was thinking about:
- 將控制器與視圖完全分開.
然后他們將共享服務(wù)層.視圖不再屬于控制器,Response
將成為視圖的依賴項(xiàng). - 在控制器中使用setter 注入
例如Request
、Session
、Logger
等; - 在控制器動(dòng)作中注入依賴
僅在需要時(shí).
比如Request
、Session
、記錄器
等; - 使用裝飾器模式.
比如在操作調(diào)用后進(jìn)行記錄. - 實(shí)施一些工廠.
- 為了構(gòu)造函數(shù)僅在子控制器上注入所需的依賴項(xiàng).
所以不再在AbstractController
中.
- Totally separate controllers from views.
They would then share the service layer. The views wouldn't belong to controllers anymore and theResponse
would be a dependency of the views. - Use setter injection in controllers
Like forRequest
,Session
,Logger
, etc; - Inject dependencies in the controller actions
Only when needed.
Like forRequest
,Session
,Logger
, etc; - Use decorator pattern.
Like for logging after an action call. - Implement some factories.
- To constructor inject only the needed dependencies only on child controllers.
So not inAbstractController
anymore.
我正在努力尋找一種優(yōu)雅的方式來處理這項(xiàng)任務(wù),我將不勝感激任何建議.謝謝.
I'm trying to find an elegant way to deal with this task and I'll appreciate any suggestions. Thank you.
推薦答案
我會(huì)回答我自己的問題.當(dāng)我編寫它時(shí),我已經(jīng)很好地概述了許多有經(jīng)驗(yàn)的開發(fā)人員關(guān)于 MVC 和 MVC 結(jié)構(gòu)中的依賴注入的建議.
I'll answer to my own question. When I wrote it I already had a good overview of what many experienced developers recommend regarding dependency injection in MVC and MVC structure.
- 構(gòu)造函數(shù)注入是正確的選擇.但它似乎我認(rèn)為,按照這一行,我最終會(huì)得到太多構(gòu)造函數(shù)中的依賴項(xiàng)/參數(shù).因此給控制器太多的責(zé)任(讀取請(qǐng)求的值,改變域?qū)ο蟮臓顟B(tài)、日志操作、請(qǐng)求視圖加載模板并呈現(xiàn)數(shù)據(jù)等).
- Setter 注入 也是一個(gè)需要考慮的解決方案.但是,在我項(xiàng)目的開發(fā)過程中,我意識(shí)到這個(gè)解決方案真的不適合(至少)我的控制器-視圖關(guān)系.
- 將依賴項(xiàng)直接注入控制器行動(dòng)也讓我感到困難(但很開心),記住我已經(jīng)將 url 值作為操作參數(shù)注入,并且我沒有使用任何路由調(diào)度程序.
- 實(shí)施工廠也是一個(gè)好主意,以便能夠在每個(gè)控制器操作中都有對(duì)象可供我使用.工廠是一個(gè)很好用的工具,但前提是需要的運(yùn)行時(shí)對(duì)象,而不僅僅是減少數(shù)量構(gòu)造函數(shù)中的依賴項(xiàng).
- 裝飾模式也是一個(gè)不錯(cuò)的選擇.但是,例如,如果您想在控制器操作中記錄某些內(nèi)容,那么這不是解決方案:您仍然必須將記錄器作為依賴項(xiàng)傳遞(在構(gòu)造函數(shù)、setter 或動(dòng)作中).
- 我想過只注入所需的依賴項(xiàng)子控制器.但是接下來的問題是多相應(yīng)控制者的職責(zé)保持不變.
- The constructor injection is the proper option. But it seemed to me that, following this line, I'll end up with too many dependencies/arguments in constructors. Therefore giving controllers too many responsibilities (reading the requested values, changing the state of the domain objects, logging operations, requesting the view to load the templates and to render the data, etc).
- Setter injection was also a solution to take in consideration. But, in the course of the developing time on my project, I realised that this solution really didn't fit (at least) into the picture of my controller-view relationships.
- The injection of the dependencies directly into the controller actions caused me hard-time (but great time) too, having in mind that I already had the url values injected as action arguments and that I didn't used any routing dispatchers.
- Implementing factories was also a great idea, in order to be able to have objects at my disposal within each controller action. Factories are a great tool to use, but only going from the premise of needed run-time objects, not of just reducing the number of dependencies in constructors.
- Decorator pattern is also a good alternative. But if, for example, you want to log something within a controller action, then this is not a solution: you still have to pass a logger as dependency (in constructor, setter or action).
- I had a thought about injecting the needed dependencies only on child controllers. But then the problem of multiple responsibilities of the corresponding controllers remains the same.
因此,無論我做了什么,這些解決方案似乎都不適合我的 HMVC 項(xiàng)目的結(jié)構(gòu).所以,我進(jìn)一步挖掘,直到我意識(shí)到缺失的環(huán)節(jié)是什么.為此,我非常感謝 Tom Butler,他撰寫了以下精彩文章:
So, none of this solutions seemed to entirely fit into the structure of my HMVC project, whatever I did. So, i dug further, until I realised what the missing link was. For this I give my whole appreciation to Tom Butler, the creator of the following great articles:
- 模型-視圖-混淆第 1 部分:視圖從這模型
- 模型-視圖-混淆第 2 部分:MVC 模型不是域模型
他的作品基于對(duì) MVC 概念的深入、充分論證的分析.它們不僅很容易理解,而且還可以通過不言自明的例子來支持.一句話:對(duì) MVC 和開發(fā)者社區(qū)的精彩貢獻(xiàn).
His works are based on an in-depth, well argumented analyse of the MVC concepts. They are not only very easy to follow, but also sustained by self-explanatory examples. In a word: a wonderful contribution to the MVC and developer community.
我接下來要寫的只是用我自己的話來介紹他的原則,考慮以某種方式完成它們,提供更簡(jiǎn)潔的視角,并展示我在實(shí)施時(shí)遵循的步驟他們?cè)谖业捻?xiàng)目中.此處描述的主題、想法和原則以及工作流程的所有功勞均歸功于 Tom Butler.
What I'll write further is meant to be just a presentation of his principles with my own words, having in mind to somehow complete them, to provide a compacter perspective of them and to show the steps followed by me when I implemented them in my project. All credit on the subject, ideas and principles and workflows depicted here goes to Tom Butler.
那么,我的 HMVC 項(xiàng)目中缺少的鏈接是什么?它被命名為關(guān)注分離.
So, what was the missing link in my HMVC project? It's named SEPARATION OF CONCERNS.
為簡(jiǎn)單起見,我將嘗試通過僅將自己稱為一個(gè)控制器、一個(gè)控制器操作、一個(gè)視圖、一個(gè)模型(域?qū)ο?和一個(gè)模板(文件)來解釋這一點(diǎn),并將它們引入 User代碼>上下文.
For simplicity I'll try to explain this by referring myself to only one controller, one controller action, one view, one model (domain object) and one template (file), introducing them into a User
context.
Web 上描述最多的 MVC 概念 - 也由我研究過的一些流行框架實(shí)現(xiàn) - 以讓控制器控制視圖和模型的原則為中心.為了在屏幕上顯示一些東西,你必須告訴控制器——他進(jìn)一步通知視圖加載和渲染模板.如果這個(gè)顯示過程也暗示使用一些模型數(shù)據(jù),那么控制器也會(huì)操縱模型.
The MVC concept most described on web - and also implemented by some popular frameworks that I studied - is centered around the principle of giving the controller the control over the view and the model. In order to display something on screen you'll have to tell that to the controller - he further notifies the view to load and render the template. If this display process implies the use of some model data too, then the controller manipulates the model too.
以經(jīng)典的方式,控制器創(chuàng)建和動(dòng)作調(diào)用過程包括兩個(gè)步驟:
In a classical way, the controller creation and action calling process involve two steps:
- 創(chuàng)建控制器 - 將所有依賴項(xiàng)傳遞給它,視圖包含在內(nèi);
- 調(diào)用控制器操作.
代碼:
$controller = new UserController(/* Controller dependencies */);
$controller->{action}(/* Action dependencies */);
這意味著,控制器負(fù)責(zé)一切.所以,難怪一個(gè)控制器必須注入這么多的依賴.
This implies, that the controller is responsible for, well, everything. So, no wonder why a controller must be injected with so many dependencies.
但是控制器是否應(yīng)該參與或負(fù)責(zé)在屏幕上有效地顯示任何類型的信息?不,這應(yīng)該是視圖的責(zé)任.為了實(shí)現(xiàn)這一點(diǎn),讓我們開始將視圖與控制器分離——從不需要任何模型的前提出發(fā).涉及的步驟是:
But should the controller be involved in, or responsible for effectively displaying any kind of information on the screen? No. This should be the responsibility of the view. In order to achieve this let's begin separating the view from the controller - going from the premise of not needing any model yet. The involved steps would be:
- 定義一個(gè)
output
方法,用于在屏幕上顯示信息查看. - 創(chuàng)建控制器 - 將所有依賴項(xiàng)傳遞給它,除了視圖及其相關(guān)依賴項(xiàng)(響應(yīng)、模板對(duì)象等).
- 創(chuàng)建視圖 - 將相應(yīng)的依賴項(xiàng)傳遞給它(響應(yīng)、模板對(duì)象等).
- 調(diào)用控制器操作.
- 調(diào)用視圖的
output
方法:
- Define an
output
method for displaying information on screen in the view. - Create the controller - passing it all dependencies, excepting the view and its related dependencies (response, template object, etc).
- Create the view - passing it the corresponding dependencies (response, template object, etc).
- Call the controller action.
- Call the
output
method of the view:
和代碼:
class UserView {
//....
// Display information on screen.
public function output () {
return $this
->load('<template-name>')
->render(array(<data-to-display>))
;
}
//....
}
$controller = new UserController(/* (less) controller dependencies */);
$view = new UserView(/* View dependencies */);
$controller->{action}(/* Action dependencies */);
echo $view->output();
通過完成上面的五個(gè)步驟,我們?cè)O(shè)法將控制器與視圖完全分離.
By accomplishing the five upper steps we managed to completely decouple the controller from the view.
但是,有一個(gè)方面,我們之前假設(shè)過:我們沒有使用任何模型.那么控制器在這個(gè)星座中的作用是什么?答案是:沒有.控制器應(yīng)該僅作為某個(gè)存儲(chǔ)位置(數(shù)據(jù)庫(kù)、文件系統(tǒng)等)和視圖之間的中間人存在.否則,例如只是在屏幕上輸出某種格式的一些信息,視圖的output
方法就完全足夠了.
But, there is an aspect, that we hypothesised earlier: we did not made use of any model. So what's the role of the controller in this constellation then? The answer is: none. The controller should exist only as a middlemann between some storage place (database, file system, etc) and the view. Otherwise, e.g. only to output some information in a certain format on screen, is the output
method of the view fully sufficient.
如果模特被帶到現(xiàn)場(chǎng),事情就會(huì)改變.但是應(yīng)該在哪里注射呢?在控制器中還是在視圖中?同時(shí).它們共享相同的模型實(shí)例.在這一刻,控制器憑借自己的權(quán)利獲得了中間人的角色——介于存儲(chǔ)和視圖之間.一種理論形式是:
Things change if a model is beeing brought on the scene. But where should it be injected? In the controller or in the view? In both. They share the same model instance. In this moment the controller gains the role of the middleman - between storage and view - in his own right. A theoretical form would be:
$model = new UserModel;
$controller = new UserController($model, /* Other controller dependencies */);
$view = new UserView($model, /* Other view dependencies */);
$controller->{action}(/* Action dependencies */);
echo $view->output();
這樣做,控制器可以改變模型的狀態(tài)并確保它保存在存儲(chǔ)系統(tǒng)中.視圖讀取并顯示相同的模型實(shí)例及其狀態(tài).控制器通過模型將顯示邏輯信息傳遞給視圖.問題是,這些信息不屬于模型應(yīng)該專有的業(yè)務(wù)邏輯.他們只是顯示邏輯參與者.
Doing so, the controller can change the state of the model and ensure that it's saved in the storage system. The same model instance, respective its state, is read by the view and displayed. The controller passes display logic informations to the view through the model. The problem is, that these informations don't belong to the business logic, which a model is supposed to exclusively have. They are only display logic participants.
為了避免將顯示邏輯責(zé)任交給模型,我們不得不在圖中引入一個(gè)新組件:view-model.控制器和視圖將共享一個(gè)視圖模型實(shí)例,而不是共享模型對(duì)象.只有這個(gè)將接收模型作為依賴項(xiàng).一個(gè)實(shí)現(xiàn):
In order to avoid giving display logic responsibilities to the model, we have to introduce a new component in the picture: the view-model. Instead of sharing a model object, the controller and the view will share a view-model instance. Only this one will receive the model as dependency. An implementation:
$model = new UserModel;
$viewModel = new UserViewModel($model, /* Other view-model dependencies */);
$controller = new UserController($viewModel /* Other controller dependencies */);
$view = new UserView($viewModel, /* Other view dependencies */);
$controller->{action}(/* Action dependencies */);
echo $view->output();
工作流程可以這樣描述:
And the workflow can be described like this:
- 請(qǐng)求值由瀏覽器(用戶")發(fā)送到控制器.
- 控制器將它們作為屬性存儲(chǔ)在視圖模型實(shí)例中(數(shù)據(jù)成員),因此改變了顯示邏輯狀態(tài)視圖模型.
- 在其
output
方法中,視圖從視圖模型并請(qǐng)求模型查詢其上的存儲(chǔ) - 模型運(yùn)行相應(yīng)的查詢并將結(jié)果傳回給視圖.
- 視圖讀取并將它們傳遞給相應(yīng)的模板.
- 模板渲染后,結(jié)果顯示在屏幕上.
- The request values are sent by the browser ("the user") to the controller.
- The controller stores them in the view-model instance as properties (data members), therefore changing the display logic state of the view-model.
- Within its
output
method, the view reads the values from the view-model and requests the model to query the storage on their basis. - The model runs the corresponding query and passes the results back to the view.
- The view reads and passes them to the corresponding template.
- After template rendering, the results are displayed on the screen.
視圖模型不屬于域模型,所有域?qū)ο蠖捡v留在域模型中,真正的業(yè)務(wù)邏輯發(fā)生在域模型中.它也不屬于操作域?qū)ο蟆⒋鎯?chǔ)庫(kù)和數(shù)據(jù)映射器的服務(wù)層.它屬于應(yīng)用程序模型,例如應(yīng)用程序邏輯發(fā)生的地方.視圖模型獲得從控制器獲取顯示邏輯狀態(tài)并將其傳遞給控制器??的唯一責(zé)任.
The view-model does not belong to the domain model, where all domain objects reside and the real business logic takes place. It does also not belong to the service layer, which manipulates the domain objects, repositories and data mappers. It belongs to the application model, e.g to the place where the application logic takes place. The view-model obtains the sole responsibility of gaining display logic state from the controller and conducting it to the controller.
可以看出,只有視圖模型接觸"了模型.控制器和視圖不僅彼此完全解耦,而且還與模型完全解耦.這種方法最重要的方面是,所涉及的所有組件中的每一個(gè)都只獲得它應(yīng)該獲得的職責(zé).
As can be seen, only the view-model "touches" the model. Both, the controller and the view were not only completely decoupled from each other, but also from the model. The most important aspect of this approach is, that each of all the components involved gains only the responsibilities, that it is supposed to gain.
通過使用這種組件分離和依賴注入容器,控制器依賴過多的問題消失了.并且可以以一種非常靈活的方式應(yīng)用我的問題中提出的所有選項(xiàng)的組合.無需考慮其中一個(gè)組件(模型、視圖或控制器)承擔(dān)了太多責(zé)任.
By making use of this kind of component separation and of a dependency injection container, the problem of too many dependencies in controllers vanishes. And one can apply a combination of all options presented in my question in a really flexible manner. Without having in mind that one of the components (model, view or controller) gains too many responsibilities.
這篇關(guān)于PHP MVC:控制器中的依賴項(xiàng)太多?的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!