管理Laravel中的关系,遵守存储库模式

ehp*_*ehp 114 oop repository-pattern laravel eloquent laravel-4

在阅读了T. Otwell关于Laravel优秀设计模式的书之后,在Laravel 4中创建应用程序时,我发现自己为应用程序中的每个表创建了存储库.

我最终得到了以下表格结构:

  • 学生:身份证,姓名
  • 课程:id,name,teacher_id
  • 老师:id,姓名
  • 作业:id,name,course_id
  • 分数(充当学生和作业之间的枢轴):student_id,assignment_id,分数

我有所有这些表的查找,创建,更新和删除方法的存储库类.每个存储库都有一个与数据库交互的Eloquent模型.根据Laravel的文档,模型中定义了关系:http://laravel.com/docs/eloquent#relationships .

在创建新课程时,我所做的就是在课程资源库中调用create方法.该课程有作业,因此在创建作业时,我还想在课程中为每个学生在乐谱表中创建一个条目.我是通过Assignment Repository完成的.这意味着赋值存储库使用Assignment和Student模型与两个Eloquent模型进行通信.

我的问题是:由于此应用程序的大小可能会增加并且会引入更多关系,因此在存储库中与不同的Eloquent模型进行通信是一种好的做法,还是应该使用其他存储库来完成(我的意思是从Assignment存储库调用其他存储库) )还是应该在Eloquent模型中一起完成?

此外,将分数表用作作业与学生之间的转轴还是应该在其他地方完成,这是一种好习惯吗?

Kyl*_*and 212

我正在使用Laravel 4完成一个大型项目,并且必须回答您现在提出的所有问题.阅读所有可用的Laravel书籍超过在Leanpub,和谷歌搜索吨后,我想出了以下结构.

  1. 每个数据表的一个Eloquent Model类
  2. 每个Eloquent模型一个Repository类
  3. 可以在多个Repository类之间进行通信的Service类.

所以我要说我正在建立一个电影数据库.我至少会有以下以下Eloquent Model类:

  • 电影
  • 工作室
  • 导向器
  • 演员
  • 评论

存储库类将封装每个Eloquent Model类,并负责数据库上的CRUD操作.存储库类可能如下所示:

  • MovieRepository
  • StudioRepository
  • DirectorRepository
  • ActorRepository
  • ReviewRepository

每个存储库类都将扩展一个BaseRepository类,该类实现以下接口:

interface BaseRepositoryInterface
{
    public function errors();

    public function all(array $related = null);

    public function get($id, array $related = null);

    public function getWhere($column, $value, array $related = null);

    public function getRecent($limit, array $related = null);

    public function create(array $data);

    public function update(array $data);

    public function delete($id);

    public function deleteWhere($column, $value);
}
Run Code Online (Sandbox Code Playgroud)

Service类用于将多个存储库粘合在一起,并包含应用程序的真实"业务逻辑".控制器与Service类进行通信,以进行创建,更新和删除操作.

因此,当我想在数据库中创建一个新的Movie记录时,我的MovieController类可能有以下方法:

public function __construct(MovieRepositoryInterface $movieRepository, MovieServiceInterface $movieService)
{
    $this->movieRepository = $movieRepository;
    $this->movieService = $movieService;
}

public function postCreate()
{
    if( ! $this->movieService->create(Input::all()))
    {
        return Redirect::back()->withErrors($this->movieService->errors())->withInput();
    }

    // New movie was saved successfully. Do whatever you need to do here.
}
Run Code Online (Sandbox Code Playgroud)

您可以决定如何将数据发送到控制器,但是假设postCreate()方法中的Input :: all()返回的数据如下所示:

$data = array(
    'movie' => array(
        'title'    => 'Iron Eagle',
        'year'     => '1986',
        'synopsis' => 'When Doug\'s father, an Air Force Pilot, is shot down by MiGs belonging to a radical Middle Eastern state, no one seems able to get him out. Doug finds Chappy, an Air Force Colonel who is intrigued by the idea of sending in two fighters piloted by himself and Doug to rescue Doug\'s father after bombing the MiG base.'
    ),
    'actors' => array(
        0 => 'Louis Gossett Jr.',
        1 => 'Jason Gedrick',
        2 => 'Larry B. Scott'
    ),
    'director' => 'Sidney J. Furie',
    'studio' => 'TriStar Pictures'
)
Run Code Online (Sandbox Code Playgroud)

