MFormations
Modern PHP Engineering

Chapitre 22

22 — Simulations d'Entretiens Techniques

22 — Simulations d'Entretiens Techniques

Cours 22 — Interviews PHP

22.1 Questions Comportementales (STAR Method)

Méthode STAR

S — Situation : Contexte dans lequel vous étiez
T — Task : Tâche que vous deviez accomplir
A — Action : Actions que vous avez entreprises
R — Result : Résultats obtenus (chiffrés si possible)

30 Questions Comportementales

Question 1 : Parlez-moi d'un projet complexe que vous avez mené.

S : Projet de migration d'une application legacy PHP 5.6 vers PHP 8.2 avec 500k+ utilisateurs. T : Moderniser l'architecture sans downtime, remplacer le code procédural par OOP. A : J'ai proposé une migration progressive par modules, mis en place PHPStan niveau max, ajouté des tests de régression (Pest), créé un ADR pour documenter les décisions. R : Migration terminée en 4 mois, 0 incident, performance améliorée de 40%, dette technique réduite de 60%.

Question 2 : Décrivez une situation où vous avez géré un conflit technique.

S : Désaccord avec un collègue sur l'utilisation de Livewire vs Inertia.js pour un nouveau module. T : Choisir la bonne technologie et maintenir la cohésion d'équipe. A : J'ai organisé un coding dojo de 2 jours où nous avons prototype les deux approches, puis comparé : performance, DX, maintenabilité. R : L'équipe a choisi Livewire à l'unanimité, le projet a livré 2 semaines avant la date prévue.

Question 3 : Avez-vous déjà dû livrer sous une pression extrême ?

S : Bug critique en production un vendredi 18h — les paiements étaient doublés. T : Corriger le bug et rembourser les clients impactés. A : J'ai identifié le bug (race condition sur les webhooks Stripe), appliqué un hotfix avec queue déduplication, écrit un script de remboursement automatisé, ajouté un test de régression. R : Bug corrigé en 45 minutes, 120 clients remboursés automatiquement, 0 perte pour l'entreprise.

Question 4 : Comment gérez-vous l'échec ?

S : J'ai déployé une mise à jour qui a cassé l'API pour 10% des utilisateurs. T : Résoudre l'incident et éviter qu'il ne se reproduise. A : J'ai immédiatement revert le déploiement, communiqué sur le statut, analysé la cause (manque de tests d'intégration), mis en place une CI avec tests automatisés. R : Plus aucun déploiement cassé depuis. J'ai aussi présenté un REX lors du rétrospective d'équipe.

Question 5 : Décrivez comment vous avez mentoré un développeur junior.

S : Nouveau développeur junior dans l'équipe, bon en PHP mais découvrait Laravel. T : L'aider à monter en compétence rapidement. A : Mise en place de pair programming 2x/semaine, revue de code systématique, création de guides internes. R : Après 3 mois, le junior livrait en autonomie. Il a été promu confirmé à 6 mois.

Question 6 : Comment priorisez-vous la dette technique ?

S : Projet avec 40% de dette technique, tests lents (45 min), code non typé. T : Réduire la dette sans bloquer les features. A : Création d'un backlog dette technique, règle des 20% (20% de chaque sprint pour la dette), prioritisation par risque business. R : Dette réduite de 60% en 6 mois, tests passés à 5 minutes, PHPStan niveau max.

Question 7 : Parlez-moi d'une initiative d'amélioration continue que vous avez menée.

S : L'équipe n'avait pas de standard de code. T : Implémenter des outils de qualité automatisés. A : J'ai proposé et configuré : Pint (PSR-12), PHPStan niveau 9, Pint dans pre-commit hook, CI avec tests. R : Réduction de 80% des erreurs de style en PR, reviews plus rapides.

Question 8 : Comment gérez-vous le désaccord avec un supérieur ?

