当前位置: 首页 > 知识库问答 >
问题:

使用具有关系的存储库模式(和查询范围)

法玮
2023-03-14

在Laravel 4中,查询范围可用于所有查询(包括由关系查询生成的查询)。这意味着对于以下(示例)模型:

顾客php:

<?php
class Customer extends Eloquent {
    public function order() { return $this->hasMany('Order'); }
}

顺序php:

<?php
class Order extends Eloquent {
   public function scopeDelivered($query) { return $query->where('delivered', '=', true); }
   public function customer() { return $this->belongsTo('Customer'); }
}

以下两项工作:

var_dump(Order::delivered()->get()); // All delivered orders
var_dump(Customer::find(1)->orders()->delivered()->get()); // only orders by customer #1 that are delivered

这在控制器中很有用,因为查找已交付订单的查询逻辑不必重复。

不过,最近我确信,存储库模式不仅对于分离关注点,而且对于ORM/DB切换的可能性或添加缓存等中间件的必要性都是最佳的。存储库感觉非常自然,因为现在不再让作用域膨胀到我的模型上,而是将关联查询作为存储库的一部分(这更有意义,因为这自然是一种集合方法,而不是项)。

例如

<?php
class EloquentOrderRepository {
    protected $order;

    public function __construct(Order $order) { $this->order = $order; }
    public function find($id) { /* ... */ }
    /* etc... */
    public function allDelievered() { return $this->order->where('delivered', '=', true)->get(); }
}

但是,现在我重复了交付的范围,因此为了避免违反DRY,我将其从模型中删除(根据上面的理由,这似乎是合乎逻辑的)。但是现在,我不能再在关系上使用作用域(比如$customer)-

鉴于这一困境,这是对存储库的滥用吗?如果不是,我的解决方案是恢复我想要的功能的唯一途径吗?或者模型中的作用域耦合不够紧密,不足以证明这个额外代码的合理性?如果作用域不是紧密耦合的,那么有没有一种方法可以同时使用存储库模式和作用域?

注意:我知道一些关于类似主题的类似问题,但没有一个通过不依赖存储库的关系生成的查询来解决这里提出的问题。


共有1个答案

鲜于德业
2023-03-14

我设法找到了解决办法。它相当笨拙,我不确定我是否认为它是可以接受的(它使用了很多东西,它们可能不是用来使用的)。总之,该解决方案允许您将作用域移动到存储库。每个存储库(实例化时)启动一次,在此过程中,通过illights\Database\eloquent\ScopeInterface提取所有作用域方法,并将其添加到由雄辩模型(通过宏)创建的每个查询中。

app/lib/PhpMyCoder/Repository/Repository。php:

<?php namespace PhpMyCoder\Repository;

interface Repository {

    public function all();

    public function find($id);
}

App/lib/PhpMyCoder/仓库/订单/OrderRepository.php:

<?php namespace PhpMyCoder\Repository\Order;

interface OrderRepository extends PhpMyCoder\Repository\Repository {}

app/lib/PhpMyCoder/Repository/Order/EloquentOrderRepository。php:

<?php namespace PhpMyCoder\Repository\Order;

use PhpMyCoder\Repository\EloquentBaseRepository;

class EloquentOrderRepository extends EloquentBaseRepository implements OrderRepository {

    public function __construct(\Order $model) {

        parent::__construct($model);
    }

    public function finished() {

        return $this->model->finished()->get();
    }

    public function scopeFinished($query) {

        return $query->where('finished', '=', true);
    }
}

请注意存储库如何包含通常存储在订单模型类中的范围。在数据库中(对于本例),订单需要有一个布尔列完成。我们将在下面介绍EloquentBaseRepository的详细信息。

app/lib/PhpMyCoder/Repository/EloquentBaseRepository。php:

<?php namespace PhpMyCoder\Repository;

use Illuminate\Database\Eloquent\Model;

abstract class EloquentBaseRepository implements Repository {

    protected $model;

    // Stores which repositories have already been booted
    protected static $booted = array();

    public function __construct(Model $model) {

        $this->model = $model;

        $this->bootIfNotBooted();
    }

    protected function bootIfNotBooted() {

        // Boot once per repository class, because we only need to
        // add the scopes to the model once
        if(!isset(static::$booted[get_class($this)])) {

            static::$booted[get_class($this)] = true;
            $this->boot();
        }
    }