由于MovieRepository不应该知道如何在数据库中创建Actor,Director或Studio记录,我们将使用我们的MovieService类,它可能如下所示:

public function __construct(MovieRepositoryInterface $movieRepository, ActorRepositoryInterface $actorRepository, DirectorRepositoryInterface $directorRepository, StudioRepositoryInterface $studioRepository)
{
    $this->movieRepository = $movieRepository;
    $this->actorRepository = $actorRepository;
    $this->directorRepository = $directorRepository;
    $this->studioRepository = $studioRepository;
}

public function create(array $input)
{
    $movieData    = $input['movie'];
    $actorsData   = $input['actors'];
    $directorData = $input['director'];
    $studioData   = $input['studio'];

    // In a more complete example you would probably want to implement database transactions and perform input validation using the Laravel Validator class here.

    // Create the new movie record
    $movie = $this->movieRepository->create($movieData);

    // Create the new actor records and associate them with the movie record
    foreach($actors as $actor)
    {
        $actorModel = $this->actorRepository->create($actor);
        $movie->actors()->save($actorModel);
    }

    // Create the director record and associate it with the movie record
    $director = $this->directorRepository->create($directorData);
    $director->movies()->associate($movie);

    // Create the studio record and associate it with the movie record
    $studio = $this->studioRepository->create($studioData);
    $studio->movies()->associate($movie);

    // Assume everything worked. In the real world you'll need to implement checks.
    return true;
}
Run Code Online (Sandbox Code Playgroud)

