Archives de l’auteur : admin

PHP : un exemple simple de design pattern Decorator

Le design pattern Decorator (en français, décorateur) a pour but d’étendre les fonctionnalités d’un objet grâce à l’utilisation de l’héritage. Mon père m’a toujours dit que d’un âne, on ne pouvait pas faire un cheval de course; je vais m’employer à lui donner tort ! Voici un diagramme de classe qui sert de base à notre exemple, je l’ai réalisé avec l’outil ArgoUML sur GNU/LInux Debian :

Diagrammedeclasses

Au sommet de notre diagramme trône fièrement la classe abstraite Equide : elle possède une variable d’instance protégée de type chaîne de caractères qui stocke une description très sommaire de l’équidé ainsi que deux méthodes dont une (donne Description) est abstraite. Voici son code en détail :


abstract class Equide {
    protected $_description = 'équidé commun';
    abstract public function courir();

    public function donneDescription() {
        return $this->_description;
    }
}

Ce super-type est dérivé en deux classes concrètes :


class AneSauvage extends Equide {
    public function __construct() {
        $this->_description = 'âne sauvage';
    }

    public function courir() {
        echo "Il m'arrive de courir à l'occasion...";
    }
}

class AneDomestique extends Equide {
    public function __construct() {
        $this->_description = 'âne domestique';
    }

    public function courir() {
        echo "Si vraiment on m'y oblige, je trotte...";
    }
}

La méthode courir, signalée abstraite dans la mère est implémentée dans les filles et la valeur par défaut stockée dans la variable d’instance _description est écrasée avec une valeur un peu plus censée lors de la construction de l’objet.

La nouveauté arrive maintenant…


abstract class DecorateurEquide extends Equide {
    protected $_equide;

    public function __construct(Equide $equide) {
        $this->_equide = $equide;
    }
}

class ChevalDeCourse extends DecorateurEquide {

    public function donneDescription() {
        return $this->_equide->donneDescription() .
               ' qui court très très vite !' . PHP_EOL;
    }

    public function courir() {
        return $this->_equide->courir() .
               ' et maintenant je galope tel un cheval de course !' .
               PHP_EOL;
    }
}

Notre classe décorateur est DecorateurEquide; voyez-le par vous-mêmes, c’est une classe abstraite…il faudra donc la dériver. Cette classe prend en composition un objet de la classe Equide, puisque c’est précisément cet objet qu’elle va décorer ! C’est lors de l’instanciation d’une de ses classes filles que l’on passera notre instance d‘Equide au constructeur.

La seule classe fille d’Equide dans notre exemple est ChevalDeCourse; elle ajoute des fonctionnalités aux méthodes courir et donneDescription qui proviennent de DecorateurEquide et donc de Equide. Dans ce cet exemple trivial ces fonctionnalités se résument à une simple chaîne de caractères.

Les décorateurs ont le même type que les objets qu’ils décorent, c’est la raison pour laquelle DecorateurEquide hérite d’Equide, pour en être un sous-type ! L’héritage n’est pas réalisé à des fins « comportementales » mais simplement pour des raisons de typage. J’ai mis la variable d’instance _equide et le constructeur dans DecorateurEquide mais j’aurais pu tout aussi bien laisser cette classe vide (ne m’en servir vraiment que pour le typage) et déporter ce code dans les classes concrètes qui spécialisent (et spécialiseront à terme) DecorateurEquide. C’est un choix de conception parmi d’autres…

Pour utiliser ce code :


$aneDomestique = new AneDomestique;
$cheval = new ChevalDeCourse($aneDomestique);

echo $cheval->donneDescription();
echo $cheval->courir();

Tu vois Papa, c’est bien la preuve que d’un âne, on peut faire un cheval de course !

smiling donkey

Source : blog Terapias Naturales

Le design pattern Factory Method en PHP

Le design pattern Factory Method est appelé en français fabrique; c’est un design pattern dit « de création » puisque le but de Factory Method est de créer des objets. La fabrique est utilisée pour réduire le couplage entre les classes; son but est qu’une classe cliente ne fasse plus des instanciations elle-même mais qu’elle passe par une autre classe qui connait le processus (potentiellement complexe) de la création d’un objet dans les détails.

Ouvrières chargées d'instancier des objets dans une factory method

Ouvrières chargées d’instancier des objets

Présentons la facture !

Soit une classe Facturation exposant une méthode declencher et qui constitue une facture en créant une entête, un corps et un pied de page sur lesquels elle va invoquer la méthode formater.

interface RubriqueInterface
{
    public function formater(): void; 
}

class Entete implements RubriqueInterface
{
    public function formater(): void
    {
        echo "Je formate mon entête".PHP_EOL;
    }
}

class Corps implements RubriqueInterface
{
    private $produits;

    public function __construct(array $produits)
    {
        $this->produits = $produits;
    }

    public function formater(): void
    {
        echo "Corps avec ".count($this->produits)." produits".PHP_EOL;
    }
}

class PiedPage implements RubriqueInterface
{
    public function formater(): void
    {
        echo "Je formate mon pied de page".PHP_EOL;
    }
}

class Facturation
{
    private $entete;

    private $corps;

    private $piedPage;

    public function __construct(array $produits)
    {
        $this->entete = new Entete();
        $this->corps = new Corps($produits);
        $this->piedPage = new PiedPage();
    }

    public function declencher(): void
    {
        $this->entete->formater();
        $this->corps->formater();
        $this->piedPage->formater();
    }
}

$facture = new Facturation([['nom' => 'Gourde', 'prix' => 9.99]]);
$facture->declencher();

Ici Facturation est très fortement couplée avec Entete, Corps et PiedPage puisque les instanciations sont faites directement par cette classe. Nous allons diminuer ce couplage en faisant appel à nos fabriques et leur factory methods. La factory method de notre fabrique abstraite est fabriquer (comme c’était prévisible…):

abstract class FabriqueFacture {
    abstract public function fabriquer();
}

class Facturation
{
    private $entete;

    private $corps;

    private $piedPage;

    public function __construct(array $produits)
    {
        $fabriqueEntete = new FabriqueEnteteFacture();
        $this->entete = $fabriqueEntete->fabriquer();

        $fabriqueCorps = new FabriqueCorpsFacture($produits);
        $this->corps = $fabriqueCorps->fabriquer();

        $fabriquePiedPage = new FabriquePiedPageFacture();
        $this->piedPage = $fabriquePiedPage->fabriquer();
    }

    public function declencher(): void
    {
        $this->entete->formater();
        $this->corps->formater();
        $this->piedPage->formater();   
    }
}

$facture = new Facturation([['nom' => 'Gourde', 'prix' => 9.99]]);
$facture->declencher();
 
class FabriqueEnteteFacture extends FabriqueFacture
{
    private $classeCible = 'Entete';

    public function fabriquer(): RubriqueInterface
    {
        return new $this->classeCible();
    }
}
 
class FabriqueCorpsFacture extends FabriqueFacture
{
    private $classeCible = 'Corps';

    private $produitsAFacturer;

    public function __construct(array $produits)
    {
        $this->produitsAFacturer = $produits;
    }

    public function fabriquer(): RubriqueInterface
    {
        return new $this->classeCible($this->produitsAFacturer);
    }
}
 
class FabriquePiedPageFacture extends FabriqueFacture
{
    private $classeCible = 'PiedPage';

    public function fabriquer(): RubriqueInterface
    {
        return new $this->classeCible();
    }
}

Alors oui, vous allez me dire « Oui mais, elle instancie des fabriques notre classe Facturation » et je ne le contesterai pas ! Facturation fait effectivement appel à des fabriques à qui elle délègue les instanciations. C’est là que réside le découplage !

Ce sont les classes concrètes dérivant FabriqueFacture qui auront la responsabilité de l’instanciation des classes qui constituent les différentes parties d’une facture : une fabrique de pieds de pages sait qu’elle doit instancier des pieds de pages. Souvenez-vous : une classe = une responsabilité ! Afin de vous faciliter la vie durant les tests unitaires, le mieux serait d’injecter directement ses dépendances au constructeur de Facturation, à savoir les 3 fabriques. C’est très simple.

class Facturation
{
    private $entete;

    private $corps;

    private $piedPage;

    public function __construct(FabriqueFacture $fabriqueEntete, 
        FabriqueFacture $fabriqueCorps,
        FabriqueFacture $fabriquePiedPage)
    {
        $this->entete = $fabriqueEntete->fabriquer();
        $this->corps = $fabriqueCorps->fabriquer();
        $this->piedPage = $fabriquePiedPage->fabriquer();
    }

    public function declencher(): void
    {
        $this->entete->formater();
        $this->corps->formater();
        $this->piedPage->formater();
    }
}

$produits = [['nom' => 'Gourde', 'prix' => 9.99]];

$facture = new Facturation(
  new FabriqueEnteteFacture(),
  new FabriqueCorpsFacture($produits),
  new FabriquePiedPageFacture()
);
$facture->declencher();

Résumons les unités de code en présence dans le design pattern Factory Method :

  • La classe cliente : Facturation
  • La fabrique abstraite : FabriqueFacture (notez qu’une interface aurait suffi, essayez !)
  • Les fabriques concrètes : FabriqueEnteteFacture, FabriqueCorpsFacture, FabriquePiedPageFacture
  • Les classes « produits » (ce qu’on veut obtenir au final) : Corps, Entete, PiedPage

PHP : petit exemple mêlant type-hint, abstraction et héritage.

Nous allons écrire un tout petit snippet pour mettre en évidence des concepts de base de la programmation orientée objet. Le but ici est d’utiliser l’abstraction, l’héritage, le typage (type-hint) et le principe de substitution de Liskov. Le tronc de notre arbre d’héritage est ici la classe abstraite Animal; elle ne possède qu’une seule fonction membre : communiquer.

Pourquoi communiquer est une fonction abstraite (un prototype, donc) dans Animal ? Tout simplement parce qu’un animal ne communique pas forcément comme un autre. Nous déléguons ici la responsabilité de l’implémentation de cette méthode aux classes filles d’Animal.

Nous avons ensuite deux classes concrètes Chameau et Belette qui elles détaillent la manière dont nos animaux communiquent: dès lors qu’elles dérivent Animal, elles sont tenues d’implémenter la méthode communiquer, ce qu’elles font, chacune à leur manière (le chameau blatère tandis que la belette…belote, et oui !).

Nous avons finalement une classe Communicateur, dont une variable d’instance privée et nommée _animal va contenir l’instance d’Animal sur laquelle nous allons opérer. Notez que nous avons forcé le type de paramètre en disant « Nous exigeons une instance de la classe Animal » dans le constructeur de cette classe. C’est une bonne chose que de ne pas accepter n’importe quoi

Souvenez-vous toujours de cette phrase de feu-Jon Postel :

Be liberal in what you accept, and conservative in what you send

Vous restez libéral dans ce que vous acceptez (tout en étant il faut le dire un peu conservateur aussi, puisque vous contraignez le type malgré tout), vous acceptez tout ce qui est un Animal : donc, tout ce qui dans l’arbre d’héritage se situe dans les branches (et les feuilles) partant du tronc qui est Animal.


abstract class Animal {
    abstract public function communiquer();
}

class Chameau extends Animal{
     public function communiquer() {
         echo "Je blatère", PHP_EOL;
     }
}

class Belette extends Animal{
    public function communiquer() {
        echo "Je belote", PHP_EOL;
    }
}

class Communicateur {

    private $_animal;

    public function __construct(Animal $animal) {
        $this->_animal = $animal;
    }

    public function faireCommuniquer() {
       $this->_animal->communiquer();
    }
}

$communicateur = new Communicateur(new Belette);
$communicateur->faireCommuniquer();

$communicateur = new Communicateur(new Chameau);
$communicateur->faireCommuniquer();

Une instance de la classe Communicateur a donc en composition un objet du super-type Animal. La seule méthode de cette classe est faireCommuniquer, qui se base sur la méthode communiquer de l’instance d’Animal (et classes filles) en composition. Lorsqu’on appelle faireCommuniquer de Communicateur, on appelle en fait en coulisses Animal->communiquer.

Nous garantissons que quelque soit l’instance de la sous-classe d’Animal passée en paramètre du constructeur de Communicateur, le comportement de Communicateur sera le même…Cela ne vous rappelle pas un certain principe de substitution de Liskov ?

chameau

Source photo : 20 Minutes

MySQL : comparaison rapide des types de données CHAR et VARCHAR

Quand on choisit d’utiliser des champs de type chaîne de caractères dans une table MySQL (mais pas que…), on en vient rapidement à se poser la question suivante :

CHAR ou VARCHAR ?

Voici le tableau comparatif que nous donne la documentation MySQL. Il suppose que nous utilisions un jeu de caractères codés sur 1 octet comme latin1 par exemple (de son vrai nom ISO 8859-1) :

snapshot2

CHAR va de 0 à 255 caractères. La longueur d’un champ de type CHAR est fixée à la valeur déclarée lors de la définition du champ : si vous créez un champ de type CHAR(30) et que vous souhaitez y insérer une chaîne de 31 caractères, cette valeur sera stockée sous une forme tronquée. Si la valeur insérée est inférieure à 30, le « reste » (les caractères manquants pour arriver à 30) sera comblé avec des espaces. Lorsque la valeur sera récupérée, les espaces seront enlevés automatiquement, vous n’y verrez que du feu !

Les VARCHAR sont eux utilisés pour des chaînes de longueur variable et donc appropriés pour des données dont on ne peut prédire la longueur de façon certaine. VARCHAR peut stocker jusqu’à 65535 caractères (bien plus que les 255 qu’une grande partie des gens ont en tête). Leur taille étant variable, elle doit être stockée quelque part…Ainsi, pour tout type VARCHAR, MySQL réserve un préfixe d’un octet si la taille des données est inférieure ou égale à 255 (un octet = 8 bits et 28 = 256) et deux octets dans le cas contraire (2 octets = 16 bits, 216 = 65536).

Que lit-on sur ce tableau ? Lorsque l’on stocke une chaîne de caractères vide dans un champ en CHAR(4), quatre caractères « espace » sont réservés et donc 4 octets alloués ; avec un VARCHAR(4), on n’alloue que l’octet nécessaire au préfixe des chaînes de moins de 255 caractères. Si l’on stocke deux caractères, en CHAR(4), deux espaces sont alloués « pour rien » alors qu’en VARCHAR(4) on a toujours l’octet nécessité par le préfixe et les deux octets de chaque caractère. Jusqu’ici, VARCHAR est moins gourmand en espace disque. La tendance s’inverse lorsque l’on remplit CHAR avec le nombre exact de caractères attendus : on économise un octet par rapport à un VARCHAR ! Lorsque la chaîne dépasse la longueur maximale prévue, elle est tronquée dans les deux cas, mais c’est toujours CHAR qui est plus économique !

Conclusion, quand on sait qu’une chaîne de caractères aura une longueur définie, mieux vaut privilégier CHAR (si cette longueur est évidemment inférieure à 255, mais ce sera dans 99,99% des cas, n’est-ce pas ?).

Les principes SOLID expliquées à ma fille

Ma fille ne comprend rien à l’informatique. Il faut dire qu’à 6 ans, on a autre chose à faire que de se passionner pour ces choses bizarres ! Et puis l’informatique ça a l’air compliqué ! On voit souvent Papa se prendre la tête à deux mains en se demandant pourquoi c’est fait de manière si compliquée alors que le problème adressé est on ne peut plus simple. J’ai tout de même essayé de m’imaginer en train d’expliquer à ma petite tête brune à couettes les principes SOLID, énoncés par « Uncle Bob » il y a maintenant plus d’une décennie.

Commençons par le commencement : le S de SOLID, c’est pour Single Responsibility (responsabilité unique) : une classe n’est pas comme ces équipiers qu’on voit au comptoir des fast-foods; elle ne doit pas faire le drive-in, servir les boissons, nettoyer les tables, faire chauffer les frites, non : UNE SEULE chose à la fois.

Le O c’est pour Open/Closed (ouvert/fermé) : une classe doit être fermée à la modification (de son code source) mais ouverte à l’extension : on n’essaie pas de faire rentrer un carré dans un rond, on crée une « forme », qu’on spécialisera ensuite en rond, carré, losange, triangle, etc.

Le L rend hommage à Madame Barbara Liskov et son principe de substitution: mon objet chimpanzé de la classe Singe a une méthode manger(Fruit) : la banane, la mangue et la noix de coco sont des fruits; ma méthode manger doit avoir le même comportement que je fasse manger(Banane), manger(Mangue) ou manger(Coco), c’est à dire si je substitue le sous-type Banane, Mangue ou Coco au super-type Fruit.

Le I évoque la ségrégation des interfaces (Interface Segregation) : certains animaux savent voler, le singe est un animal mais ne sait pas voler. Pourquoi le faire dépendre d’une classe Animal qui implémenterait une interface AnimalVolant si on sait qu’il ne volera jamais ? Ce principe est très simple : une classe ne doit pas dépendre d’interfaces dont elle n’a pas l’utilité ! L’objectif est ici, une fois de plus, de réduire le couplage inutile entre les classes.

Le dernier de ces 5 principes est le principe d’inversion des dépendances (D pour Dependency Inversion). A est client de B, lui-même client de C; on peut dire que A dépend de B qui dépend de C. Prenons A et B: ce n’est plus A qui va dépendre de B, mais A qui va dire à B ce dont il a besoin. B ne dit plus à A « voilà comment je fonctionne, débrouille toi ! », c’est A qui dit à B « voilà ce dont j’ai besoin, donne le moi ! »…Nous avons donc bien inversé le rapport de force entre fournisseur et client !

Allez, je vous laisse, j’ai un match entre les Littlest Pet Shop et les My little Pony à gérer…

IMG_3521

Sources :

Pour ceux que l’anglais ne rebute pas, je vous conseille l’excellent site d’Oncle Bob et ses hilarants (autant qu’instructifs) codecastsCleanCoders

Toujours dans la langue de Shakespeare…

http://aspiringcraftsman.com/2008/12/28/examining-dependency-inversion/

MySQL : poser un index qui…pénalise le temps d’exécution !

Avant de poser un index sur un (ou plusieurs) champs d’une table, il faut évaluer de la manière la plus précise possible l’impact qu’aura cette pose sur le temps d’exécution des requêtes. Lorsque les développeurs découvrent les index (j’en connais qui sont en poste depuis des années et ne savent même pas ce que c’est…), ils ont tendance à en mettre de partout. Gare ! Un index posé à tort peut pénaliser les sélections, les mises à jour/suppressions/insertions. Regardons un exemple très simple; Henri a décidé que puisque les index sont là pour accélérer les recherches, il va en poser un sur sa table, dont voici le schéma (l’intention) :


create table if not exists test_index
(
   id mediumint unsigned not null primary key,
   nom char(4) not null,
   valid tinyint unsigned not null
);

Sa table comporte 100 000 tuples, qu’il insère avec une procédure stockée :

DROP PROCEDURE IF EXISTS insertion;

DELIMITER //
CREATE PROCEDURE insertion()
BEGIN
    DECLARE i INT DEFAULT 1;

    WHILE (i<=100000) DO
        INSERT INTO test_index VALUES(i,'test', 1);
        SET i=i+1;
    END WHILE;
END
//
CALL insertion();

Il part du principe (plutôt intelligent, au demeurant) que la requête qu’il fait le plus souvent étant :


select * from test_index where valid = 1;

Il serait judicieux qu’il pose un index sur valid :


alter table test_index add index(valid);

Voilà la pose réalisée ! Une fois l’index en place, il a quand même le réflexe de valider que la pose de cet index accélère bien les recherches, comme il le souhaitait…

Il a bien ses 100 000 enregistrements…


mysql> select count(*) from test_index;
+----------+
| count(*) |
+----------+
| 100000 |
+----------+

Avant la pose de l’index, un EXPLAIN sur sa requête favorite donnait :

snapshot2

On voit au passage qu’il réalisait un full table scan (type = ALL, possible_keys = NULL), c’est à dire qu’il parcourait l’ensemble des tuples de sa table (rows). Dorénavant, cela donne :

snapshot2

Henri constate avec satisfaction que la pose de son index lui fait lire moitié moins d’enregistrements (regardez la colonne rows). Pourtant, lorsqu’il lance sa requête, Henri est très déçu :


100000 rows in set (0.19 sec)

Alors que lorsqu’il détruit l’index, il obtient :


100000 rows in set (0.10 sec)

Pour résumer, Henri lit moitié moins de tuples…en deux fois plus de temps ! Ce n’est pas du tout conforme à l’idée qu’il se faisait des index et ses espoirs sont déçus ! En posant un index inutile sur un champ qui n’a qu’une valeur possible (1), Henri a posé un index qui pénalise le temps d’exécution de sa requête…Il aura tout de même compris que le seul vrai moyen de vérifier qu’on tire des bénéfices d’un index est le benchmarking des requêtes qui l’utilisent.

PHP : héritage multiple des interfaces

Vous le savez sûrement, les interfaces servent à contraindre des classes à implémenter une ou des méthodes publiques. N’importe quelle classe, abstraite ou concrète, peut les implémenter. Mais saviez-vous que, contrairement aux classes, les interfaces peuvent hériter de plusieurs parents ?

Prenons l’exemple suivant ;


interface Counterializable extends Serializable, Countable {
    public function counterialiser();
}

class CompteEtSerialise implements Counterializable {}

Ici, l’interface Counterializable (contraction de Countable et de Serializable) impose à notre classe concrète CompteEtSerialise l’implémentation de 4 méthodes:
– deux provenant de Serializable (serialize et unserialize)
– une provenant de Countable (count)
– une provenant de Counterializable (counterialiser)

Si l’on exécute ce code, il se produit l’erreur suivante :

PHP Fatal error: Class CompteEtSerialise contains 4 abstract methods
and must therefore be declared abstract or implement the remaining
methods (Counterializable::counterialiser, Serializable::serialize,
Serializable::unserialize, ...)

Ce message d’erreur est bien la preuve que l’héritage multiple, qui ne s’applique pas aux classes, s’applique en revanche aux interfaces !

MySQL : l’opérateur ensembliste UNION (2/2)

Revenons sur l’opérateur ensembliste UNION et voyons comment il traite les duplicatas :

create table employe (
    id tinyint unsigned NOT NULL PRIMARY KEY,
    nom varchar(20) NOT NULL,
    prenom varchar(30) NOT NULL
);

create table manager(
    id smallint unsigned NOT NULL PRIMARY KEY,
    nom varchar(30),
    prenom varchar(20)
);

INSERT INTO employe VALUES (1, 'Klein', 'Roger'),
                           (2, 'Bagnole', 'Marcel'),
                           (3, 'De Narvale', 'Nadine');

INSERT INTO manager VALUES (3, 'Avignon', 'Eric'),
                           (2, 'Avril', 'Mathilda'),
                           (1, 'Klein', 'Roger');

Faisons l’union de ces deux relations :


select * from employe union select * from manager;

Roger Klein n’apparaît qu’une seule fois : UNION supprime bien les doublons !

Pour avoir l’intégralité de l’union des deux extensions de ces relations, il suffit de faire :


select * from employe union ALL select * from manager;

Et voilà que nous avons tout à présent !

snapshot2

 

Utiliser un UNION ALL vous permettra de faire apparaître l’intégralité des extensions des schémas unis.

MySQL : l’opérateur ensembliste UNION (1/2)

En algèbre relationnelle, UNION est un opérateur dit « ensembliste ». Par définition, il travaille sur au moins deux ensembles (l’extension de deux relations), qu’il agrège tout en éliminant les éventuels doublons. Créons deux relations matérialisant des employés et des managers au sein d’une entreprise pour voir comment tout cela fonctionne :


create table employe (
    id tinyint unsigned NOT NULL PRIMARY KEY,
    nom varchar(20) NOT NULL,
    prenom varchar(30) NOT NULL
);

create table manager(
    id smallint unsigned NOT NULL PRIMARY KEY,
    nom varchar(30),
    prenom varchar(20)
);

INSERT INTO employe VALUES (1, 'Klein', 'Roger'),
                           (2, 'Bagnole', 'Marcel'),
                           (3, 'De Narvale', 'Nadine');

INSERT INTO manager VALUES (1, 'Avignon', 'Eric'),
                           (2, 'Avril', 'Mathilda'),
                           (3, 'Klein', 'Roger');

Ces deux relations ont le même schéma, c’est à dire le même nombre d’attributs; c’est une des conditions de l’UNION. Lorsque l’on fait l’union de nos deux relations, par la requête :


select * from employe union select * from manager;

On obtient les tuples suivants :

snapshot2

Vous notez que Roger Klein est présent deux fois, car il est à la fois employé et manager; ce n’est pas un doublon au sens strict du terme car il a deux valeurs d’identifiant différentes dans chaque table. Il apparaît donc logiquement deux fois, ce qui n’aurait pas été le cas s’il avait eu la valeur d’id 3 dans employe ou de 1 dans manager.