S : Mon CTO voulait utiliser MongoDB, mais les données étaient résolument relationnelles. T : Proposer une meilleure solution sans créer de conflit. A : J'ai préparé une comparaison (MongoDB vs PostgreSQL) avec des benchmarks sur nos données réelles, montré les avantages coûts/maintenance. R : PostgreSQL adopté, économie de 30% sur l'infrastructure.

Question 9 : Racontez un projet où vous avez dû apprendre une nouvelle technologie rapidement.

S : Projet client nécessitant Livewire que je ne connaissais pas. T : Livrer un module Livewire en 2 semaines. A : Formation intensive via Laracasts (2 jours), POC, pair programming avec un expert. R : Module livré dans les temps, utilisé comme référence pour les projets suivants.

Question 10 : Comment assurez-vous la qualité du code en équipe ?

S : Équipe en croissance rapide, qualité hétérogène. T : Standardiser la qualité. A : Mise en place de PR template, checklist qualité, code review guidelines, CI avec analyse statique, mob programming sur les sujets complexes. R : Qualité homogène, vélocité augmentée de 20%.

Question 11 : Décrivez un moment où vous avez dû refactorer un code legacy.

S : Classe de 3000 lignes avec 15 responsabilités. T : Refactorer sans casser le comportement. A : Écriture de tests de caractérisation, extraction progressive par responsabilité, Strangler Fig Pattern. R : Classe divisée en 8 classes, tests couvrant 95% du code.

Question 12 : Comment gérez-vous le stress en période de crise ?

S : Crash base de données un lundi matin. T : Rétablir le service. A : J'ai coordonné l'équipe (1 personne aux logs, 1 à la restauration, 1 à la communication), communiqué toutes les 15 minutes. R : Service rétabli en 2h, client informé en continu, plan d'action correctif mis en place.

Question 13 : Racontez une situation où vous avez dû faire un compromis technique.

S : Deadline serrée, tests E2E trop longs. T : Livrer à temps sans sacrifier la qualité. A : Compromis : réduire les tests E2E aux parcours critiques, augmenter les tests unitaires, planifier les tests E2E manquants après la release. R : Livraison à l'heure, tests E2E critiques passés, backlog pour les tests restants.

Question 14 : Comment avez-vous contribué à l'agilité de l'équipe ?

S : Daily meetings trop longs (45 min), rétrospectives inefficaces. T : Améliorer les rituels agiles. A : Proposition de timeboxing (15 min daily), format de rétro "Start/Stop/Continue", utilisation de outils visuels. R : Daily réduits à 15 min, rétrospectives plus actionnables, vélocité +15%.

Question 15 : Décrivez une situation où vous avez fait preuve de leadership technique.

S : Projet bloqué par des choix architecturaux. T : Débloquer l'équipe. A : Organisation d'un Architecture Decision Record (ADR) workshop, facilitation de la décision collective. R : Décision prise en 2 jours, implémentation démarrée, ADR documenté pour référence future.

Question 16 : Parlez-moi d'un bug complexe que vous avez résolu.

S : Bug intermittent en production, impossible à reproduire en dev. T : Trouver la cause racine. A : Ajout de logging contextuel, analyse des patterns (heure, utilisateur, données), instrumentation avec Blackfire. R : Bug identifié : race condition sur cache Redis. Solution : atomic locks.

Question 17 : Comment gérez-vous le feedback négatif ?

S : Review sévère sur une PR. T : Améliorer la qualité de mon code. A : J'ai remercié le reviewer, listé les points, créé une checklist personnelle, demandé une session pair programming. R : Mes PR suivantes ont été approuvées plus rapidement, j'ai adopté ces standards dans mon workflow.

Question 18 : Racontez un projet réussi grâce à la collaboration.

S : Construction d'une API publique avec 3 équipes. T : Coordination inter-équipes. A : Mise en place de API guild, spécifications partagées (OpenAPI), contrat d'interface, démos bi-hebdomadaires. R : API livrée à temps, 0 breaking change après la release.

