pipe理Laravel中的关系,坚持存储库模式
在阅读T. Otwell关于Laravel良好devise模式的书后,我在Laravel 4中创build了一个应用程序,结果发现自己为应用程序中的每个表创build了一个存储库。
我结束了以下表结构:
- 学生:身份证,姓名
- 课程:id,名字,teacher_id
- 教师:身份证,姓名
- 作业:id,name,course_id
- 分数(作为学生和作业之间的枢纽):student_id,assignment_id,分数
我有存储库类查找,创build,更新和删除所有这些表的方法。 每个存储库都有一个与数据库交互的Eloquent模型。 关系在每个Laravel的文档模型中定义: http ://laravel.com/docs/eloquent#relationships。
创build新课程时,我所做的只是在课程资料库中调用create方法。 那个课程有作业,所以当创build一个时,我也想为每个学生在课程中创build一个分数表的入口。 我通过Assignment Repository来完成。 这意味着分配库与两个Eloquent模型进行通信,使用Assignment和Student模型。
我的问题是:由于这个应用程序的规模可能会增加,更多的关系将被引入,是否是与存储库中的不同Eloquent模型进行通信的良好实践,或者应该使用其他存储库来完成(我的意思是从分配存储库调用其他存储库)还是应该一起在雄辩模型中完成?
另外,将分数表作为作业和学生之间的关键是否是好的做法,还是应该在其他地方做?
请记住你在征求意见:D
这是我的:
TL; DR:是的,没关系。
你做的不错!
我经常做你正在做的事情,并发现它很好。
然而,我经常把业务逻辑组织成仓库,而不是每个表都有一个仓库。 这是非常有用的,因为它是以您的应用程序如何解决您的“业务问题”为中心的观点。
课程是一个“实体”,具有属性(标题,ID等),甚至其他实体(分配,它们有自己的属性和可能的实体)。
您的“课程”存储库应该能够返回课程和课程的属性/作业(包括作业)。
幸运的是,你可以用雄辩来完成。
(我经常最终为每个表格创build一个仓库,但是一些仓库的使用比其他仓库更多,所以还有更多的方法。比如你的Assignments仓库,你的“courses”仓库可能function更全面,比如你的应用程序中心更多地围绕课程而不是关于课程的作业集合)。
棘手的部分
我经常在我的仓库中使用仓库来执行一些数据库操作。
任何实施Eloquent来处理数据的仓库都可能会返回Eloquent模型。 有鉴于此,如果您的课程模型使用内置关系来检索或保存作业(或任何其他用例),那就没问题了。 我们的“实施”是build立在雄辩的基础上的。
从实际的angular度来看,这是有道理的。 我们不太可能将数据源更改为雄辩不能处理(对于非sql数据源)。
运筹学和pipe理学
这个设置中最棘手的部分,至less对我来说,决定了雄辩是否真的帮助或伤害了我们。 ORM是一个棘手的问题,因为尽pipe他们从实际的angular度帮助我们,他们还将“业务逻辑实体”代码与执行数据检索的代码耦合在一起。
这种混淆了你的存储库的责任究竟是处理数据还是处理实体(业务域实体)的检索/更新。
而且,它们是你传递给你的观点的对象。 如果您以后不得不在存储库中使用Eloquent模型,则需要确保传递给您的视图的variables具有相同的行为方式或具有相同的方法,否则更改数据源将会改变意见,而且你已经(部分)失去了首先将你的逻辑抽象出来的目的 – 你的项目的可维护性就会降低。
无论如何,这些都是不完整的想法。 如上所述,他们只是我的看法,这恰好是阅读Domain Driven Design并在去年在Ruby Midwest观看像“叔叔鲍勃”主题演讲的结果 。
我正在使用Laravel 4完成一个大型项目,并且必须回答您现在要问的所有问题。 在阅读了Leanpub上所有的Laravel书籍和大量的Googlesearch之后,我想出了以下结构。
- 每个可数表的一个雄辩模型类
- 一个存储库类每个雄辩模型
- 可以在多个存储库类之间进行通信的服务类。
所以我们说我正在build立一个电影数据库。 我会至less有以下的雄辩模型类:
- 电影
- 工作室
- 导向器
- 演员
- 评论
存储库类将封装每个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); }
Service类用于将多个存储库粘合在一起,并包含应用程序的真实“业务逻辑”。 控制器只与“创build,更新和删除”操作的“服务”类进行通信。
所以,当我想要在数据库中创build一个新的电影logging时,我的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不应该知道如何在数据库中创buildActor,Director或Studiologging,所以我们将使用我们的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模型。 控制器不关心仓库,他们只是交出他们从用户那里收集的数据,并将其传递给相应的服务。 该服务并不关心它接收到的数据如何保存到数据库,它只是将控制器给出的相关数据交给相应的存储库。
我喜欢从我的代码正在做什么以及它负责什么的angular度来思考,而不是“正确或错误”。 这是我如何分解我的责任:
- 控制器是HTTP层,并通过路由请求到底层的apis(aka,它控制stream)
- 模型表示数据库模式,并告诉应用程序什么样的数据,它可能有什么样的关系,以及任何可能必要的全局属性(例如返回连接的姓和名的名称方法)
- 存储库代表更复杂的查询和与模型的交互(我不会对模型方法进行任何查询)。
- search引擎 – 帮助我构build复杂search查询的类。
考虑到这一点,每次使用存储库(无论您是否创buildinterfaces.etc。都是一个完整的其他主题)都是有意义的。 我喜欢这种方法,因为这意味着当我需要做某些工作时,我确切知道该去哪里。
我也倾向于build立一个基础知识库,通常是一个定义主要缺省的抽象类 – 基本上是CRUD操作,然后每个孩子可以根据需要扩展和添加方法,或者重载默认值。 注入模型也有助于这种模式相当强大。
将存储库视为数据的一致文件柜(不仅仅是您的ORM)。 这个想法是,你想抓住一个简单的API来使用数据。
如果你发现自己只是做Model :: all(),Model :: find(),Model :: create(),那么抽象出一个版本库可能不会带来太多好处。 另一方面,如果您想为查询或操作做更多的业务逻辑,则可能需要创build一个存储库,以便使用API处理数据。
我想你是在问一个存储库是否是处理连接相关模型所需的一些更详细的语法的最好方法。 根据情况,我可以做一些事情:
-
将一个新的子模型挂在父模型(一个或一个)上,我会添加一个方法到子库中,像
createWithParent($attributes, $parentModelInstance)
,这只会添加$parentModelInstance->id
进入属性的parent_id
字段并调用create。 -
附加一个多关系,我实际上在模型上创build函数,以便我可以运行$ instance-> attachChild($ childInstance)。 请注意,这需要双方的现有元素。
-
在一次运行中创build相关的模型,我创build了一个我称之为网关的东西(这可能与福勒的定义有点相似)。 我可以调用$ gateway-> createParentAndChild($ parentAttributes,$ childAttributes),而不是一堆可能会改变的逻辑,或者会使控制器或命令中的逻辑复杂化。