所以我们留下的是一个很好的,明智的关注点分离.存储库只知道它们从数据库中插入和检索的Eloquent模型.控制器不关心存储库,它们只是将从用户收集的数据交给用户并将其传递给适当的服务.该服务不关心它接收的数据如何保存到数据库,它只是将控制器给出的相关数据移交给适当的存储库.

  • 关于这个的问题:为什么你在这个例子中使用存储库?这是一个诚实的问题 - 对我而言,看起来你正在使用存储库,但至少在这个例子中,存储库并没有真正做任何事情,只提供与Eloquent相同的界面,最后你仍然依赖于Eloquent,因为你的服务类直接使用eloquent(``$ studio-> movies() - > associate($ movie);```). (15认同)
  • 很好写,虽然我不确定你为什么要将movieRepository注入MovieController,因为控制器不应该直接对存储库做任何事情,你的postCreate方法也不是使用movieRepository,所以我假设你错误地把它留在了? (11认同)
  • 这个评论是迄今为止更清晰,更具可扩展性和可维护性的方法. (8认同)
  • 就像@PauloFreitas所说的那样,看看你如何处理验证部分会很有趣,我也会对异常部分感兴趣(你是否使用异常,事件,或者你只是处理这个,因为你似乎在你的建议中控制器通过服务中的布尔返回?).谢谢! (6认同)
  • +1!这对我很有帮助,谢谢你与我们分享!想知道如何设法验证服务内部的东西,如果可能的话,你能简单解释一下你做了什么吗?还是非常感谢!:) (4认同)
  • 很好,但不应该Repository返回雄辩的模型依赖结果(即:数组)?那么为什么在MovieService中,有一些代码直接操作滔滔不绝的模型($ director-> movies() - > associate($ movie);). (4认同)

fid*_*per 69

请记住,你在征求意见:D

这是我的:

TL; DR:是的,没关系.

你做的不错!

我经常做你正在做的事情,发现它很有效.

但是,我经常围绕业务逻辑组织存储库,而不是使用每个表的repo.这很有用,因为它是一个以应用程序如何解决"业务问题"为中心的观点.

课程是一个"实体",具有属性(标题,身份等)甚至其他实体(分配,具有自己的属性和可能的​​实体).

您的"课程"存储库应该能够返回课程和课程的属性/作业(包括作业).

幸运的是,你可以用Eloquent来实现这一目标.

(我经常最终得到每个表的存储库,但是一些存储库比其他存储库使用得多,因此有更多的方法.你的"课程"存储库可能比你的Assignments存储库更全面,例如,如果你的应用程序中心更多地围绕课程,而不是关于课程的作业集合.

棘手的部分

我经常在我的存储库中使用存储库来执行一些数据库操作.

任何实现Eloquent以处理数据的存储库都可能返回Eloquent模型.在这种情况下,如果您的课程模型使用内置关系来检索或保存分配(或任何其他用例),那就没问题了.我们的"实施"是围绕Eloquent建立的.

从实际的角度来看,这是有道理的.我们不太可能将数据源更改为Eloquent无法处理的内容(对于非sql数据源).

运筹学和管理学

至少对我而言,这种设置中最棘手的部分是确定Eloquent是否真的帮助或伤害了我们.ORM是一个棘手的主题,因为虽然它们从实际的角度帮助我们,但它们也将您的"业务逻辑实体"代码与执行数据检索的代码相结合.

无论您的存储库的责任是实际处理数据还是处理实体(业务域实体)的检索/更新,这都会让您感到困惑.

此外,它们充当您传递给视图的对象.如果您以后不得不在存储库中使用Eloquent模型,则需要确保传递给视图的变量以相同的方式运行或使用相同的方法,否则更改数据源将会更改视图,你已经(部分)失去了将逻辑抽象到存储库的目的 - 项目的可维护性降低了.

无论如何,这些都是一些不完整的想法.如上所述,他们只是我的观点,这恰好是去年在Ruby Midwest 阅读领域驱动设计和观看"叔叔鲍勃"主题演讲等视频的结果.

  • 继续使用延迟加载的模型.如果您跳过使用Eloquent,您可以使真正的域模型像这样工作.但是说真的,你是否会*切换出Eloquent?一分钱,一磅!(不要过分试图坚持"规则"!我一直打破我的全部). (5认同)
  • @fideloper我觉得如果我使用存储库,我会失去Eloquent提供的整个ORM之美.通过我的存储库方法检索帐户对象`$ a = $ this-> account-> getById(1)`我不能简单地链接像`$ a-> getActiveUsers()`这样的方法.好吧,我可以使用`$ a-> users - > ...`,但后来我返回了一个Eloquent集合而没有stdClass对象,并再次与Eloquent绑定.这是什么解决方案?在用户存储库中声明另一个方法,如`$ user-> getActiveUsersByAccount($ a-> id);`?很想听听你如何解决这个问题...... (4认同)

Odd*_*man 5

我喜欢从我的代码正在做什么以及它负责什么的角度来考虑它,而不是"正确或错误".这就是我分担责任的方式:

  • 控制器是HTTP层并将请求路由到底层api(也称为控制流)
  • 模型表示数据库模式,并告诉应用程序数据是什么样的,它可能具有什么关系,以及可能需要的任何全局属性(例如返回连接的名字和姓氏的名称方法)
  • 存储库代表更复杂的查询和与模型的交互(我不对模型方法进行任何查询).
  • 搜索引擎 - 帮助我构建复杂搜索查询的类.

考虑到这一点,每次使用存储库都是有意义的(无论您是否创建interfaces.etc.都是另一个主题).我喜欢这种方法,因为这意味着当我需要做某些工作时,我确切地知道要去哪里.

我还倾向于构建一个基本存储库,通常是一个定义主要默认值的抽象类 - 基本上是CRUD操作,然后每个子项可以根据需要扩展和添加方法,或者重载默认值.注入模型也有助于此模式非常强大.


小智 5

将存储库视为数据的一致文件柜(而不仅仅是您的ORM).我们的想法是,您希望以一致易用的API获取数据.

如果你发现自己只是在做Model :: all(),Model :: find(),Model :: create(),你可能不会从抽象存储库中获益.另一方面,如果您想为查询或操作执行更多业务逻辑,您可能需要创建一个存储库,以便更轻松地使用API​​来处理数据.

我想你问的是,存储库是否是处理连接相关模型所需的一些更详细的语法的最佳方法.根据具体情况,我可以做一些事情:

  1. 从父模型(一个或一个多个)挂起一个新的子模型,我会向子存储库添加一个类似的方法createWithParent($attributes, $parentModelInstance),这只会添加$parentModelInstance->idparent_id属性的字段中并调用create.

  2. 我实际上在模型上创建函数以便运行$ instance-> attachChild($ childInstance).请注意,这需要两侧的现有元素.

  3. 在一次运行中创建相关模型,我创建了一些我称之为网关的东西(它可能与Fowler的定义有点不同).我可以调用$ gateway-> createParentAndChild($ parentAttributes,$ childAttributes)而不是一堆可能会改变的逻辑,或者会使我在控制器或命令中的逻辑复杂化.