- Fonctionnalités
- Pricing

Les principes SOLID facilitent la maintenance et l'adaptation de ton code. Ces cinq pratiques de conception orientée objet t'aident à écrire un code plus propre et plus fiable.
Explorons chaque principe à l'aide d'exemples concrets pour créer des applications faciles à maintenir, qu'il s'agisse de bases de code uniques ou de systèmes distribués.
Écrire du code maintenable est un défi. Les principes SOLID te fournissent des conseils pratiques pour écrire un meilleur code. Robert C. Martin a créé ces principes pour résoudre des problèmes courants tels que le code rigide, les bugs en cascade et les systèmes complexes.
Chaque principe SOLID cible un défi de codage spécifique :
Le principe de responsabilité unique permet de garantir la maintenabilité de ton code. Chaque classe effectue une tâche spécifique, ni plus, ni moins.
Pense-y de cette façon : sépare l'authentification des utilisateurs de la journalisation ou des notifications. Lorsqu'une classe tente d'effectuer plusieurs tâches, les tests et les mises à jour deviennent plus complexes.
Le diagramme utilise la composition pour montrer comment UserRegistration interagit avec d'autres classes. Chaque flèche en losange creuse signifie que UserRegistration fait référence à une classe mais ne contrôle pas son cycle de vie.
Légende du diagramme :
Voici comment la séparation des responsabilités du code permet de créer des applications plus claires et plus faciles à maintenir :
// This example demonstrates the Single Responsibility Principle (SRP)
// Each class has one specific job, making the code more maintainable and testable
// UserDataFormatter: Responsible for converting user data into a consistent format
class UserDataFormatter {
public function formatUserData($user) {
// Extracts and formats essential user information
return [
'name' => $user->getFullName(), // Get user's full name
'email' => $user->getEmail(), // Get user's email
'joined' => $user->getJoinDate()->format('Y-m-d') // Format join date
];
}
}
// UserDataValidator: Responsible for ensuring data meets requirements
class UserDataValidator {
public function validateUserData($userData) {
// Performs two validation checks:
// 1. Verifies email is in valid format
// 2. Ensures name field is not empty
return filter_var($userData['email'], FILTER_VALIDATE_EMAIL)
&& !empty($userData['name']);
}
}
// UserDataPersistence: Responsible for database operations
class UserDataPersistence {
private $database;
// Initialize with database connection
public function __construct($database) {
$this->database = $database;
}
// Handles saving user data to the database
public function saveUser($userData) {
// Performs the actual database insert operation
return $this->database->insert('users', $userData);
}
}
// UserRegistration: Orchestrates the registration process
// Acts as a facade, coordinating between other specialized classes
class UserRegistration {
private $formatter;
private $validator;
private $persistence;
// Constructor injection of dependencies
public function __construct(
UserDataFormatter $formatter,
UserDataValidator $validator,
UserDataPersistence $persistence
) {
$this->formatter = $formatter;
$this->validator = $validator;
$this->persistence = $persistence;
}
// Main registration method that coordinates the entire process
public function registerUser($user) {
// Step 1: Format the user data
$userData = $this->formatter->formatUserData($user);
// Step 2: Validate the formatted data
if ($this->validator->validateUserData($userData)) {
// Step 3: If validation passes, save to database
return $this->persistence->saveUser($userData);
}
// If validation fails, throw an exception
throw new ValidationException('Invalid user data');
}
}
// EXAMPLE USAGE DEMONSTRATION
// Below shows how all components work together
// Create a mock database for demonstration
$database = new class {
public function insert($table, $data) {
// Simulates database insertion and returns success
echo "Inserted into $table: " . json_encode($data) . "\\n";
return true;
}
};
try {
// Step 1: Initialize all required components
$formatter = new UserDataFormatter();
$validator = new UserDataValidator();
$persistence = new UserDataPersistence($database);
// Step 2: Create the main UserRegistration service
$userRegistration = new UserRegistration($formatter, $validator, $persistence);
// Step 3: Create a mock user object for testing
$user = new class {
public function getFullName() {
return "Jane Doe";
}
public function getEmail() {
return "jane@example.com";
}
public function getJoinDate() {
return new DateTime();
}
};
// Step 4: Attempt to register the user
$userRegistration->registerUser($user);
echo "User successfully registered!";
} catch (ValidationException $e) {
// Handle any validation errors that occur
echo "Failed to register user: " . $e->getMessage();
}
// Custom exception class for validation-specific errors
class ValidationException extends Exception {}
Chaque classe a une seule tâche bien définie. Le formateur formate les données. Le validateur vérifie les données. La couche de persistance enregistre les données. La classe d'enregistrement coordonne ces éléments de manière claire. Cette structure facilite les tests et la maintenance.
Cette conception claire s’aligne sur le principe ouvert-fermé. Tu veux ajouter de nouvelles règles de formatage ou de nouveaux contrôles de validation ? Crée de nouvelles classes qui suivent le modèle existant. Ton code reste intact tandis que les fonctionnalités s’enrichissent.
Le principe ouvert-fermé (OCP) t'aide à ajouter des fonctionnalités sans modifier le code existant. Ta base de code reste stable à mesure qu'elle s'enrichit. Au lieu de modifier le code existant, étends les fonctionnalités à l'aide de nouvelles classes et interfaces.
Le principe de substitution de Liskov (LSP) signifie que les classes filles doivent fonctionner partout où leurs classes pères fonctionnent. Si ce n'est pas le cas, ton code va se casser de manière inattendue.
Voici une violation courante du LSP et comment y remédier :
// ❌ Violates LSP: This violates Liskov Substitution Principle because
// a subclass (Penguin) changes the expected behavior of its parent class (Bird)
class Bird {
public function fly() {
// Logic for flying
}
}
class Penguin extends Bird {
public function fly() {
// This breaks the contract established by the parent class
throw new Exception("Penguins can't fly!");
}
}
// ✅ Refactored: Better design using composition over inheritance
// This approach separates flying capability from bird classification
interface Flyable {
public function fly();
}
// Only birds that can actually fly implement the Flyable interface
class Sparrow implements Flyable {
public function fly() {
// Flying birds implement their specific flying behavior
}
}
// Penguin doesn't implement Flyable, avoiding the forced inheritance problem
class Penguin {
// Penguins have their own behaviors without being forced to implement fly()
}Le principe de séparation des interfaces t'aide à écrire un code plus propre en gardant les interfaces petites et ciblées. De cette façon, les classes n'implémentent que les méthodes qu'elles utilisent réellement.
Voyons comment diviser des interfaces trop lourdes en interfaces plus petites et ciblées.
// ❌ Violates ISP: Interface forces classes to implement methods they don't need
interface Animal {
public function fly(); // Problem: Fish shouldn't need this
public function swim(); // Problem: Birds shouldn't need this
}
class Fish implements Animal {
public function swim() {
// Swimming logic
}
public function fly() {
// Forced to implement irrelevant method
throw new Exception("Fish cannot fly");
}
}
// ✅ Refactored: Following ISP with focused, specific interfaces
// Each class only implements the methods it actually needs
interface Flyable {
public function fly(); // Interface for flying creatures
}
interface Swimmable {
public function swim(); // Interface for swimming creatures
}
class Fish implements Swimmable {
public function swim() {
// Fish only implements what it can actually do
// This follows ISP by not forcing unnecessary methods
}
}
class Bird implements Flyable {
public function fly() {
// Birds only implement flying behavior
// No unused methods are forced upon the class
}
}
Le principe d'inversion des dépendances t'aide à écrire un meilleur code en utilisant des interfaces plutôt que des implémentations directes. Tes modules de haut niveau fonctionnent avec des abstractions et non avec des détails concrets. Cela te permet d'échanger des composants sans perturber ton système.
Voici un exemple concret :
// ❌ Violates DIP: High-level module depends on low-level module
class EmailNotifier {
public function send($message) {
// Logic for sending email
}
}
class Notification {
private $emailNotifier;
public function __construct() {
$this->emailNotifier = new EmailNotifier();
}
public function notify($message) {
$this->emailNotifier->send($message);
}
}
// ✅ Refactored: Depend on abstractions, not concrete implementations
interface Notifier {
public function send($message);
}
class EmailNotifier implements Notifier {
public function send($message) {
// Logic for sending email
}
}
class SMSNotifier implements Notifier {
public function send($message) {
// Logic for sending SMS
}
}
class Notification {
private $notifier;
public function __construct(Notifier $notifier) {
$this->notifier = $notifier;
}
public function notify($message) {
$this->notifier->send($message);
}
}
// This design allows swapping EmailNotifier with SMSNotifier without modifying Notification
Les principes SOLID sont des outils simples qui fonctionnent ensemble. Lorsqu’ils sont combinés correctement, ils t’aident à écrire un code propre, plus facile à maintenir et à déboguer.
Voici une présentation claire de ce que fait chaque principe :
| Principe | Résumé | Avantage principal |
| Responsabilité unique (SRP) | Une classe ne doit avoir qu'une seule raison d'être modifiée | Réduit la complexité de la maintenance |
| Principe d'ouverture-fermeture (OCP) | Les entités logicielles doivent être ouvertes à l'extension mais fermées à la modification | Permet l'ajout de fonctionnalités évolutives |
| Substitution de Liskov (LSP) | Les classes dérivées doivent être substituables à leurs classes de base | Assure la cohérence comportementale |
| Séparation des interfaces (ISP) | Les clients ne devraient pas être obligés de dépendre d'interfaces qu'ils n'utilisent pas | Réduit au minimum le couplage entre les composants |
| Inversion des dépendances (DIP) | Dépendre d'abstractions, pas d'implémentations concrètes | Améliore la flexibilité du système |
La ségrégation des interfaces et l'inversion des dépendances aident à construire des systèmes adaptables. La ségrégation des interfaces permet à chaque microservice de n'exposer que les fonctions essentielles. L'inversion des dépendances permet de remplacer des composants sans modifier le code de base.
L'inversion des dépendances fonctionne particulièrement bien pour les systèmes de plugins. L'utilisation d'interfaces au lieu de dépendances codées en dur rend les composants interchangeables, ce qui facilite l'intégration de nouveaux outils.
Par exemple, un CMS peut basculer entre SQL, NoSQL ou des fichiers pour le stockage. La ségrégation d'interface permet de garder les connexions propres et minimales. Ensemble, ces principes créent des systèmes qui s'adaptent aux nouvelles exigences.
Écrire du code maintenable est un défi. Les solutions de fortune mises en place pour respecter les délais créent une dette technique difficile à corriger par la suite. Les principes SOLID offrent une solution.
Ces principes aident les équipes à créer un code stable et flexible, plus facile à comprendre et à modifier. Résultat : moins de bugs et des cycles de développement plus rapides.
Une structure de code claire favorise un meilleur travail d'équipe. Lorsque chaque composant a un objectif spécifique, les tests et le débogage deviennent simples.
SOLID favorise la croissance. Grâce au principe d'ouverture-fermeture, tu peux étendre les fonctionnalités sans modifier le code existant, ce qui est essentiel pour les projets en évolution.
À mesure que les exigences évoluent, les principes SOLID garantissent que ton code peut s'adapter. Ajoute des fonctionnalités ou modifie des fonctionnalités sans refactorisation majeure.
Si les raccourcis peuvent sembler efficaces à court terme, ils créent des problèmes à long terme : un développement plus lent, davantage de bugs et une frustration des développeurs. Les principes SOLID mènent à un code plus propre et plus facile à maintenir.
Robert C. Martin le dit clairement : « Une bonne architecture rend le système facile à comprendre, à développer, à maintenir et à déployer. L'objectif ultime est de minimiser le coût du système sur son cycle de vie et de maximiser la productivité des programmeurs. » Les principes SOLID rendent cela possible.
Chaque classe ne doit faire qu'une seule chose.
Prenons l’exemple d’une classe Report qui formate les données et les écrit dans une base de données. Cela pose des problèmes : lorsque tu modifies la mise en forme ou que tu changes de base de données, tu risques de casser ces deux fonctionnalités.
La solution ? Divise-la en ReportFormatter et ReportWriter. Chaque classe a un objectif clair. Tu veux modifier la mise en forme du rapport ? Mets à jour ReportFormatter. Tu dois changer de base de données ? Modifie ReportWriter.
Cette séparation simplifie également les tests, car tu peux vérifier le formatage et les opérations de base de données séparément.
Voici un exemple de code :
// ❌ Violates SRP: One class handling multiple responsibilities
class Report {
public function formatData($data) {
// Format data as JSON
// This violates SRP by mixing formatting and storage concerns
}
public function writeToDatabase($data) {
// Write data to database
// This creates tight coupling between data formatting and storage
}
}
// ✅ Refactored: Following SRP with clear separation of concerns
class ReportFormatter {
public function formatData($data) {
// Format data as JSON
// This class has a single responsibility: data formatting
}
}
class ReportWriter {
public function writeToDatabase($data) {
// Write data to database
// This class has a single responsibility: data persistence
// Decoupling allows for easier testing and maintenance
}
}
Même les développeurs expérimentés peuvent avoir du mal avec les principes SOLID. Voici les erreurs courantes à éviter :
Ne crée pas trop de petites interfaces. Regroupe les fonctions apparentées de manière logique. Par exemple, un système d'autorisations fonctionne mieux avec une seule interface UserPermissions plutôt qu'avec des interfaces distinctes pour la lecture, l'écriture et la gestion des rôles.
Évite de regrouper plusieurs tâches dans des classes « d'aide ». Décompose-les plutôt en composants spécialisés. Exemple : remplace une classe DataProcessorHelper par des classes distinctes DataValidator, DataFormatter et Logger.
Ne crée des interfaces que lorsqu’elles apportent une valeur ajoutée. Une simple classe StringFormatter n’a pas besoin d’interface : cela ajoute de la complexité sans apporter d’avantage.
N'abuse pas d'un seul principe. Une séparation excessive crée une complexité inutile. Une séparation insuffisante rend le code rigide. Laisse les principes SOLID guider tes décisions de conception sans les contraindre.
Suivre ces directives aide à créer un code maintenable et évolutif.
Écrire du code maintenable est essentiel. Les principes SOLID facilitent la modification et l'évolutivité de ton code. Voici comment :
Ton code doit s'adapter à mesure que ton projet évolue. Grâce aux principes d'ouverture-fermeture et d'inversion des dépendances, tu peux ajouter des fonctionnalités sans perturber celles qui existent déjà.
Par exemple : imaginons que tu développes un site de commerce électronique qui gère le calcul des taxes. En utilisant ces principes, tu peux ajouter de nouvelles règles fiscales pour différents pays sans modifier le code central de ton processus de paiement.
Voyons cela en pratique :
// Example demonstrating violation of Open-Closed Principle (OCP)
// ❌ Bad Practice: This implementation requires modifying existing code to add new shapes
class Shape {
// This method uses conditional logic that must be modified for each new shape type
// Breaking OCP as the class must be modified to extend functionality
public function calculateArea($type, $dimensions) {
if ($type === 'circle') {
return pi() * pow($dimensions['radius'], 2);
} elseif ($type === 'square') {
return pow($dimensions['side'], 2);
}
}
}
// Example demonstrating proper implementation following OCP
// ✅ Good Practice: Using polymorphism to allow extension without modification
// Abstract base class defines the interface that all shapes must implement
// This creates a contract that concrete classes must fulfill
abstract class Shape {
// Abstract method forces all child classes to implement their own area calculation
abstract public function calculateArea();
}
// Concrete implementation for Circle
// Each shape is responsible for its own area calculation
class Circle extends Shape {
private $radius;
// Constructor initializes the circle with its radius
public function __construct($radius) {
$this->radius = $radius;
}
// Specific implementation for calculating circle area
// π * r²
public function calculateArea() {
return pi() * pow($this->radius, 2);
}
}
// Concrete implementation for Square
// New shapes can be added by creating new classes without modifying existing code
class Square extends Shape {
private $side;
// Constructor initializes the square with its side length
public function __construct($side) {
$this->side = $side;
}
// Specific implementation for calculating square area
// side * side
public function calculateArea() {
return pow($this->side, 2);
}
}
// To add a new shape, simply create a new class that extends Shape
// This follows OCP as existing code remains unchangedUpsun te fournit les outils dont tu as besoin pour écrire un meilleur code, que tu développes des monolithes ou des microservices.
Voici comment appliquer efficacement les principes SOLID :
Suivre ces pratiques permet d'obtenir un code plus facile à maintenir et plus adaptable, ce qui est essentiel pour les projets en pleine croissance.
