Biome : le couteau suisse qui remplace ESLint + Prettier
Découvrez Biome : l'outil qui remplace ESLint + Prettier en bien plus rapide. Fini les configs interminables, place au code qui marche !
PHPMD : le détecteur de code pourri qui va démolir votre estime de développeur. Découvrez la complexité cyclomatique et comment refactoriser votre PHP avant qu'il ressemble à un roman de Balzac
Ah, la complexité cyclomatique ! Ce terme barbare qui fait fuir les développeurs plus vite qu'un commercial qui propose une "solution innovante disruptive". Pour ceux qui auraient séché les cours d'informatique pour aller boire des bières, laissez-moi vous expliquer ce concept avec la délicatesse que j’aurais dans un magasin de porcelaine.
La complexité cyclomatique, inventée par Thomas McCabe en 1976, époque où les développeurs croyaient encore aux promesses des chefs de projet, mesure le nombre de chemins d'exécution possibles dans votre code. En gros, c'est le nombre de fois où votre code peut dire "et si ?" avant de se perdre dans ses propres méandres comme un touriste parisien dans le métro.
<?php
// Complexité cyclomatique = 1 (un seul chemin)
function simpleFunction() {
return "Hello World";
}
// Complexité cyclomatique = 3 (3 chemins possibles)
function complexFunction($age) {
if ($age < 18) {
return "Trop jeune pour comprendre PHP";
} elseif ($age > 65) {
return "Trop sage pour encore coder";
} else {
return "Parfait pour devenir développeur et souffrir";
}
}
Plus votre complexité est élevée, plus votre code ressemble à un labyrinthe conçu par un sadique. Au-delà de 10, on entre dans la zone rouge. Au-delà de 20, on appelle ça de l'art contemporain.
Et c'est là qu'intervient PHPMD (PHP Mess Detector), le superhéros dont personne ne voulait mais dont tout le monde a besoin. Créé par Manuel Pichler, ce formidable outil d'analyse statique a pour mission de vous dire que votre code pue plus qu'un fromage oublié dans une voiture en plein été.
PHPMD, c'est un peu le prof qui corrige vos copies avec un stylo rouge et qui n'hésite pas à écrire « Peut mieux faire » en gros sur votre travail. Sauf qu'au lieu de corriger votre orthographe, il corrige votre logique de développement défaillante.
Installer PHPMD, c'est comme installer une alarme anti-vol : au début, on se dit que c'est une bonne idée, puis on réalise qu'elle va sonner tout le temps.
# Via Composer (parce qu'on est civilisés)
composer require --dev phpmd/phpmd
# Via PHAR (pour les nostalgiques)
wget https://phpmd.org/static/latest/phpmd.phar
chmod +x phpmd.phar
Une fois installé, PHPMD vous attend, tapi dans l'ombre, prêt à démolir votre estime de soi de développeur.
PHPMD propose plusieurs ensembles de règles, comme autant de façons différentes de vous expliquer que vous codez comme un pied.
Les règles CleanCode s'attaquent à ces petites habitudes qui transforment votre code en déchetterie numérique.
<?php
// ❌ Mauvais : ElseExpression
function validateUser($user) {
if ($user->isValid()) {
return true;
} else {
return false; // Cette ligne offense Robert C. Martin personnellement
}
}
// ✅ Bon : Direct et sans fioritures
function validateUser($user) {
return $user->isValid();
}
// ❌ Mauvais : BooleanArgumentFlag
function sendEmail($message, $isUrgent = false) {
if ($isUrgent) {
// Logique urgente
} else {
// Logique normale
}
}
// ✅ Bon : Séparer les responsabilités
function sendEmail($message) {
// Logique normale
}
function sendUrgentEmail($message) {
// Logique urgente
}
Ces règles détectent quand votre code devient plus long qu'un discours de Macron.
<?php
// ❌ Trop de paramètres (TooManyParameters)
function createUser($firstName, $lastName, $email, $phone, $address,
$city, $zipCode, $country, $birthDate, $gender,
$preferredLanguage, $newsletter, $marketingOptIn) {
// Félicitations, vous venez de créer un monstre
}
// ✅ Utilisez un objet de transfert de données
class UserData {
public function __construct(
public string $firstName,
public string $lastName,
public string $email,
// ... autres propriétés
) {}
}
function createUser(UserData $userData) {
// Beaucoup mieux pour votre santé mentale
}
// ❌ Classe trop longue (TooManyMethods)
class GodClass {
public function createUser() {}
public function updateUser() {}
public function deleteUser() {}
public function sendEmail() {}
public function generateReport() {}
public function processPayment() {}
public function manageSessions() {}
public function handleFiles() {}
public function validateData() {}
public function logErrors() {}
// ... 50 autres méthodes parce que "c'est pratique"
}
<?php
// ❌ Superglobales : Le mal incarné
function getUser() {
return $_SESSION['user']; // PHPMD n'aime pas, et il a raison
}
// ✅ Injection de dépendance, comme les grands
class UserController {
public function __construct(
private SessionInterface $session
) {}
public function getUser() {
return $this->session->get('user');
}
}
// ❌ Camel case pour les propriétés (Symfony style)
class User {
public $user_name; // PHPMD pleure
public $email_address;
}
// ✅ Cohérence avec les standards PHP
class User {
public string $userName;
public string $emailAddress;
}
<?php
// ❌ Couplage excessif (CouplingBetweenObjects)
class OrderProcessor {
public function process(Order $order) {
$user = new UserRepository();
$payment = new PaymentGateway();
$email = new EmailService();
$logger = new FileLogger();
$cache = new RedisCache();
$notification = new SlackNotification();
// Félicitations, vous venez de coupler votre classe avec la Terre entière
}
}
// ✅ Inversion de dépendance
class OrderProcessor {
public function __construct(
private UserRepositoryInterface $userRepository,
private PaymentGatewayInterface $paymentGateway,
private EmailServiceInterface $emailService,
private LoggerInterface $logger
) {}
public function process(Order $order) {
// Maintenant c'est testable et maintenable
}
}
// ❌ Profondeur d'héritage excessive
class Animal {}
class Mammal extends Animal {}
class Primate extends Mammal {}
class Hominid extends Primate {}
class Human extends Hominid {}
class Developer extends Human {}
class PHPDeveloper extends Developer {}
class SymfonyDeveloper extends PHPDeveloper {
// À ce niveau, même Darwin se retournerait dans sa tombe
}
$data
n'est pas un nom de variable<?php
// ❌ Noms courts et inutiles
function calc($d, $r) {
return $d * $r;
}
$u = getUserById($id);
$tmp = processData($u);
// ✅ Noms expressifs
function calculateTotalPrice($duration, $rate) {
return $duration * $rate;
}
$user = getUserById($id);
$processedUserData = processUserData($user);
// ❌ Nom de classe trop long (LongClassName)
class AbstractFactoryProviderSingletonBuilderDecoratorAdapterFacade {
// Non, ce n'est pas une parodie de Java, c'est un vrai problème PHP
}
// ✅ Simplicité
class UserFactory {
// Parfait, on comprend immédiatement
}
<?php
class UserService {
private $dbConnection; // ❌ Propriété privée non utilisée
public function getUser($id) {
$query = "SELECT * FROM users WHERE id = ?"; // ❌ Variable locale non utilisée
return User::find($id);
}
private function validateEmail($email) { // ❌ Méthode privée non utilisée
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
public function updateUser($id, $data, $timestamp) { // ❌ $timestamp non utilisé
User::find($id)->update($data);
}
}
PHPMD permet de configurer ses règles via un fichier XML, parce que souffrir, c'est bien, mais souffrir intelligemment, c'est mieux.
<?xml version="1.0"?>
<ruleset name="Mon PHPMD personnalisé"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>
Règles PHPMD pour projet Symfony - Configuration pour développeurs masochistes
</description>
<!-- Clean Code rules -->
<rule ref="rulesets/cleancode.xml">
<!-- On exclut ElseExpression parce que parfois, on a besoin d'être explicite -->
<exclude name="ElseExpression"/>
</rule>
<!-- Code Size rules avec des limites adaptées à la réalité -->
<rule ref="rulesets/codesize.xml/CyclomaticComplexity">
<properties>
<property name="reportLevel" value="12"/> <!-- Au lieu de 10, parce qu'on n'est pas des machines -->
</properties>
</rule>
<rule ref="rulesets/codesize.xml/TooManyParameters">
<properties>
<property name="minimum" value="8"/> <!-- Symfony permet parfois plus de paramètres -->
</properties>
</rule>
</ruleset>
PHPStorm peut intégrer PHPMD directement, transformant votre IDE en machine à humiliation continue.
Settings > Tools > External Tools > Add
- Name: PHPMD
- Program: vendor/bin/phpmd
- Arguments: $FileDir$ text cleancode,codesize,controversial,design,naming,unusedcode
- Working directory: $ProjectFileDir$
name: Code Quality
on: [push, pull_request]
jobs:
phpmd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.4
- name: Install dependencies
run: composer install --no-dev --optimize-autoloader
- name: Run PHPMD
run: vendor/bin/phpmd src text phpmd.xml
qa-phpmd: ## Run PHP Mess Detector
@docker run -v $(PWD):/app -w /app -t --rm php:8.4-cli-alpine sh -c "php -d error_reporting='E_ALL & ~E_DEPRECATED' ./vendor/bin/phpmd src text phpmd.xml --suffixes php --exclude vendor,var,tests,migrations 2>/dev/null; exit 0"
PHPMD vous balance ses résultats comme un prof rend les copies : sans ménagement.
/src/Controller/UserController.php:45 The method processUserRegistration() has a Cyclomatic Complexity of 23. The configured cyclomatic complexity threshold is 10.
/src/Service/PaymentService.php:12 Avoid using static access to class '\App\Helper\TaxCalculator' in method 'calculateTax'.
/src/Entity/User.php:156 The method validateComplexBusinessRules() has 127 lines of code. Current threshold is 100. Avoid really long methods.
/src/Repository/UserRepository.php:89 The class UserRepository has 47 public methods. Consider whether some could be made private or protected to reduce the visibility.
Chaque ligne est une petite gifle qui vous rappelle que votre code pourrait être mieux. Mais au moins, elle est constructive, cette gifle.
PHP_CodeSniffer s'occupe du style (espaces, tabulations, position des accolades), PHPMD s'occupe de la logique. C'est la différence entre critiquer votre façon de vous habiller et critiquer votre personnalité.
PHPStan et Psalm analysent les types et trouvent les bugs potentiels. PHPMD trouve les problèmes d'architecture. C'est complémentaire : l'un trouve vos erreurs, l'autre trouve vos mauvaises décisions de vie (de développeur).
<?php
// Avant : Code legacy cauchemardesque
// GIF de Phoebe "my eyes my eyes"
class UserManager {
public function handleUser($action, $userData, $options = null, $validate = true, $sendEmail = false) {
if ($action == 'create') {
if ($validate) {
if (isset($userData['email'])) {
if (filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
if (!$this->userExists($userData['email'])) {
$user = new User();
$user->email = $userData['email'];
if (isset($userData['name'])) {
$user->name = $userData['name'];
if (strlen($userData['name']) > 2) {
$user->save();
if ($sendEmail) {
$this->sendWelcomeEmail($user);
}
return $user;
} else {
throw new Exception('Name too short');
}
} else {
throw new Exception('Name required');
}
} else {
throw new Exception('User exists');
}
} else {
throw new Exception('Invalid email');
}
} else {
throw new Exception('Email required');
}
} else {
// Logique sans validation...
}
} elseif ($action == 'update') {
// Encore 50 lignes de if imbriqués...
} elseif ($action == 'delete') {
// Et encore 30 lignes...
}
}
}
Complexité cyclomatique : 47 (Félicitations, vous avez battu le record du monde)
<?php
// Après : Code civilisé grâce aux conseils de PHPMD
class UserCreator {
public function __construct(
private UserRepository $repository,
private EmailService $emailService
) {}
public function create(array $userData, bool $validate = true, bool $sendEmail = false): User {
if ($validate) {
$this->validateUserData($userData);
}
if ($this->repository->existsByEmail($userData['email'])) {
throw new UserAlreadyExistsException('User exists');
}
$user = $this->createUserFromData($userData);
$this->repository->save($user);
if ($sendEmail) {
$this->emailService->sendWelcomeEmail($user);
}
return $user;
}
private function validateUserData(array $userData): void {
if (!isset($userData['email'])) {
throw new InvalidArgumentException('Email required');
}
if (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid email');
}
if (!isset($userData['name'])) {
throw new InvalidArgumentException('Name required');
}
if (strlen($userData['name']) <= 2) {
throw new InvalidArgumentException('Name too short');
}
}
private function createUserFromData(array $userData): User {
$user = new User();
$user->email = $userData['email'];
$user->name = $userData['name'];
return $user;
}
}
// Et pour une approche encore plus moderne avec Symfony :
class UserCreator {
public function __construct(
private UserRepository $repository,
private EmailService $emailService,
private ValidatorInterface $validator
) {}
public function create(CreateUserRequest $request): User {
// Validation automatique via les constraints Symfony
$violations = $this->validator->validate($request);
if (count($violations) > 0) {
throw new ValidationException($violations);
}
if ($this->repository->existsByEmail($request->email)) {
throw new UserAlreadyExistsException();
}
$user = User::fromRequest($request);
$this->repository->save($user);
if ($request->sendWelcomeEmail) {
$this->emailService->sendWelcomeEmail($user);
}
return $user;
}
}
// Avec le DTO correspondant
class CreateUserRequest {
#[Assert\NotBlank(message: 'Email required')]
#[Assert\Email(message: 'Invalid email')]
public string $email;
#[Assert\NotBlank(message: 'Name required')]
#[Assert\Length(min: 3, minMessage: 'Name too short')]
public string $name;
public bool $sendWelcomeEmail = false;
}
Complexité cyclomatique : 3 (Votre psychiatre vous remercie)
PHPMD n'est pas parfait. Parfois, il râle pour rien, comme un voisin qui se plaint du bruit alors que vous écoutez une cantate de Bach.
<?php
// PHPMD va râler sur cette factory, mais elle est justifiée
class PaymentProcessorFactory {
public function create(string $type): PaymentProcessorInterface {
return match ($type) {
'stripe' => new StripeProcessor(),
'paypal' => new PaypalProcessor(),
'bank_transfer' => new BankTransferProcessor(),
'crypto' => new CryptoProcessor(),
'apple_pay' => new ApplePayProcessor(),
'google_pay' => new GooglePayProcessor(),
'amazon_pay' => new AmazonPayProcessor(),
'klarna' => new KlarnaProcessor(),
default => throw new InvalidPaymentTypeException()
};
}
}
Dans ce cas, la complexité est justifiée. Vous pouvez ignorer cette règle spécifique avec :
// @SuppressWarnings(PHPMD.CyclomaticComplexity)
PHPMD calcule plusieurs métriques intéressantes :
# Générer un rapport avec toutes les métriques
vendor/bin/phpmd src xml codesize,unusedcode --reportfile report.xml
# Analyser uniquement les violations critiques
vendor/bin/phpmd src text cleancode --minimumpriority 1
Sur de gros projets, PHPMD peut être lent. Quelques astuces :
# Exclure les dossiers lourds
vendor/bin/phpmd src text cleancode --exclude vendor,var,public
# Analyser seulement les fichiers modifiés
git diff --name-only --diff-filter=AM | grep "\.php$" | xargs vendor/bin/phpmd text cleancode
# Parallélisation avec GNU parallel
find src -name "*.php" | parallel -j4 vendor/bin/phpmd {} text cleancode
PHPMD, c'est comme une salle de sport pour votre code : c'est douloureux, vous n'avez pas envie d'y aller, mais au final, vous vous sentez mieux après. Et comme pour la salle de sport, la régularité est la clé du succès.
Oui, PHPMD va vous humilier. Oui, il va vous montrer que votre code ressemble à un plat de spaghettis jeté contre un mur. Mais c'est exactement ce dont vous avez besoin pour progresser.
L'objectif n'est pas d'avoir un score parfait à PHPMD (sinon, vous passerez plus de temps à optimiser vos métriques qu'à développer des fonctionnalités), mais d'avoir une base de code maintenable où l'on peut naviguer sans avoir envie de tout reécrire.
Alors installez PHPMD, configurez-le intelligemment, et acceptez que parfois, la vérité fait mal. Mais au moins, elle fait progresser.
Et rappelez-vous : un code avec une complexité cyclomatique de 50, c'est comme une blague de Bigard : tout le monde comprend qu'il y a un problème, mais personne n'ose rien dire.