PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Laravel vs Symfony – DI, Doctrine vs Eloquent, when to choose which

by Henryk Tews / Tuesday, 14 March 2023 / Published in Laravel, PHP, Symfony

Laravel and Symfony are the two dominant PHP frameworks, and choosing between them is a real decision in every new project. They share a Symfony core (Laravel uses several Symfony components) but have fundamentally different philosophies: Symfony favours explicitness and flexibility, Laravel favours convention and developer ergonomics. I compare them where it matters most for a PHP developer making an architecture choice.

Philosophy – the core difference

Symfony: explicit configuration, stable contracts, enterprise-grade flexibility. Every behaviour is configured; nothing happens by magic. Steep learning curve, exceptional long-term maintainability.

Laravel: convention over configuration, developer happiness, rapid prototyping. Things work out of the box with sensible defaults. Faster to start, some magic can surprise you later.

Dependency Injection

<?php

// Symfony DI - explicit, XML/YAML/PHP config, auto-wiring optional
// services.yaml:
// App\Service\OrderService:
//     arguments:
//         $repository: '@App\Repository\OrderRepository'
//         $logger: '@monolog.logger'

class OrderService
{
    public function __construct(
        private OrderRepository $repository,
        private LoggerInterface $logger
    ) {}
}

// Laravel DI - auto-wiring by default, service providers for explicit binding
// Automatically resolves OrderRepository from the constructor type hint

class OrderService
{
    public function __construct(
        private OrderRepository $repository,
        private LoggerInterface $logger  // resolved via service provider binding
    ) {}
}

// Laravel service provider
class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(LoggerInterface::class, FileLogger::class);
        $this->app->singleton(OrderRepository::class, fn($app) =>
            new OrderRepository($app->make(DatabaseConnection::class))
        );
    }
}

ORM – Doctrine vs Eloquent

<?php

// Doctrine (Symfony default) - Data Mapper pattern
// Entity knows nothing about persistence
#[\Doctrine\ORM\Mapping\Entity]
#[\Doctrine\ORM\Mapping\Table(name: 'orders')]
class Order
{
    #[\Doctrine\ORM\Mapping\Id]
    #[\Doctrine\ORM\Mapping\GeneratedValue]
    private ?int $id = null;
    private string $status = 'pending';
    private float $total   = 0.0;

    public function getId(): ?int    { return $this->id; }
    public function getStatus(): string { return $this->status; }
    public function getTotal(): float   { return $this->total; }
}

// Repository handles all DB interaction - entity stays clean
class OrderRepository
{
    public function __construct(private EntityManagerInterface $em) {}

    public function find(int $id): ?Order
    {
        return $this->em->find(Order::class, $id);
    }

    public function findPending(): array
    {
        return $this->em->getRepository(Order::class)
            ->findBy(['status' => 'pending']);
    }

    public function save(Order $order): void
    {
        $this->em->persist($order);
        $this->em->flush();
    }
}
<?php

// Eloquent (Laravel default) - Active Record pattern
// Model knows about persistence, provides fluent query builder
class Order extends \Illuminate\Database\Eloquent\Model
{
    protected $table    = 'orders';
    protected $fillable = ['status', 'total', 'customer_id'];

    // Relationships
    public function customer(): \Illuminate\Database\Eloquent\Relations\BelongsTo
    {
        return $this->belongsTo(Customer::class);
    }

    public function items(): \Illuminate\Database\Eloquent\Relations\HasMany
    {
        return $this->hasMany(OrderItem::class);
    }
}

// Usage - fluent, expressive, less boilerplate
$pending = Order::where('status', 'pending')->with('customer')->get();
$order   = Order::find(42);
$order->update(['status' => 'processing']);

// N+1 prevention
$orders = Order::with(['items', 'customer'])->paginate(20); // eager loading

When to choose which

Criterion Choose Symfony Choose Laravel
Project scale Large, long-lived enterprise app Small to medium, startup speed matters
Team size Larger team, strict contracts needed Small team, fast iteration
Domain complexity Complex domain logic (DDD) CRUD-heavy application
API-only backend Symfony API Platform Laravel API Resources
Magento developer More familiar (DI, events) Different conventions
Community packages Large Larger (more “batteries included”)

Shared DNA – Symfony components in Laravel

# Laravel uses these Symfony components under the hood:
composer show | grep symfony/

# symfony/console    - Artisan CLI
# symfony/finder     - file system traversal
# symfony/http-foundation - Request/Response
# symfony/mailer     - email sending
# symfony/process    - executing processes
# symfony/routing    - URL routing
# symfony/var-dumper - dd() and dump()

# Point: both frameworks share the same quality foundation

Summary

Laravel and Symfony are not competitors in the way React and Vue are – they target different project profiles. Symfony is the right choice when you need explicit contracts, strict separation of concerns, and long-term maintainability in a large codebase. Laravel is the right choice when developer velocity matters more and the codebase will be owned by a small team. For Magento developers, Symfony’s DI container and event system will feel immediately familiar – the architecture philosophy is very similar.

About Henryk Tews

What you can read next

PHP 7.2 – object type hint, sodium instead of mcrypt, deprecations
Symfony components without the framework – Console, Validator, HttpClient

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}