优化PHP应用程序:为什么单独阅读和写入模型很重要

优化php应用程序:为什么单独阅读和写入模型很重要

模型是与数据存储交互的理想工具。它们定义数据的结构,确保与数据存储(通常是数据库)兼容。模型不仅验证输入数据,辅助数据写入,还能用于数据检索。然而,除了简单的CRUD应用之外,将同一个模型用于读写通常并非最佳实践。让我们深入探讨原因。

创建模型

让我们以一个简单的用户模型和存储库接口为例,这里无需详细说明其内部实现。假设我们有一些断言库来验证每个创建的模型的有效性。

class User{    public function __construct(        public string $email,        public string $name,        public string $password,    ) {        Assert::email($email);        Assert::notEmpty($name);        Assert::password($password, strength: 3);    }}interface UserRepository{    public function save(User $user): void;}

典型用例:我们获取新用户的输入数据,验证姓名非空、邮箱有效、密码符合强度要求(级别3)。然后将数据保存到存储库。完成。

$user = new User(    $request->get('email'),    $request->get('name'),    $request->get('password'),);$repository->save($user);

问题:模型属性不应用于读取现在,假设我们要根据数据库中的邮箱读取用户信息,并将其以JSON格式返回给客户端,用于显示用户配置文件。如果我们向存储库添加读取方法,并重用相同的模型,会发生什么?

interface UserRepository{    public function save(User $user): void;    public function get(string $email): User;}// 在某个控制器类中return new Response(    json_encode(        $repository->get($request->get('email'))    ),);

结果可能如下:

立即学习“PHP免费学习笔记(深入)”;

{  "email": "peter@dailybugle.com",  "name": "peter parker",  "password": "$2y$10$oeatphgkw0hqv4qnxtptqoe.bsqdnpwrb./vgqigjmehvir22jnfk"}

首先,密码(即使是加密的)也不应通过服务器发送。这是一个严重的安全性问题。虽然这是使用写入模型作为读取模型导致的信息泄露最糟糕的情况,但并非唯一的情况。另一个常见问题是向客户端发送不相关的信息。例如,我们可能有一个 active 布尔值,用于启用或禁用用户,这对客户端来说毫无用处,因为如果用户不活跃,请求将返回 404。不相关的数据会导致额外的字节传输,影响性能。虽然单个影响可能很小,但累积起来会造成问题,而解决方法很简单。

解决方案?返回一个包含有限数据列表的响应。这可以解决这些问题。

