PHP - 使用 MVC 构建 Slim3 Web 应用程序并理解模型的作用
我正在尝试使用 Slim3 框架和 Twig 模板系统在 php 中创建一个身份验证系统,对于数据库,我使用带有 PDO 的 MySQL.我也在尝试使用模型视图控制器设计模式来实现它.但是,我很难理解如何将 MVC 结构用于 Web 应用程序.我在网上看了很多解释,似乎没有一个明确的答案.很多人说要使用 php 框架,例如 Laravel、Symfony 或 CodeIgniter,因为它们显然采用了类似 MVC 的结构.但是,我更愿意保持简单并手动编写代码,而不是使用框架.
目前我看到的 MVC 有两种解释.此图中描绘的第一个:
我看到的另一种解释是:(取自
我已经完成了我的研究.问题和答案,例如
通常,您可以将 Web MVC 应用程序视为由以下部分组成:
- 领域模型(例如模型,例如模型层);
- 服务层(可选);
- 交付机制;
- 其他组件(例如自己的库等).
1)领域模型应包含以下组件:
- 实体(例如域对象)和值对象.它们根据属性和行为对业务规则进行建模,并且独立于应用程序,可供多种(类型的)应用程序使用.
myapp/domain:包含域模型类和服务的文件夹.这个目录可以放到myapp/web/src"文件夹中,但不应该,因为模型层和服务层不是交付机制的一部分.
myapp/web:包含传递机制类的文件夹.它的名称描述了应用程序的类型 - 可以是网络应用程序、cli 应用程序等.
myapp/web/src:
资源:
*) Sandro Mancuso:交互驱动设计简介
*) 我的旧答案.
*) Alejandro Gervasio 提供的教程:
- 构建领域模型——持久性不可知论简介莉>
- 构建领域模型——集成数据映射器
- 处理聚合根的集合——存储库模式
- 服务简介
*) Slim 3 页面上的例子:Action-Slim 域响应器.
I’m trying to create an authentication system in php with the Slim3 framework along with the Twig template system, and for the database I’m using MySQL with PDO. I’m also trying to implement it using a model view controller design pattern. However I’m having difficulty understanding how to use an MVC structure for a web application. I’ve looked at a plethora of explanations on the web and there doesn’t seem to be a clear cut answer. A lot of people say to to use a php framework such as Laravel, Symfony or CodeIgniter as they apparently employ an MVC like structure. However I would much rather keep things simple and to write the code manually rather than using a framework.
Currently there are two interpretations of MVC that I see. The first one being depicted in this diagram:
The other interpretation I’ve seen is this: (which is taken from this YouTube video)
I have done my research. Questions and answers such as this and this have been helpful. But I’m still not sure how I might structure my own applications, specifically indentifying and understanding the model aspect of MVC. I’ll now explain the register process of my authentication app. So you have an idea how my code is works.
Firstly I have an SQLQueries class that simply puts a series of SQL statements into functions. I then have a SQLWrapper class that has functions that can for example store a new users details inside the database. This class also calls functions from the SQLQueries class. I also have a ValidateSanitize class that has functions that cleans user input as well as checking if user input is valid in the form. These three classes I think are part of the model aspect of MVC but I'm not sure. I see a lot of other tutorials using a ‘User Model class’ but I can’t find the need for one in my application.
My views are simply Twig templates that display html, such as the homepage, register, login etc. I then have controllers. I intend to have multiple controllers that do different things. For now I’ve only implemented the AuthController which is responsible for Registering and Signing a user in.
So the first thing the AuthController does is to display the register form in a function called getRegisterForm. Once the user has submitted the form the postRegisterForm function takes that user input and assigns it to tainted variables.
public function postRegisterForm($request, $response) { $arr_tainted_params = $request->getParsedBody(); $tainted_email = $arr_tainted_params['email']; it a variable $tainted_username = $arr_tainted_params['username']; $tainted_password = $arr_tainted_params['password']; $tainted_password_confirm = $arr_tainted_params['password_confirm'];
Next all of the three previous classes as well as the database details are instantiated so their functions can be used in the AuthController:
$sanitizer_validator = $this->container->ValidateSanitize; $sql_wrapper = $this->container->SQLWrapper; $sql_queries = $this->container->SQLQueries; $db_handle = $this->container->get('dbase');
The tainted user details are then cleaned with the sanitize_input function. The cleaned user details are then fed into the validate functions to make sure they don’t trigger any validation violations. The password is also hashed here:
$cleaned_email = $sanitizer_validator->sanitize_input($tainted_email, FILTER_SANITIZE_EMAIL); $cleaned_username = $sanitizer_validator->sanitize_input($tainted_username, FILTER_SANITIZE_STRING); $cleaned_password = $sanitizer_validator->sanitize_input($tainted_password, FILTER_SANITIZE_STRING); $cleaned_password_confirm = $sanitizer_validator->sanitize_input($tainted_password_confirm, FILTER_SANITIZE_STRING); $hashed_cleaned_password = password_hash($cleaned_password, PASSWORD_DEFAULT); $sanitizer_validator->check_email_exists($cleaned_email); $sanitizer_validator->validate_email($cleaned_email); $sanitizer_validator->validate_username($cleaned_username); $sanitizer_validator->validate_password($cleaned_password); $sanitizer_validator→validate_password_confirm($cleaned_password_confirm);
Finally there is an if statement that checks to see if all validation error messages are empty. If they are we provide the SQLWrapper class with the database details as well as a SQLQueries class object. We then insert the users details into the database by calling the SQLWrapper classes store-details function. Finally we direct the user to the login page, so the user can sign into their newly registered account.
if ($sanitizer_validator->get_validate_messages('email_error') == ' ' && $sanitizer_validator->get_validate_messages('username_error') == ' ' && $sanitizer_validator->get_validate_messages('password_error') == ' ' && $sanitizer_validator->check_passwords_match($cleaned_password, $cleaned_password_confirm ) == true && $sanitizer_validator->check_email_exists($cleaned_email) == false) { $sql_wrapper->set_db_handle($db_handle); $sql_wrapper->set_sql_queries($sql_queries); $sql_wrapper->store_details($cleaned_email, $cleaned_username, $hashed_cleaned_password); return $response→withRedirect($this→container→router→pathFor('login')); }
However if any of the validate error messages are not blank, then we call the SanitiseValidate display_validate_messages which simply sets the messages into a session to be displayed on the register twig template. We then redirect back to the register page so the user can see the validation error messages.
else { $sanitizer_validator->display_validate_messages(); return $response->withRedirect($this->container->router->pathFor('register')); } }
So based on this structure of a user registering an account. Does this adhere to a clean simple MVC structure or do some changes need to be made? Do any of my classes take the role of a model? Any suggestions and tips regarding my structure will be appreciated.
The full application can be seen on my GitHub if that would be helpful. Note that this version is slightly older than the sample code I used in this question.
解决方案Indeed, there are multiple approaches regarding how the MVC pattern should be applied in web applications. This multitude of variants is the result of the simple fact, that the original MVC pattern - developed for desktop applications (by Trygve Reenskaug, in 1979) - can not be applied as is to the web applications. Here is a little description. But, from this set of approaches, you can choose one which best complies with your requirements. Maybe you'll try more of them before you'll make your mind. Though, at some point, you'll know which one fits to your vision.
In the following diagrams I tried to present my chosen approach on the web MVC workflow - mainly inspired by Robert Martin's presentation Keynote: Architecture the Lost Years (licensed under a Creative Commons Attribution ShareAlike 3.0).
In general, you could think of a web MVC application as composed of the following parts:
- Domain model (e.g. model, e.g. model layer);
- Service layer (optional);
- Delivery mechanism;
- Other components (like own libraries, etc).
1) The domain model should consist of the following components:
- Entities (e.g. domain objects) and value objects. They model the business rules in terms of properties and behavior and, being application-independent, can be used by multiple (types of) applications.
- (Data) mappers and, optional, repositories. These components are responsible with the persistence logic.
- External services. They are used to perform different tasks involving the use of external/own libraries (like sending emails, parsing documents, etc).
Further, the domain model could be split into two parts:
a) Domain model abstraction. This would be the only space of the model layer accessed by the components of the delivery mechanism, or by the services of the service layer - if one is implemented:
- Entities and value objects;
- (Data) mapper abstractions and, optional, repository abstractions;
Abstractions of external services.
Note: By abstractions I mean interfaces and abstract classes.
b) Domain model implementation. This space would be the one in which the implementations of the different domain model abstractions (see a) would reside. The dependency injection container (as part of the delivery mechanism) will be responsible with passing instances of these concrete classes as dependencies - as constructor arguments, for example - to the other components of the application (like controllers, views, services, etc).
2) Service layer (optional): Technically, the components of the delivery mechanism could directly interact with the elements of the domain model. Though such interactions involve (a lot of) operations, specific only to the model, not to the delivery mechanism. Therefore, a good choice is to defer the execution of these operations to service classes (e.g. services), as part of the so-called service layer. The delivery mechanism components will then use only these services to access the domain model components.
Note: The service layer can, actually, be seen as part of the model layer. In my diagrams bellow I preferred to display it as a layer residing outside the model. But, in the file system example, I put the corresponding folder in the domain space.
3) The delivery mechanism sums up the constructs used to assure the interaction between the user and the model layer's components. By user I don't mean a person, but an interface with which a person can interact - like a browser, a console (e.g. CLI), a desktop GUI, etc.
Web server: parses the user request through a single point of entry (index.php).
Dependency injection container: provides the proper dependencies to the different components of the application.
HTTP message (e.g. HTTP request and HTTP response) abstraction (see PSR-7: HTTP message interfaces).
Router: matches the request components (HTTP method and URI path) against the components of each route (HTTP method and pattern) in a predefined list of routes and returns the matched route, if found.
Front controller: matches the user request against a route and dispatches it to a certain controller and/or view action.
Controllers. They write (e.g. perform create, update and delete operations) to the model layer and (should) expect no results. This can happen by directly interacting with the components defined in the domain model, or, preferably, by only interacting with the service classes.
Views. They should be classes, not template files. They can receive a template engine as dependency. They only fetch data (e.g. perform read operations) from the model layer. Either by directly interacting with the components defined in the domain model, or, preferably, by only interacting with the service classes. Also, they decide which result (like a string), or template file content, will be displayed to the user. A view action should always return a HTTP response object (maybe as defined by the PSR-7 specification), whose body will be before-hand updated with the mentioned result or template file content.
Template files. Should be kept as simple as possible. The whole presentation logic should happen only in the view instances. So, the template files should contain only variables (be they pure PHP ones, or presented with the used template engine syntax) and, maybe, some simple conditional statements, or loops.
Response emitter: reads the body of the HTTP response instance returned by the view and prints it.
4) Other components. As wished. For example some libraries developed by your own. Like an implementation of the PSR-7 abstraction.
How I chose to dispatch the user request:
As you see in the diagrams above, the front controller dispatches the user request not only to a controller action (in order to update the domain model), but also to a view action (in order to read and display the updated state/data from the model layer). Kind of a splitted dispatch. This can be relatively easy achieved by assigning the controller action and the view action to each route (like bellow), and telling the front controller to call them successively:
<?php use MyAppUIWebApplicationView; use MyAppUIWebApplicationController; // Note: $this specifies a RouteCollection to which the route is added. $this->post('/upload', [ 'controller' => [ControllerUpload::class, 'uploadFiles'], 'view' => [ViewUpload::class, 'uploadFiles'], ]);
This approach gives flexibility in regard to the user request dispatch. For example, the name of the view action can be different from the name of the controller action. Or, in order to only fetch model layer data, you don't need to dispatch the user request to a controller, but only to a view. Therefore you don't need to assign a controller action in the route at all:
<?php use MyAppUIWebApplicationView; $this->get('/upload', [ViewUpload::class, 'listFiles']);
File system structure example:
myapp/domain: folder containing the domain model classes and the services. This directory could be brought into the "myapp/web/src" folder, but it shouldn't, because the model layer and the service layer are not part of the delivery mechanism.
myapp/web: folder containing the delivery mechanism classes. Its name depicts the type of application - can be a web app, a cli app, etc.
myapp/web/src:
Resources:
*) Sandro Mancuso : An introduction to interaction-driven design
*) The ones listed in an older answer of mine.
*) The tutorials presented by Alejandro Gervasio:
- Building a Domain Model – An Introduction to Persistence Agnosticism
- Building a Domain Model – Integrating Data Mappers
- Handling Collections of Aggregate Roots – the Repository Pattern
- An Introduction to Services
*) The example on the Slim 3 page: Action-Domain-Responder with Slim.
相关文章