Question 19 : Comment restez-vous à jour techniquement ?

  • Veille quotidienne (15 min) : newsletters, Reddit, Twitter
  • Conférences : 2/an (Laracon, SymfonyCon)
  • Open source : contribution à Spatie packages, Laravel core
  • Formation : Laracasts, SymfonyCasts, livres techniques
  • Projets personnels : side projects pour expérimenter

Question 20 : Parlez-moi d'un échec technique et ce que vous en avez appris.

S : Indexation ElasticSearch sans versioning. T : Corriger les données corrompues. A : Ajout de versioning, migration, tests de régression. R : Leçon : toujours versionner les schémas de données. Mis en place dans toutes les équipes.

Question 21 : Comment abordez-vous l'estimation d'un projet ?

S : Projet complexe, fortes incertitudes. T : Estimer réalistement. A : Découpage en user stories, planning poker, identification des risques, marge de 20%. R : Estimation à 3 semaines → livré en 3.5 semaines (dans la marge).

Question 22 : Décrivez une situation où vous avez dû dire non.

S : Feature urgente demandée par le CEO sans analyse d'impact. T : Éviter une décision précipitée. A : Proposition d'une analyse d'impact rapide (2h) avant de dire oui, identification des risques. R : Feature reportée au sprint suivant, développement plus simple que prévu grâce à l'analyse.

Question 23 : Comment gérez-vous plusieurs priorités concurrentes ?

S : 3 features bloquantes en parallèle. T : Prioriser sans mécontenter les stakeholders. A : Matrice impact/effort, communication transparente, négociation des délais, focus sur une priorité à la fois. R : Priorisation acceptée par tous, livraison sans burnout.

Question 24 : Parlez-moi d'une innovation que vous avez apportée.

S : Processus de déploiement manuel long et risqué. T : Automatiser. A : Mise en place de CI/CD avec GitHub Actions + Deployer, déploiements blue/green. R : Déploiement passé de 1h à 5 minutes, zéro downtime.

Question 25 : Comment intégrez-vous la sécurité dans votre développement ?

  • Security review dans la définition of done
  • OWASP Top 10 checklist en PR
  • Tests de sécurité automatisés (SQL injection, XSS)
  • Revue de dépendances avec composer audit
  • Sessions de sécurité avec l'équipe

Question 26 : Décrivez votre gestion du temps au quotidien.

  • Pomodoro (25 min focus / 5 min break)
  • Matin : tâches complexes (code, architecture)
  • Midi : veille technique
  • Après-midi : meetings, reviews, documentation
  • Blocage de 2h sans meeting

Question 27 : Racontez un projet où vous avez dépassé les attentes.

S : Projet de migration cloud. T : Migrer vers AWS. A : Au-delà de la migration, j'ai optimisé les coûts (réserved instances, auto-scaling), amélioré la sécurité (WAF, Secrets Manager). R : Coûts réduits de 40% vs prévision, sécurité améliorée, documentation complète.

Question 28 : Comment gérez-vous le code des autres ?

  • Bienveillance : "improve not blame"
  • Suggestions avec explications (pourquoi)
  • PR reviews : équilibrer qualité et vélocité
  • Standards clairs (checklist)
  • Mentorat si nécessaire

Question 29 : Parlez-moi de votre expérience avec la revue de code.

  • Approche : revue fonctionnelle + technique + style
  • 30 min max par PR (> 400 lignes → décomposer)
  • Suggestions de code plutôt qu'ordres
  • Approbation conditionnelle si mineur
  • Objectif : partager la connaissance

Question 30 : Où vous voyez-vous dans 5 ans ?

  • Lead Developer / Architecte PHP
  • Contribution open-source significative (Laravel core, Spatie)
  • Speaker en conférence
  • Mentorat de développeurs juniors
  • Veille continue sur l'évolution du langage

22.2 Questions Techniques — PHP 8 Features

