Управление отношениями в Laravel, придерживаясь шаблона хранилища

99

При создании приложения в Laravel 4 после прочтения книги Т. Отуэлла о хороших шаблонах дизайна в Laravel я обнаружил, что создаю репозитории для каждой таблицы приложения.

В итоге я получил следующую структуру таблицы:

  • Учащиеся: id, name
  • Курсы: id, name, teacher_id
  • Учителя: id, name
  • Назначения: id, name, course_id
  • Баллы (действует как стержень между учениками и заданиями): student_id, assign_id, score

У меня есть классы репозитория с методами поиска, создания, обновления и удаления для всех этих таблиц. Каждый репозиторий имеет модель Eloquent, которая взаимодействует с базой данных. Отношения определены в документации по каждой Laravel: http://laravel.com/docs/eloquent#relationships.

При создании нового курса все, что я делаю, это вызов метода create в репозитории курсов. У этого курса есть задания, поэтому при его создании я также хочу создать запись в таблице баллов для каждого учащегося курса. Я делаю это через репозиторий присвоений. Это означает, что репозиторий присваивания связывается с двумя черными моделями с помощью модели Assignment and Student.

Мой вопрос в том, что, поскольку это приложение, вероятно, будет расти по размеру, и будет добавлено больше отношений, хорошо ли использовать связь с различными моделями Eloquent в репозиториях, или это должно быть сделано с использованием других репозиториев (я имею в виду вызов других репозиториев из репозиторий Assignment), или это должно быть сделано в моделях Eloquent вместе?

Кроме того, хорошо ли использовать таблицу оценок в качестве точки между назначениями и учениками или это должно быть сделано где-то еще?

Теги:
eloquent
oop
repository-pattern
laravel-4

4 ответа

62
Лучший ответ

Имейте в виду, что вы просите мнения: D

Здесь моя:

TL; DR: Да, это нормально.

У тебя все в порядке!

Я делаю то, что вы часто делаете, и обнаружите, что он отлично работает.

Я часто, однако, организую репозитории вокруг бизнес-логики вместо того, чтобы иметь репо-таблицу. Это полезно, поскольку точка зрения сосредоточена на том, как ваше приложение должно решить вашу "бизнес-проблему".

Курс - это "сущность", с атрибутами (название, идентификатор и т.д.) и даже другими объектами (присваивания, которые имеют свои собственные атрибуты и, возможно, сущности).

Ваш репозиторий "Курс" должен иметь возможность возвращать атрибуты "Курс" и "Курсы" / "Задания" (включая "Назначение" ).

Вы можете добиться этого с помощью "Красноречивого", к счастью.

(я часто получаю репозиторий на таблицу, но некоторые репозитории используются гораздо больше, чем другие, и, следовательно, имеют много других методов. Репозиторий ваших курсов может быть гораздо более полнофункциональным, чем ваш репозиторий Assignments, например, если ваше приложение больше ориентируется на курсы, а меньше - на подборку заданий курсов).

Сложная часть

Я часто использую репозитории внутри своих репозиториев, чтобы выполнять некоторые действия с базой данных.

Любой репозиторий, который реализует Eloquent для обработки данных, скорее всего, вернет модели Eloquent. В этом свете это прекрасно, если ваша модель курса использует встроенные отношения для извлечения или сохранения заданий (или любого другого варианта использования). Наша "реализация" построена вокруг Eloquent.

С практической точки зрения это имеет смысл. Мы вряд ли сможем изменить источники данных на то, что не может обработать Eloquent (для источника данных, отличного от sql).

ORMS

Самая сложная часть этой установки, по крайней мере для меня, определяет, действительно ли "Красноречивый" помогает нам или вредит нам. ORM - сложный вопрос, потому что, хотя они очень помогают нам с практической точки зрения, они также связывают ваш код сущности бизнес-логики с кодом, выполняющим поиск данных.

Этот тип запутывает, действительно ли ответственность за репозиторий за обработку данных или обработку поиска/обновления объектов (объектов бизнес-домена).

Кроме того, они действуют как те самые объекты, которые вы передаете своим представлениям. Если позже вам придется уйти от использования моделей Eloquent в репозитории, вам необходимо убедиться, что переменные, переданные вашим представлениям, ведут себя одинаково или имеют одни и те же методы, в противном случае изменение источников данных приведет к изменению вашего просмотров, и вы (частично) потеряли цель абстрагирования своей логики с репозиториями в первую очередь - ремонтопригодность вашего проекта снижается как.

Во всяком случае, это несколько неполные мысли. Они, как сказано, просто мое мнение, которое, оказывается, является результатом чтения Domain Driven Design и просмотра видеороликов, таких как "дядя Боб," основной доклад" на Ruby Midwest в течение прошлого года.

  • 1
    По вашему мнению, будет ли хорошей альтернативой, если репозитории возвращают объекты передачи данных вместо красноречивых объектов? Конечно, это подразумевало бы дополнительное преобразование из eloquent в dto, но таким образом, по крайней мере, вы изолируете свои контроллеры / представления от текущей реализации orm.
  • 1
    Я немного поэкспериментировал с этим и обнаружил, что это немного непрактично. При этом, мне нравится эта идея в резюме. Тем не менее, объекты Collection базы данных Illuminate действуют так же, как массивы, а объекты Model действуют так же, как объекты StdClass, так что мы можем, практически говоря, придерживаться Eloquent и при необходимости использовать массивы / объекты в будущем.