class User{    // ...    public function read(): array    {        return [            'email' => $this->email,            'name' => $this->name,        ];    }}

但这只是冰山一角,还有更多问题需要解决。

问题:不必要的验证谈到性能,我们在模型构造函数中进行了验证。但是,当我们从数据库中获取已存在的数据时,还需要这些验证吗?它们在保存时就应该有效,因此再次运行这些验证是浪费资源。但这不仅仅是浪费,它可能是一个严重的问题。验证规则可能会发生变化,如果我们使用相同的模型进行读写,这可能会影响检索数据的能力。假设一个验证用户邮箱格式的应用添加了一个新的规则,禁止某些域名。验证规则更新了,但现有用户无法更新,因为他们的邮箱仍然有效。

现在,假设我们收到一个包含100个用户的请求,其中一个用户的邮箱域名在黑名单中,会发生什么?整个请求将被视为错误。我们应该向用户返回什么?400 Bad Request 响应,就好像用户输入错误一样?这不是客户端的错,而是服务器的错。在这种情况下,应该返回某种 500 错误。

为了避免这种情况,一些复杂的解决方案涉及反射和无构造函数的实例化。如果我们必须在不需要验证的情况下使用写入模型,我建议将断言移到静态工厂方法中。

class User{    public function __construct(        public string $email,        public string $name,        public string $password,    ) {}    public static function create(string $email, string $name, string $password): self    {        Assert::email($email);        Assert::notEmpty($name);        Assert::password($password, strength: 3);        return new self($email, $name, $password);    }}

这样,当创建一个需要验证的新模型时,我们可以使用 User::create(),而从数据库获取数据时则使用构造函数。这解决了一些问题,但还有更多。

问题:向模型添加额外数据另一种常见情况是客户端需要更多数据用于视图。在我们的例子中,视图可能需要显示用户在系统中创建的评论数量。这不是模型的一部分,但为了避免客户端等待额外的请求,我们可能希望在同一个HTTP响应中包含此数据。即使我们尝试在同一个请求中添加数据,坚持使用同一个模型意味着我们不能使用单个数据库请求来获取整个数据集(尽管在许多情况下,可以使用简单的SQL JOIN来实现)。相反,我们获取写入模型,然后执行另一个数据库请求以获取缺失的数据,然后在将其发送给客户端之前进行组合。

return new Response(    json_encode(        array_merge(            $repository->get($request->get('email')),            ['comments' => $commentRepository->count($request->get('email'))]        )    ),);

这有效,但这意味着额外的数据库查询,从而影响性能。这也降低了可重用性,因为你不能简单地在其他地方调用存储库,你还需要复制粘贴评论部分。

问题:插入和更新真的相同吗?最后一个问题与写入与读取模型本身无关,而是当我们更新模型时,我们是否可以使用创建时使用的相同类?

如果我们使用此模型创建新用户,我们需要姓名、邮箱和密码。对于创建用户来说这是合理的,但在我们的例子中,安全专家要求以特定方式更新密码,这涉及用户请求更改密码,发送包含时间限制令牌的邮件给用户,然后验证该令牌并接受新密码。永远不要以其他方式更新密码。那么,如果我们使用相同的模型来更新用户,该怎么办?我们的代码中将有两个不同的位置来更新用户,一个用于密码,另一个用于其他任何内容。

interface UserRepository{    public function save(User $user): void;    public function update(User $user): void;}// 更新姓名$user = new User(    $request->get('email'),    $request->get('name'),    '密码怎么办?',);$repository->update($user);// 更新密码$user = new User(    $request->get('email'),    '姓名怎么办?',    $request->get('password'),);$repository->update($user);

现在,我们必须处理模型中不应该处理的数据,这将使我们的存储库实现更加复杂。它还会迫使模型创建提供不会使用的数据,使代码难以理解。最后,我们得到一个脆弱的实现,如果使用不当,可能会导致不应该更新的内容被更新,仅仅因为它存在于模型中。如果我们以触发密码更新的方式处理用户名更改,那将是一个严重的问题。

解决方案:针对每种情况使用单独的模型我们如何解决读取用户时遇到的所有问题?专用模型可以做到。

final class UserRead{    public function __construct(        public string $email,        public string $name,        public int $commentCount,    ) {}}

我们可以为其创建一个新的存储库。

interface UserReadRepository{    public function get(string $email): UserRead;}

假设使用关系型SQL数据库,此实现将不会选择读取模型中不存在的密码字段,解决了问题1。此读取模型不包含密码,解决了问题2。通过在新的存储库中使用单个查询中的JOIN来获取评论计数,解决了问题3。更进一步,如果我们对用户有更多表示,则应该有不同的读取模型来覆盖每种情况。例如,我们可以使用 UserWithLastCommentsRead

更新问题呢?你可能猜到了,针对每种更新操作使用单独的模型。

final class UserDataUpdate{    public function __construct(        public string $email,        public string $name,    ) {        Assert::notEmpty($name);    }}final class UserPasswordUpdate{    public function __construct(        public string $email,        public string $password,    ) {        Assert::password($password, strength: 3);    }}interface UserRepository{    public function save(User $user): void;    public function updateData(UserDataUpdate $userDataUpdate): void;    public function updatePassword(UserPasswordUpdate $userPasswordUpdate): void;}

现在没有错误或不必要的数据。每个更新都是隔离的,并且更不容易出错。

请注意,在更新模型中,我没有添加邮箱验证。这是有意的,因为它将用于查找用户。如果我们进行了进化的验证,如前所述,我们将无法找到不再有效的邮箱,但仍在数据库中。

结语这与我们在现实世界中对对象的建模方式没有什么不同。在特定情况下,我们从不考虑现实生活中对象的所有方面。例如,汽车。

如果汽车是由司机建模的,我们可以期望座椅位置和后视镜非常重要,而与此同时,这与机械师进行维护无关。机械师可能更关心发动机上对司机不重要的指标。一个学习交通方式的孩子可能只关心它是一个有四个轮子的陆地交通工具。

如果我们对相同的现实生活对象使用不同的模型,那么我们当然也可以对代码中的模型做同样的事情。

以上就是优化PHP应用程序:为什么单独阅读和写入模型很重要的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1252752.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月10日 00:14:31
下一篇 2025年12月8日 10:15:39

相关推荐

