public function postLogin()
{
$path = '/login';
if ($this->identification->isUserLoggedIn()) {
$path = '/dashboard';
}
return new RedirectResponse($path);
}
Disclaimer: the following is a description of how I understand MVC-like patterns in the context of PHP-based web applications. All the external links that are used in the content are there to explain terms and concepts, and not to imply my own credibility on the subject.
The first thing that I must clear up is: the model is a layer.
Second: there is a difference between classical MVC and what we use in web development. Here's a bit of an older answer I wrote, which briefly describes how they are different.
What a model is NOT:
The model is not a class or any single object. It is a very common mistake to make (I did too, though the original answer was written when I began to learn otherwise), because most frameworks perpetuate this misconception.
Neither is it an Object-Relational Mapping technique (ORM) nor an abstraction of database tables. Anyone who tells you otherwise is most likely trying to 'sell' another brand-new ORM or a whole framework.
What a model is:
In proper MVC adaptation, the M contains all the domain business logic and the Model Layer is mostly made from three types of structures:
A domain object is a logical container of purely domain information; it usually represents a logical entity in the problem domain space. Commonly referred to as business logic.
This would be where you define how to validate data before sending an invoice, or to compute the total cost of an order. At the same time, Domain Objects are completely unaware of storage - neither from where (SQL database, REST API, text file, etc.) nor even if they get saved or retrieved.
These objects are only responsible for the storage. If you store information in a database, this would be where the SQL lives. Or maybe you use an XML file to store data, and your Data Mappers are parsing from and to XML files.
You can think of them as "higher level Domain Objects", but instead of business logic, Services are responsible for interaction between Domain Objects and Mappers. These structures end up creating a "public" interface for interacting with the domain business logic. You can avoid them, but at the penalty of leaking some domain logic into Controllers.
There is a related answer to this subject in the ACL implementation question - it might be useful.
The communication between the model layer and other parts of the MVC triad should happen only through Services. The clear separation has a few additional benefits:
For both the View and Controller instances (what you could call: "UI layer") to have access these services, there are two general approaches:
You can inject the required services in the constructors of your views and controllers directly, preferably using a DI container.
Using a factory for services as a mandatory dependency for all of your views and controllers.
As you might suspect, the DI container is a lot more elegant solution (while not being the easiest for a beginner). The two libraries, that I recommend considering for this functionality would be Syfmony's standalone DependencyInjection component or Auryn.
Both the solutions using a factory and a DI container would let you also share the instances of various servers to be shared between the selected controller and view for a given request-response cycle.
Alteration of model's state
Now that you can access to the model layer in the controllers, you need to start actually using them:
public function postLogin(Request $request)
{
$email = $request->get('email');
$identity = $this->identification->findIdentityByEmailAddress($email);
$this->identification->loginWithPassword(
$identity,
$request->get('password')
);
}
Your controllers have a very clear task: take the user input and, based on this input, change the current state of business logic. In this example the states that are changed between are "anonymous user" and "logged in user".
Controller is not responsible for validating user's input, because that is part of business rules and controller is definitely not calling SQL queries, like what you would see here or here (please don't hate on them, they are misguided, not evil).
Showing user the state-change.
Ok, user has logged in (or failed). Now what? Said user is still unaware of it. So you need to actually produce a response and that is the responsibility of a view.
public function postLogin()
{
$path = '/login';
if ($this->identification->isUserLoggedIn()) {
$path = '/dashboard';
}
return new RedirectResponse($path);
}
In this case, the view produced one of two possible responses, based on the current state of model layer. For a different use-case you would have the view picking different templates to render, based on something like "current selected of article" .
Of course, there are situations, when this is a overkill.
MVC is just a concrete solution for Separation of Concerns principle. MVC separates user interface from the business logic, and it in the UI it separated handling of user input and the presentation. This is crucial. While often people describe it as a "triad", it's not actually made up from three independent parts. The structure is more like this:
It means, that, when your presentation layer's logic is close to none-existent, the pragmatic approach is to keep them as single layer. It also can substantially simplify some aspects of model layer.
Using this approach the login example (for an API) can be written as:
While this is not sustainable, when you have complicate logic for rendering a response body, this simplification is very useful for more trivial scenarios. But be warned, this approach will become a nightmare, when attempting to use in large codebases with complex presentation logic.
How to build the model?
Since there is not a single "Model" class (as explained above), you really do not "build the model". Instead you start from making Services, which are able to perform certain methods. And then implement Domain Objects and Mappers.
An example of a service method:
In the both approaches above there was this login method for the identification service. What would it actually look like. I am using a slightly modified version of the same functionality from a library, that I wrote .. because I am lazy:
public function loginWithPassword(Identity $identity, string $password): string
{
if ($identity->matchPassword($password) === false) {
$this->logWrongPasswordNotice($identity, [
'email' => $identity->getEmailAddress(),
'key' => $password, // this is the wrong password
]);
throw new PasswordMismatch;
}
$identity->setPassword($password);
$this->updateIdentityOnUse($identity);
$cookie = $this->createCookieIdentity($identity);
$this->logger->info('login successful', [
'input' => [
'email' => $identity->getEmailAddress(),
],
'user' => [
'account' => $identity->getAccountId(),
'identity' => $identity->getId(),
],
]);
return $cookie->getToken();
}
As you can see, at this level of abstraction, there is no indication of where the data was fetched from. It might be a database, but it also might be just a mock object for testing purposes. Even the data mappers, that are actually used for it, are hidden away in the private methods of this service.
private function changeIdentityStatus(Entity\Identity $identity, int $status)
{
$identity->setStatus($status);
$identity->setLastUsed(time());
$mapper = $this->mapperFactory->create(Mapper\Identity::class);
$mapper->store($identity);
}
Ways of creating mappers
To implement an abstraction of persistence, on the most flexible approaches is to create custom data mappers.
In practice they are implemented for interaction with specific classes or superclasses. Lets say you have Customer and Admin in your code (both inheriting from a User superclass). Both would probably end up having a separate matching mapper, since they contain different fields. But you will also end up with shared and commonly used operations. For example: updating the "last seen online" time. And instead of making the existing mappers more convoluted, the more pragmatic approach is to have a general "User Mapper", which only update that timestamp.
Some additional comments:
Database tables and model
While sometimes there is a direct 1:1:1 relationship between a database table, Domain Object, and Mapper, in larger projects it might be less common than you expect:
Information used by a single Domain Object might be mapped from different tables, while the object itself has no persistence in the database.
Example: if you are generating a monthly report. This would collect information from different of tables, but there is no magical MonthlyReport table in the database.
A single Mapper can affect multiple tables.
Example: when you are storing data from the User object, this Domain Object could contain collection of other domain objects - Group instances. If you alter them and store the User, the Data Mapper will have to update and/or insert entries in multiple tables.
Data from a single Domain Object is stored in more than one table.
Example: in large systems (think: a medium-sized social network), it might be pragmatic to store user authentication data and often-accessed data separately from larger chunks of content, which is rarely required. In that case you might still have a single User class, but the information it contains would depend of whether full details were fetched.
For every Domain Object there can be more than one mapper
Example: you have a news site with a shared codebased for both public-facing and the management software. But, while both interfaces use the same Article class, the management needs a lot more info populated in it. In this case you would have two separate mappers: "internal" and "external". Each performing different queries, or even use different databases (as in master or slave).
A view is not a template
View instances in MVC (if you are not using the MVP variation of the pattern) are responsible for the presentational logic. This means that each View will usually juggle at least a few templates. It acquires data from the Model Layer and then, based on the received information, chooses a template and sets values.
One of the benefits you gain from this is re-usability. If you create a ListView class, then, with well-written code, you can have the same class handing the presentation of user-list and comments below an article. Because they both have the same presentation logic. You just switch templates.
You can use either native PHP templates or use some third-party templating engine. There also might be some third-party libraries, which are able to fully replace View instances.
What about the old version of the answer?
The only major change is that, what is called Model in the old version, is actually a Service. The rest of the "library analogy" keeps up pretty well.
The only flaw that I see is that this would be a really strange library, because it would return you information from the book, but not let you touch the book itself, because otherwise the abstraction would start to "leak". I might have to think of a more fitting analogy.
What is the relationship between View and Controller instances?
The MVC structure is composed of two layers: ui and model. The main structures in the UI layer are views and controller.
When you are dealing with websites that use MVC design pattern, the best way is to have 1:1 relation between views and controllers. Each view represents a whole page in your website and it has a dedicated controller to handle all the incoming requests for that particular view.
For example, to represent an opened article, you would have \Application\Controller\Document and \Application\View\Document. This would contain all the main functionality for UI layer, when it comes to dealing with articles (of course you might have some XHR components that are not directly related to articles).
class Database {
protected $_conn;
public function __construct($connection) {
$this->_conn = $connection;
}
public function ExecuteObject($sql, $data) {
// stuff
}
}
abstract class Model {
protected $_db;
public function __construct(Database $db) {
$this->_db = $db;
}
}
class User extends Model {
public function CheckUsername($username) {
// ...
$sql = "SELECT Username FROM" . $this->usersTableName . " WHERE ...";
return $this->_db->ExecuteObject($sql, $data);
}
}
$db = new Database($conn);
$model = new User($db);
$model->CheckUsername('foo');
Everything that is business logic belongs in a model, whether it is a database query, calculations, a REST call, etc.
You can have the data access in the model itself, the MVC pattern doesn't restrict you from doing that. You can sugar coat it with services, mappers and what not, but the actual definition of a model is a layer that handles business logic, nothing more, nothing less. It can be a class, a function, or a complete module with a gazillion objects if that's what you want.
It's always easier to have a separate object that actually executes the database queries instead of having them being executed in the model directly: this will especially come in handy when unit testing (because of the easiness of injecting a mock database dependency in your model):
class Database {
protected $_conn;
public function __construct($connection) {
$this->_conn = $connection;
}
public function ExecuteObject($sql, $data) {
// stuff
}
}
abstract class Model {
protected $_db;
public function __construct(Database $db) {
$this->_db = $db;
}
}
class User extends Model {
public function CheckUsername($username) {
// ...
$sql = "SELECT Username FROM" . $this->usersTableName . " WHERE ...";
return $this->_db->ExecuteObject($sql, $data);
}
}
$db = new Database($conn);
$model = new User($db);
$model->CheckUsername('foo');
Also, in PHP, you rarely need to catch/rethrow exceptions because the backtrace is preserved, especially in a case like your example. Just let the exception be thrown and catch it in the controller instead.
The original concept (1) described the model as the business logic. It should represent the application state and enforce some data consistency. That approach is often described as "fat model".
Most PHP frameworks follow a more shallow approach, where the model is just a database interface. But at the very least these models should still validate the incoming data and relations.
Either way, you're not very far off if you separate the SQL stuff or database calls into another layer. This way you only need to concern yourself with the real data/behaviour, not with the actual storage API. (It's however unreasonable to overdo it. You'll e.g. never be able to replace a database backend with a filestorage if that wasn't designed ahead.)
就我而言,我有一个数据库类来处理所有直接数据库交互,例如查询、获取等。因此,如果我必须将数据库从 MySQL 更改为 PostgreSQL 不会有任何问题。因此添加额外的层可能会很有用。
每个表都可以有自己的类并有其特定的方法,但要真正获取数据,它让数据库类来处理它:
文件Database.php
class Database {
private static $connection;
private static $current_query;
...
public static function query($sql) {
if (!self::$connection){
self::open_connection();
}
self::$current_query = $sql;
$result = mysql_query($sql,self::$connection);
if (!$result){
self::close_connection();
// throw custom error
// The query failed for some reason. here is query :: self::$current_query
$error = new Error(2,"There is an Error in the query.\n<b>Query:</b>\n{$sql}\n");
$error->handleError();
}
return $result;
}
....
public static function find_by_sql($sql){
if (!is_string($sql))
return false;
$result_set = self::query($sql);
$obj_arr = array();
while ($row = self::fetch_array($result_set))
{
$obj_arr[] = self::instantiate($row);
}
return $obj_arr;
}
}
Table object classL
class DomainPeer extends Database {
public static function getDomainInfoList() {
$sql = 'SELECT ';
$sql .='d.`id`,';
$sql .='d.`name`,';
$sql .='d.`shortName`,';
$sql .='d.`created_at`,';
$sql .='d.`updated_at`,';
$sql .='count(q.id) as queries ';
$sql .='FROM `domains` d ';
$sql .='LEFT JOIN queries q on q.domainId = d.id ';
$sql .='GROUP BY d.id';
return self::find_by_sql($sql);
}
....
}
我希望这个示例可以帮助您创建一个良好的结构。
In my case I have a database class that handle all the direct database interaction such as querying, fetching, and such. So if I had to change my database from MySQL to PostgreSQL there won't be any problem. So adding that extra layer can be useful.
Each table can have its own class and have its specific methods, but to actually get the data, it lets the database class handle it:
File Database.php
class Database {
private static $connection;
private static $current_query;
...
public static function query($sql) {
if (!self::$connection){
self::open_connection();
}
self::$current_query = $sql;
$result = mysql_query($sql,self::$connection);
if (!$result){
self::close_connection();
// throw custom error
// The query failed for some reason. here is query :: self::$current_query
$error = new Error(2,"There is an Error in the query.\n<b>Query:</b>\n{$sql}\n");
$error->handleError();
}
return $result;
}
....
public static function find_by_sql($sql){
if (!is_string($sql))
return false;
$result_set = self::query($sql);
$obj_arr = array();
while ($row = self::fetch_array($result_set))
{
$obj_arr[] = self::instantiate($row);
}
return $obj_arr;
}
}
Table object classL
class DomainPeer extends Database {
public static function getDomainInfoList() {
$sql = 'SELECT ';
$sql .='d.`id`,';
$sql .='d.`name`,';
$sql .='d.`shortName`,';
$sql .='d.`created_at`,';
$sql .='d.`updated_at`,';
$sql .='count(q.id) as queries ';
$sql .='FROM `domains` d ';
$sql .='LEFT JOIN queries q on q.domainId = d.id ';
$sql .='GROUP BY d.id';
return self::find_by_sql($sql);
}
....
}
I hope this example helps you create a good structure.
发布评论
评论(5)
我必须澄清的第一件事是: 模型是一个层。
其次:经典 MVC 与我们在 Web 开发中使用的 MVC 之间存在差异。 这里是我写的一个旧答案,它简要描述了它们的不同之处。
模型不是什么:
模型不是一个类或任何单个对象。 这是一个非常常见的错误(我也犯过,尽管最初的答案是在我开始学习其他方法时写的),因为大多数框架都会延续这种误解。
它既不是对象关系映射技术(ORM),也不是数据库表的抽象。任何告诉你其他情况的人很可能试图'销售'另一个全新的 ORM 或整个框架。
模型是什么:
在正确的 MVC 适应中,M 包含所有领域业务逻辑,并且模型层主要由三种类型的结构组成:
域对象
<块引用>
领域对象是纯领域信息的逻辑容器;它通常代表问题域空间中的逻辑实体。通常称为业务逻辑。
您可以在此处定义如何在发送发票之前验证数据或计算订单的总成本。与此同时,域对象完全不知道存储 - 既不知道存储位置(SQL 数据库、REST API、文本文件等),甚至if< /em> 它们被保存或检索。
数据映射器
这些对象只负责存储。如果您将信息存储在数据库中,那么这就是 SQL 所在的位置。或者,您可能使用 XML 文件来存储数据,并且您的数据映射器正在解析 XML 文件。
服务
您可以将它们视为“更高级别的域对象”,但服务负责域对象和映射器<之间的交互,而不是业务逻辑/em>.这些结构最终创建一个“公共”接口,用于与域业务逻辑交互。您可以避免它们,但代价是将一些域逻辑泄漏到控制器中。
ACL 实现问题中有与此主题相关的答案 - 它可能有用。
模型层和 MVC 三元组其他部分之间的通信只能通过服务进行。明确的分离还有一些额外的好处:
,如果您需要外部 API ,
如何与模特互动?
获取对服务实例的访问权限
对于 View 和 Controller 实例(您可以称之为:“UI 层”)都具有访问权限对于这些服务,有两种通用方法:
正如您可能怀疑的那样,DI 容器是一个更优雅的解决方案(但对于初学者来说并不是最简单的)。我建议考虑此功能的两个库是 Syfmony 的独立 DependencyInjection 组件或Auryn。
使用工厂和 DI 容器的解决方案都可以让您共享各种服务器的实例,以便在给定的请求响应周期的选定控制器和视图之间共享。
模型状态的更改
现在您可以访问控制器中的模型层,您需要开始实际使用它们:
您的控制器有一个非常明确的任务:获取用户输入,并根据此输入更改当前的业务状态逻辑。在此示例中,在“匿名用户”和“登录用户”之间更改的状态。
控制器不负责验证用户的输入,因为这是业务规则的一部分,并且控制器绝对不会调用 SQL 查询,就像您所看到的 此处或此处< /a> (请不要讨厌他们,他们是被误导的,而不是邪恶的)。
向用户显示状态更改。
好的,用户已登录(或失败)。 现在怎么办? 所述用户仍然是浑然不觉。所以你需要实际产生一个响应,这是视图的责任。
在这种情况下,视图根据模型层的当前状态生成两种可能的响应之一。对于不同的用例,您将让视图根据“当前选择的文章”之类的内容选择不同的模板进行渲染。
表示层实际上可以变得非常复杂,如下所述:了解 PHP 中的 MVC 视图。
但我只是在制作一个 REST API!
当然,在某些情况下,这有点矫枉过正。
MVC 只是关注点分离原则的具体解决方案。 MVC 将用户界面与业务逻辑分开,并且在 UI 中将用户输入的处理和表示分开。这一点至关重要。虽然人们经常将其描述为“三合会”,但它实际上并不是由三个独立的部分组成。结构更像是这样的:
这意味着,当你的表示层逻辑几乎不存在时,务实的方法是将它们保持为单层。它还可以大大简化模型层的某些方面。
使用这种方法,登录示例(对于 API)可以写成:
虽然这是不可持续的,但当您有复杂的逻辑来呈现响应主体时,这种简化对于更琐碎的场景非常有用。但是请注意,当尝试在具有复杂表示逻辑的大型代码库中使用时,这种方法将成为一场噩梦。
如何建立模型?
由于没有单个“模型”类(如上所述),因此您实际上并没有“构建模型”。相反,您从创建能够执行某些方法的服务开始。然后实现域对象和映射器。
服务方法的示例:
在上述两种方法中,都有用于识别服务的登录方法。它实际上会是什么样子。我正在使用我编写的一个库中相同功能的稍微修改版本..因为我是惰性:
正如您所看到的,在这个抽象级别,没有指示数据是从哪里获取的。它可能是一个数据库,但也可能只是一个用于测试目的的模拟对象。即使是实际使用的数据映射器也隐藏在该服务的
private
方法中。创建映射器的方法
要实现持久性的抽象,最灵活的方法是创建自定义数据映射器。
来自:PoEAA book
在实践中,它们是为了与特定类或超类交互而实现的。假设您的代码中有
Customer
和Admin
(两者都继承自User
超类)。两者最终可能都会有一个单独的匹配映射器,因为它们包含不同的字段。但您最终也会得到共享和常用的操作。例如:更新“上次在线查看”时间。更实用的方法是拥有一个通用的“用户映射器”,它只更新时间戳,而不是让现有的映射器更加复杂。一些附加评论:
数据库表和模型
虽然有时数据库表、域对象和映射器之间存在直接的 1:1:1 关系,但在较大的项目中,这种关系可能不像您那么常见期望:
单个域对象使用的信息可能来自不同的表,而对象本身在数据库中没有持久性。
示例:如果您要生成月度报告。这将从不同的表中收集信息,但数据库中没有神奇的
MonthlyReport
表。单个Mapper可以影响多个表。
示例:当您存储来自
User
对象的数据时,此域对象可以包含其他域对象的集合 -对实例进行分组。如果您更改它们并存储
用户
,数据映射器将必须更新和/或在多个表中插入条目。来自单个域对象的数据存储在多个表中。
示例:在大型系统中(例如:中型社交网络),将用户身份验证数据和经常访问的数据与较大的内容块分开存储可能是务实的,而这种情况很少见必需的。在这种情况下,您可能仍然有一个
User
类,但它包含的信息将取决于是否获取完整的详细信息。对于每个域对象,可以有多个映射器
示例:您有一个新闻网站,该网站具有面向公众的共享代码和管理软件。但是,虽然两个界面都使用相同的
Article
类,但管理人员需要在其中填充更多信息。在这种情况下,您将有两个单独的映射器:“内部”和“外部”。每个执行不同的查询,甚至使用不同的数据库(如主数据库或从数据库)。视图不是模板
MVC 中的
View 实例(如果您没有使用该模式的 MVP 变体)负责表示逻辑。这意味着每个视图通常至少会处理一些模板。它从模型层获取数据,然后根据接收到的信息选择模板并设置值。
您从中获得的好处之一是可重用性。如果您创建一个
ListView
类,那么,通过编写良好的代码,您可以让同一类处理文章下方的用户列表和评论的呈现。因为它们都有相同的呈现逻辑。您只需切换模板即可。您可以使用原生 PHP 模板 或使用一些第三方 -派对模板引擎。还可能有一些第三方库,它们能够完全替换 View 实例。
旧版本的答案怎么样?
唯一的重大变化是,旧版本中所谓的Model,实际上是一个Service。 “图书馆类比”的其余部分保持得很好。
我看到的唯一缺陷是,这将是一个非常奇怪的库,因为它会返回书中的信息,但不会让你接触书本身,因为否则抽象就会开始“泄漏”。我可能不得不想一个更合适的类比。
View 和 Controller 实例之间的关系是什么?
MVC结构由两层组成:ui层和model层。 UI层的主要结构是视图和控制器。
当您处理使用 MVC 设计模式的网站时,最好的方法是在视图和控制器之间建立 1:1 的关系。每个视图代表您网站中的整个页面,并且它有一个专用控制器来处理该特定视图的所有传入请求。
例如,要表示打开的文章,您将拥有
\Application\Controller\Document
和\Application\View\Document
。当涉及到处理文章时,这将包含 UI 层的所有主要功能(当然,您可能有一些 与文章不直接相关的 XHR 组件)。The first thing that I must clear up is: the model is a layer.
Second: there is a difference between classical MVC and what we use in web development. Here's a bit of an older answer I wrote, which briefly describes how they are different.
What a model is NOT:
The model is not a class or any single object. It is a very common mistake to make (I did too, though the original answer was written when I began to learn otherwise), because most frameworks perpetuate this misconception.
Neither is it an Object-Relational Mapping technique (ORM) nor an abstraction of database tables. Anyone who tells you otherwise is most likely trying to 'sell' another brand-new ORM or a whole framework.
What a model is:
In proper MVC adaptation, the M contains all the domain business logic and the Model Layer is mostly made from three types of structures:
Domain Objects
This would be where you define how to validate data before sending an invoice, or to compute the total cost of an order. At the same time, Domain Objects are completely unaware of storage - neither from where (SQL database, REST API, text file, etc.) nor even if they get saved or retrieved.
Data Mappers
These objects are only responsible for the storage. If you store information in a database, this would be where the SQL lives. Or maybe you use an XML file to store data, and your Data Mappers are parsing from and to XML files.
Services
You can think of them as "higher level Domain Objects", but instead of business logic, Services are responsible for interaction between Domain Objects and Mappers. These structures end up creating a "public" interface for interacting with the domain business logic. You can avoid them, but at the penalty of leaking some domain logic into Controllers.
There is a related answer to this subject in the ACL implementation question - it might be useful.
The communication between the model layer and other parts of the MVC triad should happen only through Services. The clear separation has a few additional benefits:
How to interact with a model?
Gaining access to service instances
For both the View and Controller instances (what you could call: "UI layer") to have access these services, there are two general approaches:
As you might suspect, the DI container is a lot more elegant solution (while not being the easiest for a beginner). The two libraries, that I recommend considering for this functionality would be Syfmony's standalone DependencyInjection component or Auryn.
Both the solutions using a factory and a DI container would let you also share the instances of various servers to be shared between the selected controller and view for a given request-response cycle.
Alteration of model's state
Now that you can access to the model layer in the controllers, you need to start actually using them:
Your controllers have a very clear task: take the user input and, based on this input, change the current state of business logic. In this example the states that are changed between are "anonymous user" and "logged in user".
Controller is not responsible for validating user's input, because that is part of business rules and controller is definitely not calling SQL queries, like what you would see here or here (please don't hate on them, they are misguided, not evil).
Showing user the state-change.
Ok, user has logged in (or failed). Now what? Said user is still unaware of it. So you need to actually produce a response and that is the responsibility of a view.
In this case, the view produced one of two possible responses, based on the current state of model layer. For a different use-case you would have the view picking different templates to render, based on something like "current selected of article" .
The presentation layer can actually get quite elaborate, as described here: Understanding MVC Views in PHP.
But I am just making a REST API!
Of course, there are situations, when this is a overkill.
MVC is just a concrete solution for Separation of Concerns principle. MVC separates user interface from the business logic, and it in the UI it separated handling of user input and the presentation. This is crucial. While often people describe it as a "triad", it's not actually made up from three independent parts. The structure is more like this:
It means, that, when your presentation layer's logic is close to none-existent, the pragmatic approach is to keep them as single layer. It also can substantially simplify some aspects of model layer.
Using this approach the login example (for an API) can be written as:
While this is not sustainable, when you have complicate logic for rendering a response body, this simplification is very useful for more trivial scenarios. But be warned, this approach will become a nightmare, when attempting to use in large codebases with complex presentation logic.
How to build the model?
Since there is not a single "Model" class (as explained above), you really do not "build the model". Instead you start from making Services, which are able to perform certain methods. And then implement Domain Objects and Mappers.
An example of a service method:
In the both approaches above there was this login method for the identification service. What would it actually look like. I am using a slightly modified version of the same functionality from a library, that I wrote .. because I am lazy:
As you can see, at this level of abstraction, there is no indication of where the data was fetched from. It might be a database, but it also might be just a mock object for testing purposes. Even the data mappers, that are actually used for it, are hidden away in the
private
methods of this service.Ways of creating mappers
To implement an abstraction of persistence, on the most flexible approaches is to create custom data mappers.
From: PoEAA book
In practice they are implemented for interaction with specific classes or superclasses. Lets say you have
Customer
andAdmin
in your code (both inheriting from aUser
superclass). Both would probably end up having a separate matching mapper, since they contain different fields. But you will also end up with shared and commonly used operations. For example: updating the "last seen online" time. And instead of making the existing mappers more convoluted, the more pragmatic approach is to have a general "User Mapper", which only update that timestamp.Some additional comments:
Database tables and model
While sometimes there is a direct 1:1:1 relationship between a database table, Domain Object, and Mapper, in larger projects it might be less common than you expect:
Information used by a single Domain Object might be mapped from different tables, while the object itself has no persistence in the database.
Example: if you are generating a monthly report. This would collect information from different of tables, but there is no magical
MonthlyReport
table in the database.A single Mapper can affect multiple tables.
Example: when you are storing data from the
User
object, this Domain Object could contain collection of other domain objects -Group
instances. If you alter them and store theUser
, the Data Mapper will have to update and/or insert entries in multiple tables.Data from a single Domain Object is stored in more than one table.
Example: in large systems (think: a medium-sized social network), it might be pragmatic to store user authentication data and often-accessed data separately from larger chunks of content, which is rarely required. In that case you might still have a single
User
class, but the information it contains would depend of whether full details were fetched.For every Domain Object there can be more than one mapper
Example: you have a news site with a shared codebased for both public-facing and the management software. But, while both interfaces use the same
Article
class, the management needs a lot more info populated in it. In this case you would have two separate mappers: "internal" and "external". Each performing different queries, or even use different databases (as in master or slave).A view is not a template
View instances in MVC (if you are not using the MVP variation of the pattern) are responsible for the presentational logic. This means that each View will usually juggle at least a few templates. It acquires data from the Model Layer and then, based on the received information, chooses a template and sets values.
One of the benefits you gain from this is re-usability. If you create a
ListView
class, then, with well-written code, you can have the same class handing the presentation of user-list and comments below an article. Because they both have the same presentation logic. You just switch templates.You can use either native PHP templates or use some third-party templating engine. There also might be some third-party libraries, which are able to fully replace View instances.
What about the old version of the answer?
The only major change is that, what is called Model in the old version, is actually a Service. The rest of the "library analogy" keeps up pretty well.
The only flaw that I see is that this would be a really strange library, because it would return you information from the book, but not let you touch the book itself, because otherwise the abstraction would start to "leak". I might have to think of a more fitting analogy.
What is the relationship between View and Controller instances?
The MVC structure is composed of two layers: ui and model. The main structures in the UI layer are views and controller.
When you are dealing with websites that use MVC design pattern, the best way is to have 1:1 relation between views and controllers. Each view represents a whole page in your website and it has a dedicated controller to handle all the incoming requests for that particular view.
For example, to represent an opened article, you would have
\Application\Controller\Document
and\Application\View\Document
. This would contain all the main functionality for UI layer, when it comes to dealing with articles (of course you might have some XHR components that are not directly related to articles).所有业务逻辑都属于模型,无论是数据库查询、计算、REST 调用等。
您可以在模型本身中进行数据访问,MVC 模式不限制你不要那样做。您可以用服务、映射器等来粉饰它,但模型的实际定义是一个处理业务逻辑的层,仅此而已。如果您想要的话,它可以是一个类、一个函数或一个包含无数对象的完整模块。
拥有一个实际执行数据库查询的单独对象,而不是直接在模型中执行它们总是更容易:这在单元测试时尤其有用(因为在模型中注入模拟数据库依赖项很容易):
此外,在 PHP 中,您很少需要捕获/重新抛出异常,因为回溯被保留,特别是在像您的示例这样的情况下。只需让异常被抛出并在控制器中捕获它即可。
Everything that is business logic belongs in a model, whether it is a database query, calculations, a REST call, etc.
You can have the data access in the model itself, the MVC pattern doesn't restrict you from doing that. You can sugar coat it with services, mappers and what not, but the actual definition of a model is a layer that handles business logic, nothing more, nothing less. It can be a class, a function, or a complete module with a gazillion objects if that's what you want.
It's always easier to have a separate object that actually executes the database queries instead of having them being executed in the model directly: this will especially come in handy when unit testing (because of the easiness of injecting a mock database dependency in your model):
Also, in PHP, you rarely need to catch/rethrow exceptions because the backtrace is preserved, especially in a case like your example. Just let the exception be thrown and catch it in the controller instead.
在Web-“MVC”中你可以做任何你想做的事。
原始概念(1)描述了该模型作为业务逻辑。它应该代表应用程序状态并强制执行某些数据一致性。这种方法通常被描述为“胖模型”。
大多数 PHP 框架都遵循更浅层的方法,其中模型只是一个数据库接口。但至少这些模型仍然应该验证传入的数据和关系。
无论哪种方式,如果将 SQL 内容或数据库调用分离到另一层,就不会太遥远。这样您只需要关心真实的数据/行为,而不需要关心实际的存储 API。 (但是,过度使用是不合理的。如果没有提前设计,您将永远无法用文件存储替换数据库后端。)
In Web-"MVC" you can do whatever you please.
The original concept (1) described the model as the business logic. It should represent the application state and enforce some data consistency. That approach is often described as "fat model".
Most PHP frameworks follow a more shallow approach, where the model is just a database interface. But at the very least these models should still validate the incoming data and relations.
Either way, you're not very far off if you separate the SQL stuff or database calls into another layer. This way you only need to concern yourself with the real data/behaviour, not with the actual storage API. (It's however unreasonable to overdo it. You'll e.g. never be able to replace a database backend with a filestorage if that wasn't designed ahead.)
更常见的是,大多数应用程序都会有数据、显示和处理部分,我们只需将所有这些都放在字母
M
、V
和C
中。Model(
M
)-->具有保存应用程序状态的属性,并且它不知道有关V
和C 的任何信息
。View(
V
)-->具有应用程序的显示格式,并且只知道如何消化其上的模型,而不关心C
。控制器(
C
)---->具有应用程序的处理部分,并充当 M 和 V 之间的接线,它取决于M
,V
与M
和V
不同。总而言之,每个人之间的关注点是分离的。
将来任何更改或增强都可以非常轻松地添加。
More oftenly most of the applications will have data,display and processing part and we just put all those in the letters
M
,V
andC
.Model(
M
)-->Has the attributes that holds state of application and it dont know any thing aboutV
andC
.View(
V
)-->Has displaying format for the application and and only knows about how-to-digest model on it and does not bother aboutC
.Controller(
C
)---->Has processing part of application and acts as wiring between M and V and it depends on bothM
,V
unlikeM
andV
.Altogether there is separation of concern between each.
In future any change or enhancements can be added very easily.
就我而言,我有一个数据库类来处理所有直接数据库交互,例如查询、获取等。因此,如果我必须将数据库从 MySQL 更改为 PostgreSQL 不会有任何问题。因此添加额外的层可能会很有用。
每个表都可以有自己的类并有其特定的方法,但要真正获取数据,它让数据库类来处理它:
文件
Database.php
Table object classL
我希望这个示例可以帮助您创建一个良好的结构。
In my case I have a database class that handle all the direct database interaction such as querying, fetching, and such. So if I had to change my database from MySQL to PostgreSQL there won't be any problem. So adding that extra layer can be useful.
Each table can have its own class and have its specific methods, but to actually get the data, it lets the database class handle it:
File
Database.php
Table object classL
I hope this example helps you create a good structure.