    protected function boot() {

        $modelScope = new ModelScope();  // covered below
        $selfReflection = new \ReflectionObject($this);

        foreach (get_class_methods($this) as $method) {

            // Find all scope methods in the repository class
            if (preg_match('/^scope(.+)$/', $method, $matches)) {

                $scopeName = lcfirst($matches[1]);
                // Get a closure for the scope method
                $scopeMethod = $selfReflection->getMethod($method)->getClosure($this)->bindTo(null);

                $modelScope->addScope($scopeName, $scopeMethod);
            }
        }

        // Attach our special ModelScope to the Model class
        call_user_func([get_class($this->model), 'addGlobalScope'], $modelScope);
    }

    public function __call($method, $arguments) {

        // Handle calls to scopes on the repository similarly to
        // how they are handled on Eloquent models
        if(method_exists($this, 'scope' . ucfirst($method))) {

            return call_user_func_array([$this->model, $method], $arguments)->get();
        }
    }

    /* From PhpMyCoder\Repository\Order\OrderRepository (inherited from PhpMyCoder\Repository\Repository) */
    public function all() {

        return $this->model->all();
    }

    public function find($id) {

        return $this->model->find($id);
    }
}

每次第一次实例化存储库类的实例时,我们都会启动存储库。这涉及到将存储库中的所有“scope”方法聚合到一个ModelScope对象中,然后将其应用于模型。ModelScope将把我们的范围应用于模型创建的每个查询(如下所示)。

app/lib/PhpMyCoder/Repository/ModelScope。php:

<?php namespace PhpMyCoder\Repository;

use Illuminate\Database\Eloquent\ScopeInterface;
use Illuminate\Database\Eloquent\Builder;

class ModelScope implements ScopeInterface {

    protected $scopes = array(); // scopes we need to apply to each query

    public function apply(Builder $builder) {

        foreach($this->scopes as $name => $scope) {

            // Add scope to the builder as a macro (hack-y)
            // this mimics the behavior and return value of Builder::callScope()
            $builder->macro($name, function() use($builder, $scope) {

                $arguments = func_get_args();

                array_unshift($arguments, $builder->getQuery());

                return call_user_func_array($scope, $arguments) ?: $builder->getQuery();
            });
        }
    }

    public function remove(Builder $builder) {

        // Removing is not really possible (no Builder::removeMacro),
        // so we'll just overwrite the method with one that throws a
        // BadMethodCallException

        foreach($this->scopes as $name => $scope) {

            $builder->macro($name, function() use($name) {

                $className = get_class($this);
                throw new \BadMethodCallException("Call to undefined method {$className}::{$name}()");
            });
        }
    }

    public function addScope($name, \Closure $scope) {

        $this->scopes[$name] = $scope;
    }
}

App/lib/PhpMyCoder/RepositoryServiceProvider.php:

<?php namespace PhpMyCoder\Repository;

use Illuminate\Support\ServiceProvider;
use PhpMyCoder\Repository\Order\EloquentOrderRepository;

class RepositoryServiceProvider extends ServiceProvider {

    public function register() {

        // Bind the repository interface to the eloquent repository class
        $this->app->bind('PhpMyCoder\Repository\Order\OrderRepository', function() {

            return new EloquentOrderRepository(new \Order);
        });
    }

}

确保将此服务提供商添加到应用程序中的提供商数组中。phpconfig:

'PhpMyCoder\Repository\RepositoryServiceProvider',

然后将app/lib添加到composer的自动加载中

"autoload": {
    "psr-0": {
        "PhpMyCoder\\": "app/lib" 
    },
    /* etc... */
},

这将需要一个composer.phar转储-自动加载

App/模型/Customer.php:

<?php

class Customer extends Eloquent {

    public function orders() {

        return $this->hasMany('Order');
    }
}

请注意,为了简洁起见,我排除了为客户编写存储库,但在实际应用程序中,您应该这样做。

应用程序/型号/订单。php:

<?php

class Order extends Eloquent {

    public function customer() {

        return $this->belongsTo('Customer');
    }
}

