• Czas czytania ~8 min
  • 14.07.2023

Repozytorium można zdefiniować jako warstwę abstrakcji między domeną a warstwami mapowania danych, która zapewnia ścieżkę mediacji między nimi, za pośrednictwem interfejsu podobnego do kolekcji w celu uzyskania dostępu do obiektów domeny.

Nowoczesne frameworki PHP, takie jak Laravel i Symfony, współdziałają z bazami danych za pośrednictwem mapperów obiektowo-relacyjnych (ORM); Symfony używa Doctrine jako domyślnego ORM, a Laravel używa Eloquent.

Oba przyjmują różne podejścia do działania interakcji z bazą danych. Dzięki Eloquent modele są generowane dla każdej tabeli bazy danych, tworząc podstawę interakcji. Doktryna używa jednak wzorca repozytorium, w którym każda jednostka ma odpowiednie repozytorium zawierające funkcje pomocnicze do interakcji z bazą danych. Chociaż Laravel nie zapewnia tej funkcji po wyjęciu z pudełka, możliwe jest użycie wzorca repozytorium w projektach Laravel.

Kluczową zaletą wzorca repozytorium jest to, że pozwala nam on używać zasady odwrócenia zależności (lub kodu do abstrakcji, a nie konkretów). Dzięki temu nasz kod jest bardziej odporny na zmiany, na przykład jeśli później podjęto decyzję o przełączeniu do źródła danych, które nie jest obsługiwane przez Eloquent.

Pomaga również w utrzymaniu porządku kodu i unikaniu duplikacji, ponieważ logika związana z bazą danych jest przechowywana w jednym miejscu. Chociaż korzyść ta nie jest od razu widoczna w przypadku małych projektów, staje się bardziej widoczna w projektach na dużą skalę, które muszą być utrzymywane przez wiele lat.

W tym artykule pokażę, jak zaimplementować wzorzec repozytorium w aplikacjach Laravel. W tym celu zbudujemy API do zarządzania zamówieniami otrzymywanymi od klientów dla firmy.

Warunki wstępne

Pierwsze kroki

Utwórz nowy projekt Laravel i cd do katalogu za pomocą następujących poleceń.

laravel new order_api
cd order_api

Konfigurowanie bazy danych W tym samouczku użyjemy MySQL jako naszej bazy danych

. Aby to zrobić, w pliku env zaktualizuj parametry związane z bazą danych, jak pokazano poniżej.

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>

Na koniec, korzystając z preferowanej aplikacji do zarządzania bazami danych, utwórz nową bazę danych o nazwie order_api.

Generowanie danych początkowych dla bazy danych

Budujemy aplikację do zarządzania zamówieniami, więc stwórzmy dla niej model, uruchamiając następujące polecenie.

php artisan make:model Order -a

Argument -a informuje Artisana, że chcemy utworzyć plik migracji , seeder, fabrykę i kontroler dla modelu Order .

Powyższe polecenie utworzy pięć nowych plików:

  • Kontroler w app/ Http/Controllers/OrderController.php
  • Fabryka baz danych w database/factories/orderFactory.php
  • Plik migracji w bazie danych/migracjach/YYYY_MM_DD_HHMMSS_create_orders_table.php
  • Model znajdujący się w app/Models/Order.php
  • Plik seedera w database/ seeders/OrderSeeder.php oraz

W polu database/migrations/YYYY_MM_DD_HHMMSS_create_orders_table.php zaktualizuj funkcjęup, aby była zgodna z poniższymi.

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();
    });
 }

Jak określono w pliku migracji, tabela będzie miała następujące kolumny:

  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.

Następnie zaktualizujmyOrderFactory, order aby można było wygenerować fikcyjne zamówienie do zapoczątkowania bazy danych. W polu database/factories/OrderFactory.php zaktualizuj funkcjędefinition, aby była zgodna z poniższymi.

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

Następnie otwórz database/seeders/OrderSeeder.php i zaktualizuj run funkcję, aby pasowała do następującej wartości.

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

Służy OrderFactory to do utworzenia 50 zamówień w bazie danych.

Nie zapomnij dodać tego importu:

use App\Models\Order;

W src/database/seeders/DatabaseSeeder.php dodaj następujące elementy do run funkcji.

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

Spowoduje to uruchomienie QuoteSeeder polecenia Artisanadb:seed.

Na koniec uruchom migracje i rozsiadaj bazę danych za pomocą następującego polecenia.

php artisan migrate --seed

Jeśli otworzysz tabelę zamówień, zobaczysz nowo rozstawione zamówienia.

List of Orders

Tworzenie repozytorium Zanim utworzymy repozytorium dla Order modelu, zdefiniujmy interfejs, aby określić wszystkie metody, które repozytorium

musi zadeklarować. Zamiast polegać bezpośrednio na klasie repozytorium, nasz kontroler (i każdy komponent zamówienia, który możemy zbudować w przyszłości) będzie zależał od interfejsu.

To sprawia, że nasz kod jest elastyczny, ponieważ jeśli w przyszłości zajdzie potrzeba wprowadzenia zmiany, kontroler pozostaje nienaruszony. Na przykład, jeśli zdecydowaliśmy się zlecić zarządzanie zamówieniami aplikacji 3rd, możemy zbudować nowy moduł, który jest zgodny z OrderRepositoryInterfacepodpisem i zamienić wiążące deklaracje, a nasz kontroler będzie działał dokładnie tak, jak oczekiwano - bez dotykania ani jednej linii kodu w kontrolerze.