PHP 8.0

<?php

// Q1 : Qu'est-ce que les named arguments ?
// R : Permettent de passer des arguments par nom
function createUser(
    string $name,
    string $email,
    bool $isAdmin = false,
    ?string $phone = null,
): User {}

createUser(
    name: 'John',
    email: 'john@example.com',
    isAdmin: true,
);

// Q2 : Qu'est-ce que les attributes ?
// R : Métadonnées structurées pour classes, méthodes, propriétés
#[Route('/api/users', methods: ['GET'])]
class UserController {}

// Q3 : Match expression vs switch
$status = match ($code) {
    200, 201 => 'success',
    400, 422 => 'client_error',
    500 => 'server_error',
    default => 'unknown',
};

// Q4 : Nullsafe operator
$city = $user?->address?->city;

// Q5 : Constructor promotion
class User
{
    public function __construct(
        readonly public string $name,
        readonly public string $email,
    ) {}
}

// Q6 : Union types
function process(int|string $input): int|float {}

// Q7 : Stringable interface
class User implements \Stringable
{
    public function __toString(): string
    {
        return $this->name;
    }
}

PHP 8.1

<?php

// Q8 : Enums
enum Status: string
{
    case Pending = 'pending';
    case Approved = 'approved';
    case Rejected = 'rejected';

    public function label(): string
    {
        return match ($this) {
            self::Pending => 'En attente',
            self::Approved => 'Approuvé',
            self::Rejected => 'Rejeté',
        };
    }
}

// Q9 : Readonly properties (avant PHP 8.2)
class Configuration
{
    public function __construct(
        readonly public array $values,
    ) {}
}

// Q10 : Fiber (async)
$fiber = new Fiber(function (): void {
    $result = Fiber::suspend('starting');
    // traitement
});
$value = $fiber->start();

// Q11 : Array unpacking avec string keys
$array1 = ['a' => 1];
$array2 = ['b' => 2, ...$array1];

// Q12 : First-class callable syntax
$callback = strlen(...);

// Q13 : Pure intersection types
function process(Countable&Iterator $collection): void {}

PHP 8.2

<?php

// Q14 : Readonly classes
readonly class Configuration
{
    public function __construct(
        public string $apiKey,
        public string $apiUrl,
    ) {}
}

// Q15 : DNF types
function process((Countable&ArrayAccess)|null $collection): void {}

// Q16 : true type
function isAdmin(): true
{
    return true;
}

// Q17 : Constants in traits
trait HttpStatus
{
    final const OK = 200;
    final const CREATED = 201;
}

PHP 8.3

<?php

// Q18 : json_validate
$valid = json_validate('{"name": "John"}'); // true

// Q19 : Deep cloning readonly
readonly class Config
{
    public function __construct(
        public User $user,
    ) {}
}

// Q20 : Randomizer
$random = new \Random\Randomizer();
$token = bin2hex($random->getBytes(32));

PHP 8.4

<?php

// Q21 : Property hooks
class User
{
    public string $name {
        set => ucfirst(strtolower($value));
    }

    public string $email {
        set (string $value) {
            if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                throw new \InvalidArgumentException('Email invalide');
            }
            $this->email = $value;
        }
        get => strtolower($this->email);
    }
}

// Q22 : Asymmetric visibility
class Post
{
    public private(set) string $title;

    public function __construct(string $title)
    {
        $this->title = $title;
    }
}

// Q23 : Lazy objects
$initializer = function (User $user): User {
    $user->name = 'John';
    $user->email = 'john@example.com';
    return $user;
};

$user = \Symfony\Component\VarExporter\LazyGhostTrait::createLazyGhost(
    User::class,
    $initializer,
);

22.3 Questions Techniques — OOP & SOLID

SOLID

<?php

// Q24 : Single Responsibility Principle
// ❌ Une classe qui fait tout
class Invoice
{
    public function calculateTotal(): void {}
    public function saveToDatabase(): void {}
    public function sendEmail(): void {}
    public function generatePdf(): void {}
}

