Chapitre 17
17 — Exercices PHP Modernes
17 — Exercices PHP Modernes
Chapitre 17 — Exercices PHP Modernes
17.0 Introduction
Ce chapitre contient 40 exercices progressifs couvrant l'ensemble du syllabus Modern PHP Engineering. Chaque exercice est conçu pour valider une compétence spécifique, de la POO PHP 8 aux architectures modernes.
17.1 Groupe A : OOP PHP 8 (Exercices 1-6)
Exercice 1 : Classes avec readonly properties
Créez une classe User PHP 8 avec :
readonlystring$uuidreadonlystring$emailreadonlyDateTimeImmutable$createdAtstring $namemutable- Un constructeur avec promotion de paramètres
- Une méthode
toArray(): array
class User
{
public function __construct(
public readonly string $uuid,
public readonly string $email,
public readonly DateTimeImmutable $createdAt,
public string $name,
) {}
public function toArray(): array
{
return [
'uuid' => $this->uuid,
'email' => $this->email,
'name' => $this->name,
'created_at' => $this->createdAt->format('c'),
];
}
}
Consigne : Ajoutez un Value Object Email qui valide le format et l'utilisez dans User.
Exercice 2 : Enums backed avec méthode statique
Créez un BackedEnum OrderStatus : Pending, Confirmed, Shipped, Delivered, Cancelled.
- Chaque case a un label en français
- Méthode statique
fromLabel(string $label): self - Méthode
canTransitionTo(OrderStatus $target): bool(ex: Cancelled → Pending interdit)
Implémentez une classe Order qui utilise cet enum.
Exercice 3 : Pattern Strategy avec interfaces
Système de notification :
- Interface
NotificationChannelavecsend(string $to, string $message): bool - Implémentez
EmailChannel,SMSChannel,SlackChannel - Classe
NotificationServicequi prend un tableau de channels - La méthode
notifyAll(string $to, string $message): arrayretourne les résultats
Exercice 4 : Generic-like collections typées
Créez une classe Collection générique en PHP 8 :
- Support de
array<T>avec@template T - Méthodes :
map(callable): static,filter(callable): static,first(): ?T,toArray(): array - Implémentez
ArrayAccess,Countable,IteratorAggregate
/**
* @template T
*/
class Collection implements \ArrayAccess, \Countable, \IteratorAggregate
{
/** @param array<T> $items */
public function __construct(private array $items = []) {}
/** @return Collection<U> */
public function map(callable $callback): Collection
{
return new self(array_map($callback, $this->items));
}
/** @return Collection<T> */
public function filter(callable $callback): Collection
{
return new self(array_values(array_filter($this->items, $callback)));
}
/** @return ?T */
public function first(): mixed
{
return $this->items[0] ?? null;
}
/** @return array<T> */
public function toArray(): array
{
return $this->items;
}
public function offsetExists(mixed $offset): bool
{
return isset($this->items[$offset]);
}
public function offsetGet(mixed $offset): mixed
{
return $this->items[$offset] ?? null;
}
public function offsetSet(mixed $offset, mixed $value): void
{
$offset === null ? $this->items[] = $value : $this->items[$offset] = $value;
}
public function offsetUnset(mixed $offset): void
{
unset($this->items[$offset]);
}
public function count(): int
{
return count($this->items);
}
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator($this->items);
}
}
Exercice 5 : Value Objects immutables
Créez un Value Object Money :
int $centsprivé- Méthodes :
add(Money $other): Money,subtract(Money $other): Money,multiply(float $factor): Money static fromFloat(float $amount, string $currency): self__toString(): string→ affiche "12,34 €"- Implémentez l'immutabilité (toutes les opérations retournent une nouvelle instance)
Exercice 6 : Active Record vs Data Mapper
Créez deux approches pour un modèle Product :
- Active Record : Étendez une classe
ActiveRecordqui fait du CRUD via PDO magiquement - Data Mapper : Créez une classe
ProductMapperqui transforme entre tableau et objet
Comparez les deux approches en termes de testabilité et couplage.
17.2 Groupe B : PDO & ORM (Exercices 7-12)
Exercice 7 : CRUD avec requêtes préparées PDO
Créez un repository UserRepository avec PDO :
findById(int $id): ?UserfindByEmail(string $email): ?Usersave(User $user): void(INSERT ou UPDATE)delete(int $id): bool- Toutes les méthodes utilisent des requêtes préparées (
prepare/execute) - Gérez les exceptions PDO
Exercice 8 : Pagination et filtres sécurisés
Implémentez PaginatedQuery :
public static function paginate(PDO $pdo, string $baseQuery, array $params, int $page, int $perPage): array- Retourne :
items,total,page,perPage,lastPage - Les paramètres sont bindés proprement via
bindValue - Échappement des LIKE pour la recherche textuelle
Exercice 9 : Transactions et rollback
Scénario e-commerce : OrderProcessor qui :
- Crée une commande
- Décrémente le stock
- Crée un paiement
- Envoie une notification
- Si une étape échoue, tout est rollback
- Utilise PDO::beginTransaction/commit/rollback
Exercice 10 : Relations Eloquent complexes
Modèles Laravel : Team, User, Role, Project
- Un Team a plusieurs Users (belongsToMany)
- Un User a plusieurs Roles (belongsToMany via team_user_role)
- Un Team a plusieurs Projects (hasMany)
- Un Project appartient à plusieurs Users (belongsToMany via project_user)
- Scope :
team.active()pour filtrer les équipes actives - Implémentez un
HasRolestrait
Exercice 11 : Doctrine DQL avancé
Avec Doctrine ORM :
- Entités :
BlogPost,Category,Comment,Author - DQL : Pagination avec jointures, agrégation COUNT des commentaires par post
- Query de recherche plein texte (LIKE sur title et content)
- QueryBuilder conditionnel (filtres optionnels : category, date, author)
Exercice 12 : Eager/Lazy loading optimization
Analyse du problème N+1 :
- Créez un seeder avec 100 posts, chacun avec 10 commentaires
- Comparez le nombre de requêtes avec et sans eager loading
- Utilisez
withCount,loadMissing,cursorpour les gros volumes - Benchmark avec
DB::enableQueryLog()
17.3 Groupe C : Laravel (Exercices 13-18)
Exercice 13 : Artisan command personnalisée
Créez php artisan user:create :
- Arguments :
{name},{email} - Option :
{--admin}pour rôle admin - Crée l'utilisateur et envoie un email de bienvenue via notification
- Affiche un tableau (Table style) avec les détails créés
- Gère les erreurs (email déjà pris)
Exercice 14 : Validation et Form Request
Créez StoreProductRequest :
- Règles : name (required, max:255), price (required, numeric, min:0), category_id (exists:categories,id)
- Messages personnalisés en français
aftervalidation hook : vérifie que le prix n'a pas changé depuis la dernière mise à jour- Testez avec
$this->postJson()dans un test HTTP
Exercice 15 : Events, Listeners, Jobs
Système de notification d'événements :
- Event :
OrderShipped - Listeners :
SendShipmentNotification,UpdateOrderStatus,LogShipment - Job :
ProcessCustomerRewards— dispatché 24h après l'expédition - Queue : utilise la connection Redis avec
queue:work - Test : vérifiez que le Listener est bien appelé
Exercice 16 : Policy et Gates
Autorisation sur Post :
- Policy :
view,create,update,delete,restore,forceDelete - Gates :
admin(role admin),moderate(role moderator) - Utilisez
@canet@cannotdans Blade - Testez avec
$this->actingAs($user)->assertCan(),assertCannot()
Exercice 17 : Service Provider personnalisé
Créez PaymentServiceProvider :
- Bind :
PaymentGateway::class→StripeGateway::class - Singleton :
CurrencyConverter::class - Deferrable si possible
- Publiez la config
config/payments.php - Ajoutez une facade
Paymentavec@method static string charge(int $amount, string $token)
Exercice 18 : Multi-tenancy avec scoping
Implémentez le multi-tenancy par scope :
- Trait
BelongsToTenantsur les modèles - Trait
HasTenantssur le modèleTeam - Middleware
IdentifyTenantqui set le tenant_id dans la session - Global scope
TenantScopequi filtre automatiquement - Migration : ajoutez
tenant_idaux tables concernées
17.4 Groupe D : API REST (Exercices 19-23)
Exercice 19 : CRUD API avec Sanctum
API RESTful pour une ressource Article :
- Routes protégées par Sanctum (sauf index/show)
- CRUD complet avec API Resource
- Pagination avec meta data
- Tests HTTP Pest complets
Exercice 20 : API Resources et collections
Utilisez ArticleResource et ArticleCollection :
- Resource : inclut les relations (author, category, tags)
- Collection : pagination meta, links (first, last, prev, next)
- Conditionally apply attributes (ex: email visible que si admin)
- Merging de valeurs optionnelles
Exercice 21 : Rate limiting et Throttle
Middleware de rate limiting :
- 60 requêtes/min pour les guests
- 120 requêtes/min pour les utilisateurs authentifiés
- 300 requêtes/min pour les admins
- Retourne 429 Too Many Requests avec headers
X-RateLimit-* - Implémentez un cache driver personnalisé pour Redis
Exercice 22 : Versioning d'API
Implémentez le versioning par header Accept :
Accept: application/vnd.app.v1+jsonAccept: application/vnd.app.v2+json- Middleware
ApiVersionqui route vers le bon controller - Ou namespaces :
App\Http\Controllers\Api\V1\,App\Http\Controllers\Api\V2\ - Stratégie : rétrocompatibilité garantie sur 2 versions
Exercice 23 : Spatie Query Builder
API avancée avec spatie/laravel-query-builder :
allowedFilters: name, email, created_at (avec scope personnalisé)allowedSorts: name, -created_atallowedIncludes: posts, roles, teamsallowedFields: limiter les colonnes retournées- Pagination avec
JsonApiPaginate
17.5 Groupe E : Tests Pest (Exercices 24-27)
Exercice 24 : Test unitaire de Value Object
Testez Money Value Object :
it can add moneyit cannot subtract more than availableit formats correctlyit is immutable
Exercice 25 : Test HTTP d'API
Testez l'API des Articles :
it lists articles with paginationit creates an article when authenticatedit returns 422 on invalid datait returns 401 when not authenticated- Utilisez
actingAs(),assertJsonStructure(),assertJsonCount()
Exercice 26 : Test de commande Artisan
Testez la commande user:create :
expectsQuestionpour les inputs interactifsassertExitCode(0)pour succèsassertDatabaseHaspour vérifier la création- Testez le cas où l'email existe déjà
Exercice 27 : Test de notification
Testez OrderShipped notification :
assertSentTovérifie l'envoiassertNotSentTopour les non-envoisassertSentViavérifie le canal (mail, database)- Fake les notifications avec
Notification::fake()
17.6 Groupe F : Performance (Exercices 28-30)
Exercice 28 : Configuration OpCache optimale
Analysez et optimisez OpCache :
- php.ini avec valeurs recommandées
- Script de monitoring
opcache_get_status() - Preloading avec
opcache.preloadpour Laravel/Symfony - Comparaison avant/après avec microtime
Exercice 29 : JIT Tuning PHP 8.4
Optimisez le JIT :
opcache.jit=tracingopcache.jit_buffer_size=256M- Script benchmark comparant JIT on/off
- Analyse des fonctions qui bénéficient du JIT (calcul intensif vs I/O)
jit_profileetjit_debugpour le profilage
Exercice 30 : Profiling avec Xdebug
Profilez une application Laravel :
- Xdebug profiler activation
- Analyse avec QCacheGrind / WebGrind
- Identifiez le goulot d'étranglement
- Proposez et implémentez une optimisation
- Comparez les performances avant/après
17.7 Groupe G : Symfony (Exercices 31-33)
Exercice 31 : Messenger handler asynchrone
Système de traitement d'images :
- Message
ProcessImage: imageId, transformations - Handler avec plusieurs transports (async, priority)
- Middleware custom pour logging et timing
- Retry avec backoff exponentiel
- Test avec
Transport::setup()
Exercice 32 : Serializer custom normalizer
Créez un normalizer pour Money :
- Normalise :
{"amount": 12.34, "currency": "EUR"} - Denormalise : retourne un objet
Money - Support des groupes de sérialisation
- Utilisez-le avec le Serializer Symfony
Exercice 33 : Security voter personnalisé
Voter ArticleVoter :
- Attributes : VIEW, EDIT, DELETE, PUBLISH
- Supporte les règles métier : author peut éditer, admin peut tout, moderator peut publier
- Utilise
AccessDecisionManageravec stratégie affirmative/unanimous - Testez avec
AuthenticationTest
17.8 Groupe H : Livewire (Exercices 34-36)
Exercice 34 : Component CRUD avec validation
Livewire 3 component TaskList :
- Propriétés :
$tasks(Collection),$newTaskTitle - Méthodes :
addTask(),toggleTask($id),deleteTask($id) - Validation en temps réel : title required, min:3, max:255
- Événements :
task-added,task-toggled,task-deleted - Loading state avec
loadingwire:loading
Exercice 35 : File upload avec progression
Component FileUploader :
- Upload avec validation : max:10MB, mimes:pdf,docx,jpg
- Barre de progression avec Alpine.js
$wire.upload - Preview : image (thumbnail), PDF (icône)
- Drag and drop avec Alpine.js
- Stockage S3 avec
storeAs
Exercice 36 : Alpine.js intégration avancée
Component ShoppingCart :
- Alpine.js gestion du panier côté client
- Synchronisation avec Livewire $wire
- Animation de transition (ajout/suppression)
- Persistance localStorage + sync serveur
- Utilisez
x-data,x-show,x-transition,x-model
17.9 Groupe I : WordPress (Exercices 37-39)
Exercice 37 : Custom Gutenberg block
Créez un block Gutenberg team-members :
- block.json avec supports, attributes, styles
- Edit : InspectorControls pour ajouter/modifier des membres
- Save : RenderCallback côté PHP avec rendu sécurisé
- Attributs : name, role, photo, bio, socialLinks
- Dynamic block avec
render_callback
Exercice 38 : WP REST API endpoint
Endpoint WordPress REST API personnalisé :
- Route :
/wp-json/mp/v1/stats - Méthodes : GET, POST
- Retourne : total users, total posts, recent comments
- Permission : admin uniquement
- Cache : 5 minutes avec transient
Exercice 39 : WP-CLI command
Commande WP-CLI mp generate :
wp mp generate posts --count=50wp mp generate users --count=10wp mp generate all(posts + users + terms)- Barre de progression
- Gestion d'erreurs et rollback
17.10 Groupe J : Architecture (Exercice 40)
Exercice 40 : Refactoring Legacy vers Clean Architecture
Vous recevez un contrôleur Laravel legacy de 500 lignes :
class OrderController extends Controller
{
public function store(Request $request)
{
// Validation dans le contrôleur
// Logique métier dans le contrôleur
// Envoi d'email dans le contrôleur
// Pas de tests
}
}
Refactorisez vers :
- Action class :
StoreOrderAction(single responsibility) - DTO :
OrderData(Spatie Laravel Data) - Service :
OrderService - Repository :
OrderRepository(interface + implémentation) - Notification :
OrderConfirmation(Laravel Notification) - Tests : Pest pour chaque couche
- Validation : Form Request
StoreOrderRequest
Critères :
- SOLID principles respectés
- Testable unitairement
- Pas de régression fonctionnelle
17.11 Rendu et Évaluation
Format de rendu
- Repository Git par exercice (ou dossier structuré)
composer.jsonetphpunit.xmlinclus- Tests verts (green suite)
- Documentation courte d'installation
Grille d'évaluation
| Critère | Points |
|---|---|
| Fonctionnalité correcte | 40% |
| Qualité du code (SOLID, PSR) | 25% |
| Tests (coverage > 80%) | 20% |
| Documentation | 10% |
| Performance | 5% |
Durée recommandée par exercice
- Débutants (⭐) : 10-15 min
- Intermédiaires (⭐⭐) : 20-30 min
- Avancés (⭐⭐⭐) : 45-60 min
- Expert (⭐⭐⭐⭐⭐) : 3-4h