怎么理解Laravel存储库模式

发布时间:2021-11-15 16:04:26 作者:iii
来源:亿速云 阅读:94

这篇文章主要讲解了“怎么理解Laravel存储库模式”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么理解Laravel存储库模式”吧!

活动记录

默认情况下,Laravel 使用 Active Record 模式。每个 Laravel 程序员都直观地使用它,因为它是在抽象的 Model 基类中实现的,而模型通常从它继承而来。让我们来看一个例子:

use Illuminate\Database\Eloquent\Model;

/**
 * @property int    $id
 * @property string $first_name
 * @property string $last_name
 */
class Person extends Model {
}

// --- 使用:

$person = new Person();
$person->first_name = 'Jack';
$person->last_name = 'Smith';
$person->save();

当然,您可以读写您在 Person 上创建的属性。 但是要保存模型,您也可以 直接在模型上调用方法。 不需要另一个对象——模型已经提供了访问相应数据库表的所有方法。

这意味着,域模型将您的自定义属性和方法与同一类中的所有数据访问方法相结合。 第二部分是通过继承 Model 来实现的。

要点:

Repository

Repository 模式是 Active Record 模式的替代方案。它还提供了处理数据访问的抽象。但更广泛地说,它可以被视为域对象的概念性存储库或集合。

与活动记录模式相反,存储模式将数据库访问与域模型分离。它提供了一个高级接口,你可以在其中创建、读取、更新和删除域模型,而不必考虑实际的底层数据存储。

底层的存储库可以通过构建和执行 SQL 查询访问数据库,通过 REST API 访问远程系统,或者仅仅管理包含所有域模型的内存数据结构。这对测试很有用。存储库模式关键部分是它为其余代码提供的高级接口。

要点:

在 Laravel 中实现 Repository 模式时,我主要看到两种变体。

变体1:特定方法

在第一个变体中,存储库方法是重点和特定的。名称解释了调用者获得的内容,用于参数化底层查询的选项是有限的。

class InvoiceRepository {

    public function findAllOverdue(Carbon $since, int $limit = 10): Collection {
        return Invoice::where('overdue_since', '>=', $since)
            ->limit($limit)
            ->orderBy('overdue_since')
            ->get();
    }

    public function findInvoicedToCompany(string $companyId): Collection {
        return Invoice::where('company_id', $companyId)
            ->orderByDesc('created_at')
            ->get();
    }
}

这种方法的优势在于方法的表现力。阅读代码时,很清楚从方法中期望什么以及如何调用它们。这会导致更少的错误。 Repository 方法很容易测试,因为参数有限。

这种方法的一个缺点是,最终可能会在存储库中使用大量的方法。由于方法无法轻松重用,因此必须为新用例添加其他方法。

要点:

变式2: 一般方法

另一方面的方法是提供一般的方法。这导致了方法的减少。但是这些方法有一个很大的 API 曲面,因为每个方法都可以使用不同的参数组合来调用。

其中的关键问题是参数表示。这种表示应该引导调用方理解方法签名并避免无效的输入。为此,您可以引入一个特殊的类,例如使用 Query Object 模式。

但是我在实践中经常看到的是标量参数和 PHP 数组的混合。调用方可以传递完全无效的输入,仅类型数据并不能说明要传递什么。但是如果使用得当,这种轻量级的方法可以避免更繁琐的抽象。

class InvoiceRepository {

    public function find(array $conditions, string $sortBy = 'id', string $sortOrder = 'asc', int $limit = 10): Collection {
        return Invoice::where($conditions)
            ->orderBy($sortBy, $sortOrder)
            ->limit($limit)
            ->get();
    }
}

// --- 使用:

$repo = new InvoiceRepository();
$repo->find(['overdue_since', '>=', $since], 'overdue_since', 'asc');
$repo->find(['company_id', '=', $companyId], 'created_at', 'asc', 100);

这种方法减轻了第一种方法的问题:你可以得到更少的 Repository 方法,这些方法更灵活,并且可以更频繁地重用。

从消极的方面看,Repository 变得更加难以测试,因为有更多的案例需要覆盖。方法签名更难理解,正因为如此,调用者可能会犯更多错误。此外,还将引入某种查询对象表示形式。无论它是显式的还是隐式的(比如数组),您的 Repository 实现及其调用者都将与它耦合。

要点:

当然,这两种方法可以结合起来使用。也许你想要一些特定的方法用于复杂的查询,而一些通用的方法用于简单的 where 查询。

实现

现在,我们来谈谈如何实现方法体。

在上面的例子中,我使用了 Model 类的方法来获得对 Eloquent 查询构造器的访问。所以 Repository 的实现实际上使用了 Active Record 模式作为实现。

你不需要这样做。你可以使用 DB facade 来获得一个查询构建器,同时避免使用 Model 类。或者你可以直接编写 SQL 查询:

class InvoiceRepository {

    public function findAllOverdue(Carbon $since, int $limit = 10): Collection {
        return DB::table('invoices')
            ->where('overdue_since', '>=', $since)
            ->limit($limit)
            ->orderBy('overdue_since')
            ->get();
    }

    public function findInvoicedToCompany(string $companyId): Collection {
        return DB::select('SELECT * FROM invoices
                           WHERE company_id = ?
                           ORDER BY created_at
                           LIMIT 100', [$companyId]);
    }
}

存储模式的优点是,实现可以是任何东西,只要它满足接口。你还可以管理内存中的对象或者包(和缓存)一个 API。

但是最常见的是,底层数据存储是一个 SQL 数据库。要访问它,你可以根据每个方法选择最佳实现。对于性能关键的或者复杂的查询,你可能希望直接使用 SQL 语句。更简单的查询可以使用 Eloquent 查询生成器。

当你没有使用 模型 类来实现你的 Repository ,你可能会考虑在模型中不继承它。但是这个方法违反了很多内置的 Laravel 魔术方法,在我看来并不是一个好的方法。

要点:

接口

你的另一个选择是,是否要引入一个接口。上面的例子可以用一个接口和一个或多个实现来分隔:

// --- 接口:

public interface InvoiceRepositoryInterface {

    public function findAllOverdue(Carbon $since, int $limit = 10): Collection;

    public function findInvoicedToCompany(string $companyId): Collection;
}

// --- 具体的类,实现了该接口

class InvoiceRepository implements InvoiceRepositoryInterface {

    public function findAllOverdue(Carbon $since, int $limit = 10): Collection {
        // 实现
    }

    public function findInvoicedToCompany(string $companyId): Collection {
        // 实现
    }
}

添加接口是一种额外的间接方法,并不一定是好的。如果您的应用程序是 Repository 的唯一用户,并且您不希望它有多个实现,那么我不认为引入接口有什么意义。对于测试,Repository 可以用 PHPUnit 模拟,只要它不被标记为 final

如果你知道你将有多个实现,你应该使用一个接口。如果你正在编写一个将在多个项目中使用的 包。或者你想要测试一个特殊的 Repository 实现,那么可能会发生不同的实现。

为了从 Laravel 的依赖注入中获益,你必须将具体的实现绑定到接口上。这必须在服务提供者的注册方法中完成。

use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider {

    public function register(): void {
        $this->app->bind(InvoiceRepositoryInterface::class, InvoiceRepository::class);
    }
}

要点:

感谢各位的阅读,以上就是“怎么理解Laravel存储库模式”的内容了,经过本文的学习后,相信大家对怎么理解Laravel存储库模式这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

推荐阅读:
  1. Blob存储如何理解
  2. 单例模式简单理解

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

laravel

上一篇:怎么理解Java即时编译

下一篇:如何快速搭建并使用ThinkPHP5

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》