// ✅ Classes séparées
class Invoice {}
class InvoiceRepository {}
class InvoiceMailer {}
class InvoicePdfGenerator {}

// Q25 : Open-Closed Principle
// ❌ Modification pour ajouter un type
class PaymentProcessor
{
    public function process(string $type): void
    {
        if ($type === 'stripe') { /* ... */ }
        elseif ($type === 'paypal') { /* ... */ }
    }
}

// ✅ Extension sans modification
interface PaymentMethod
{
    public function process(): void;
}

class StripePayment implements PaymentMethod
{
    public function process(): void {}
}

class PaypalPayment implements PaymentMethod
{
    public function process(): void {}
}

class PaymentProcessor
{
    public function __construct(
        private PaymentMethod $method,
    ) {}

    public function process(): void
    {
        $this->method->process();
    }
}

// Q26 : Liskov Substitution
abstract class Bird
{
    abstract public function eat(): void;
}

abstract class FlyingBird extends Bird
{
    abstract public function fly(): void;
}

class Sparrow extends FlyingBird
{
    public function eat(): void {}
    public function fly(): void {}
}

class Penguin extends Bird
{
    public function eat(): void {}
    // Penguin ne vole pas → pas de fly()
}

// Q27 : Interface Segregation
// ❌ Interface trop grosse
interface Worker
{
    public function work(): void;
    public function eat(): void;
    public function sleep(): void;
}

// ✅ Interfaces spécifiques
interface Workable
{
    public function work(): void;
}

interface Eatable
{
    public function eat(): void;
}

interface Sleepable
{
    public function sleep(): void;
}

class Human implements Workable, Eatable, Sleepable {}
class Robot implements Workable {}

// Q28 : Dependency Inversion
// ❌ Dépendance concrète
class OrderService
{
    private StripeGateway $gateway;

    public function __construct()
    {
        $this->gateway = new StripeGateway();
    }
}

// ✅ Dépendance abstraite
class OrderService
{
    public function __construct(
        private PaymentGateway $gateway,
    ) {}
}

Design Patterns

<?php

// Q29 : Factory Pattern
interface Logger
{
    public function log(string $message): void;
}

class FileLogger implements Logger
{
    public function log(string $message): void
    {
        file_put_contents('app.log', $message . PHP_EOL, FILE_APPEND);
    }
}

class CloudLogger implements Logger
{
    public function log(string $message): void
    {
        // Envoyer vers CloudWatch
    }
}

class LoggerFactory
{
    public static function create(string $type): Logger
    {
        return match ($type) {
            'file' => new FileLogger(),
            'cloud' => new CloudLogger(),
            default => throw new \InvalidArgumentException("Type $type inconnu"),
        };
    }
}

// Q30 : Repository Pattern
interface UserRepositoryInterface
{
    public function find(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function save(User $user): void;
    public function delete(User $user): void;
}

class EloquentUserRepository implements UserRepositoryInterface
{
    public function find(int $id): ?User
    {
        return User::find($id);
    }
}

22.4 Questions Techniques — Laravel Internals

Service Container

<?php

// Q31 : Comment fonctionne le Service Container ?
// R : Conteneur d'injection de dépendances qui gère l'instanciation et la résolution

// Binding simple
app()->bind('PaymentGateway', function () {
    return new StripeGateway(config('services.stripe.secret'));
});

// Binding singleton
app()->singleton('cache', function () {
    return new CacheManager();
});

// Binding d'interface
app()->bind(UserRepositoryInterface::class, EloquentUserRepository::class);

// Résolution
$gateway = app('PaymentGateway');
$gateway = resolve('PaymentGateway');
$gateway = app()->make('PaymentGateway');

// Auto-resolution
class UserController
{
    public function __construct(
        private UserRepositoryInterface $users, // Résolu automatiquement
    ) {}
}

// Contextual binding
app()->when(PhotoController::class)
    ->needs(Filesystem::class)
    ->give(function () {
        return Storage::disk('s3');
    });

Service Providers

<?php

// Q32 : Cycle de vie des Service Providers
// R : Enregistrement → Boot

class AppServiceProvider extends ServiceProvider
{
    // 1. register() : bindings dans le container (ne pas utiliser de services résolus)
    public function register(): void
    {
        $this->app->bind(PaymentGateway::class, StripeGateway::class);
        $this->app->singleton(Logger::class, CloudLogger::class);
    }

