• Время чтения ~8 мин
  • 14.07.2023

Репозиторий может быть определен как уровень абстракции между уровнями домена и сопоставления данных, который обеспечивает путь посредничества между ними через интерфейс, подобный коллекции, для доступа к объектам домена.

Современные PHP-фреймворки, такие как Laravel и Symfony, взаимодействуют с базами данных через объектно-реляционные картографы (ORM); Symfony использует Doctrine в качестве ORM по умолчанию, а Laravel использует Eloquent.

Оба используют разные подходы к взаимодействию с базами данных. С помощью Eloquent модели генерируются для каждой таблицы базы данных, формируя основу взаимодействия. Doctrine, однако, использует шаблон Repository , где каждая сущность имеет соответствующий репозиторий, содержащий вспомогательные функции для взаимодействия с базой данных. Хотя Laravel не предоставляет эту функциональность из коробки, можно использовать шаблон репозитория в проектах Laravel.

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

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

В этой статье я покажу вам, как реализовать паттерн Repository в ваших приложениях Laravel. Для этого мы создадим API для управления заказами, полученными от клиентов для компании.

Необходимые условия

Начало работы

Создайте новый проект Laravel и вставьте его в каталог с помощью следующих команд.

laravel new order_api
cd order_api

Настройка базы данных В этом уроке мы будем использовать MySQL в качестве базы данных

. Для этого в файле . env обновите параметры, связанные с базой данных, как показано ниже.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=order_api
DB_USERNAME=<YOUR_DATABASE_USERNAME>
DB_PASSWORD=<YOUR_DATABASE_PASSWORD>

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

Генерация исходных данных для базы данных

Мы создаем приложение для управления заказами, поэтому создадим для него модель, выполнив следующую команду.

php artisan make:model Order -a

Этот -a аргумент сообщает Artisan, что мы хотим создать файл миграции , сеялку, фабрику и контроллер для модели Order .

Приведенная выше команда создаст пять новых файлов:

  • Контроллер в app/ Http/Controllers/OrderController.php
  • Фабрика баз данных в database/factories/orderFactory.php
  • Файл миграции в database/ migrations/YYYY_MM_DD_HHMMSS_create_orders_table.php
  • Модель, находящаяся в app/Models/Order.php
  • Файл сеялки в database/seeders/OrderSeeder.php и

В database/migrations/YYYY_MM_DD_HHMMSS_create_orders_table.php обновите функцию, up чтобы она соответствовала следующему.

public function up()
{
    Schema::create('orders', function (Blueprint $table) {
        $table->id();
        $table->text('details');
        $table->string('client');
        $table->boolean('is_fulfilled')->default(false);
        $table->timestamps();
    });
 }

Как указано в файле миграции, таблица будет иметь следующие столбцы:

  1. An ID. This will be the table's primary key.
  2. The details of the order.
  3. The name of the client who placed the order.
  4. Whether or not the order has been fulfilled.
  5. When the order was created and updated, created_at and updated_at, provided by the timestamps function.

Далее давайте обновимOrderFactory, order чтобы он мог генерировать фиктивный порядок для заполнения базы данных. В database/factories/OrderFactory.php обновите definition функцию, чтобы она соответствовала следующему.

public function definition() 
{
    return [
        'details'       => $this->faker->sentences(4, true),
        'client'         => $this->faker->name(),
        'is_fulfilled' => $this->faker->boolean(),
    ];
}

Затем откройте database/seeders/OrderSeeder и обновите run функцию.php чтобы она соответствовала следующему.

public function run() 
{
    Order::factory()->times(50)->create();
}

Это используется OrderFactory для создания 50 заказов в базе данных.

Не забудьте добавить этот импорт:

use App\Models\Order;

В src/database/seeders/DatabaseSeeder.php добавьте в run функцию следующее.

$this->call(
    [
        OrderSeeder::class
    ]
); 

Это запускается при QuoteSeeder выполнении команды Artisandb:seed.

Наконец, запустите миграцию и заполните базу данных с помощью следующей команды.

php artisan migrate --seed

Если вы откроете таблицу заказов, вы увидите только что сформированные заказы.

List of Orders

Создание репозитория Прежде чем мы создадим репозиторий для модели, давайте определим интерфейс для Order указания всех методов, которые должен объявлять репозиторий

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

