PHP - Structuring a Slim3 web application using MVC and understanding the role of the model

孤人 提交于 2019-11-28 14:31:00

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:

  1. Domain model (e.g. model, e.g. model layer);
  2. Service layer (optional);
  3. Delivery mechanism;
  4. 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 MyApp\UI\Web\Application\View;
use MyApp\UI\Web\Application\Controller;

// Note: $this specifies a RouteCollection to which the route is added. 
$this->post('/upload', [
    'controller' => [Controller\Upload::class, 'uploadFiles'],
    'view' => [View\Upload::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 MyApp\UI\Web\Application\View;

$this->get('/upload', [View\Upload::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:

The ones listed in an older answer of mine.

The tutorials presented by Alejandro Gervasio:

The example on the Slim 3 page: Action-Domain-Responder with Slim.

There is a course where you get walked through making MVC with slim 3. Ill link it here : https://codecourse.com/courses/slim-3-authentication . Hope this helped, its a really easy to follow course and you learn alot.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!