Паттерн «Репозиторий» в Laravel

Паттерн Репозиторий в Ларавел

В этой статье я расскажу, как настроить с нуля паттерн Репозиторий (Repository, Хранилище) в Laravel. Использую версию Laravel 5.8, но по идее версия не имеет большого значения. Прежде, чем приступим к коду, вам нужно кое-что знать об этом шаблоне.

паттерн Репозиторий

Репозиторий позволяет использовать объекты, не зная, как эти объекты сохраняются. По сути, это абстракция слоя данных.

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

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

Хватит Теории, да будет Код

Поскольку мы делаем все с нуля, то начнем с создания нового проекта Laravel:

composer create-project --prefer-dist laravel/laravel repository

Для этого урока я сделаю небольшой блог. Создаем Контроллер и Модель для нашего блога.

php artisan make:controller BlogController

Это создаст BlogController в папке app/Http/Controllers.

php artisan make:model Models/Blog -m

Примечание: Опция -m создает миграцию для базы данных. Этот файл можно найти в папке database/migrations.

Команда выше создаст модель Blog и сохранит ее в папке app/Models. Это лишь один из способов хранения моделей, но я предпочитаю именно его.

Теперь, когда у нас есть наш Контроллер и Модель, пришло время взглянуть на созданный нами файл миграции. Нашему блогу пока нужны только title (Заголовок), content (содержимое) и поле user_id, не считая дефолтных полей меток времени Laravel.

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateBlogsTable extends Migration
{
    public function up()
    {
        Schema::create('blogs', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->text('content');
            $table->integer('user_id');
            $table->timestamps();
            
            $table->foreign('user_id')
                  ->references('id')
                  ->on('users');
        });
    }

    public function down()
    {
        Schema::dropIfExists('blogs');
    }
}

Примечание: Если вы используете Laravel старее чем 5.8, то вам следует заменить строку

$table->bigIncrements(‘id’);

на

$table->increments('id');

Настройка базы данных

Для этого примера я буду использовать базу данных MySQL. Создаем новую базу.

mysql -u root -p 
create database laravel_repository;

Это создаст базу данных с именем laravel_repository. Теперь нужно добавить её учетные данные в файл .env.

DB_DATABASE=laravel_repository
DB_USERNAME=root
DB_PASSWORD=secret

После изменения файла .env надо очистить кеш конфигурации:

php artisan config:clear

Запуск миграции

У нас есть настроенная база данных, можно запускать миграцию:

php artisan migrate

Это создаст таблицу blogs с полями title , content и user_id, которые мы объявили в миграции.

Реализация паттерна Репозиторий

Теперь, когда все готово, можно приступать к реализации шаблона. Начнем с создания папки Repositories в папке app. Вторая папка, которую мы создадим, это папка Interfaces. Она будет находиться прямо в свеже созданной папке Repositories.

В папке Interfaces мы создаем класс BlogRepositoryInterface, который, на данный момент, содержит два метода:

  1. Метод all, который возвращает все блоги.
  2. Метод getByUser, который возвращает все блоги, конкретного пользователя.
<?php

namespace App\Repositories\Interfaces;

use App\User;

interface BlogRepositoryInterface
{
    public function all();

    public function getByUser(User $user);
}

Последний класс, который мы создадим, это BlogRepository, который реализует BlogRepositoryInterface. Будем придерживаться очень простой имплементации.

<?php

namespace App\Repositories;

use App\Models\Blog;
use App\User;
use App\Repositories\Interfaces\BlogRepositoryInterface;

class BlogRepository implements BlogRepositoryInterface
{
    public function all()
    {
        return Blog::all();
    }

    public function getByUser(User $user)
    {
        return Blog::where('user_id'. $user->id)->get();
    }
}

Папка Repositories должна выглядеть следующим образом:

app/
└── Repositories/
    ├── BlogRepository.php
    └── Interfaces/
        └── BlogRepositoryInterface.php

Вы успешно создали свой Репозиторий! Но мы еще не закончили. Настало время использовать его.

Репозиторий в действии

Чтобы начать использовать BlogRepository, мы должны внедрить его в BlogController. Поскольку Репозиторий будет внедрен, то его легко можно заменить другой реализацией. Вот как будет выглядеть контроллер:

<?php

namespace App\Http\Controllers;


use App\Repositories\Interfaces\BlogRepositoryInterface;
use App\User;

class BlogController extends Controller
{
    private $blogRepository;

    public function __construct(BlogRepositoryInterface $blogRepository)
    {
        $this->blogRepository = $blogRepository;
    }

    public function index()
    {
        $blogs = $this->blogRepository->all();

        return view('blog')->withBlogs($blogs);
    }

    public function detail($id)
    {
        $user = User::find($id);
        $blogs = $this->blogRepository->getByUser($user);

        return view('blog')->withBlogs($blogs);
    }
}

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

Шаблон проектирования Репозиторий позволяет легко переключаться между источниками данных. В этом примере мы используем базу данных для получения наших блогов. Мы полагаемся на Eloquent, который делает это за нас. Но допустим, где-то в Интернете мы видели отличный API для блогов, и мы хотим им воспользоваться. Все, что нам нужно, это переписать BlogRepository, чтобы он использовал этот API вместо Eloquent.

RepositoryServiceProvider

Вместо внедрения BlogRepository в BlogController мы внедрим BlogRepositoryInterface и затем позволим сервис-контейнеру решать, какой репозиторий будет использоваться. Это можно сделать в методе boot в AppServiceProvider, но я предпочитаю сделать нового провайдера для этого, для сохранения чистоты кода.

php artisan make:provider RepositoryServiceProvider

Мы делаем нового провайдера по причине, что когда проект начнет расти, то код станет очень грязным.
Представьте проект с десятком моделей и у каждой свой репозиторий — AppServiceProvider станет нечитаемым.

Вот так выглядит наш RepositoryServiceProvider:

<?php

namespace App\Providers;

use App\Repositories\BlogRepository;
use App\Repositories\Interfaces\BlogRepositoryInterface;
use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(
            BlogRepositoryInterface::class, 
            BlogRepository::class
        );
    }
}

Обратите внимание, как легко можно поменять BlogRepository на другое хранилище.

Не забудьте добавить RepositoryServiceProvider в список провайдеров в файле config/app.php. После этого нужно очистить кеш конфигурации еще раз.

php artisan config:clear

Вот и все!

Вы успешно использовали паттерн Репозиторий. Было не так уж и сложно, верно?

 

Автор: Daan
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.