    // 2. boot() : après que tous les providers sont enregistrés
    public function boot(): void
    {
        // Services résolus disponibles ici
        $gateway = $this->app->make(PaymentGateway::class);

        // Publisher de config
        $this->publishes([
            __DIR__.'/../config/monpackage.php' => config_path('monpackage.php'),
        ], 'config');
    }
}

// Ordre d'exécution :
// 1. Tous les register()
// 2. Tous les boot()
// 3. Les providers sont cached en production

Facades

<?php

// Q33 : Comment fonctionnent les Facades ?
// R : Proxy statique vers une instance résolue du container

// Définition
class Cache extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return 'cache'; // Clé dans le container
    }
}

// Utilisation
Cache::get('key'); // Équivalent à : app('cache')->get('key')
Cache::put('key', 'value', 3600);

// Résolution réelle
// Cache::get() → __callStatic → app('cache')->get()

// Avantages : syntaxe concise, testable (Facade::shouldReceive)
// Inconvénients :耦合 statique, moins explicite

Eloquent

<?php

// Q34 : Expliquez l'Eloquent ORM
// R : Active Record implementation — chaque modèle = une table

class User extends Model
{
    // Table associée (déduite : 'users' pour User)
    protected $table = 'users';

    // Relations
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }

    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class)
            ->withTimestamps();
    }

    // Scopes
    public function scopeActive(Builder $query): Builder
    {
        return $query->where('active', true);
    }

    // Accessors / Mutators
    protected function fullName(): Attribute
    {
        return Attribute::make(
            get: fn ($value) => "{$this->first_name} {$this->last_name}",
        );
    }
}

// Utilisation
$activeUsers = User::active()->get();
$user = User::with('posts.comments')->find(1);

Queues

<?php

// Q35 : Comment fonctionne le système de queues ?
// R : Délégation de tâches asynchrones

// Job
class SendWelcomeEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        private User $user,
    ) {}

    public function handle(): void
    {
        Mail::to($this->user)->send(new WelcomeMail($this->user));
    }

    public function failed(\Throwable $e): void
    {
        Log::error('Welcome email failed', [
            'user' => $this->user->id,
            'error' => $e->getMessage(),
        ]);
    }
}

// Dispatch
SendWelcomeEmail::dispatch($user)->onQueue('emails');
dispatch(new SendWelcomeEmail($user));

// Configuration
// config/queue.php : 'default' => env('QUEUE_CONNECTION', 'redis')
// Horizon pour monitoring

22.5 Questions Techniques — Performance

<?php

// Q36 : Optimisation des requêtes Eloquent
// ❌ N+1 problem
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name; // N queries !
}

// ✅ Eager loading
$posts = Post::with('author')->get();

// ✅ Lazy eager loading
$posts = Post::all();
$posts->load('author');

// ✅ Subqueries
$users = User::addSelect([
    'last_post_at' => Post::select('created_at')
        ->whereColumn('user_id', 'users.id')
        ->latest()
        ->limit(1),
])->get();

// Q37 : Cache
// Mise en cache des requêtes fréquentes
$users = Cache::remember('active_users', 3600, function () {
    return User::active()->with('posts')->get();
});

// Cache tags (Redis/Memcached)
Cache::tags(['users', 'stats'])->put('active_count', 150, 3600);

