Nova Logika
← Nazad na blog

Kako strukturirati Laravel projekat za dugoročni rast

4 min
Dijagram softverske arhitekture

Laravel projekti često počinju isto — standardna struktura, nekoliko kontrolera, par modela, i sve funkcioniše. Problem nastaje šest meseci kasnije, kada aplikacija naraste i svaka promena u jednom delu sistema neočekivano pokvari nešto drugo.

Struktura projekta nije nešto o čemu razmišljate prvog dana. Ali odluke koje donesete (ili ne donesete) na početku, definišu koliko će vam biti lako da dodate novu funkcionalnost godinu dana kasnije.

Podrazumevana struktura nije dovoljna

Laravel dolazi sa jasnom strukturom direktorijuma — app/Http/Controllers, app/Models, app/Services. Za manje projekte, ovo je sasvim dovoljno. Ali kada broj kontrolera pređe trideset, a modeli počnu da imaju po dvesta linija, postaje jasno da organizacija po tipu fajla ne skalira.

Problem nije u Laravel-u — problem je u tome što grupišemo kod po tome šta jeste (kontroler, model, servis), umesto po tome šta radi (upravljanje korisnicima, procesiranje narudžbina, generisanje izveštaja).

Organizacija po domenu

Alternativa je organizacija po poslovnom domenu. Umesto:

app/
  Http/Controllers/
    UserController.php
    OrderController.php
    InvoiceController.php
  Models/
    User.php
    Order.php
    Invoice.php
  Services/
    UserService.php
    OrderService.php
    InvoiceService.php

Kod možete organizovati ovako:

app/
  Domain/
    Users/
      UserController.php
      User.php
      UserService.php
      UserPolicy.php
    Orders/
      OrderController.php
      Order.php
      OrderService.php
      CreateOrderAction.php
    Invoices/
      InvoiceController.php
      Invoice.php
      GenerateInvoiceAction.php

Sada, kada trebate da razumete kako funkcionišu narudžbine, sve je na jednom mestu. Ne skačete između pet različitih direktorijuma da biste pratili tok jedne operacije.

Izdvajanje poslovne logike iz kontrolera

Najčešća greška u Laravel projektima su kontroleri od trista linija. Kontroler treba da primi zahtev, pozove odgovarajuću logiku i vrati odgovor. Ništa više.

Umesto:

public function store(Request $request)
{
    $validated = $request->validate([
        'email' => 'required|email|unique:users',
        'name' => 'required|string|max:255',
    ]);

    $user = User::create($validated);
    $user->assignRole('member');

    Mail::to($user)->send(new WelcomeEmail($user));

    Activity::log('user_created', $user);

    return response()->json($user, 201);
}

Izdvojite logiku u dedicirani action:

public function store(CreateUserRequest $request, CreateUserAction $action)
{
    $user = $action->execute($request->validated());

    return response()->json($user, 201);
}

CreateUserAction klasa enkapsulira svu logiku kreiranja korisnika — validacija je u Form Request-u, a kontroler samo koordinira. Ovo čini kod testabilnim bez HTTP sloja i omogućava pozivanje iste logike iz Artisan komandi, queue job-ova ili drugih delova sistema.

Form Request za validaciju

Laravel-ov Form Request mehanizam je jedan od najkorisnijih, a najređe korišćenih alata. Umesto validacije u kontroleru, kreirajte dedicirane request klase:

class CreateUserRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', User::class);
    }

    public function rules(): array
    {
        return [
            'email' => ['required', 'email', 'unique:users'],
            'name' => ['required', 'string', 'max:255'],
            'role' => ['sometimes', 'string', 'in:admin,member'],
        ];
    }
}

Autorizacija i validacija su na jednom mestu, kontroler je čist, a pravila su lako čitljiva i testabilna.

Eloquent modeli — manje je više

Eloquent modeli imaju tendenciju da postanu “god objects” — klase koje znaju sve i rade sve. Scope-ovi, mutatori, relacije, pomoćne metode — sve završi u jednom fajlu.

Držite modele fokusiranim na ono što Eloquent radi najbolje — definiciju tabele, relacija i cast-ova:

class Order extends Model
{
    protected $fillable = ['user_id', 'status', 'total'];

    protected $casts = [
        'total' => 'decimal:2',
        'status' => OrderStatus::class,
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function items(): HasMany
    {
        return $this->hasMany(OrderItem::class);
    }
}

Query scope-ove koje koristite na više mesta možete zadržati, ali poslovnu logiku poput calculateDiscount() ili sendConfirmation() premestite u action klase ili servise.

Konfiguracija i environment varijable

Česta greška je direktno čitanje env() funkcije van config fajlova. Ovo ne radi kada je konfiguracija keširana (php artisan config:cache), a trebalo bi da bude keširana u produkciji.

Uvek čitajte vrednosti kroz config():

// Loše
$apiKey = env('PAYMENT_API_KEY');

// Dobro
$apiKey = config('services.payment.key');

Za servise koji zahtevaju više konfiguracionih vrednosti, kreirajte dedicirane config fajlove umesto trpanja svega u services.php.

Migracije i šema baze

Migracije su hronologija vaše baze — ne menjajte postojeće migracije koje su već pokrenute u produkciji. Umesto toga, kreirajte nove migracije za izmene.

Koristite deskriptivna imena:

php artisan make:migration add_discount_column_to_orders_table

Za enum kolone, koristite PHP enum-e sa cast-ovima umesto string vrednosti direktno u bazi. Ovo centralizuje moguće vrednosti i daje vam type safety.

Testiranje kao osnova

Struktura projekta koja omogućava lako testiranje je struktura koja omogućava lak razvoj. Ako ne možete da testirate klasu bez da pokrenete celu aplikaciju, to je znak da su zavisnosti previše isprepletane.

Action klase su inherentno testabilne:

public function test_creating_user_assigns_default_role(): void
{
    $action = new CreateUserAction();

    $user = $action->execute([
        'name' => 'Test User',
        'email' => 'test@example.com',
    ]);

    $this->assertTrue($user->hasRole('member'));
}

Nema mock-ovanja HTTP zahteva, nema simuliranja request lifecycle-a. Pozovete metodu, proverite rezultat.

Zaključak

Dobra struktura projekta nije nešto što implementirate odjednom. To je proces koji evoluira sa projektom. Počnite sa konvencijama koje Laravel nudi, a kada osetite da određeni deo sistema postaje previše složen — tada je pravi trenutak za refaktorisanje.

Ključ je u konzistentnosti. Izaberite pristup, dokumentujte ga, i primenite ga dosledno kroz ceo projekat. Tim koji zna gde da traži kod i kako da doda novu funkcionalnost je tim koji brže isporučuje.