  • PHP本地开发工具5

    > phpstudy Web:Web开发的综合工具 PhPstudy Web是一种非常流行且用户友好的软件,旨在帮助开发人员有效地设置和管理Web服务器和PHP环境。 PhpStudy Web以其简单性和多功能性而闻名,在网络开发社区中广泛使用,尤其是用于本地开发和测试。 什么是phpstud…

    2025年12月10日
    000
  • 为什么我讨厌WordPress,但是为什么它仍然很棒和必要

    WordPress占据着超过40%的网站市场份额,是目前最流行的内容管理系统(CMS)。然而,在软件工程师群体中,特别是后端开发和可扩展Web应用领域的工程师,WordPress因其低效、臃肿和令人沮丧的特性而臭名昭著。 我个人非常不喜欢WordPress。我花费了大量时间处理其混乱的代码库、解决插…

    2025年12月10日
    000
  • Windows RDP托管:远程开发工作区的完整指南

    作为当今数字时代的开发人员,拥有一个可靠且强大的远程工作空间不仅是一种奢侈品,而且是必不可少的。 Windows远程桌面协议(RDP)托管在开发人员中越来越受欢迎,这是有充分理由的。在本指南中,我将分享我在Windows RDP托管方面的经验,以及为什么它可能是您想要的解决方案。 **为什么每个开发…

    好文分享 2025年12月10日
    000
  • 设计一个数字容器系统

