SOLID w Laravelu: Użyteczne granice, nie ceremonia

2 min. czytaniaZaktualizowano

SOLID jest słownikiem presji zmian. Pojedyncza odpowiedzialność znaczy, że akcja ma jeden powód do zmiany. Otwarte/zamknięte znaczy dodanie reguły cenowej przez strategię, zamiast edycji centralnego warunku. Odwrócenie zależności znaczy zależenie od znaczącego kontraktu tam, gdzie potrzebnych jest wiele implementacji albo testy — nie tworzenie interfejsów dla każdego modelu.

Kontener Laravela rozwiązuje zależności konstruktora, lecz nie czyni projektu automatycznie modularnym. Preferuj konkretne klasy, dopóki nie pojawi się prawdziwa granica, kontrakty utrzymuj wąskie, a kod przed/po potwierdź testem. Celem jest łatwiejsza zmiana, nie wyższe drzewo folderów.

Jedna odpowiedzialność to jeden rodzaj zmiany

Zostaw kontroler jako adapter HTTP. Wielokrotnie używany przypadek użycia umieść w typowanej akcji, aby działał z API, polecenia i joba bez odtwarzania stanu HTTP.

php
<?php

declare(strict_types=1);

final readonly class CreateInvoice
{
    public function __construct(private InvoiceNotifier $notifier) {}

    public function handle(User $user, int $amount): Invoice
    {
        $invoice = Invoice::query()->create(['user_id' => $user->getKey(), 'amount' => $amount]);
        $this->notifier->sendCreated($invoice);

        return $invoice;
    }
}

Rozszerzaj zmienność, nie centralny warunek

match jest dobry dla dwóch stabilnych przypadków. Kontrakt wyciągnij, gdy zachowanie przychodzi niezależnie, aby każdą regułę testować bez pozostałych.

php
<?php

declare(strict_types=1);

interface TaxCalculator { public function calculate(int $netCents): int; }

final readonly class PolishTaxCalculator implements TaxCalculator
{
    public function calculate(int $netCents): int { return intdiv($netCents * 23, 100); }
}

Co kontener naprawdę robi dla DIP

Kontener rozwiązuje zależności konstruktora; nie wybiera implementacji runtime tylko dlatego, że widzi interfejs. Zbindowaj implementację domyślną dla wszystkich konsumentów. Contextual binding stosuj tylko dla znanego konsumenta, a resolver lub factory, gdy wybór zależy od tenant, kraju albo requestu.

php
<?php

declare(strict_types=1);

use Illuminate\Support\ServiceProvider;

final class BillingServiceProvider extends ServiceProvider
{
    public function register(): void { $this->app->bind(TaxCalculator::class, PolishTaxCalculator::class); }
}

app(TaxCalculator::class) działa, bo istnieje konkretne bindowanie. Nie wstrzykuj kontenera do domeny, aby odroczyć decyzję; wstrzyknij TaxCalculatorResolver, gdy wybór wynika z danych runtime.

LSP, ISP i kiedy tego NIE używać

Nie każ ReadOnlyStorage implementować Storage, jeśli delete() musi rzucać wyjątek: rozdziel capability read() i write(). Nie twórz też interfejsu dla jednego stabilnego repository Eloquent ani jednorazowej akcji. Granica ma sens przy zmiennym zachowaniu, zewnętrznej zależności do fake'owania lub niezależnej polityce. Alternatywą jest skupiona klasa konkretna, nie splątany kod.

Powiązane artykuły

Wsparcie istniejącego systemu

Potrzebujesz pomocy z działającą aplikacją?

Pomagam firmom rozwijać działające systemy, porządkować wdrożenia i dodawać nowe funkcje bez dokładania chaosu do projektu.

Komentarze (0)
Zaloguj się, aby dodać komentarz

Musisz być zalogowany, aby dodać komentarz.

Zaloguj się

Potrzebujesz kogoś, kto weźmie odpowiedzialność za kolejny krok?

Porozmawiajmy o Twoim projekcie i określmy zakres, który ma sens dla Twoich celów.