请注意,范围如何不再存储在订单模型中。这在结构上更有意义,因为集合级别(存储库)应该负责应用于所有订单的范围,而Order应该只关注特定于一个订单的细节。要使此演示正常工作,订单必须有一个整数外键customer\u idtocustomers。id和布尔标志已完成

App/控制器/OrderController.php:

<?php

// IoC will handle passing our controller the proper instance
use PhpMyCoder\Repository\Order\OrderRepository;

class OrderController extends BaseController {

    protected $orderRepository;

    public function __construct(OrderRepository $orderRepository) {

        $this->orderRepository = $orderRepository;
    }

    public function test() {

        $allOrders = $this->orderRepository->all();

        // Our repository can handle scope calls similarly to how
        // Eloquent models handle them
        $finishedOrders = $this->orderRepository->finished();

        // If we had made one, we would instead use a customer repository
        // Notice though how the relation query also has order scopes
        $finishedOrdersForCustomer = Customer::find(1)->orders()->finished();
    }
}

我们的存储库不仅包含子模型的作用域,而且更为坚实。它们还具有处理范围调用的能力,就像真正雄辩的模型一样。它们将所有作用域添加到模型创建的每个查询中,以便您在检索相关模型时可以访问它们。

  • 用大量代码换取很少的功能:可能太多而无法达到预期的结果
  • 这很有意思:illumb\Database\Eloquent\Builderillumb\Database\Eloquent\ScopeInterface(与illumb\Database\Eloquent\Model::addGlobalScope结合使用)上的宏很可能是以不希望的方式使用的
  • 它需要实例化存储库(主要问题):如果您在CustomerController中,并且只实例化了CustomerRepository,$this-

我将调查是否有更优雅的解决方案(可以解决上面列出的问题),但这是迄今为止我能找到的最好的解决方案。

  • 使用存储库在Laravel 4中创建灵活的控制器
  • 雄辩的技巧为更好的仓库

 类似资料:
  • 我正在尝试学习存储库模式,似乎有点困惑,当我急于加载关系并将db逻辑排除在控制器之外时,如何使用此存储库模式。 快速概述我的存储库/应用程序结构。 示例ProductInterface。php 示例类别接口。php 好的,最简单的部分是使用DI向控制器注入模型依赖关系。 列出与相关产品的所有类别更加困难,因为我不再使用雄辩的模型。我正在使用一个界面,它没有暴露所有雄辩的方法。 如果我没有在我的El

  • 我是新来laravel的,所以原谅我可能的愚蠢问题。此外,我确实研究了所有其他“类似”的问题,但要么它们没有重现正确的解决方案,要么我真的很难理解。 情景: 我有一个帖子模型和一个主题模型。这就是他们现在的样子。 在岗位上。php 在主题上。php 现在,我需要实现的是: 如果将查询参数传递给请求(即q=Food),我只想返回在主题关系中包含主题食物的帖子,而不返回其他帖子。如果什么都没有通过,那

  • 我有两个模型。模型有一个(关系)。 关于模型,我有以下关系:

  • 问题内容: 我有一个类似(简化)的表结构: 内容 content_has_content topic_has_content 任何主题都可以具有多个“内容”,任何“内容”都可以具有多个“子内容”(内容实例)。对于给定的id_topic,我想从链接的内容,子内容,子内容的子内容等中接收所有my_string1的列表。 我了解“ WITH”不适用于mysql,但找不到很好的递归替代方法。 谢谢丹尼尔

  • 在阅读了T. Otwell关于Laravel中良好设计模式的书后,我在Laravel 4中创建了一个应用程序,我发现自己为应用程序上的每个表创建了存储库。 我最终得到了以下表格结构: 学生:身份证,姓名 我有用于所有这些表的find、create、update和delete方法的存储库类。每个存储库都有一个与数据库交互的雄辩模型。根据Laravel的文档在模型中定义了关系:http://larav

  • 问题内容: 我有以下POJO。 我正在尝试实现一个查询,该查询查找包含标签列表的所有。我尝试了以下方法: 但这仅在我传递给该方法的标记的完全匹配在Mongo中分配给该问题的标记的列表时才有效。例如,如果我在Mongo中有一个带有标签列表的问题,当我传递给该方法时,它不会返回。 我也尝试了以下方法: 但是我根本无法部署到我的servlet容器。(在这种情况下,我收到以下错误消息: 您能否建议如何实施