// Q38 : Indexation
Schema::table('posts', function (Blueprint $table) {
    $table->index('user_id');
    $table->index(['published_at', 'status']);
    $table->fullText('content');
    // Éviter les indexes composites inutiles
});

// Q39 : Chunk pour gros volumes
User::chunk(1000, function ($users) {
    foreach ($users as $user) {
        // Traitement par lots
    }
});

// lazy() pour économiser la mémoire
foreach (User::lazy(500) as $user) {
    // Traitement sans tout charger
}

// Q40 : JIT PHP 8
// Activation dans php.ini
opcache.jit = tracing
opcache.jit_buffer_size = 256M

// Benchmarks
// PHP 7.4 → PHP 8.4 : jusqu'à 3x plus rapide sur CPU-bound
// Application web : 20-40% d'amélioration

22.6 Questions Techniques — Sécurité

<?php

// Q41 : SQL Injection
// ❌ Vulnérable
$users = DB::select("SELECT * FROM users WHERE id = $id");

// ✅ Safe
$users = DB::select('SELECT * FROM users WHERE id = ?', [$id]);

// ✅ Eloquent (safe)
User::where('id', $id)->get();

// Q42 : XSS
// ❌ Vulnérable
<div>{{ $userInput }}</div>
// → <script>alert('xss')</script>

// ✅ Blade échappe automatiquement
<div>{{ $userInput }}</div>

// ❌ Raw non échappé
{!! $userInput !!}

// Q43 : CSRF
// Protection automatique via @csrf
<form method="POST">
    @csrf
</form>

// Exception pour API
Route::get('/api/users', fn() => ...)->withoutMiddleware([VerifyCsrfToken::class]);

// Q44 : Mass assignment
class User extends Model
{
    // ✅ Whitelist
    protected $fillable = ['name', 'email'];

    // ❌ Blacklist (moins sûr)
    protected $guarded = ['is_admin'];
}

22.7 System Design

Concevoir une API REST

**1. Définir les ressources**
- /api/users — CRUD utilisateurs
- /api/posts — CRUD articles
- /api/posts/{post}/comments — CRUD commentaires

**2. Choix techniques**
- Laravel + Sanctum (authentication)
- API Resources (transformation)
- Rate limiting : 100 req/min
- Pagination : cursor-based
- Versioning : Accept header (application/vnd.api.v1+json)

**3. Patterns**
- Repository pattern
- Action classes
- Form requests
- API Resources
- Events pour actions secondaires

**4. Sécurité**
- Sanctum tokens
- Scopes (posts:read, posts:write)
- Throttling
- Validation
- SQL injection protection

**5. Performance**
- Eager loading
- Sparse fieldsets
- Include (relations)
- Cache
- Pagination
- Indexation base de données

Concevoir un CMS modulaire

**1. Architecture**
- Modules : Pages, Posts, Media, Users, Settings
- Module system (Laravel modules : nwidart/laravel-modules)
- Event-driven : ModuleEvents

**2. Data Model**
- Page : id, title, slug, content, status, author_id
- Post : id, title, slug, excerpt, content, category_id, tags
- Media : id, name, path, type, size, user_id
- User : id, name, email, role_id

**3. Admin**
- Filament admin panel
- Permissions (spatie/laravel-permission)
- Activity log (spatie/laravel-activitylog)
- Media library (spatie/laravel-medialibrary)

**4. Cache**
- Page cache (full response)
- Fragment cache (menus, widgets)
- Query cache
- Static site generation

Concevoir une SaaS

**1. Architecture Multi-tenant**
- Database per tenant
- Schema per tenant
- Tenancy (stancl/tenancy)

**2. Plan management**
- Plans table : id, name, price, features (JSON)
- Subscription : Stripe/Billable
- Features : feature flags

**3. Scaling**
- Read replicas
- Queues (Horizon)
- Cache (Redis)
- CDN assets
- Auto-scaling