W katalogu aplikacji utwórz nowy folder o nazwie Interfejsy. Następnie w interfejsach utwórz nowy plik o nazwie OrderRepositoryInterface.php i dodaj do niego następujący kod.

<?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();
}

Następnie w folderze aplikacji utwórz nowy folder o nazwie Repozytoria. W tym folderze utwórz nowy plik o nazwie OrderRepository i dodaj do niego następujący kod.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);
    }
}

Oprócz elastyczności zapewnianej przez interfejs, hermetyzacja zapytań w ten sposób ma tę dodatkową zaletę, że nie musimy duplikować zapytań w całej aplikacji.

Jeśli w przyszłości zdecydujemy się odzyskać tylko niezrealizowane zamówienia w funkcji, musielibyśmy dokonać zmiany tylko w getAllOrders() jednym miejscu, zamiast śledzić wszystkie zgłoszone miejscaOrder::all(), ryzykując pominięcie niektórych.

Tworzenie kontrolerów

Po umieszczeniu naszego repozytorium dodajmy trochę kodu do naszego kontrolera. Otwórz app/Http/Controllers/OrderController.php i zaktualizuj kod, aby był zgodny z poniższymi.

<?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);
    }
}

Kod wstrzykuje instancję OrderRepositoryInterface za pośrednictwem konstruktora i używa odpowiednich metod obiektu w każdej metodzie kontrolera.

Po pierwsze, w ramach metody wywołuje getAllOrders() metodę zdefiniowaną w celu pobrania listy zamówień i zwraca odpowiedź w index() orderRepository formacie JSON.

store() Następnie metoda wywołuje createOrder() metodę z , orderRepository aby utworzyć nowe zamówienie. Spowoduje to pobranie szczegółów zamówienia, które należy utworzyć jako tablicę, a następnie zwrócenie pomyślnej odpowiedzi.

show() W ramach metody w kontrolerze pobiera unikatową kolejność Id z trasy i przekazuje ją do getOrderById() parametru as. Spowoduje to pobranie szczegółów zamówienia z pasującym identyfikatorem z bazy danych i zwrócenie odpowiedzi w formacie JSON.

Następnie, aby zaktualizować szczegóły już utworzonego zamówienia, wywołuje updateOrder() metodę z repozytorium. Przyjmuje to dwa parametry: unikalny identyfikator zamówienia i szczegóły, które należy zaktualizować.

destroy() Na koniec metoda pobiera unikalny identyfikator określonego zamówienia z trasy i wywołuje deleteOrder() metodę z repozytorium, aby ją usunąć.

Dodawanie tras

Aby zmapować każdą metodę zdefiniowaną w sterowniku na określone trasy, dodaj następujący kod do 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']);

Pamiętaj, aby dołączyć instrukcję import dla OrderControllerpliku .

use App\Http\Controllers\OrderController;

Powiąż interfejs i implementację

Ostatnią rzeczą, którą musimy zrobić, jest powiązanie OrderRepository w OrderRepositoryInterface kontenerze usług Laravel; robimy to za pośrednictwem usługodawcy. Utwórz go za pomocą następującego polecenia.

php artisan make:provider RepositoryServiceProvider

Otwórz app/Providers/RepositoryServiceProvider.php i zaktualizuj register funkcję aby była zgodna z poniższymi.

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

Pamiętaj, aby dołączyć instrukcję import dla OrderRepository i OrderRepositoryInterface.

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

Na koniec dodaj nowego usługodawcę do tablicy providers w config/app.php.

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

Przetestuj aplikację Uruchom aplikację

za pomocą następującego polecenia.

Domyślnie udostępniona aplikacja będzie dostępna pod adresem http://127.0.0.1:8000/. Korzystając z Postmana lub cURL, możemy wysyłać żądania do naszego nowo utworzonego API.

Uruchom następujące polecenie, aby przetestować /api/orders punkt końcowy przy użyciu cURL:

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

Odpowiedź została sformatowana do JSON przy użyciu jq.

Zobaczysz dane wyjściowe JSON podobne do poniższego przykładu w terminalu, który został obcięty, aby poprawić czytelność.

{
  "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"
    }
  ]
}

Jak używać wzorca repozytorium w aplikacji Laravel W tym artykule dowiedzieliśmy się o wzorcu repozytorium i sposobie jego używania w aplikacji

Laravel. Widzieliśmy również niektóre korzyści, jakie oferuje projektowi na dużą skalę - jednym z nich jest luźno powiązany kod, w którym kodujemy abstrakcję, a nie konkretne implementacje.

Zakończę jednak pewną ostrożnością. W przypadku małych projektów takie podejście będzie wymagało dużo pracy i standardowego kodu zwrotów, które mogą nie być od razu widoczne. Dlatego ważne jest, aby odpowiednio rozważyć skalę projektu przed przyjęciem tego podejścia.

Cała baza kodu dla tego samouczka jest dostępna w witrynie GitHub.The entire codebase for this tutorial is available on GitHub. Zapraszam do dalszej eksploracji. Miłego kodowania!


Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297