    设计一个高效的数字容器系统,支持以下操作: 插入/替换: 将指定索引处的值替换为新值。如果索引不存在,则插入新值。查找最小索引: 返回给定数字在容器中出现的最小索引。如果数字不存在,则返回 -1。 挑战难度: 中等 相关主题: 哈希表,设计模式,最小堆(优先队列) 示例: [“NumberConta…

    2025年12月10日
    000
  • 与作曲家制作和共享PHP库

    Composer已成为PHP项目依赖管理和代码复用的核心工具。无论您是贡献开源项目还是提升个人开发效率,学习创建Composer包都是一项非常有价值的技能。本文将引导您完成构建和共享个人PHP库的完整流程。 准备工作 在开始之前,请确保您已具备以下条件: 扎实的PHP和Composer基础知识。已在…

    2025年12月10日
    000
  • 升级到PHP

    本文档记录了在Ubuntu系统上安装或升级PHP 8.2的步骤,希望能帮助到您和其他人。 首先,更新系统软件包列表: sudo dpkg -l | grep php | tee packages.txtsudo add-apt-repository ppa:ondrej/php # 按提示键入sud…

    2025年12月10日
    000
  • Laravel注入命令:如何检测和防止它

    Laravel 命令注入漏洞:检测与防御 命令注入是严重的服务器端安全漏洞,允许攻击者执行任意系统命令。如果 laravel 应用在处理系统命令时未妥善处理用户输入,则极易受到此类攻击。本文将深入探讨命令注入,提供代码示例,并讲解如何保护您的 laravel 应用免受此类威胁。 我们还将介绍一款免费…

    2025年12月10日
    000
  • 创建数据库

    项目概述:构建旅游代理信息系统 本项目旨在开发一个基于MySQL数据库的旅游代理信息系统,支持代理商的未来发展和营销策略。系统将管理代理商、客户、住宿信息(公寓、房屋、酒店)、航班信息以及预订等功能。项目团队由3名成员组成,预计完成时间为12小时。最终成果将包含两个虚拟机,并包含数据库、逻辑数据模型…

    2025年12月10日
    000
  • Laravel请求枚举方法

    在学习Laravel中使用PHP枚举进行请求处理时,您可能会有疑问:为什么需要这种方法来进行简单的验证? Laravel控制器方法默认会处理$request->validate或$validator->validated方法中出现的重定向行为,这并非枚举方法的用途。 那么,枚举方法的真正…

    2025年12月10日
    000
  • PHP中的PSR-容器接口

    PSR-11 规范定义了 PHP 依赖注入容器的标准接口。这一标准化使得库能够从任何容器实现中检索服务,从而提升不同框架和库之间的互操作性。 理解依赖注入容器 (DIC) 依赖注入容器负责: 管理服务定义创建服务实例解析依赖项管理对象生命周期 容器接口示例 立即学习“PHP免费学习笔记(深入)”; …

    2025年12月10日
    000
  • 拉维尔队列:巴士与链条

    Laravel 队列:提升应用性能的 Bus 和 Chain Laravel 队列用于处理耗时的后台任务,从而提升应用性能。核心概念是 Bus 和 Chain,它们赋予作业控制和链接能力。本文将深入探讨如何利用 Bus 和 Chain 在 Laravel 中构建高效的执行流程。 Laravel Bu…

    2025年12月10日
    000
  • 防止Laravel应用中的比赛条件

    竞争条件:laravel应用中的隐患及解决方案 竞争条件是并发系统(例如Web应用)中一个常见且严重的漏洞,可能导致不可预测的行为。本文将探讨竞争条件的成因、影响以及如何在Laravel框架中有效避免它们。 什么是竞争条件? 竞争条件发生在多个进程同时修改共享数据时,导致结果不可预测。这常见于:文件…

    2025年12月10日
    000
  • 冻结时间:测试Laravel临时存储URL

    上一篇文章探讨了两种测试Laravel Storage::temporaryUrl() 方法的技术。文章演示了如何使用模拟来处理本地不支持临时URL的情况。本文将深入探讨如何利用“冻结时间”技术提升测试临时URL的可靠性,尤其针对时间敏感型功能。我们将结合Laravel内置的测试助手和Carbon的…

    2025年12月10日
    000
  • 为什么在数据库中进行整理?

    MySQL中的校对规则定义了数据的分类和比较方式,尤其影响字符编码和字符串操作,例如大小写敏感性(’é’ 与 ‘e’ 的比较)。校对规则与字符集关联,决定数据库可存储的字符。 为什么要使用校对规则? 数据排序: 校对规则决定数据排序的顺序。例如,在 u…

    2025年12月10日
    000
  • 防止DNS在Laravel中重新启动:综合指南

    laravel安全指南:防御dns重绑定攻击 DNS重绑定是一种隐蔽的网络攻击,攻击者利用DNS欺骗绕过同源策略,访问私有网络资源。对于Laravel开发者而言,理解并防御DNS重绑定漏洞至关重要。本文将深入探讨DNS重绑定的工作机制、对Laravel应用的影响,以及有效的防御策略。我们将提供代码示…

    2025年12月10日
    000
  • 如何使用PHP合并PDF

    本教程将指导您如何使用php和nutrino api轻松合并多个pdf文件。nutrino api提供免费计划,包含100个积分,可用于各种pdf操作,包括文件合并。 您需要创建一个免费帐户获取api密钥。此方法尤其适用于处理大量文档上传的php开发人员,例如人力资源应用中简历、求职信和推荐信的合并…

    2025年12月10日
    000
  • Laravel参数化中间件深入潜水

    >我正在阅读一篇有关参数化中间件的文章,而我在想参数的奇怪语法。它看起来像是静态方法调用中的错字,someclass :: class。’:: somemethod’。 如果该参数比字符串更复杂,例如基本枚举。 进入水 ,所以我查看了中间件方法的代码。 /** * ge…

    好文分享 2025年12月10日
    000
  • 优化Laravel应用程序性能的提示

    Laravel应用的性能直接影响用户体验。本文将分享十个有效提升Laravel应用性能的技巧,涵盖缓存策略、数据库优化等方面,助您缩短加载时间,提升服务器效率,最终优化应用速度。 巧用路由缓存 重要性: Laravel每次请求都加载路由,大量路由会拖慢响应速度。路由缓存可有效解决此问题。 操作方法:…

    2025年12月10日
    000
  • 使用Ruby实施Bayarcash付款API:验证校验和

    集成支付平台时,缺乏目标语言的文档往往令人头疼。最近,我协助吉隆坡一家旅行社ZFB Travel在其Ruby on Rails后端集成了马来西亚本地支付平台Bayarcash。Bayarcash文档虽然提供了PHP示例,却缺少Ruby版本,给Rails开发者带来了挑战。 本文将逐步指导您如何在Rai…

    2025年12月10日
    000
  • 构建了一个将面试准备过程变成博客文章的功能

    我开发了一个工具,利用Claude 3生成PHP面试准备资源,并将其自动转换为博客文章。此工具涵盖了面试中可能遇到的问题、答案和提示,帮助求职者更好地准备面试。 该工具的输出示例可见于:https://www.php.cn/link/4d1d732a3fd7efdacb4b26a0ca945eba …

    2025年12月10日
    000

发表回复

登录后才能评论
关注微信