PHP Frameworks: Laravel & Symfony
Laravel — Key Concepts
Laravel is the most popular PHP framework. It follows MVC, ships with Eloquent ORM, Artisan CLI, queues, broadcasting, and a rich ecosystem.
# Create project
composer create-project laravel/laravel myapp
cd myapp && php artisan serve
# Artisan commands
php artisan make:model Article -mcr # model + migration + controller + resource
php artisan make:controller ArticleController --resource
php artisan make:middleware EnsureIsAdmin
php artisan make:job SendWelcomeEmail
php artisan make:event OrderPlaced
php artisan make:listener SendOrderConfirmation --event=OrderPlaced
php artisan make:mail WelcomeMail --markdown
php artisan make:request StoreArticleRequest
php artisan make:policy ArticlePolicy --model=Article
php artisan migrate
php artisan migrate:fresh --seed
php artisan db:seed
php artisan tinker # REPL<?php
// Routes (routes/web.php or routes/api.php)
Route::get('/articles', [ArticleController::class, 'index']);
Route::apiResource('articles', ArticleController::class); // all CRUD routes
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
// Eloquent ORM
class Article extends Model
{
protected $fillable = ['title', 'content', 'user_id'];
protected $casts = ['published_at' => 'datetime', 'is_featured' => 'boolean'];
public function user(): BelongsTo { return $this->belongsTo(User::class); }
public function tags(): BelongsToMany { return $this->belongsToMany(Tag::class); }
public function scopePublished(Builder $query): Builder {
return $query->whereNotNull('published_at')->where('published_at', '<=', now());
}
}
// Queries
Article::all();
Article::find(1);
Article::where('user_id', $userId)->latest()->paginate(15);
Article::published()->with('user', 'tags')->get(); // eager load
Article::create(['title' => 'New', 'content' => '...', 'user_id' => auth()->id()]);
$article->update(['title' => 'Updated']);
$article->delete();
// Form Request validation
class StoreArticleRequest extends FormRequest {
public function rules(): array {
return [
'title' => ['required', 'string', 'min:5', 'max:200'],
'content' => ['required', 'string', 'min:50'],
'tags' => ['array', 'exists:tags,id'],
];
}
}
// Controller
class ArticleController extends Controller {
public function store(StoreArticleRequest $request): JsonResponse {
$article = Article::create($request->validated() + ['user_id' => auth()->id()]);
$article->tags()->sync($request->tags ?? []);
return response()->json($article->load('tags'), 201);
}
}Laravel — Queues, Events & Cache
<?php
// Jobs (queued background work)
class SendWelcomeEmail implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(private User $user) {}
public function handle(Mailer $mailer): void {
$mailer->to($this->user)->send(new WelcomeMail($this->user));
}
public function failed(\Throwable $e): void {
Log::error("Failed to send welcome email to {$this->user->email}");
}
}
// Dispatch: SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(5));
// Events & Listeners
class OrderPlaced {
public function __construct(public readonly Order $order) {}
}
// Dispatch: event(new OrderPlaced($order));
// Listener: public function handle(OrderPlaced $event): void { ... }
// Cache
Cache::put('key', $value, ttl: 3600);
Cache::get('key', default: fn() => expensiveQuery());
Cache::remember('articles', 600, fn() => Article::published()->get());
Cache::forget('key');
Cache::tags(['articles'])->remember('published', 600, fn() => ...);
Cache::tags(['articles'])->flush();Symfony — Key Concepts
Symfony is a modular enterprise framework. Many Laravel internals are built on Symfony components. Symfony favors explicit configuration and is the foundation for Drupal, API Platform, and more.
# Create project
composer create-project symfony/skeleton myapp # minimal
composer create-project symfony/webapp myapp # full stack
# Symfony CLI
symfony new myapp
symfony server:start
symfony console make:controller ArticleController
symfony console make:entity Article
symfony console doctrine:migrations:diff
symfony console doctrine:migrations:migrate
symfony console cache:clear<?php
// Controller
#[Route('/api/articles', name: 'article_')]
class ArticleController extends AbstractController
{
public function __construct(
private ArticleRepository $articles,
private EntityManagerInterface $em,
) {}
#[Route('', name: 'index', methods: ['GET'])]
public function index(Request $request): JsonResponse
{
$page = $request->query->getInt('page', 1);
$articles = $this->articles->findPublished($page);
return $this->json($articles);
}
#[Route('/{id}', name: 'show', methods: ['GET'])]
public function show(Article $article): JsonResponse // ParamConverter auto-fetches by id
{
return $this->json($article, context: ['groups' => ['article:read']]);
}
#[Route('', name: 'create', methods: ['POST'])]
#[IsGranted('ROLE_USER')]
public function create(Request $request, ValidatorInterface $validator): JsonResponse
{
$article = $this->serializer->deserialize($request->getContent(), Article::class, 'json');
$article->setAuthor($this->getUser());
$errors = $validator->validate($article);
if (count($errors) > 0) {
return $this->json($errors, 422);
}
$this->em->persist($article);
$this->em->flush();
return $this->json($article, 201, context: ['groups' => 'article:read']);
}
}
// Doctrine Entity
#[ORM\Entity(repositoryClass: ArticleRepository::class)]
#[ORM\HasLifecycleCallbacks]
class Article
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 200)]
#[Assert\NotBlank, Assert\Length(min: 5, max: 200)]
private string $title = '';
#[ORM\ManyToOne(inversedBy: 'articles')]
private ?User $author = null;
#[ORM\PrePersist]
public function onPrePersist(): void {
$this->createdAt = new \DateTimeImmutable();
}
}Laravel vs Symfony — When to Use
Laravel: rapid application development, startups, SaaS products, REST APIs. Convention over configuration — sensible defaults out of the box.
Symfony: enterprise applications, large teams, complex domain logic, when you need explicit control over every component.
API Platform (Symfony): if you need a full REST/GraphQL API with OpenAPI docs auto-generated — it reads Doctrine entities and generates everything.
Both use Composer, PSR standards, and PHPUnit. Skills transfer between them.
Livewire (Laravel) / Hotwire (Symfony): add dynamic UI without writing JavaScript — server-rendered reactive components.
Laravel Octane: run Laravel on Swoole or RoadRunner for persistent process model — 5-10x more throughput.
Symfony Messenger: framework-agnostic message bus for commands/events/queries — cleaner than Laravel Jobs for complex CQRS setups.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free