在 Laravel 管理关系,坚持存储库模式

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

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

  • 学生: 身份证,姓名
  • 课程: id,name,acher _ id
  • 老师: 身份证,姓名
  • 作业: id,name,course _ id
  • 得分(作为学生和作业之间的轴心) : Student _ id,庭作业 id,得分

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

当创建一个新的课程时,我所做的就是调用 CourseRepository 上的 create 方法。这门课有作业,所以在创建作业的时候,我还想在分数表中为这门课的每个学生创建一个条目。我通过分配存储库完成这项工作。这意味着赋值存储库与两个 Eloquent 模型通信,分配和学生模型。

我的问题是: 由于这个应用程序的规模可能会增长,更多的关系将被引入,这是一个良好的做法,通信与不同的雄辩模型在仓库或应该这样做,而是使用其他仓库(我的意思是调用其他仓库从分配仓库)或应该在雄辩模型所有一起完成?

此外,使用分数表作为作业和学生之间的轴心是否是一种好的做法,还是应该在其他地方使用?

41252 次浏览

记住,你是在征求意见

这是我的:

是的,没问题。

你做得很好!

我经常做你经常做的事情,并且发现它很有效。

然而,我经常围绕业务逻辑组织存储库,而不是每个表一个回购。这很有用,因为这是一个围绕应用程序应该如何解决“业务问题”的观点。

课程是一个“实体”,有属性(标题、标识等) ,甚至还有其他实体(分配,它们有自己的属性,可能还有实体)。

你的「课程」资料库应能显示课程及其属性/习作(包括习作)。

幸运的是,你可以通过雄辩来实现这一点。

(我经常在每个表中使用一个存储库,但是有些存储库的使用要比其他存储库多得多,因此有更多的方法。例如,如果您的应用程序更多地围绕“课程”而不是围绕“课程作业集合”,那么您的“课程”存储库可能比“作业存储库”功能更全面。

棘手的部分

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

任何实现 Eloquent 以处理数据的存储库都可能返回 Eloquent 模型。在这种情况下,如果您的课程模型使用内置关系来检索或保存作业(或任何其他用例) ,那么这是可以的。我们的“实施”是围绕雄辩而建立的。

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

虫子

至少对我来说,这个设置中最棘手的部分是确定雄辩是真的在帮助我们还是在伤害我们。ORM 是一个棘手的主题,因为尽管它们从实用的角度对我们有很大的帮助,但它们也会将“业务逻辑实体”代码与进行数据检索的代码结合起来。

这种情况会混淆存储库的责任实际上是处理数据还是处理实体(业务域实体)的检索/更新。

此外,它们还充当传递给视图的对象。如果你以后不得不放弃在存储库中使用 Eloquent 模型,你需要确保传递给你的视图的变量以相同的方式运行,或者有相同的方法可用,否则改变你的数据源就会变成改变你的视图,并且你已经(部分地)失去了首先将你的逻辑抽象到存储库的目的-你的项目的可维护性下降为。

不管怎么说,这些都是些不完整的想法。如上所述,它们仅仅是我的观点,而这恰好是去年在 Ruby Midwest 阅读 领域驱动设计和观看像 “ Bob 叔叔”的主题演讲这样的视频的结果。

我喜欢从我的代码正在做什么和它负责什么的角度来考虑它,而不是“对或错”。这就是我如何分开我的责任:

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

记住这一点,每次使用存储库(无论您是否创建接口等)都是有意义的。是一个完全不同的话题)。我喜欢这种方法,因为它意味着当我需要做某些工作时,我确切地知道去哪里。

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

可以将仓库看作是数据(而不仅仅是 ORM)的一个一致的文件柜。这个想法是希望以一致的简单方式获取数据,以便使用 API。

如果您发现自己只是在处理 Model: : all ()、 Model: : find ()、 Model: : create () ,那么您可能不会从抽象存储库中获得太多好处。另一方面,如果希望对查询或操作执行更多的业务逻辑,则可能需要创建一个存储库,以便更容易地使用 API 处理数据。

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

  1. 将一个新的子模型挂起在父模型(一个或一个多个)之外,我将向子存储库添加一个类似于 createWithParent($attributes, $parentModelInstance)的方法,这只是将 $parentModelInstance->id添加到属性的 parent_id字段中并调用 create。

  2. 附加一个多-多关系,我实际上在模型上创建函数,这样我就可以运行 $instance-> attachChild ($child Instance)。注意,这需要两边都有现有的元素。

  3. 在一次运行中创建相关的模型,我创建一个我称为 Gateway 的东西(它可能有点偏离 Fowler 的定义)。我可以调用 $Gate-> createParentAndChild ($ParentAttritribute,$children Attritribute)来代替一堆可能会改变或者会使我在控制器或命令中的逻辑复杂化的逻辑。

我完成了一个大型项目使用 Laravel4,必须回答所有的问题,你正在提出的权利。在阅读了 Leanpub 上所有可用的 Laravel 书籍和大量的谷歌搜索之后,我想出了以下结构。

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

假设我正在构建一个电影数据库,那么我至少会有以下的 Eloquent Model 类:

  • 电影
  • 工作室
  • 局长
  • 演员
  • 复习

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

  • 电影资料库
  • StudioRepository
  • 主管资料库
  • 演员仓库
  • 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);
}

服务类用于将多个存储库粘合在一起,并包含应用程序的真正“业务逻辑”。控制器 只有与用于创建、更新和删除操作的服务类通信。

因此,当我想在数据库中创建一个新的 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.
}

由您决定如何将数据发送到控制器,但是假设 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'
)

由于 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;
}

所以我们剩下的就是一个漂亮明智的关注点分离。存储库只知道它们从数据库中插入和检索的 Eloquent 模型。控制器不关心存储库,它们只是将从用户那里收集的数据传递给适当的服务。服务并不关心 怎么做接收到的数据是否被保存到数据库中,它只是将控制器提供给相应存储库的相关数据交给相应的存储库。