Показать ещё 7 комментариев
186

Я заканчиваю большой проект с использованием Laravel 4 и должен отвечать на все вопросы, которые вы задаете прямо сейчас. Прочитав все доступные книги Laravel в Leanpub и тонны Googling, я придумал следующую структуру.

  • Один класс Eloquent Model для каждой доступной таблицы
  • Один класс репозитория для Eloquent Model
  • Класс службы, который может связываться между несколькими классами репозитория.

Итак, скажем, я создаю базу данных фильмов. У меня были бы, по крайней мере, следующие классы класса Eloquent Model:

  • Фильм
  • Студия
  • директор
  • Actor
  • Обзор

Класс репозитория будет инкапсулировать каждый класс 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 для создания, обновления и удаления.

Поэтому, когда я хочу создать новую запись 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.
}

Это зависит от вас, чтобы определить, как вы используете POST-данные для своих контроллеров, но пусть говорят, что данные, возвращаемые Input:: all() в методе postCreate(), выглядят примерно так:

$data = array(
    'movie' => array(
        'title'    => 'Iron Eagle',
        'year'     => '1986',
        'synopsis' => 'When Doug\ 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\ 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, которую они вставляют и извлекают из базы данных. Контроллерам не нужны репозитории, они просто передают данные, которые они собирают у пользователя, и передают их в соответствующую службу. Службе все равно, как данные, которые он получает, сохраняются в базе данных, он просто передает данные, которые он дал контроллеру, в соответствующие репозитории.

  • 8
    Этот комментарий на сегодняшний день является более чистым, более масштабируемым и обслуживаемым подходом.
  • 4
    +1! Это мне очень поможет, спасибо, что поделились с нами! Хотите знать, как вам удалось проверить вещи внутри служб, если это возможно, не могли бы вы кратко объяснить, что вы сделали? В любом случае, спасибо! :)
Показать ещё 9 комментариев
5

Мне нравится думать об этом с точки зрения того, что мой код делает и за что он несет ответственность, а не "правильно или неправильно". Вот как я нарушаю свои обязанности:

  • Контроллеры - это уровень HTTP и запросы маршрута к базовому apis (он управляет потоком)
  • Модели представляют схему базы данных и сообщают приложению, как выглядят данные, какими отношениями они могут обладать, а также любые глобальные атрибуты, которые могут быть необходимы (например, метод имени для возврата конкатенированного имени и фамилии)
  • Репозитории представляют собой более сложные запросы и взаимодействия с моделями (я не делаю никаких запросов в методах модели).
  • Поисковые системы - классы, которые помогают мне создавать сложные поисковые запросы.

Имея это в виду, каждый раз смысл использовать репозиторий (независимо от того, создаете ли вы interface.etc) - это целая другая тема). Мне нравится этот подход, потому что это означает, что я точно знаю, куда идти, когда мне нужно выполнить определенную работу.

Я также склонен создавать базовый репозиторий, обычно это абстрактный класс, который определяет основные значения по умолчанию - в основном CRUD-операции, а затем каждый ребенок может просто расширять и добавлять методы по мере необходимости или перегружать значения по умолчанию. Внедрение вашей модели также помогает этому шаблону быть достаточно надежным.

  • 0
    Можете ли вы показать свою реализацию вашего BaseRepository? Я тоже так делаю, и мне любопытно, что ты сделал.
  • 0
    Подумайте о getById, getByName, getByTitle, сохраните методы типа .etc. - обычно методы, которые применяются ко всем репозиториям в разных доменах.
5

Подумайте о Хранилищах как о едином картотеке ваших данных (а не только о ORM). Идея состоит в том, что вы хотите захватить данные в последовательном простом использовании API.

Если вы обнаружите, что просто делаете Model:: all(), Model:: find(), Model:: create(), вы, вероятно, не сильно выиграете от абстрагирования от репозитория. С другой стороны, если вы хотите сделать немного больше бизнес-логики для своих запросов или действий, вы можете захотеть создать репозиторий, чтобы упростить использование API для обработки данных.

Я думаю, вы спрашивали, будет ли репозиторий лучшим способом справиться с некоторым более подробным синтаксисом, необходимым для подключения связанных моделей. В зависимости от ситуации есть несколько вещей, которые я могу сделать:

  • Подвешивая новую дочернюю модель от родительской модели (один-один или один-много), я бы добавил метод к репозиторию для детей как-то вроде createWithParent($attributes, $parentModelInstance), и это просто добавит $parentModelInstance->id в поле parent_id атрибутов и вызов create.

  • Прикрепляя отношения многих-многих, я фактически создаю функции на моделях, чтобы я мог запускать $instance- > attachChild ($ childInstance). Обратите внимание, что для этого требуются существующие элементы с обеих сторон.

  • Создание связанных моделей за один проход, я создаю что-то, что я называю шлюзом (это может быть немного от определений Fowler). Я могу вызвать $gateway- > createParentAndChild ($ parentAttributes, $childAttributes) вместо связки логики, которая может измениться или что усложнит логику, которая у меня есть в контроллере или команде.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню