MODULE 06

Programmation Orientée Objet

Classes, héritage, interfaces, traits et les fondements de la POO en PHP 8.

1 Classes & Objets

<?php
class Produit {
    // Propriétés typées (PHP 7.4+)
    public string  $nom;
    public float   $prix;
    private int    $stock = 0;
    protected string $categorie;

    // Constructeur avec promotion de propriétés (PHP 8.0+)
    public function __construct(
        public readonly string $sku,   // readonly = lecture seule après init
        string $nom,
        float  $prix,
        string $categorie = 'général'
    ) {
        $this->nom       = $nom;
        $this->prix      = $prix;
        $this->categorie = $categorie;
    }

    // Getter / Setter
    public function getStock(): int   { return $this->stock; }
    public function setStock(int $n): void {
        if ($n < 0) throw new InvalidArgumentException("Stock négatif interdit");
        $this->stock = $n;
    }

    // __toString : appelé automatiquement par echo
    public function __toString(): string {
        return "{$this->nom} ({$this->sku}) — {$this->prix} €";
    }

    // Méthode chaînable (fluent interface)
    public function appliquerRemise(float $pct): static {
        $this->prix *= (1 - $pct / 100);
        return $this; // retourne $this pour chaîner
    }
}

$p = new Produit('SKU-001', 'Clavier', 49.90, 'informatique');
$p->setStock(100);
echo $p;                              // Clavier (SKU-001) — 49.9 €
$p->appliquerRemise(10)->appliquerRemise(5); // chaînage
echo $p->prix;                        // 42.41

2 Héritage

<?php
class Animal {
    public function __construct(
        protected string $nom,
        protected int    $age
    ) {}

    // Peut être surchargée
    public function parler(): string {
        return "{$this->nom} fait un bruit.";
    }

    // final empêche la surcharge dans les sous-classes
    final public function description(): string {
        return "{$this->nom}, {$this->age} an(s)";
    }
}

class Chien extends Animal {
    public function __construct(string $nom, int $age, private string $race) {
        parent::__construct($nom, $age); // appel obligatoire si parent a un constructeur
    }

    // override (PHP 8.3+ : attribut #[Override] optionnel pour validation)
    public function parler(): string {
        return "{$this->nom} aboie ! Race : {$this->race}";
    }
}

class Chat extends Animal {
    public function parler(): string { return "{$this->nom} miaule."; }
}

$animaux = [new Chien('Rex', 3, 'Labrador'), new Chat('Mimi', 5)];
foreach ($animaux as $a) {
    echo $a->parler() . "\n"; // polymorphisme
}

// instanceof vérifie le type
var_dump($animaux[0] instanceof Animal); // true — Chien hérite d'Animal

3 Interfaces

<?php
// Interface : contrat sans implémentation
interface Payable {
    public function calculerMontant(): float;
    public function getDevise(): string;
}

interface Exportable {
    public function toArray(): array;
    public function toJson(): string;
}

// Une classe peut implémenter plusieurs interfaces
class Facture implements Payable, Exportable {
    public function __construct(
        private float  $montantHT,
        private float  $tauxTVA = 20.0
    ) {}

    public function calculerMontant(): float {
        return $this->montantHT * (1 + $this->tauxTVA / 100);
    }

    public function getDevise(): string { return 'EUR'; }

    public function toArray(): array {
        return [
            'ht'    => $this->montantHT,
            'tva'   => $this->tauxTVA,
            'ttc'   => $this->calculerMontant(),
            'devise'=> $this->getDevise(),
        ];
    }

    public function toJson(): string {
        return json_encode($this->toArray(), JSON_PRETTY_PRINT);
    }
}

$f = new Facture(100.0);
echo $f->calculerMontant(); // 120.0
echo $f->toJson();

4 Classes Abstraites

<?php
// Classe abstraite : ne peut pas être instanciée directement
// Mixe méthodes concrètes ET méthodes abstraites (à implémenter)
abstract class Forme {
    public function __construct(protected string $couleur = 'noir') {}

    // Méthode abstraite : les enfants DOIVENT l'implémenter
    abstract public function aire(): float;
    abstract public function perimetre(): float;

    // Méthode concrète partagée par tous les enfants
    public function description(): string {
        return sprintf(
            "%s %s — aire: %.2f, périmètre: %.2f",
            $this->couleur,
            static::class, // late static binding : nom de la classe réelle
            $this->aire(),
            $this->perimetre()
        );
    }
}

class Cercle extends Forme {
    public function __construct(private float $rayon, string $couleur = 'rouge') {
        parent::__construct($couleur);
    }
    public function aire(): float      { return M_PI * $this->rayon ** 2; }
    public function perimetre(): float { return 2 * M_PI * $this->rayon; }
}

class Rectangle extends Forme {
    public function __construct(private float $l, private float $h, string $couleur = 'bleu') {
        parent::__construct($couleur);
    }
    public function aire(): float      { return $this->l * $this->h; }
    public function perimetre(): float { return 2 * ($this->l + $this->h); }
}

$formes = [new Cercle(5), new Rectangle(4, 6)];
foreach ($formes as $f) echo $f->description() . "\n";

5 Traits

<?php
// Trait : réutilisation de code sans héritage (composition)
trait Horodatable {
    private ?DateTime $createdAt = null;
    private ?DateTime $updatedAt = null;

    public function initTimestamps(): void {
        $this->createdAt = new DateTime();
        $this->updatedAt = new DateTime();
    }

    public function touch(): void {
        $this->updatedAt = new DateTime();
    }

    public function getCreatedAt(): string {
        return $this->createdAt?->format('Y-m-d H:i:s') ?? 'non défini';
    }
}

trait Validatable {
    private array $errors = [];

    public function addError(string $field, string $msg): void {
        $this->errors[$field] = $msg;
    }

    public function isValid(): bool { return empty($this->errors); }
    public function getErrors(): array { return $this->errors; }
}

class Article {
    use Horodatable, Validatable; // composition de plusieurs traits

    public function __construct(public string $titre, public string $contenu) {
        $this->initTimestamps();
        $this->validate();
    }

    private function validate(): void {
        if (strlen($this->titre) < 3)
            $this->addError('titre', "Titre trop court");
        if (strlen($this->contenu) < 10)
            $this->addError('contenu', "Contenu trop court");
    }
}

$a = new Article('Hi', 'court');
var_dump($a->isValid());       // false
var_dump($a->getErrors());     // ['titre' => '...', 'contenu' => '...']

6 Méthodes & Propriétés Statiques

<?php
class Compteur {
    private static int $instances = 0;

    public function __construct(private string $nom) {
        self::$instances++;   // self:: = cette classe exacte
    }

    public static function getInstances(): int { return self::$instances; }
}

// Pattern Singleton
class Config {
    private static ?Config $instance = null;
    private array $data = [];

    private function __construct() {}   // constructeur privé

    public static function getInstance(): static {
        if (static::$instance === null) {
            static::$instance = new static(); // static:: = classe appelante (LSB)
        }
        return static::$instance;
    }

    public function set(string $key, mixed $val): void { $this->data[$key] = $val; }
    public function get(string $key, mixed $default = null): mixed {
        return $this->data[$key] ?? $default;
    }
}

$cfg = Config::getInstance();
$cfg->set('debug', true);
$cfg2 = Config::getInstance(); // même objet
var_dump($cfg === $cfg2);      // true