在 Laravel 中管理关系,遵循存储库模式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18817615/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Managing relationships in Laravel, adhering to the repository pattern
提问by ehp
While creating an app in Laravel 4 after reading T. Otwell's book on good design patterns in Laravel I found myself creating repositories for every table on the application.
在阅读了 T. Otwell 关于 Laravel 良好设计模式的书后,在 Laravel 4 中创建应用程序时,我发现自己为应用程序中的每个表创建了存储库。
I ended up with the following table structure:
我最终得到了以下表结构:
- Students: id, name
- Courses: id, name, teacher_id
- Teachers: id, name
- Assignments: id, name, course_id
- Scores (acts as a pivot between students and assignments): student_id, assignment_id, scores
- 学生:身、姓名
- 课程:id、name、teacher_id
- 教师:身、姓名
- 作业:id、name、course_id
- 分数(作为学生和作业之间的支点):student_id、assignment_id、score
I have repository classes with find, create, update and delete methods for all of these tables. Each repository has an Eloquent model which interacts with the database. Relationships are defined in the model per Laravel's documentation: http://laravel.com/docs/eloquent#relationships.
我有所有这些表的查找、创建、更新和删除方法的存储库类。每个存储库都有一个与数据库交互的 Eloquent 模型。关系在每个 Laravel 文档的模型中定义:http://laravel.com/docs/eloquent#relationships 。
When creating a new course, all I do is calling the create method on the Course Repository. That course has assignments, so when creating one, I also want to create an entry in the score's table for each student in the course. I do this through the Assignment Repository. This implies the assignment repository communicates with two Eloquent models, with the Assignment and Student model.
创建新课程时,我所做的就是调用课程存储库上的 create 方法。该课程有作业,因此在创建作业时,我还想为课程中的每个学生在分数表中创建一个条目。我通过作业存储库执行此操作。这意味着作业存储库与两个 Eloquent 模型进行通信,分别是作业和学生模型。
My question is: as this app will probably grow in size and more relationships will be introduced, is it good practice to communicate with different Eloquent models in repositories or should this be done using other repositories instead (I mean calling other repositories from the Assignment repository) or should it be done in the Eloquent models all together?
我的问题是:由于这个应用程序的规模可能会增长并且会引入更多的关系,因此在存储库中与不同的 Eloquent 模型进行通信是一种好习惯,还是应该使用其他存储库来完成(我的意思是从 Assignment 存储库调用其他存储库) ) 还是应该一起在 Eloquent 模型中完成?
Also, is it good practice to use the scores table as a pivot between assignments and students or should it be done somewhere else?
此外,使用分数表作为作业和学生之间的支点是一种好习惯还是应该在其他地方完成?
采纳答案by fideloper
Keep in mind you're asking for opinions :D
请记住,您是在征求意见:D
Here's mine:
这是我的:
TL;DR: Yes, that's fine.
TL;DR:是的,那很好。
You're doing fine!
你做的不错!
I do exactly what you are doing often and find it works great.
我做的正是你经常做的事情,并且发现它非常有效。
I often, however, organize repositories around business logic instead of having a repo-per-table. This is useful as it's a point of view centered around how your application should solve your "business problem".
然而,我经常围绕业务逻辑组织存储库,而不是每个表都有一个存储库。这很有用,因为它是一个以您的应用程序应该如何解决您的“业务问题”为中心的观点。
A Course is a "entity", with attributes (title, id, etc) and even other entities (Assignments, which have their own attributes and possibly entities).
课程是一个“实体”,具有属性(标题、ID 等)甚至其他实体(作业,它们具有自己的属性和可能的实体)。
Your "Course" repository should be able to return a Course and the Courses' attributes/Assignments (including Assignment).
您的“课程”存储库应该能够返回课程和课程的属性/作业(包括作业)。
You can accomplish that with Eloquent, luckily.
幸运的是,您可以使用 Eloquent 实现这一点。
(I often end up with a repository per table, but some repositories are used much more than others, and so have many more methods. Your "courses" repository may be much more full-featured than your Assignments repository, for instance, if your application centers more around Courses and less about a Courses' collection of Assignments).
(我经常为每个表创建一个存储库,但有些存储库比其他存储库使用得更多,因此有更多的方法。例如,您的“课程”存储库可能比您的作业存储库功能更全面,例如,如果您应用程序更多地围绕课程而不是课程的作业集合)。
The tricky part
棘手的部分
I often use repositories inside of my repositories in order to do some database actions.
我经常在我的存储库中使用存储库来执行一些数据库操作。
Any repository which implements Eloquent in order to handle data will likely return Eloquent models. In that light, it's fine if your Course model uses built-in relationships in order to retrieve or save Assignments (or any other use case). Our "implementation" is built around Eloquent.
任何实现 Eloquent 以处理数据的存储库都可能返回 Eloquent 模型。从这个角度来看,如果您的 Course 模型使用内置关系来检索或保存作业(或任何其他用例),那很好。我们的“实现”是围绕 Eloquent 构建的。
From a practical point of view, this makes sense. We're unlikely to change data sources to something Eloquent can't handle (to a non-sql data source).
从实践的角度来看,这是有道理的。我们不太可能将数据源更改为 Eloquent 无法处理的(非 sql 数据源)。
ORMS
资源管理系统
The trickiest part of this setup, for me at least, is determing if Eloquent is actually helping or harming us. ORMs are a tricky subject, because while they help us greatly from a practical point of view, they also couple your "business logic entities" code with the code doing the data retrieval.
至少对我来说,这个设置中最棘手的部分是确定 Eloquent 是否真的在帮助或伤害我们。ORM 是一个棘手的主题,因为虽然它们从实践的角度对我们有很大帮助,但它们还将您的“业务逻辑实体”代码与执行数据检索的代码耦合在一起。
This sort of muddles up whether your repository's responsibility is actually for handling data or handling the retrieval / update of entities (business domain entities).
这种混淆了您的存储库的责任实际上是处理数据还是处理实体(业务域实体)的检索/更新。
Furthermore, they act as the very objects you pass to your views. If you later have to get away from using Eloquent models in a repository, you'll need to make sure the variables passed to your views behave in the same way or have the same methods available, otherwise changing your data sources will roll into changing your views, and you've (partially) lost the purpose of abstracting your logic out to repositories in the first place - the maintainability of your project goes down as.
此外,它们充当传递给视图的对象。如果您以后不得不在存储库中使用 Eloquent 模型,则需要确保传递给视图的变量以相同的方式运行或具有相同的可用方法,否则更改数据源将导致更改您的数据源视图,并且您(部分)失去了将逻辑抽象到存储库的目的 - 您的项目的可维护性下降为。
Anyway, these are somewhat incomplete thoughts. They are, as stated, merely my opinion, which happens to be the result of reading Domain Driven Designand watching videos like "uncle bob's" keynoteat Ruby Midwest within the last year.
总之,这些都是有些不完整的想法。如前所述,它们只是我的意见,这恰好是去年在 Ruby Midwest上阅读领域驱动设计和观看“鲍勃叔叔”主题演讲等视频的结果。
回答by Kyle Noland
I am finishing up a large project using Laravel 4 and had to answer all of the questions you are asking right now. After reading all of the available Laravel books over at Leanpub, and tons of Googling, I came up with the following structure.
我正在使用 Laravel 4 完成一个大型项目,并且必须回答您现在提出的所有问题。在阅读了 Leanpub 上所有可用的 Laravel 书籍和大量谷歌搜索后,我想出了以下结构。
- One Eloquent Model class per datable table
- One Repository class per Eloquent Model
- A Service class that may communicate between multiple Repository classes.
- 每个数据表一个 Eloquent Model 类
- 每个 Eloquent 模型一个 Repository 类
- 可以在多个 Repository 类之间进行通信的 Service 类。
So let's say I'm building a movie database. I would have at least the following following Eloquent Model classes:
假设我正在构建一个电影数据库。我至少会有以下 Eloquent Model 类:
- Movie
- Studio
- Director
- Actor
- Review
- 电影
- 工作室
- 导向器
- 演员
A repository class would encapsulate each Eloquent Model class and be responsible for CRUD operations on the database. The repository classes might look like this:
存储库类将封装每个 Eloquent Model 类并负责对数据库的 CRUD 操作。存储库类可能如下所示:
- MovieRepository
- StudioRepository
- DirectorRepository
- ActorRepository
- ReviewRepository
- 电影资料库
- 工作室资料库
- DirectorRepository
- 演员库
- 评论库
Each repository class would extend a BaseRepository class which implements the following interface:
每个存储库类将扩展一个 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);
}
A Service class is used to glue multiple repositories together and contains the real "business logic" of the application. Controllers onlycommunicate with Service classes for Create, Update and Delete actions.
Service 类用于将多个存储库粘合在一起,并包含应用程序的真正“业务逻辑”。控制器仅与用于创建、更新和删除操作的服务类通信。
So when I want to create a new Movie record in the database, my MovieController class might have the following methods:
所以当我想在数据库中创建一个新的电影记录时,我的 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.
}
It's up to you to determine how you POST data to your controllers, but let's say the data returned by Input::all() in the postCreate() method looks something like this:
由您决定如何将数据 POST 到控制器,但假设 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'
)
Since the MovieRepository shouldn't know how to create Actor, Director or Studio records in the database, we'll use our MovieService class, which might look something like this:
由于 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;
}
So what we're left with is a nice, sensible separation of concerns. Repositories are only aware of the Eloquent model they insert and retrieve from the database. Controllers don't care about repositories, they just hand off the data they collect from the user and pass it to the appropriate service. The service doesn't care howthe data it receives is saved to the database, it just hands off the relevant data it was given by the controller to the appropriate repositories.
所以我们剩下的是一个很好的、合理的关注点分离。存储库只知道它们从数据库中插入和检索的 Eloquent 模型。控制器不关心存储库,他们只是传递他们从用户那里收集的数据并将其传递给适当的服务。该服务并不关心它接收到的数据如何保存到数据库中,它只是将控制器提供的相关数据交给适当的存储库。
回答by Oddman
I like to think of it in terms of what my code is doing and what it is responsible for, rather than "right or wrong". This is how I break apart my responsibilities:
我喜欢从我的代码在做什么以及它负责什么的角度来考虑它,而不是“对或错”。这就是我分解我的职责的方式:
- Controllers are the HTTP layer and route requests through to the underlying apis (aka, it controls the flow)
- Models represent the database schema, and tell the application what the data looks like, what relationships it may have, as well as any global attributes that may be necessary (such as a name method for returning a concatenated first and last name)
- Repositories represent the more complex queries and interactions with the models (I don't do any queries on model methods).
- Search engines - classes that help me build complex search queries.
- 控制器是 HTTP 层,并将请求路由到底层 apis(也就是,它控制流)
- 模型表示数据库模式,并告诉应用程序数据是什么样的,它可能有什么关系,以及可能需要的任何全局属性(例如用于返回串联的名字和姓氏的名称方法)
- 存储库代表更复杂的查询和与模型的交互(我不对模型方法进行任何查询)。
- 搜索引擎 - 帮助我构建复杂搜索查询的类。
With this in mind, it makes sense every time to use a repository (whether you create interfaces.etc. is a whole other topic). I like this approach, because it means I know exactly where to go when I'm needing to do certain work.
考虑到这一点,每次使用存储库都是有意义的(是否创建 interfaces.etc. 是另一个话题)。我喜欢这种方法,因为这意味着当我需要做某些工作时,我确切地知道该去哪里。
I also tend to build a base repository, usually an abstract class which defines the main defaults - basically CRUD operations, and then each child can just extend and add methods as necessary, or overload the defaults. Injecting your model also helps this pattern to be quite robust.
我还倾向于构建一个基本存储库,通常是一个定义主要默认值的抽象类 - 基本上是 CRUD 操作,然后每个子级都可以根据需要扩展和添加方法,或者重载默认值。注入你的模型也有助于这种模式非常健壮。
回答by Ryan Tablada
Think of Repositories as a consistent filing cabinet of your data (not just your ORMs). The idea is that you want to grab data in a consistent simple to use API.
将存储库视为数据的一致文件柜(不仅仅是您的 ORM)。这个想法是您希望以一致且易于使用的 API 获取数据。
If you find yourself just doing Model::all(), Model::find(), Model::create() you probably won't benefit much from abstracting away a repository. On the other hand, if you want to do a bit more business logic to your queries or actions, you may want to create a repository to make an easier to use API for dealing with data.
如果您发现自己只是在执行 Model::all()、Model::find()、Model::create(),那么您可能不会从抽象化存储库中受益太多。另一方面,如果您想对查询或操作执行更多业务逻辑,您可能需要创建一个存储库,以便更轻松地使用 API 来处理数据。
I think you were asking if a repository would be the best way to deal with some of the more verbose syntax required to connect related models. Depending on the situation, there are a few things I may do:
我想您是在问存储库是否是处理连接相关模型所需的一些更冗长的语法的最佳方式。根据情况,我可能会做一些事情:
Hanging a new child model off of a parent model (one-one or one-many), I would add a method to the child repository something like
createWithParent($attributes, $parentModelInstance)
and this would just add the$parentModelInstance->id
into theparent_id
field of the attributes and call create.Attaching a many-many relationship, I actually create functions on the models so that I can run $instance->attachChild($childInstance). Note that this requires existing elements on both side.
Creating related models in one run, I create something that I call a Gateway (it may be a bit off from Fowler's definitions). Way I can call $gateway->createParentAndChild($parentAttributes, $childAttributes) instead of a bunch of logic that may change or that would complicate the logic that I have in a controller or command.
从父模型(一对一或一对多)中挂起一个新的子模型,我会向子存储库添加一个类似的方法
createWithParent($attributes, $parentModelInstance)
,这只会将其添加$parentModelInstance->id
到parent_id
属性的字段中并调用 create。附加多对多关系,我实际上在模型上创建函数,以便我可以运行 $instance->attachChild($childInstance)。请注意,这需要双方现有的元素。
在一次运行中创建相关模型,我创建了一些我称之为网关的东西(它可能与 Fowler 的定义有点不同)。我可以调用 $gateway->createParentAndChild($parentAttributes, $childAttributes) 的方式,而不是一堆可能会改变或会使我在控制器或命令中的逻辑复杂化的逻辑。