MVC 中的模型应该如何构建? [关闭]

2023-12-11

我刚刚掌握 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以及我们在网络开发中使用的内容。Here's我写的一个旧答案,它简要描述了它们的不同之处。

模型不是什么:

该模型不是一个类或任何单个对象。这是一个很常见的错误(我也这样做了,尽管最初的答案是在我开始学习其他知识时写的),因为大多数框架都会延续这种误解。

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

什么是模型:

在正确的 MVC 适配中,M 包含所有领域业务逻辑和模型层 is mostly由三种类型的结构组成:

  • 领域对象

    领域对象是纯领域信息的逻辑容器;它通常代表问题域空间中的逻辑实体。通常称为商业逻辑.

    您可以在此处定义如何在发送发票之前验证数据或计算订单的总成本。同时,领域对象完全不知道存储 - 都不知道where(SQL 数据库、REST API、文本文件等)甚至也不是if它们被保存或检索。

  • 数据映射器

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

  • Services

    您可以将它们视为“更高级别的域对象”,但不是业务逻辑,Services负责之间的交互领域对象 and Mappers。这些结构最终创建一个“公共”接口,用于与域业务逻辑交互。您可以避免它们,但代价是会泄漏一些域逻辑控制器.

    这个问题有一个相关的答案ACL实施问题 - 它可能有用。

模型层和 MVC 三元组其他部分之间的通信只能通过Services。明确的分离还有一些额外的好处:

  • 它有助于执行单一责任原则 (SRP)
  • 提供额外的“回旋余地”以防逻辑发生变化
  • 使控制器尽可能简单
  • 如果您需要外部 API,则提供清晰的蓝图

 

如何与模特互动?

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

获取对服务实例的访问

对于这两个View and 控制器实例(您可以称之为:“UI 层”)来访问这些服务,有两种通用方法:

  1. 您可以直接在视图和控制器的构造函数中注入所需的服务,最好使用 DI 容器。
  2. 使用服务工厂作为所有视图和控制器的强制依赖项。

正如您可能怀疑的那样,DI 容器是一个更优雅的解决方案(但对于初学者来说并不是最简单的)。我建议考虑此功能的两个库是 Syfmony 的独立库依赖注入组件 or Auryn.

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

模型状态的改变

现在您可以访问控制器中的模型层,您需要开始实际使用它们:

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $identity = $this->identification->findIdentityByEmailAddress($email);
    $this->identification->loginWithPassword(
        $identity,
        $request->get('password')
    );
}

您的控制器有一个非常明确的任务:获取用户输入,并根据此输入更改业务逻辑的当前状态。在此示例中,在“匿名用户”和“登录用户”之间更改的状态。

控制器不负责验证用户的输入,因为这是业务规则的一部分,并且控制器绝对不会调用 SQL 查询,就像您所看到的那样here or here(请不要讨厌他们,他们是被误导的,而不是邪恶的)。

向用户显示状态更改。

好的,用户已登录(或失败)。怎么办?该用户仍不知情。所以你需要实际产生一个响应,这是视图的责任。

public function postLogin()
{
    $path = '/login';
    if ($this->identification->isUserLoggedIn()) {
        $path = '/dashboard';
    }
    return new RedirectResponse($path); 
}

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

表示层实际上可以变得非常复杂,如下所述:了解 PHP 中的 MVC 视图.

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

当然,在某些情况下,这有点矫枉过正。

MVC只是一个具体的解决方案关注点分离原则。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);
}

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

 

如何建立模型?

由于没有单个“模型”类(如上所述),因此您实际上并没有“构建模型”。相反,你从制作开始Services,它们能够执行某些方法。然后实施领域对象 and Mappers.

服务方法示例:

在上述两种方法中,都有这种用于身份识别服务的登录方法。它实际上会是什么样子。我正在使用相同功能的稍微修改版本图书馆,我写的..因为我很懒:

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);
}

创建映射器的方法

要实现持久性的抽象,最灵活的方法是创建自定义数据映射器.

Mapper diagram

From: PoEAA book

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