**4. Monitoring**
- Pulse pour métriques en temps réel
- Telescope pour débogage
- Sentry pour erreurs
- Horizon pour queues
- Logs centralisés

22.8 Coding Challenges

FizzBuzz en PHP 8

<?php

$fizzbuzz = fn (int $n): string => match (true) {
    $n % 15 === 0 => 'FizzBuzz',
    $n % 3 === 0  => 'Fizz',
    $n % 5 === 0  => 'Buzz',
    default       => (string) $n,
};

// Avec itérateur
$results = array_map($fizzbuzz, range(1, 100));
echo implode("\n", $results);

Livewire Component

<?php

namespace App\Livewire;

use Livewire\Component;
use Livewire\WithPagination;
use App\Models\Post;

class PostList extends Component
{
    use WithPagination;

    public string $search = '';
    public string $sortField = 'created_at';
    public string $sortDirection = 'desc';
    public int $perPage = 10;

    protected $queryString = [
        'search' => ['except' => ''],
        'sortField' => ['except' => 'created_at'],
        'sortDirection' => ['except' => 'desc'],
    ];

    public function sortBy(string $field): void
    {
        if ($this->sortField === $field) {
            $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
        } else {
            $this->sortField = $field;
            $this->sortDirection = 'asc';
        }
    }

    public function updatingSearch(): void
    {
        $this->resetPage();
    }

    public function render()
    {
        $posts = Post::query()
            ->when($this->search, fn ($q) => $q
                ->where('title', 'like', "%{$this->search}%")
                ->orWhere('content', 'like', "%{$this->search}%")
            )
            ->orderBy($this->sortField, $this->sortDirection)
            ->paginate($this->perPage);

        return view('livewire.post-list', [
            'posts' => $posts,
        ]);
    }
}
<div>
    <div class="mb-4">
        <input wire:model.live.debounce.300ms="search" type="search"
               placeholder="Rechercher..." class="form-input">
    </div>

    <table class="min-w-full">
        <thead>
            <tr>
                <th wire:click="sortBy('title')" class="cursor-pointer">
                    Titre @include('includes.sort-indicator', ['field' => 'title'])
                </th>
                <th wire:click="sortBy('created_at')" class="cursor-pointer">
                    Date @include('includes.sort-indicator', ['field' => 'created_at'])
                </th>
            </tr>
        </thead>
        <tbody>
            @foreach($posts as $post)
                <tr>
                    <td>{{ $post->title }}</td>
                    <td>{{ $post->created_at->format('d/m/Y') }}</td>
                </tr>
            @endforeach
        </tbody>
    </table>

    {{ $posts->links() }}
</div>

API Design Challenge

**Concevoir une API de réservation de rendez-vous médicaux**

Endpoints :
- GET /api/doctors — Liste des médecins (avec filtres : spécialité, ville)
- GET /api/doctors/{doctor} — Détail d'un médecin avec créneaux
- GET /api/doctors/{doctor}/slots — Créneaux disponibles
- POST /api/appointments — Créer un rendez-vous
- GET /api/appointments — Mes rendez-vous
- DELETE /api/appointments/{appointment} — Annuler

Contraintes :
- Créneaux de 30 minutes
- Plage horaire 9h-18h
- 1 patient par créneau
- Annulation jusqu'à 2h avant
- Notification par email

Modèle de données :
- Doctor : id, name, speciality, city, email
- Slot : id, doctor_id, start_time, end_time, is_available
- Appointment : id, doctor_id, patient_id, slot_id, status, created_at

Réponses :
- GET /api/doctors/{doctor}/slots?date=2025-06-15
{
    "data": [
        {"start": "09:00", "end": "09:30", "available": true},
        {"start": "09:30", "end": "10:00", "available": false},
    ]
}

- POST /api/appointments
{
    "doctor_id": 1,
    "slot_id": 5,
    "patient": {"name": "John", "email": "john@example.com"}
}