Это делает наш код гибким, потому что, если возникнет необходимость внести изменения в будущем, контроллер остается неизменным. Например, если мы решили передать управление заказами на аутсорсинг стороннему приложению 3rd, мы можем создать новый модуль, который соответствует OrderRepositoryInterfaceподписи, и поменять местами декларации привязки, и наш контроллер будет работать точно так, как ожидалось - не касаясь ни одной строки кода в контроллере.

В каталоге приложения создайте новую папку с именем Interfaces. Затем в интерфейсах создайте новый файл с именем OrderRepositoryInterface.php и добавьте в него следующий код.

<?php
namespace App\Interfaces;
interface OrderRepositoryInterface 
{
    public function getAllOrders();
    public function getOrderById($orderId);
    public function deleteOrder($orderId);
    public function createOrder(array $orderDetails);
    public function updateOrder($orderId, array $newDetails);
    public function getFulfilledOrders();
}

Затем в папке приложения создайте новую папку с именем Repositories. В этой папке создайте новый файл с именем OrderRepository и добавьте в него следующий код.php.

<?php
namespace App\Repositories;
use App\Interfaces\OrderRepositoryInterface;
use App\Models\Order;
class OrderRepository implements OrderRepositoryInterface 
{
    public function getAllOrders() 
    {
        return Order::all();
    }
    public function getOrderById($orderId) 
    {
        return Order::findOrFail($orderId);
    }
    public function deleteOrder($orderId) 
    {
        Order::destroy($orderId);
    }
    public function createOrder(array $orderDetails) 
    {
        return Order::create($orderDetails);
    }
    public function updateOrder($orderId, array $newDetails) 
    {
        return Order::whereId($orderId)->update($newDetails);
    }
    public function getFulfilledOrders() 
    {
        return Order::where('is_fulfilled', true);
    }
}

Помимо гибкости, обеспечиваемой интерфейсом, инкапсуляция запросов таким образом имеет дополнительное преимущество, заключающееся в том, что нам не нужно дублировать запросы по всему приложению.

Если в будущем мы решим извлекать только невыполненные заказы в функции, нам нужно будет внести изменения только в getAllOrders() одном месте вместо того, чтобы отслеживать все места, где Order::all() объявлено, рискуя пропустить некоторые.

Создание контроллеров

После того, как наш репозиторий готов, давайте добавим код в наш контроллер. Откройте app/Http/Controllers/OrderController.php и обновите код, чтобы он соответствовал следующему.

<?php
namespace App\Http\Controllers;
use App\Interfaces\OrderRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class OrderController extends Controller 
{
    private OrderRepositoryInterface $orderRepository;
    public function __construct(OrderRepositoryInterface $orderRepository) 
    {
        $this->orderRepository = $orderRepository;
    }
    public function index(): JsonResponse 
    {
        return response()->json([
            'data' => $this->orderRepository->getAllOrders()
        ]);
    }
    public function store(Request $request): JsonResponse 
    {
        $orderDetails = $request->only([
            'client',
            'details'
        ]);
        return response()->json(
            [
                'data' => $this->orderRepository->createOrder($orderDetails)
            ],
            Response::HTTP_CREATED
        );
    }
    public function show(Request $request): JsonResponse 
    {
        $orderId = $request->route('id');
        return response()->json([
            'data' => $this->orderRepository->getOrderById($orderId)
        ]);
    }
    public function update(Request $request): JsonResponse 
    {
        $orderId = $request->route('id');
        $orderDetails = $request->only([
            'client',
            'details'
        ]);
        return response()->json([
            'data' => $this->orderRepository->updateOrder($orderId, $orderDetails)
        ]);
    }
    public function destroy(Request $request): JsonResponse 
    {
        $orderId = $request->route('id');
        $this->orderRepository->deleteOrder($orderId);
        return response()->json(null, Response::HTTP_NO_CONTENT);
    }
}

Код внедряет OrderRepositoryInterface экземпляр через конструктор и использует методы соответствующего объекта в каждом методе контроллера.

Во-первых, в методе он вызывает getAllOrders() метод, определенный в каталоге index() orderRepository, для получения списка заказов и возвращает ответ в формате JSON.

store() Затем метод вызывает createOrder() метод из метода для orderRepository создания нового заказа. При этом принимаются сведения о заказе, который необходимо создать в виде массива, и после этого возвращается успешный ответ.