La condition pour réaliser une UNION, je l’ai déjà dit plus haut, est d’avoir le même schéma dans les deux tables, c’est à dire le même nombre d’attributs; prenons deux tables qui n’ont aucun rapport entre elles :


create table chevre (

    id tinyint unsigned NOT NULL PRIMARY KEY,

    nom varchar(20) NOT NULL,

    age tinyint unsigned NOT NULL default 0

);

create table chou (

    variete varchar(15) NOT NULL PRIMARY KEY,

    poids tinyint unsigned NOT NULL default 0,

    couleur varchar(10) NOT NULL

);

insert into chevre values (1, 'Biquette', 10),
                          (2, 'Lola', 13);

insert into chou values ('romanesco', 2, 'vert'),
                        ('chinois', 1, 'blanc');

Faisons l’UNION de ces deux relations de même schéma :


select * from chevre UNION select * from chou;

Je ne comprends pas, on me répète depuis tout petit qu’il ne faut pas ménager la chèvre et le chou et pourtant, stupeur…

snapshot2

C’est tout à fait possible d’avoir les deux ! Vous avez vu que mes champs sont mélangés, il ne sont pas de même type (le premier champ de chevre est un entier non signé alors que le premier champ de chou est une chaîne de caractères, le deuxième est une chaîne de caractères dans l’un et un entier dans l’autre, etc.). Au final, le résultat de notre union ne veut pas dire grand chose sémantiquement parlant, regardez le nom des colonnes : nom contient le nom de nos sympathiques caprins mais aussi le poids de nos choux, age contient l’âge de nos ruminants mais aussi la couleur de nos choux.

Maman avait donc raison : on ne doit pas ménager la chèvre et le chou !

Les classes abstraites en PHP (partie 3)

abstract class EtreVivant {
    protected $_age;
    protected $_poids;

    public function getAge() {
        echo "J'ai " . $this->_age . " ans", PHP_EOL;
    }

    public function getPoids() {
        echo "Je pèse " . $this->_poids . " grammes", PHP_EOL;
    }

    public function __construct($age = 0, $poids = 0) {
        $this->_age = $age;
        $this->_poids = $poids;
    }

    // rajoutez les setters...
}

interface Respiration {
    public function respirer();
}

interface Reproduction {
    public function seReproduire();
}

abstract class Vegetal extends EtreVivant
                       implements Respiration, Reproduction {
    protected $_chlorophyllien = false;

    public function respirer() {
        echo "Je fais ma photo-synthèse", PHP_EOL;
    }

    public function estChlorophyllien() {
        return (true == $this->_chlorophyllien);
    }
}

class Lys extends Vegetal {
    public function seReproduire() {
        echo "Le vent et les insectes font le travail", PHP_EOL;
    }

    public function __construct($age = 0, $poids = 0) {
        $this->_chlorophyllien = true;
        parent::__construct($age, $poids);
    }
}

$lys = new Lys;
$lys->seReproduire();
$lys->respirer();
$lys->getPoids();
$lys->getAge();

echo ("Je suis ") .
     (!$lys->estChlorophyllien() ? 'pas ' : '') .
     "chlorophyllien", PHP_EOL;

Pour résumer :

  • les classes abstraites ne s’instancient pas, on en hérite donc forcément !
  • ce sont des débuts d’implémentation, elles peuvent comporter des méthodes entièrement ou partiellement écrites
  • une classe abstraite n’a pas forcément de méthode abstraite…
  • …mais une classe qui a une méthode abstraite doit être abstraite
  • une méthode abstraite n’a pas de corps, c’est un prototype : à vous de la concrétiser !
  • une classe abstraite n’est pas tenue de concrétiser les méthodes des interfaces qu’elle implémente; ses filles oui, par contre !
  • mettre une méthode abstraite en mode d’accès privé n’a pas de sens
  • une classe abstraite peut très bien avoir des variables d’instance

 PS : j’ai remarqué que certains élèves étaient choqués de voir un constructeur dans une classe abstraite (« Pourquoi y a-t-il un constructeur si on ne peut PAS les instancier ??? »). Ce constructeur a tout à fait sa place, il peut être utilisé dans les classes filles ou bien être complété dans celles-ci par des actions spécifiques (et invoqué via l’appel statique parent, comme fait ici).