gpt4 book ai didi

php - 在MVC中应该如何构建模型?

转载 作者:行者123 更新时间:2023-12-01 16:08:09 25 4
gpt4 key购买 nike

关闭。这个问题是opinion-based .它目前不接受答案。












想改善这个问题吗?更新问题,以便可以通过 editing this post 用事实和引文回答问题.

2年前关闭。



Improve this question




我刚刚掌握了 MVC 框架,我经常想知道模型中应该包含多少代码。我倾向于有一个数据访问类,它有这样的方法:

public function CheckUsername($connection, $username)
{
try
{
$data = array();
$data['Username'] = $username;

//// SQL
$sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";

//// Execute statement
return $this->ExecuteObject($connection, $sql, $data);
}
catch(Exception $e)
{
throw $e;
}
}

我的模型往往是映射到数据库表的实体类。

模型对象是否应该具有所有数据库映射属性以及上面的代码,还是可以将该代码与数据库实际工作的代码分开?

我最终会有四层吗?

最佳答案

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.



我首先要弄清楚的是: 模型是一个层 .

第二:经典 MVC 与我们在 Web 开发中使用的不同。 Here's我写的一个较旧的答案,它简要描述了它们的不同之处。

模型不是什么:

模型不是一个类或任何单个对象。这是一个非常常见的错误(我也犯过,虽然最初的答案是在我开始学习时写的),因为大多数框架都延续了这种误解。

它既不是对象关系映射技术 (ORM),也不是数据库表的抽象。任何告诉你其他情况的人很可能试图“出售”另一个全新的 ORM 或整个框架。

什么是模型:

在适当的MVC适配中,M包含所有领域业务逻辑,模型层为 大部分 由三种类型的结构制成:
  • Domain Objects

    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.



    这将是您定义如何在发送发票之前验证数据或计算订单总成本的地方。同时,域对象完全不知道存储——无论是从哪里(SQL 数据库、REST API、文本文件等),甚至它们是否被保存或检索。
  • Data Mappers

    这些对象只负责存储。如果您将信息存储在数据库中,这将是 SQL 所在的位置。或者,您可能使用 XML 文件来存储数据,而您的数据映射器正在解析 XML 文件和解析 XML 文件。
  • Services

    您可以将它们视为“更高级别的域对象”,但服务负责域对象和映射器之间的交互,而不是业务逻辑。这些结构最终创建了一个用于与域业务逻辑交互的“公共(public)”接口(interface)。您可以避免它们,但代价是将某些域逻辑泄漏到 Controller 中。

    ACL implementation 中有关于这个主题的相关答案。问题 - 它可能有用。

  • 模型层和 MVC 三元组的其他部分之间的通信应该只通过服务发生。清晰的分离还有一些额外的好处:
  • 它有助于执行 single responsibility principle (SRP)
  • 提供额外的“摆动空间”以防逻辑发生变化
  • 使 Controller 尽可能简单
  • 给出清晰的蓝图,如果您需要外部 API


  • 如何与模型交互?

    Prerequisites: watch lectures "Global State and Singletons" and "Don't Look For Things!" from the Clean Code Talks.



    访问服务实例

    对于 View 和 Controller 实例(您可以称之为:“UI 层”)访问这些服务,有两种通用方法:
  • 您可以直接在 View 和 Controller 的构造函数中注入(inject)所需的服务,最好使用 DI 容器。
  • 使用服务工厂作为所有 View 和 Controller 的强制依赖项。

  • 您可能会怀疑,DI 容器是一个更优雅的解决方案(虽然对于初学者来说不是最简单的)。我建议为此功能考虑的两个库是 Syfmony 的独立库 DependencyInjection componentAuryn .

    使用工厂和 DI 容器的解决方案都可以让您共享各种服务器的实例,以便在给定的请求-响应周期内在选定的 Controller 和 View 之间共享。

    模型状态的改变

    现在您可以访问 Controller 中的模型层,您需要开始实际使用它们:
    public function postLogin(Request $request)
    {
    $email = $request->get('email');
    $identity = $this->identification->findIdentityByEmailAddress($email);
    $this->identification->loginWithPassword(
    $identity,
    $request->get('password')
    );
    }

    您的 Controller 有一个非常明确的任务:接收用户输入,并根据此输入更改业务逻辑的当前状态。在这个例子中,状态在“匿名用户”和“登录用户”之间改变。

    Controller 不负责验证用户的输入,因为这是业务规则的一部分, Controller 绝对不会调用 SQL 查询,就像您会看到的 herehere (请不要讨厌他们,他们被误导了,而不是邪恶)。

    向用户显示状态变化。

    好的,用户已登录(或失败)。 Now what?所述用户仍然不知道它。所以你需要实际产生一个响应,这是 View 的责任。
    public function postLogin()
    {
    $path = '/login';
    if ($this->identification->isUserLoggedIn()) {
    $path = '/dashboard';
    }
    return new RedirectResponse($path);
    }

    在这种情况下, View 会根据模型层的当前状态生成两种可能的响应之一。对于不同的用例,您可以让 View 根据“当前选择的文章”之类的内容选择不同的模板进行渲染。

    表示层实际上可以变得非常复杂,如下所述: Understanding MVC Views in PHP .

    但我只是在制作一个 REST API!

    当然,在某些情况下,这是一种矫枉过正的情况。

    MVC 只是 Separation of Concerns 的具体解决方案原则。 MVC 将用户界面与业务逻辑分开,并在 UI 中将用户输入的处理和呈现分开。这是至关重要的。虽然人们经常将其描述为“三合会”,但它实际上并不是由三个独立的部分组成。结构更像这样:

    MVC separation

    这意味着,当您的表示层的逻辑几乎不存在时,实用的方法是将它们保持为单层。它还可以大大简化模型层的某些方面。

    使用这种方法,登录示例(对于 API)可以写为:
    public function postLogin(Request $request)
    {
    $email = $request->get('email');
    $data = [
    'status' => 'ok',
    ];
    try {
    $identity = $this->identification->findIdentityByEmailAddress($email);
    $token = $this->identification->loginWithPassword(
    $identity,
    $request->get('password')
    );
    } catch (FailedIdentification $exception) {
    $data = [
    'status' => 'error',
    'message' => 'Login failed!',
    ]
    }

    return new JsonResponse($data);
    }

    虽然这是不可持续的,但当您有复杂的逻辑来呈现响应主体时,这种简化对于更琐碎的场景非常有用。但是 被警告 ,当尝试在具有复杂表示逻辑的大型代码库中使用时,这种方法将成为一场噩梦。

    如何建立模型?

    由于没有单个“模型”类(如上所述),因此您实际上并没有“构建模型”。相反,您从制作能够执行某些方法的服务开始。然后实现域对象和映射器。

    服务方法的一个例子:

    在上述两种方法中,身份识别服务都有这种登录方法。它实际上会是什么样子。我正在使用来自 a library 的相同功能的稍微修改版本,我写的..因为我很懒:
    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();
    }

    正如您所看到的,在这个抽象级别上,没有指示数据是从哪里获取的。它可能是一个数据库,但也可能只是一个用于测试目的的模拟对象。甚至实际用于它的数据映射器也隐藏在 private 中。本服务的方法。
    private function changeIdentityStatus(Entity\Identity $identity, int $status)
    {
    $identity->setStatus($status);
    $identity->setLastUsed(time());
    $mapper = $this->mapperFactory->create(Mapper\Identity::class);
    $mapper->store($identity);
    }

    创建映射器的方法

    要实现持久化的抽象,最灵活的方法是创建自定义 data mappers .

    Mapper diagram

    发件人: PoEAA

    在实践中,它们是为了与特定类或父类(super class)交互而实现的。假设您有 CustomerAdmin在您的代码中(都继承自 User 父类(super class))。两者最终可能都有一个单独的匹配映射器,因为它们包含不同的字段。但是您最终也会得到共享和常用的操作。例如:更新“最后一次在线查看”时间。而不是让现有的映射器更复杂,更实用的方法是拥有一个通用的“用户映射器”,它只更新那个时间戳。

    一些补充意见:
  • 数据库表和模型

    虽然有时数据库表、域对象和映射器之间存在直接的 1:1:1 关系,但在较大的项目中,它可能没有您预期的那么常见:
  • 单个域对象使用的信息可能来自不同的表,而对象本身在数据库中没有持久性。

    示例:如果您正在生成月度报告。这会从不同的表中收集信息,但没有神奇的MonthlyReport数据库中的表。
  • 单个 Mapper 可以影响多个表。

    示例:当您存储来自 User 的数据时对象,此域对象可以包含其他域对象的集合 - Group实例。如果您更改它们并存储 User ,数据映射器将不得不在多个表中更新和/或插入条目。
  • 来自单个域对象的数据存储在多个表中。

    示例:在大型系统中(想想:中型社交网络),将用户身份验证数据和经常访问的数据与大块内容分开存储可能是务实的,这很少需要。在这种情况下,您可能仍然只有一个 User类,但它包含的信息将取决于是否获取了完整的详细信息。
  • 对于每个域对象,可以有多个映射器

    示例:您有一个新闻网站,其中包含面向公众和管理软件的共享代码。但是,虽然两个接口(interface)都使用相同的 Article类,管理层需要在其中填充更多信息。在这种情况下,您将有两个单独的映射器:“内部”和“外部”。每个执行不同的查询,甚至使用不同的数据库(如在 master 或 slave 中)。
  • View 不是模板

    MVC 中的 View 实例(如果您不使用模式的 MVP 变体)负责表示逻辑。这意味着每个 View 通常会处理至少几个模板。它从模型层获取数据,然后根据接收到的信息选择模板并设置值。

    您从中获得的好处之一是可重用性。如果您创建一个 ListView类,然后,通过编写良好的代码,您可以让同一个类处理文章下方的用户列表和评论的呈现。因为它们都具有相同的呈现逻辑。您只需切换模板。

    您可以使用 native PHP templates或者使用一些第三方模板引擎。也可能有一些第三方库,它们能够完全替代 View 实例。
  • 旧版本的答案呢?

    唯一的主要变化是,旧版本中所谓的Model,实际上是一个Service。 “图书馆类比”的其余部分保持得很好。

    我看到的唯一缺陷是这将是一个非常奇怪的图书馆,因为它会从书中返回信息,但不会让您接触书本身,否则抽象将开始“泄漏”。我可能得想一个更合适的比喻。
  • View 和 Controller 实例之间是什么关系?

    MVC结构由两层组成:ui和model。 UI 层的主要结构是 View 和 Controller 。

    当您处理使用 MVC 设计模式的网站时,最好的方法是在 View 和 Controller 之间建立 1:1 的关系。每个 View 代表您网站中的整个页面,并且它有一个专用 Controller 来处理该特定 View 的所有传入请求。

    例如,要表示已打开的文章,您可以使用 \Application\Controller\Document\Application\View\Document .这将包含 UI 层的所有主要功能,在处理文章时(当然,您可能有一些 XHR 组件与文章没有直接关系)。
  • 关于php - 在MVC中应该如何构建模型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5863870/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com