В методе в контроллере show() он извлекает уникальный порядок Id из маршрута и передает его в качестве параметраgetOrderById(). При этом извлекаются сведения о заказе с совпадающим идентификатором из базы данных и возвращается ответ в формате JSON.

Затем, чтобы обновить сведения об уже созданном заказе, он вызывает updateOrder() метод из репозитория. Для этого нужны два параметра: уникальный идентификатор заказа и детали, которые необходимо обновить.

Наконец, метод извлекает уникальный идентификатор определенного заказа из маршрута и вызывает deleteOrder() метод из репозитория, destroy() чтобы удалить его.

Добавление маршрутов Чтобы сопоставить каждый метод, определенный в контроллере, с конкретными маршрутами

, добавьте следующий код в routes/api.php.

Route::get('orders', [OrderController::class, 'index']);
Route::get('orders/{id}', [OrderController::class, 'show']);
Route::post('orders', [OrderController::class, 'store']);
Route::put('orders/{id}', [OrderController::class, 'update']);
Route::delete('orders/{id}', [OrderController::class, 'delete']);

Не забудьте включить import инструкцию для OrderController.

use App\Http\Controllers\OrderController;

Привязка интерфейса и реализации

Последнее, что нам нужно сделать, это привязаться OrderRepository к OrderRepositoryInterface сервисному контейнеру Laravel; мы делаем это через поставщика услуг. Создайте его с помощью следующей команды.

php artisan make:provider RepositoryServiceProvider

Откройте app/Providers/RepositoryServiceProvider.php и обновите register функцию в соответствии со следующим.

public function register() 
{
    $this->app->bind(OrderRepositoryInterface::class, OrderRepository::class);
 }

Не забудьте включить import инструкцию для OrderRepository и OrderRepositoryInterface.

use App\Interfaces\OrderRepositoryInterface;
use App\Repositories\OrderRepository;

Наконец, добавьте нового поставщика услуг в providers массив в config/app.php.

'providers' => [
    // ...other declared providers
    App\Providers\RepositoryServiceProvider::class,
];

Тестирование приложения

Запустите приложение с помощью следующей команды.

По умолчанию обслуживаемое приложение будет доступно по адресу http://127.0.0.1:8000/. Используя Postman или cURL, мы можем делать запросы к нашему только что созданному API.

Выполните следующую команду, чтобы проверить /api/orders конечную точку с помощью cURL:

curl --silent http://localhost:8000/api/orders | jq

Ответ был отформатирован в JSON с помощью jq.

Вы увидите вывод JSON, аналогичный приведенному ниже примеру в вашем терминале, который был усечен, чтобы улучшить читаемость.

{
  "data": [
    {
      "id": 1,
      "details": "Sit ullam cupiditate dolorem in. Magnam suscipit eaque occaecati facilis amet illum. Dolor perspiciatis velit laboriosam. Enim fugiat excepturi qui natus incidunt dolorem debitis ut.",
      "client": "Cydney Conn V",
      "is_fulfilled": 0,
      "created_at": "2021-09-09T09:18:28.000000Z",
      "updated_at": "2021-09-09T09:18:28.000000Z"
    },
    {
      "id": 2,
      "details": "Eum iste eum molestiae est. Voluptatibus veritatis earum commodi. Quod et laboriosam ratione dolor adipisci. Nam et debitis nobis ea sit.",
      "client": "Willow Herzog",
      "is_fulfilled": 1,
      "created_at": "2021-09-09T09:18:28.000000Z",
      "updated_at": "2021-09-09T09:18:28.000000Z"
    },
    {
      "id": 3,
      "details": "At maxime architecto repellat quidem id. Saepe provident quo eos officiis et tenetur. Et expedita maxime atque. Et consequuntur sequi aperiam possimus odio est ab.",
      "client": "Mr. Peyton Nolan DVM",
      "is_fulfilled": 1,
      "created_at": "2021-09-09T09:18:28.000000Z",
      "updated_at": "2021-09-09T09:18:28.000000Z"
    }
  ]
}

Вот как использовать шаблон репозитория в приложении Laravel В этой статье мы узнали о шаблоне репозитория и о том, как использовать его в приложении

Laravel. Мы также увидели некоторые преимущества, которые он предлагает крупномасштабному проекту, одним из которых является слабо связанный код, в котором мы кодируем абстракции, а не конкретные реализации.

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

Вся кодовая база для этого учебника доступна на GitHub. Не стесняйтесь исследовать дальше. Удачного кодирования!


Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297