Pracując na co dzień z Magento 2 (opartym na komponentach Symfony) rzadko masz okazję spojrzeć szerzej na ekosystem PHP frameworków. Klient prosi o mikroserwis, API, panel administracyjny – i nagle pojawia się pytanie: Laravel czy Symfony? Pokazuję oba z perspektywy doświadczonego developera PHP który zna Magento, ale chce świadomie wybrać narzędzie do nowego projektu.
Filozofia – skąd różnica w podejściu
Symfony i Laravel wyrosły z różnych priorytetów. Symfony (2011) jest inspirowane Javą i Springiem – modularność, konwencja nad konfiguracją w warstwie DI, komponenty jako niezależne biblioteki. Laravel (2011, Taylor Otwell) postawił na developer experience – mniej ceremonii, więcej magii, szybkość od pierwszego uruchomienia do działającego CRUD.
Magento 2 developer czuje się w Symfony jak w domu – DI przez XML/YAML, EventDispatcher, Console, HttpFoundation to te same komponenty które Magento używa. Laravel będzie wymagał przestawienia myślenia w kilku miejscach.
Struktura projektu – pierwsza różnica
# Symfony - nowy projekt composer create-project symfony/skeleton my-project cd my-project composer require symfony/webapp-pack # dodaj to czego potrzebujesz # Laravel - nowy projekt composer create-project laravel/laravel my-project # Lub przez Laravel Installer laravel new my-project
# Symfony - minimalna struktura, dodajesz co potrzebujesz
my-project/
config/
services.yaml <- DI container
routes.yaml <- routing
packages/ <- konfiguracja bundli
src/
Controller/
Entity/
Repository/
Service/
templates/ <- Twig
tests/
# Laravel - pełna struktura od razu
my-project/
app/
Http/
Controllers/
Middleware/
Models/
Providers/
config/ <- PHP arrays zamiast YAML
database/
migrations/
seeders/
resources/
views/ <- Blade templates
routes/
web.php
api.php
Dependency Injection – to co znasz z Magento
<?php
// Symfony - DI przez autowiring + services.yaml (jak Magento di.xml)
// config/services.yaml:
// App\Service\OrderService:
// arguments:
// $repository: '@App\Repository\OrderRepository'
class OrderController extends AbstractController
{
public function __construct(
private OrderService $orderService // autowired automatycznie
) {}
#[Route('/orders/{id}', methods: ['GET'])]
public function show(int $id): JsonResponse
{
$order = $this->orderService->getById($id);
return $this->json($order);
}
}
// Laravel - DI przez Service Container (uproszczone, mniej XML)
class OrderController extends Controller
{
public function __construct(
private OrderService $orderService // też autowired, zero konfiguracji
) {}
public function show(int $id): JsonResponse
{
$order = $this->orderService->getById($id);
return response()->json($order);
}
}
W praktyce oba frameworki obsługują autowiring – wstrzykujesz przez konstruktor i działa. Symfony daje więcej kontroli przez YAML/XML konfigurację (znajome z Magento), Laravel polega bardziej na automatycznym wykrywaniu.
ORM – Doctrine (Symfony) vs Eloquent (Laravel)
<?php
// Doctrine (Symfony) - Data Mapper pattern
// Encja nie wie nic o bazie danych
#[ORM\Entity(repositoryClass: ProductRepository::class)]
#[ORM\Table(name: 'products')]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 64)]
private string $sku;
#[ORM\Column(type: 'decimal', precision: 10, scale: 2)]
private float $price;
// Gettery i settery - brak metod DB w encji
public function getSku(): string { return $this->sku; }
public function setSku(string $sku): static { $this->sku = $sku; return $this; }
}
// Repository w Doctrine - osobna klasa
class ProductRepository extends ServiceEntityRepository
{
public function findActiveBySku(string $sku): ?Product
{
return $this->createQueryBuilder('p')
->where('p.sku = :sku')
->andWhere('p.isActive = :active')
->setParameters(['sku' => $sku, 'active' => true])
->getQuery()
->getOneOrNullResult();
}
}
<?php
// Eloquent (Laravel) - Active Record pattern
// Model wie o bazie - prostszy, ale więcej sprzężenia
class Product extends Model
{
protected $fillable = ['sku', 'name', 'price', 'is_active'];
protected $casts = [
'price' => 'float',
'is_active' => 'boolean',
];
// Relacje bezpośrednio w modelu
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class);
}
// Scope - warunkowe filtry wielokrotnego użytku
public function scopeActive(Builder $query): void
{
$query->where('is_active', true);
}
}
// Użycie - bardzo czytelne, mało kodu
$products = Product::active()
->where('price', '>', 50)
->with('categories') // eager loading relacji
->paginate(20);
// Zapis
$product = Product::create(['sku' => 'NEW-001', 'name' => 'Nowy', 'price' => 29.99]);
Doctrine jest bliższe wzorcowi Repository który znasz z Magento 2 Service Contracts. Eloquent jest prostszy do szybkiego startu, ale Active Record miesza logikę domenową z dostępem do bazy.
Routing i middleware
<?php
// Symfony - routing przez atrybuty PHP 8 (lub YAML/XML)
#[Route('/api/products', name: 'api_products')]
class ProductApiController extends AbstractController
{
#[Route('', methods: ['GET'])]
public function list(Request $request): JsonResponse { /* ... */ }
#[Route('/{id}', methods: ['GET'])]
public function show(int $id): JsonResponse { /* ... */ }
#[Route('', methods: ['POST'])]
#[IsGranted('ROLE_ADMIN')] // autoryzacja przez atrybuty
public function create(Request $request): JsonResponse { /* ... */ }
}
// Symfony EventListener jako middleware (przez kernel events)
class AuthTokenListener
{
#[AsEventListener(event: KernelEvents::REQUEST)]
public function onKernelRequest(RequestEvent $event): void
{
$token = $event->getRequest()->headers->get('Authorization');
// walidacja tokenu
}
}
<?php
// Laravel - routing w routes/api.php
Route::prefix('products')->group(function () {
Route::get('/', [ProductController::class, 'index']);
Route::get('/{id}', [ProductController::class, 'show']);
Route::post('/', [ProductController::class, 'store'])
->middleware('auth:sanctum'); // middleware inline
});
// Middleware Laravel
class EnsureApiKey
{
public function handle(Request $request, Closure $next): Response
{
if ($request->header('X-API-Key') !== config('app.api_key')) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $next($request); // Chain of Responsibility!
}
}
Kiedy wybrać Symfony?
- Projekt wymaga wysokiej modularności i długiego utrzymania
- Zespół zna już Symfony lub Magento (znane konwencje DI)
- Mikroserwisy gdzie potrzebujesz tylko wybranych komponentów
- Projekt enterprise gdzie separacja warstw ma duże znaczenie
- Potrzebujesz Doctrine z zaawansowanym mapowaniem encji
Kiedy wybrać Laravel?
- Szybkie MVP lub prototyp – mniej boilerplate, szybszy start
- Mniejszy zespół lub solo developer
- CRUD-heavy aplikacje gdzie Active Record Eloquent przyspiesza pracę
- Bogaty ekosystem pakietów (Cashier, Sanctum, Horizon, Nova)
- Projekt który nie potrzebuje pełnej architektury DDD
Porównanie kluczowych cech
| Aspekt | Symfony | Laravel |
|---|---|---|
| Krzywa uczenia | Stroma | Łagodna |
| Zbliżony do Magento 2 | Tak (te same komponenty) | Nie |
| ORM | Doctrine (Data Mapper) | Eloquent (Active Record) |
| DI Container | Rozbudowany, YAML/XML | Prosty, PHP array |
| Szablony | Twig | Blade |
| Ekosystem | Komponenty niezależne | Zintegrowane pakiety Laravel |
| Popularność (2023) | Duża (enterprise) | Bardzo duża (ogólnie) |
| Testowanie | Doskonałe | Bardzo dobre |
Podsumowanie
Dla developera Magento 2 Symfony to mniejszy przeskok myślowy – konwencje DI, EventDispatcher i struktura projektu są znajome. Laravel wymaga resetowania nawyków, ale wynagradza szybkością budowania. Nie ma złego wyboru – oba frameworki są dojrzałe, dobrze utrzymane i mają świetną dokumentację. Wybierz na podstawie wymagań projektu i doświadczenia zespołu, nie na podstawie marketingu.