一些补充意见:

  1. 数据库表和模型

    虽然有时数据库表之间存在直接的 1:1:1 关系,域对象, and Mapper,在较大的项目中,它可能比您预期的要少见:

    • 单个人使用的信息域对象可能是从不同的表映射的,而对象本身在数据库中没有持久性。

      Example:如果您要生成月度报告。这将从不同的表收集信息,但没有神奇的MonthlyReport数据库中的表。

    • 单个Mapper可以影响多个表。

      Example:当您存储数据时User对象,这个域对象可以包含其他域对象的集合 -Group实例。如果您更改它们并存储User, the 数据映射器必须在多个表中更新和/或插入条目。

    • 数据来自单个域对象存储在多个表中。

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

    • 对于每一个域对象可以有多个映射器

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

  2. 视图不是模板

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

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

    您可以使用原生 PHP 模板或者使用一些第三方模板引擎。可能还有一些第三方库,它们可以完全替代View实例。

  3. 旧版本的答案怎么样?

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

    我看到的唯一缺陷是,这将是一个非常奇怪的库,因为它会返回书中的信息,但不会让你接触书本身,因为否则抽象就会开始“泄漏”。我可能不得不想一个更合适的类比。

  4. 之间有什么关系View and 控制器实例?

    MVC结构由两层组成:ui层和model层。区内主要建筑物UI layer是视图和控制器。

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

    例如,要表示一篇打开的文章,您需要\Application\Controller\Document and \Application\View\Document。这将包含 UI 层处理文章时的所有主要功能(当然你可能有一些XHR与文章不直接相关的组件).

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MVC 中的模型应该如何构建? [关闭] 的相关文章

  • 突出显示单词并提取其附近文本的函数

    我有一个文本例如 Etiam porta semmalesuada magna mollis euismod 整数取数 ante venenatis dapibus posuere velit aliquet 埃蒂亚姆 门塔 塞姆 male
  • yii2 更新后返回上一页

    更新记录后如何将用户重定向到上一页 这是典型的场景 用户在索引页面中过滤结果或对记录进行分页 然后找到想要编辑的记录并单击编辑按钮 他们更新该记录的数据 一旦单击 更新 按钮 他们将被重定向到索引视图 但具有先前选择的过滤器 页面 更新后我
  • 表单提交到白页?

    所以我有一个用于我的注册系统的表格 当表单提交并出现错误时 例如 首先输入用户名 或 您必须提供密码 它会成功刷新页面并显示这些错误 但是 当表单提交并且用户填写了所有数据并且没有错误时 表单将转到白色页面 我查看了源代码 所有显示的都是页
  • “单体”是什么意思?

    我在课堂上看到过它 我怀疑这意味着该类可以被分解为逻辑子单元 但我找不到一个好的定义 你能举一些例子吗 谢谢您的帮助 编辑 我喜欢聪明的回复 但我显然指的是软件上下文中的 整体 我了解巨石 巨石 支石墓以及所有与石头相关的背景 哎呀 我的国
  • 在 Yii2 中从数据库中多态查找模型

    我在数据库 mysql 中有一张表 但该表存储了几种略有不同类型的行 类型取决于此表的type柱子 我有一个表的抽象 ActiveRecord 类和几个后代子类 它们为不同类型的同一表的行实现略有不同的逻辑 现在我正在为所有类型的行实现更新
  • PHP:合并/添加多维数组?

    假设我有几个具有相同结构的多维数组 如下所示 basketA fruit apple 1 basketA fruit orange 2 basketA fruit banana 3 basketA drink soda 4 basketA
  • imagecreatefrompng(和 imagecreatefromstring)导致不可恢复的致命错误

    当我尝试在不正确的 png 图像上使用 php gd 函数时 出现致命的 PHP 错误 这似乎是某种错误 因为根据函数文档 imagecreatefrompng 例如 return resource an image resource id
  • 使用 PHP 显示 Mysql 中的图像

    这就是我的数据库中的表的样子 我正在尝试显示我存储的图像 它是 mimetype longblob 当我运行代码时 它会给我一个带有 的小框 没有错误 只是那个框 有谁知道错误是什么以及如何修复它 Display Index Display
  • 创建日期 - Laravel 中的 Carbon

    我开始阅读有关Carbon并且似乎不知道如何创建一个carbon date 在文档中说你可以 Carbon createFromDate year month day tz Carbon createFromTime hour minute
  • PHP json_encode 反斜杠和数组名称的问题

    我正在将一些 postgresql 数据转换为 PHP json encode 但我遇到了一些问题 json encode 将 BackSlash 添加到我的数据中的所有斜杠中 在描述中出现段落标记的结束 我认为是因为反斜杠问题 我不希望我
  • Ajax.BeginForm 帮助程序未将部分视图加载到指定的 DIV 中

    我正在使用 MVC 尝试将表单提交的结果加载到特定的 DIV 中 下面是我的表单的代码 div class segmentForm clearfix div
  • mysqldump创建空sql文件? [Windows 上的 php 和 mysql]

    我尝试转储数据库 我尝试了指定 mysqldump exe 的完整路径或仅使用 mysqldump 它仍然给我一个 0kb dumpfile sql 细节 编程语言 PHP 数据库 MySql 5 XX 操作系统 服务器 Windows S
  • 使用 C# 9.0 记录构建类智能枚举/类判别联合/类和类型数据结构?

    玩弄record在 C 中输入 看起来它对于构建类似可区分联合的数据结构非常有用 我只是想知道我是否错过了一些我以后会后悔的陷阱 例如 abstract record CardType Case types public record Ma
  • PHP登录然后重定向

    我使用以下代码将用户登录到一系列安全页面 我需要将每个用户在提交后重定向到适当的页面 我想知道需要采取哪些步骤来选出三个登录级别 管理员 特殊 用户 if isset SESSION username function check logi
  • 如何从页面获取所有网址(php)

    我有一个页面 其中的网址和描述逐一列出 例如书签 网站列表 如何使用php从该页面获取所有url并将它们写入txt文件 每行一个 只有url而没有描述 页面如下所示 一些描述 http link com 其他说明 http link2 co
  • 如何在Redis中正确存储图片?

    决定将图像存储在Redis中 如何正确执行 现在我这样做 redis gt set image path here is the base64 image code 我不确定这是否正常 将图片存储在Redis中是完全可以的 Redis 键和
  • 证明字符串算法[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • xampp openssl 调用 openssl_pkey_new() 时出错;

    所以我试图让 openssl 在我的 Windows 安装的 xampp 1 7 3 上工作 它是用 OpenSSL 0 9 8l 构建的 这只是我第二次在 amp 安装上安装 openssl 但第一次进展顺利 这是在同一台机器上的 wam
  • 根据产品类别自定义购物车总计和结帐总计文本

    我想根据产品类别 或其他一些逻辑 自定义购物车和结帐订单汇总表中的文本 例如 对于 总计 文本 参见图片 如果购物车包含名为 杂货 的类别中的产品 那么我希望订单摘要中的文本显示为 总计估计 文本 参见图片以下 如果购物车不包含任何杂货 那
  • 实现 PHP 单例:静态类属性还是静态方法变量?

    所以 我总是像这样实现一个单例 class Singleton private static instance null public static function getInstance if self instance null se

随机推荐

  • Google Chrome 扩展的 onBrowserClose 事件?

    我正在为 Google Chrome 开发一个扩展 我的后台脚本每次都会在使用 XMPP API 的服务器上进行授权 并订阅 PubSub 节点 我需要在退出时取消订阅 否则虚拟订阅将保留在服务器上 有没有onBrowserCloseGoo
  • 带有 pabot 的机器人框架:是否可以在两个测试中将两个不同的值传递给变量

    例子 我有file1 robot and file2 robot并且每个都有 var 作为变量 我可以将两个不同的值传递给这个值吗 var 在命令行中 就像是pabot v var one two file1 robot file2 rob
  • 经典 ASP 日期之间的工作日数

    经典 ASP VBScript 中有没有办法获取两个日期之间的工作日数 显然 我们有DateDiff 函数 但这会拉回总天数 但我想省略周末 你说得对 DateDiff 不包括这一点 但它可以与WeekDay 计算出是否Day适逢周末 通过
  • 如何对发送到服务器的http请求进行加密?

    是否可以隐藏 或加密 HTTP 请求的内容 以便授权人员之外的其他人无法查看它 例如 如果一个用户只是在登录页面中提交数据 即使使用 http post 那么也可以在 firebug 类工具中看到请求标头中包含的用户名和密码等内容 我知道客
  • 条件 DB2 SQL 查询

    假设我有一个名为 Company 的表 其键为 CompanyID 还有另一个名为 CompanyAddress 的相关表 它具有 CompanyID 外键 因此可以轻松建立连接 此 CompanyAddress 表可以具有给定公司的多个地
  • 从 cs 文件加载脚本并访问主机方法、属性等?

    我只是在和罗斯林玩 但不确定如何执行以下操作 为了保持简单 假设我有一个主机程序 它有一个像这样的方法 public void DisplayMessage string message MessageBox Show message 然后
  • 确定数组是否包含 n...n+m 的算法?

    我在 Reddit 上看到这个问题 但没有给出积极的解决方案 我认为在这里问这个问题是一个完美的问题 这是关于面试问题的帖子 编写一个方法 该方法接受大小为 m 的 int 数组 如果该数组包含数字 n n m 1 该范围内的所有数字以及仅
  • 实现自定义配置节处理程序

    从各种来源 包括 stackOverlflow 收集的信息 但是当我开始使用它时 我收到以下错误消息 配置属性 deviceconfig 可能不是从 ConfigurationSection 派生的 我现在已经在这个问题上挣扎了一整天 而且
  • 在java程序中执行bash命令

    自从我寻找以来已经有一段时间了 但我没有找到解决方案 我正在尝试在 Linux 上的 jar 文件中执行 bash 命令 为此 我尝试了很多方法 包括 Process p new ProcessBuilder java jar M1 MIA
  • 当变量值丢失时,Django 模板中的 Javascript 语法错误

    我有一个带有 AJAX 菜单的 Django 模板 单击不同的菜单项会重新加载页面的一部分 每次菜单单击都会通过 AJAX 调用 Django 视图函数并返回一些要在页面上显示的数据 我只加载主页中所有菜单项所需的所有 JS 据我所知 AJ
  • SerialPort.Read(....) 不尊重 ReadTimeOut

    与支付终端通信的一些旧代码中存在错误 在新的付款开始之前 代码会尝试清除串行端口的内部读取缓冲区 我将代码削减到最低限度 它使用 NET SerialPort 类型 设置读取超时为 50ms 然后它读取 512 字节并继续这样做 直到不再读
  • 是否可以在 C# 中返回对变量的引用? [复制]

    这个问题在这里已经有答案了 例如 我可以返回对双精度值的引用吗 这就是我想做的 ref double GetElement Calculate x y z return ref doubleArray x y z 像这样使用它 void f
  • socket.io 的 C 客户端

    我正在尝试使用socket io 建立从C 程序到节点服务器的连接 我能想到的唯一方法是从 C 程序向节点服务器发出 http 请求 所以我已经对此部分进行了一些介绍 现在我需要从C程序接收来自节点服务器的一些信息 是否有任何 c 客户端库
  • Python 凯撒密码解码器

    在我的课程中 我的任务是创建一个凯撒密码解码器 该解码器接受输入字符串并使用字母频率找到最佳可能的字符串 如果不确定这有多大意义 但让我们提出问题 编写一个执行以下操作的程序 首先 它应该读取一行输入 这是编码消息 由大写字母和空格组成 您
  • 使用 SALib 工具箱对测量数据进行 Python 敏感性分析

    我想了解 如何使用 SALib python 工具箱进行 Sobol 敏感性分析 研究参数和交叉参数影响 从最初的例子我应该这样做 from SALib sample import saltelli from SALib analyze i
  • 如何在 R 中将 `foreach` 和 `%dopar%` 与 `R6` 类一起使用?

    我在尝试使用时遇到了问题 dopar and foreach 与一个R6班级 四处搜索 我只能找到两个与此相关的资源 一个未答复所以问题和一个开放的GitHub问题 on the R6存储库 在一条评论 即 GitHub 问题 中 建议通过
  • Android - 一个选项卡中的多个片段

    我在网上搜索了很多有关在一个操作栏选项卡中包含多个片段的可能性的信息 这个问题最接近我的需求 但代码不起作用 是否有另一种可能性可以在一个选项卡中包含多个片段 这是StartActivity public class StartActivi
  • IIS 7.0 错误 HTTP 错误 500.19 - 内部服务器错误(错误代码 0x80070003)

    我的 Windows 2008 Server 上运行着 IIS7 它非常适合我之前托管的网站和文件夹 现在我尝试托管一个包含两个子文件夹的文件夹 子文件夹包含一些 图片 我已正确设置站点文件夹的 IIS IUSRS 权限 当我尝试浏览时 我
  • 常量值无法转换为 int

    我不明白为什么第 5 行无法编译 而第 4 行却可以 static void Main string args byte b 0 int i int 0xffffff00 b ok int j int 0xffffff00 byte 0 e
  • MVC 中的模型应该如何构建? [关闭]

    Closed 这个问题是基于意见的 目前不接受答案 我刚刚掌握 MVC 框架 经常想知道模型中应该包含多少代码 我倾向于拥有一个具有如下方法的数据访问类 public function CheckUsername connection us