Le design pattern Prototype en PHP

Prototype est un design pattern de création: son but est de répliquer des instances dites « prototypes » via un mécanisme de clonage.
Il est prescrit lorsqu’il convient d’éviter de trop nombreuses instanciations d’une classe, notamment lorsque celle-ci possède une logique assez complexe dans son constructeur, ce qui idéalement ne devrait jamais arriver.

Il est important d’économiser les ressources car le coût d’un clonage d’objet reste bien inférieur à celui d’une instanciation. Evidemment, vous ne verrez pas la différence si vous travaillez avec une dizaine d’objets mais ce ne sera sans doute pas le cas si votre application doit gérer 100 000 objets simultanément le jour d’une facturation par exemple.

prototype de voiture

Protype attendant un clonage incognito

Les composants de Protoype

  • une abstraction (classe abstraite ou interface) qui force la présence d’une méthode dédiée au clonage dans les classes concrètes
  • une ou plusieurs classes concrètes dériveront l’abstraction, implémentant de fait la méthode de clonage. Les instanciations seront faites sur ces classes
  • des clones de ces classes seront effectués par le code client, qui participe à ce pattern

Voici notre abstraction; elle force l’implémentation d’une méthode __clone() et par chance, PHP en fourni une « out of the box » comme on dit (oui, on peut aussi dire « nativement ») ! Le reste est très basique: un constructeur sans aucune logique métier, qui prend ce qu’on lui donne et le range consciencieusement, un setter et deux getters.

L’abstraction

abstract class HumainAbstract
{   
    protected $prenom;
    
    protected $sexe;
        
    public function __construct(string $prenom)
    {
        $this->prenom = $prenom;
    }
    
    public function donnerSexe(): string
    {
        return $this->sexe; 
    }
    
    public function changerPrenom(string $prenom)
    {
        $this->prenom = $prenom;
    }
    
    public function donnerPrenom(): string
    {
        return $this->prenom; 
    }
    
    abstract public function __clone();
}

Les classes concrètes

Les classes concrètes – les prototypes – vont dériver cette classe abstraite et, pour rester simples, ne feront absolument rien dans leur méthode __clone() ! Elles ont une variable d’instance qu’il est juste possible de lire mais pas de modifier. Seul le prénom est modifiable (regardez dans la classe abstraite !).

class Male extends HumainAbstract
{
    protected $sexe = 'M';

    public function __clone()
    {
    }
}

class Femelle extends HumainAbstract
{
    protected $sexe = 'F';
    
    public function __clone()
    {
    }
}

Le code client

Le client est un acteur à part entière du design pattern prototype, c’est lui qui va cloner les prototypes.
Ici nous décidons que, les premiers humains ayant sans doute communiqué entre eux par un langage très primitif, notre prototype de mâle s’appellera « Rrrnnngrrwggl », tandis que la femelle portera le doux sobriquet de « Nyyyynyaaa ».

Tous les êtres humains descendant de ces respectables parents à la pilosité prononcée ne seront que des clones dont nous changerons uniquement le prénom. Vous noterez que nous utilisons l’affectation dynamique, c’est à dire que chaque clone sera rangé dans une variable dont le nom sera incrémenté à chaque itération de notre boucle.

$prenoms = [
        'René', 'Eric', 'Jean', 'Robert', 'Marius',
        'Kevin', 'Léo', 'Jacques', 'Loïc', 'John',
        'Alexis', 'Kenneth', 'Nathanaël', 'Christophe'
    ];
    
$male1 = new Male('Rrrnnngrrwggl');

$numeroClone = 0;
$clones = [];

foreach ($prenoms as $prenom) {
    ++$numeroClone;
    
    $nomClone = 'clone'.$numeroClone;
    $$nomClone = clone $male1;
    $$nomClone->changerPrenom($prenom);
    $clones[] = $$nomClone;
}

$prenoms = [
        'Lise', 'Marie', 'Ninon', 'Rachida', 'Ana',
        'Martine', 'Svetlana', 'Eve', 'Carole',
        'Sylvie', 'Laurie', 'Zhang', 'Fatoumata'
    ];

$femelle1 = new Femelle('Nyyyynyaaa');
    
foreach ($prenoms as $prenom) {
    ++$numeroClone;
    
    $nomClone = 'clone'.$numeroClone;
    $$nomClone = clone $femelle1;
    $$nomClone->changerPrenom($prenom);
    $clones[] = $$nomClone;
}

foreach ($clones as $clone) {
    echo $clone->donnerSexe().'/'.$clone->donnerPrenom().PHP_EOL;
}

A retenir

  • N’implémentez pas Prototype si vous devez gérer un petit nombre de copies, ce serait tuer une mouche au lance-roquettes
  • Lors d’un appel à __clone(), le constructeur n’est pas appelé (c’est le but)
  • Si votre objet à cloner a des objets en compositions, pensez à les cloner eux aussi pour éviter de pointer vers une référence qui n’est pas la bonne…
  • Prenez garde aux références circulaires (A dépend de B qui dépend de C qui dépend de A…) entre les objets

Le prédicat SQL EXISTS

Le mot clé SQL EXISTS est ce que l’on appelle un prédicat, il évalue une (sous-)requête et dit si elle contient (true) ou non (false) des tuples. Pour travailler avec, nous allons utiliser deux tables au schéma simplissime, clients et commandes, dont voici les extensions, c’est à dire l’ensemble des tuples qu’elles contiennent.

clients

comm

Pour trouver les noms et prénoms des clients qui ont une commande:

SELECT nom, prenom FROM clients WHERE EXISTS (select * from commandes)

Voilà le résultat:
requete1

Heu…attendez voir, je fais quoi parmi ces résultats, je n’ai pas passé commande ! Que dit en réalité notre requête ? « Donne-moi le nom et prénom des clients tant qu’il EXISTE des commandes ». Il existe 5 commandes, j’ai donc 5 clients. Et oui, attention car à ce stade, nous n’avons absolument pas demandé de corrélation entre les résultats. Faisons-le et notez au passage que nous utilisons TRUE en lieu et place du joker « * » dans la sous-requête:

SELECT nom, prenom FROM clients c WHERE EXISTS (SELECT TRUE FROM commandes co WHERE c.id = co.client_id)

Voilà qui est nettement mieux, vous en conviendrez !

requete2

Alors, vous allez me dire, « Quel est l’intérêt d’utiliser EXISTS, nous sommes en train de faire ni plus ni moins qu’une jointure interne » et vous aurez raison. D’ailleurs, certaines personnes qui ont grand besoin d’une mise à jour en SQL s’en servent comme « jointure du pauvre ». La grosse différence, c’est que vous ne pouvez pas projeter des attributs potentiellement contenus dans la sous-requête, ce qui peut être fait avec un JOIN, interne ou externe. Ici nous ne pouvons projeter que nom et prenom, qui sont des attributs de la table clients. Pensez aussi à l’utilisation des index (utiliser EXPLAIN !)

To EXIST or not to EXIST?

Evidemment, EXISTS a aussi son contraire – comme NULL – c’est NOT EXISTS.
Reprenons la dernière requête (notez que TRUE est devenu 1)

SELECT nom, prenom FROM clients c WHERE NOT EXISTS (SELECT 1 FROM commandes co WHERE c.id = co.client_id)

Et hop ! Me voilà apparaissant dans les résultats !
requete3

Le prédicat EXISTS sur un UPDATE

Si je souhaite mettre le montant des commandes pour lesquelles il n’existe pas de client à 1, je ferai:

UPDATE commandes AS co
SET montant = 1
WHERE NOT EXISTS (SELECT * FROM clients c WHERE c.id = co.client_id)

C’est un peu moins délicat à écrire qu’un UPDATE avec une jointure.

Le prédicat EXISTS sur un DELETE

Ce n’est guère plus difficile, notez seulement que DELETE n’accepte pas nativement l’alias (AS), il faut donc mettre commandes en toutes lettres.

DELETE FROM commandes
WHERE NOT EXISTS (SELECT * FROM clients c WHERE c.id = commandes.client_id)

Autre exemple

Altérons notre relation commandes pour en faire celle qui suit:

requete4

Et rajoutons une relation produits elle aussi tout à fait basique:

requete5

Maintenant supposons que nous souhaitions trouver le nom des produits de toute commande contenant une écharpe et qui ne sont pas une écharpe justement:

SELECT p.nom FROM commandes AS co
INNER JOIN produits p ON (p.id = co.produit_id)
WHERE produit_id != 2
AND EXISTS (select * from commandes AS co2 WHERE co.client_id = co2.client_id AND co2.produit_id = 2)

Nous cherchons les commandes qui contiennent le produit 2 dans la sous-requête et nous projetons ceux dont l’id n’est pas 2.

PHP 7: Un tri(vial) avec l’opérateur combiné spaceship

Si vous êtes au courant des quelques nouveautés – plus si nouvelles que ça d’ailleurs – de PHP 7, cet opérateur combiné au nom amusant ne vous est pas inconnu ! Ce spaceship, censé imiter la forme des TIE Fighters de Star Wars, est utilisé pour effectuer des comparaisons. Cet opérateur existe déjà depuis longtemps en Ruby, avec lequel je travaille également. PHP emprunte ce qu’il y a de meilleurs à ses concurrents depuis des années et c’est très bien ainsi ! Peut-être sera-t-il un jour fortement typé ? Je digresse…

TIE Fighter

« Votre manque de typage me consterne »


Revenons à notre « vaisseau spatial »…Son rôle est de comparer deux valeurs: 0 est renvoyé si elles sont égales, 1 si la valeur à gauche est la plus grande et -1 dans le cas contraire. Il nous évite d’avoir à écrire ce genre de one-liner peu ragoûtant:

$ret = ($a === $b ? 0 : ($a > $b ? 1: -1));

Nous allons l’utiliser dans un cas assez trivial, celui d’un tri effectué à l’aide de la fonction usort. Je dis trivial car au final, on aurait pu utiliser sort et le résultat aurait été le même. Le but ici est de s’amuser un peu avec les nouveautés PHP 7 et notamment le STH (Scalar Type Hinting), dont j’ai déjà parlé ici en 2015.

La fonction usort trie les éléments d’un tableau en leur appliquant la fonction de tri que vous avez défini. C’est dans cette dernière que nous allons faire usage du spaceship. Attention toutefois, elle doit obligatoirement retourner une valeur de type entier, sinon le résultat risque de vous décevoir…Notez aussi qu’usort trie votre tableau en place, c’est à dire qu’il le modifie (passage par référence dans la liste des paramètres de la fonction) et n’en génère pas un nouveau.

Voici ce que donne le code, en faisant usage des pratiques PHP 7:


$tableau = array (10, 6, 12, 0, 687, 10, 6, 238, -6);

function compare(int $a, int $b) : int {
  return $a <=> $b;
}

usort($tableau,'compare');

print_r($tableau);

J’avais dit « trivial »…je n’ai pas menti ! En réalité on utilise plutôt usort quand le tri a effectuer est assez complexe, avec par exemple des tableaux associatifs, mais ceci est une autre histoire…

Modifier la clé d’un Hash en Ruby

Si vous tentez de modifier la clé d’un Hash en Ruby de façon pourtant « instinctive », vous allez rencontrer des problèmes…


h = {"cle" => "val"}
h.keys[0].capitalize!

16443237_1865407753716656_526630943_n

Ruby n’est pas très content et c’est un euphémisme !

RuntimeError: can't modify frozen String

Pour remédier à ça, il suffit de passer par delete
h = {"cle" => "val"}
h[h.keys[0].capitalize] = h.delete(h.keys[0])

L’appel à delete va renvoyer la valeur associée à votre clé passée en paramètre (h.keys[0]) et de ce fait, la mettre en face de votre nouvelle clé, qui est ni plus ni moins que l’ancienne avec une majuscule. L’ancienne, elle, a bien évidemment disparu avec l’appel à delete.

Ruby On Rails – utiliser first_or_create

Lorsque vous utilisez ActiveRecord, vous vous intéressez forcément tôt ou tard à la fonction first_or_create et en général, vous l’utiliser comme suit:


MonJoliModele.first_or_create(attribut: "unevaleur")

Ce qu’on pense faire…

En procédant ainsi, vous pensez « Soit un enregistrement avec comme valeur du champ attribut « unevaleur » est trouvé, soit je vais en créer un ». Et bien non ! Ouvrez-vous un rails console et regardez ce qui se passe en réalité. Et oui, Rails prend le premier enregistrement de la table liée au modèle, sans même tenir compte de la valeur du champ « attribut » que vous lui donnez, pensant logiquement qu’il allait en faire quelque chose…Évidemment si votre table est vide alors Rails créera cet enregistrement avec la valeur passée en paramètre, mais c’est loin d’être tout le temps le cas.

Développeur sceptique: ne me dites pas que vous n'avez jamais fait cette tête, vous mentiriez !

Développeur sceptique: ne me dites pas que vous n’avez jamais fait cette tête, vous mentiriez !


Ce que vous vouliez faire en réalité, c’était:

MonJoliModele.where(attribut: "unevaleur").first_or_create(attribut: "unevaleur")

ou, plus élégant :

critere = {attribut: "unevaleur"}
MonJoliModele.where(critere).first_or_create(critere)

Attention donc aux effets de bord possiblement dus à la mauvaise utilisation de cette fonction. Notez qu’on peut utiliser un bloc aussi. Mais attention, il n’est exécuté qu’en cas de création d’un nouvel enregistrement et vous l’utiliserez évidemment si vous avez de nombreuses affectations à y faire, et pas forcément deux comme dans mon exemple:


critere = {attribut: "unevaleur"}
MonJoliModele.where(critere).first_or_create do |modele|
modele.autreattribut = "une autre valeur"
modele.encoreunautreattribut = "encore une autre valeur"
end

Voilà donc pour ce crash course sur l’utilisation de first_or_create, fonction dont le nom assez ambigu peut être source de confusion et qui en même temps nous épargne d’avoir à faire un find_by suivi d’un create, ce qui n’est ni très esthétique, ni très performant !

Zend Framework 2 : supprimer l’appel à getServiceLocator dans les contrôleurs

Vous avez peut-être récemment joué avec la Skeleton Application de Zend Framework (ou pire, remarqué ça lors d’une mise à jour) et en lançant votre application, vous avez vu s’afficher le message suivant, du à l’appel de la méthode getServiceLocator dans votre contrôleur (AlbumController, pour ceux qui ont tenté de suivre le tutoriel) :

Deprecated: You are retrieving the service locator from within the class Album\Controller\AlbumController. Please be aware that ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along with the ServiceLocatorAwareInitializer. You will need to update your class to accept all dependencies at creation, either via constructor arguments or setters, and use a factory to perform the injections.

Pour faire simple, on vous avertit que dans la version future de ZF, cette façon de faire sera considérée comme obsolète (j’aime entendre les gens dire « déprécatède ») et qu’il vaudrait mieux injecter la dépendance à la création de votre objet, en l’occurrence ici celui qui résulte de l’instanciation de la classe AlbumController (voir cette page).

Ah...heu...bon...OK, soit !

Ah…heu…bon…OK, soit !


Pour mettre en place quelque chose d’un peu élégant, je vous invite à faire appel à une factory dans votre fichier module/Album/config/module.config.php :

     'factories' => array(
                      'Album\Controller\Album' => 'Album\Factory\AlbumControllerFactory'
                    ),
     ...

Voici le code de votre factory, placée si ce n’est pas déjà fait, dans un répertoire éponyme:

namespace Album\Factory;

use Album\Controller\AlbumController;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

use Album\Service\ControllerService;

class AlbumControllerFactory implements FactoryInterface
{
    /**
    * Create service
    *
    * @param ServiceLocatorInterface $sl
    *
    * @return Album\Controller\AlbumController
    */
    public function createService(ServiceLocatorInterface $sl)
    {
        $cs = new ControllerService($sl->getServiceLocator()->get('doctrine.entitymanager.orm_default'));
        return new AlbumController($cs);
    }
}

Dans ma factory, qui est la seule à savoir comment se fabrique un contrôleur de type Album, je récupère le service que j’injecte dans le constructeur d’AlbumController (relisez le message d’erreur ci-dessus, on vous dit bien qu’il vaudrait mieux injecter la dépendance à la création de votre objet, ce que vous faites). Ici j’utilise comme service l’Entity Manager de Doctrine, mais ça pourrait être n’importe quel autre service, évidemment !

Regardons maintenant le constructeur que j’ai rajouté dans mon contrôleur:

    protected $service;

    public function __construct(\Album\Service\ControllerServiceInterface $service) {
        $this->service = $service->getService();
    }

J’injecte dans mon constructeur les dépendances de celui-ci, sous la forme d’une interface (le po-ly-mor-phisme, que diable !). Cette interface c’est moi qui l’ai écrite, dans mon répertoire Service, elle est simplissime :

 namespace Album\Service;

 interface ControllerServiceInterface
 {
    public function getService();
 }

Elle dit que tout objet qui l’implémente se doit d’avoir une méthode qui permette d’accéder au service qu’il contient.

Dans le même répertoire, j’ai écrit un service dédié à mon contrôleur :

 namespace Album\Service;

 class ControllerService implements ControllerServiceInterface
 {
    protected $service;

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

    public function getService() {
        return $this->service;
    }
 }

On me reprochait d’avoir fait ça dans mon contrôleur:

$this->em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');

Désormais c’est ma factory qui se charge de faire ça, on a donc déporté les responsabilités dans la fabrique, qui est la seule à savoir comment se constitue l’objet qu’elle fabrique. J’ai choisi d’injecter les dépendances (le service) à la création de l’objet et pas via un setter, c’est un choix strictement personnel. Rien ne vous empêche de procéder autrement.

Hors micro: le télétravail

Un petit billet rapide pour démarrer cette nouvelle année (la troisième pour ce blog qui a essentiellement vocation à parler technique, vous l’aurez remarqué) avec un sujet qui me tient à cœur: le télétravail dans les NTIC en France.

Pourquoi le télétravail, qui aide à moderniser l’organisation sans toutefois trop la bousculer, est-il tellement à la traîne en France ? Les entreprises du monde des NTIC semblent toujours plus favorables à ce mode de collaboration – c’est en tous les cas ce qui ressort de chacun des sondages d’opinion faits sur le sujet tous les ans – mais dans les faits elles restent réticentes pour appliquer le changement…Qu’est-ce qui ne va pas avec le télétravail en France ? Est-il encore mal perçu en 2016 ? N’est-il pas si soluble que ça dans la culture de l’entreprise française ?

Take the turn !

Take the turn !

J’ai eu la chance de tester ce mode de collaboration il y a presque 10 ans, à la suite de la naissance de mon deuxième enfant. Mon manager de l’époque, très compréhensif, m’avait accordé de travailler 2 jours par semaine à domicile et ce alors que je résidais à 10 km de mon lieu de travail (comme quoi ce n’est pas une histoire de distance – length does NOT matter). Ce mode de fonctionnement a duré trois ans, jusqu’à mon départ de l’entreprise. Certes, il a fallu prouver ma fiabilité et mon sérieux dans le travail avant de me voir accorder ce que je considère encore à ce jour comme un énorme avantage dans la gestion personnelle du travail.

Car oui, de mon point de vue, le télétravail est beaucoup moins stressant et beaucoup plus productif que dans les locaux de l’entreprise:

  • vous n’êtes pas dérangés par le téléphone qui sonne toutes les 30 secondes ou les gens qui hurlent sans cesse à côté de vous (je m’adresse aux gens qui travaillent en open space)
  • vous n’êtes pas sollicités par vos collègues pour partir en pause chaque heure (celui de la compta, puis celui de l’info, puis celui des achats etc.)
  • vous n’êtes pas conviés à des réunions interminables où vous n’aviez pas votre place (la preuve, vous avez rempli une page de votre cahier de dessins aléatoires, ce cahier que vous emportez à chaque fois alors que vous n’y notez rien…et vous le savez pertinemment !)
  • Pour résumer : plus de concentration et de calme = plus de travail de qualité !
Pas du tout ma vision du travail

Pas du tout ma vision du travail

Les aspects pratiques pèsent aussi dans la balance:

  • Fini le temps perdu dans les embouteillages, une douche et c’est parti, vous êtes devant votre poste de combat à 8 h 30 au lieu des 9 heures et quelques (vous êtes de toutes les manières incapables de savoir quand vous arriverez, étant à la merci de la circulation, des grèves, des travaux parfois des caprices de votre véhicule).
  • Il ne vous faut rien ou presque pour bosser : un ordinateur et une connexion SSH.
  • La communication se fait par d’autres canaux, qui forcent parfois les gens à réfléchir avant de vous déranger pour rien; le mail force les gens à savoir formuler leur besoin de manière compréhensible, claire et précise. De plus n’importe qui possédant une ligne téléphonique peut suivre un conf call depuis chez lui.
  • Certes, tout n’est pas rose :

    Il faut savoir avant tout gérer le fait d’être seul, mentalement et techniquement (ça force à être autonome). Il faut aimer être à distance et il faut pouvoir se dépatouiller en toute occasion. J’ai vu des gens s’isoler complètement dans leur coin, au point de décrocher totalement et de ne plus assurer leurs tâches (ce qui a, hélas!, entrainé leur éviction), mais pour un échec cuisant, combien de salariés ou de collaborateurs y trouvent leur compte et une certaine forme d’épanouissement ? Le télétravail influe sur le moral et sur la qualité de l’ouvrage, à condition bien sûr de se sentir bien chez soi. Si l’environnement est bruyant et pas propice à la sérénité, c’est évidemment très dur, mais un collaborateur à qui vous accordez cette confiance prendra ça comme une preuve de confiance voire un avantage en nature !

    Car oui, la confiance reste encore la principale entrave au développement de ce mode de collaboration dans notre bon vieux pays. On reste dans un schéma sclérosé où la place du collaborateur est sous les yeux du manager et nulle part ailleurs. On part du principe que le collaborateur pourrait être distrait chez lui alors que ça ne dérange personne que certains passent leur temps de travail sur les réseaux sociaux ou sur leur téléphone portable quand les accès au Web sont filtrés.
    « Du moment qu’ils sont au bureau et font leurs horaires… ». Qu’est-ce qui est important ? Faire ses horaires ou accomplir ses tâches ? La rigidité ou la flexibilité ? Le sentiment qu’on nous fait confiance ou qu’on est pris pour un garnement qu’il faut constamment surveiller ? On passe 45 ans de sa vie à travailler, autant aimer son travail et s’y impliquer, non ?

    Cela fait trois ans maintenant que je suis indépendant, travaillant uniquement depuis chez moi, à l’exception évidemment des formations, que je tiens à assurer en présentiel. Je ne donnerais ma place pour rien au monde : j’aime encore mon métier après 18 ans passés à l’exercer et j’aime le fait de rester dans mon environnement pour travailler : je ne touche plus ma voiture que quelques fois par semaine pour des trajets courts et croyez-moi, je ne m’en porte que mieux. J’entends encore des gens me dire « Je ne sais pas comment tu fais, je ne pourrais pas travailler seul » ou « Je serais trop tenté de faire autre chose que travailler ». Je ne tente pas de les convaincre qu’il s’agit de la panacée – on s’en sent capable ou pas – mais travailler depuis chez moi est un choix personnel que j’estime payant et j’encourage vivement entreprises des NTIC et collaborateurs désireux de briser la routine ou hésitants à tenter l’expérience.

    Sus à la frilosité ! Le télétravail, c’est génial !

    Si vous avez des retours d’expérience (identiques ou différents !), n’hésitez pas à les évoquer dans les commentaires

    PHP 7 : les types de retour

    Un futur vers les retours

    On continue dans la série « Ces nouveautés qui rendent heureux » avec l’annonce de la possibilité de forcer des types de retour de fonctions dans PHP 7 ! Cette avancée majeure a fait là encore l’objet de débats assez vifs au sein de la communauté des développeurs PHP, avec des RFC critiquées, d’autres écartées et d’autres qui reviendront un jour ou l’autre d’outre-tombe !

    Terminator, un type de retour

    Le type de retour le plus connu au monde !

    C’est l’histoire d’un type…

    Scalaire ou pas, nous pouvons maintenant signifier un type de retour dans les déclarations de nos fonctions, sous la forme suivante :

    <MODE D'ACCES> function <NOM> (liste de paramètres) : <TYPE DE RETOUR>
    

    La position de ce type de retour a fait débat d’entrée de jeu, certains développeurs ne souhaitant pas le voir devant le nom de la fonction, comme c’est le cas en C ou Java, pour des raisons de commodité lors des recherches dans le code (chercher « function mafonc » n’aurait plus fonctionné si elle avait du s’appeler « function int mafonc »).

    Commençons avec des types non scalaires :

    function eleves(): array {
        return ["jean", "eric", null];
    }
    
    var_dump(eleves());
    
    /*
    array(3) {
      [0]=>
      string(4) "jean"
      [1]=>
      string(4) "eric"
      [2]=>
      NULL
    }
    */
    

    ou bien encore :

    class Eleve {
        private $_nom;
        private $_prenom;
    
        public function __construct ($nom, $prenom) {
            $this->_nom = $nom;
            $this->_prenom = $prenom;
        }
    }
    
    function eleve($n, $p): Eleve {
        return new Eleve ($n, $p);
    }
    
    var_dump(eleve("Rack", "Eric"));
    
    /*
    object(Eleve)#1 (2) {
      ["_nom":"Eleve":private]=>
      string(4) "Rack"
      ["_prenom":"Eleve":private]=>
      string(4) "Eric"
    }
    */
    

    Nous avons utilisé dans l’exemple précédent des types non scalaires, respectivement tableau (array) et objet (de la classe Eleve).

    Nous pouvons aussi renvoyer des types scalaires, tels que int, float, bool ou encore string :

    function qi(): int {
        return 150;
    }
    
    var_dump(qi());
    

    Si je modifie le type de retour pour essayer les quatre types susnommés, voici ce que j’obtiens :

    int(150)
    bool(true)
    string(3) "150" 
    float(150)
    

    Pensez à activer le typage strict en faisant figurer en première ligne de votre fichier de test (ou plus proprement, dans l’autoloader de votre application) la directive maintenant bien connue de ceux qui veulent bénéficier des plus récentes fonctionnalités en matière de typage strict, à savoir:

    declare(strict_types=1);
    

    Comme toujours, il faut respecter le contrat, sinon gare ! Vous ne pouvez pas écrire :

    function quotientintellectuel(): int {
        return null;
    }
    

    ou bien encore:

    function quotientintellectuel(): int {
        return "150";
    }
    

    Sous peine de courroucer PHP, qui, pour le premier exemple, vous jettera à la figure un joli :

    Fatal error: Uncaught TypeError: Return value of quotientintellectuel()
     must be of the type integer, null returned 
    

    Invariance

    Elle reste toujours de mise, à savoir que les signatures des méthodes héritées ou implémentées doivent rester EXACTEMENT les mêmes dans les sous-types. Ainsi, si vous écrivez ceci, vous vous provoquerez naturellement l’ire du compilateur :

    class Eleve {}
     
    interface EleveDao {
        function trouverParNom($nom): Eleve; 
    }
     
    class EleveSqlDao implements EleveDao {
    
        function trouverParNom($nom) {
            // mes plus belles requêtes SQL 
            return new Eleve();
        }
    }
    
    Fatal error: Declaration of EleveSqlDao::trouverParNom($nom) must be compatible with EleveDao::trouverParNom($nom): Eleve
    

    Notez que ce comportement pourrait être modifié dans les versions futures du langage.

    Peut-on renvoyer « rien » ?

    Il y a eu une tentative d’introduire un type void comme en C/C++ ou Java mais cette proposition a été rejetée. Vous savez que par défaut une fonction PHP renvoie null :

    function rien() {}
    
    var_dump(rien());
    # NULL
    

    Quand on écrit void, on notifie que la fonction ne renvoie rien. Or null par défaut est retourné par PHP, ce sont deux choses différentes.

    Avec void on ne renvoie rien, avec return null on renvoie « absence de valeur » !

    Quid des fonctions spéciales ?

    Certaines fonctions ne sont pas autorisées à faire usage des types de retour :

    • les constructeurs
    • les destructeurs
    • les fonctions de clonage

    En tentant de le faire, vous provoqueriez des erreurs fatales.

    En résumé

    Coluche disait « Voilà une nouvelle qu’elle est bonne ! »; une délicieuse formule qui s’applique à cette nouvelle fonctionnalité qui, avec le typage strict et de nombreuses autres features, constitue une avancée majeure dans le cycle de vie de notre langage préféré. L’introduction de ces possibilités est l’occasion une fois de plus de renforcer la bonne hygiène de vos développements ! Il reste encore des points à régler (void notamment, qu’on espère voir arriver un jour !) mais tout va dans la bonne direction !

    PHP 7 : le typage strict

    Une des grandes forces de PHP – qui est aussi, hélas !, une de ses plus grandes faiblesses – est l’absence de typage fort comme c’est le cas en Java, en C#, C++ ou dans bien d’autres langages dits « orientés objet ». PHP s’occupe de faire les conversions de type qui l’arrangent, notamment lorsque l’on effectue des comparaisons entre des types différents (voilà pourquoi les opérateurs stricts ont été introduits qui comparent en valeur et surtout en type). Tout ça est décidé de façon dynamique au runtime (à l’exécution, en bon français) et non pas de façon statique, à la compilation.

    Une nouveauté qui fait des heureuses !

    Une nouveauté qui fait des heureuses !

    Depuis les versions 5.X, on pouvait déjà typer des paramètres de fonction avec certains types non-scalaires : objet depuis 5.0 et array depuis 5.1 (et callable par la suite, mais passons…). Dans des temps reculés (avant 5.X), on n’avait pas trop le choix:
    function peuimporte ($param, $reparam) {
    var_dump($param);
    var_dump($reparam);
    }

    L’appel de cette fonction pouvait se faire avec des arguments de divers types : entiers, flottants, tableaux, objets, chaînes de caractères…Pour faire court, on pouvait un peu passer tout et (surtout) n’importe quoi !

    peuimporte (1, 1.23);
    peuimporte ("a", 1);
    peuimporte (array(), new StdClass());

    Un type scalaire est un type qui ne contient qu’une valeur à la fois: un entier, une chaine de caractères, un booléen etc. Un type non scalaire peut contenir plusieurs valeurs; un tableau, un objet…ce sont des types composites – ou des collections de valeurs.

    Depuis PHP 5.1 disais-je, on pouvait forcer le typage des paramètres des fonctions avec des types non scalaires comme un tableau:


    function peuimporte (Array $param) {
    // faites ce que bon vous semble
    }

    ou bien un objet d’un type particulier:


    class UnTypeAuPif {}
    function peuimporte (UnTypeAuPif $param) {
    // faites ce que bon vous semble
    }

    ou encore d’un super-type, comme une interface:


    interface Bidon {}
    class UnTypeAuPif implements Bidon {}
    class UnAutreTypeAuPif implements Bidon {}
    function peuimporte (Bidon $param) {
    // faites ce que bon vous semble
    }

    Il suffit alors de passer en argument une instance qui ne se conforme pas aux spécifications que constitue la signature de notre fonction et VLAN!


    Catchable fatal error: Argument 1 passed to peuimporte() must be an instance of UnTypeAuPif, instance of stdClass given

    Ici j’ai tenté de réaliser l’invocation suivante :
    peuimporte(new StdClass);

    J’ai allégrement violé le contrat qui me lie à cette fonction et qui stipule qu’il ne faut passer que des instances de UnTypeAuPif (dans le premier exemple).

    Depuis mars 2015 (et une RFC qui a fait l’objet d’un débat, puis d’un vote) il est donc prévu que PHP 7 nous donne la possibilité de spécifier des types scalaires pour nos arguments…ENFIN ! Il deviendra donc possible d’écrire:


    function peuimporte (int $param) {
    // faites ce que bon vous semble
    }

    ou bien:


    function peuimporte (string $param) {
    // faites ce que bon vous semble
    }

    ou encore:


    function peuimporte (float $param) {
    // faites ce que bon vous semble
    }

    Le fait de passer en argument d’un appel une variable du mauvais type provoque une erreur fatale, par exemple:


    Fatal error: Uncaught TypeError: Argument 1 passed to peuimporte() must be of the type float, string given

    Pour activer ce typage strict sous PHP 7, il faut utiliser declare que l’on connait depuis 5.3 et qui sert lors de la compilation du fichier. Ce language construct sera placé en tout début de fichier (sous peine de provoquer une erreur fatale)  comme suit:


    declare(strict_types = 1);
    function peuimporte (float $param) {
    // TODO
    }
    peuimporte(1.23);

    Notez que le mode bloc, originellement proposé pour strict_types , a été interdit dans PHP 7. Cette fonctionnalité n’est pas forcée par défaut, nul risque donc de BC break dans le code existant. L’aspect lâche du typage en PHP reste encore la règle (pour combien de temps ?), ce qui continuera de faciliter l’intégration des gens qui ne sont pas des développeurs de formation.

    Pour les développeurs qui viennent du monde des langages fortement typés et qui se sentent frustrés par le typage faible de PHP, c’est une bonne nouvelle et sans doute une raison de plus de cesser de prendre PHP pour un langage de seconde zone.

    Il me tarde que PHP 7 fasse l’objet d’une release officielle pour bénéficier de cette tant attendue fonctionnalité !

    Testé avec un PHP7 bêta compilé sur Debian Jessie…et approuvé !

    Les formes normales

    Relation, es-tu bien normale ?

    Le but de la normalisation d’un schéma de base de données relationnelle ? Principalement l’élimination de la redondance et l’assurance d’avoir des données cohérentes (pas la même adresse orthographiée de N façons différentes, par exemple). Je dis « de la redondance inutile » car même si cela semble un pléonasme, il existe des cas où elle peut avoir une utilité et notamment quand la rapidité des accès aux données est placée au centre des préoccupations du concepteur.

    normaliser

    Normalisez avant qu’il ne faille tout jeter !

    Les formes normales, des règles à suivre pour rester en forme !

    Les formes normales sont des règles à suivre pour parvenir à éliminer cette redondance inutile. Nous n’irons pas en détail dans les aspects formels de ces formes normales, parce qu’ils ne sont pas indispensables à la compréhension de celles-ci et parce qu’ils rebutent inutilement le débutant. Sim-pli-ci-té !

    1NF

    Que nous dit cette première forme normale (1st Normal Form) ? Elle nous dit que tout attribut d’une relation (toute colonne d’une table, si vous préférez) ne doit posséder qu’une valeur pour chacun de ses enregistrements (ou tuples).

    Plus concrètement, voici une table enseignant qui viole allégrement 1NF :

    Table violant 1NF

    La colonne prenom contient deux valeurs, cette colonne n’est donc pas mono-valuée comme nous l’impose 1NF mais multi-valuée, on dit que prenom ne contient pas de valeur atomique, que c’est un groupe de valeurs.

    Comment se mettre en conformité avec 1NF ?

    Dans notre cas ici, il ne faut pas faire l’économie d’une colonne dédiée au deuxième prénom (il pourrait même y en avoir un troisième, voire N car « le nombre de prénoms qui peuvent être attribués par les parents à un même enfant n’est pas fixé par la loi » nous dit le site Service Public). Créons donc cette nouvelle colonne et notons que la création de celle-ci nous permettra de faire des recherches de meilleure qualité sur les prénoms, par exemple en utilisant un index !

    Table en conformité avec 1NF

    Plutôt simple, n’est-ce pas ? Que dire de cette relation :

    Table violant 1NF

    Encore une fois, notre colonne adresse est multi-valuée ! Même si elle ne contient qu’une seule adresse, cette adresse elle-même contient plusieurs informations, de nature différente: un nom de rue, un numéro dans cette rue, un code postal, un nom de ville…Pourquoi ne pas créer une table adresse avec un champ numero, type_voie (rue, avenue, boulevard etc.), code_postal, ville etc. ? Cela peut servir si l’on veut normaliser nos adresses grâce à des outils tierce-partie et les rendre « propres ». Parfois il arrive que l’on laisse volontairement l’adresse dans cet état pour minimiser les jointures et donc accéder le plus rapidement possible à l’information…A vous d’étudier la pertinence de chacune de ces façons de faire : gardez simplement à l’esprit que l’absence de normalisation peut coûter cher plus tard…quand les données auront enflé !

    2NF

    Tout d’abord, pour être en 2NF, il faut être en 1NF : on ne peut pas être sans avoir été, lorsque l’on normalise ! Pour évoquer 2NF, il nous faut parler des dépendances fonctionnelles ! Qu’est-ce qu’une DF ? Rien de bien compliqué, regardons de plus près cette relation enseigne :

    2nf

    Nous avons ici une clé primaire composée du couple (identifiant_enseignant, identifiant_cours). Notre colonne ville nous dit dans quelle ville a lieu le cours.
    Nous voyons que la colonne ville dépend de identifiant_cours : le cours 1 est toujours donné à Avignon et le 2, toujours à Aix.

    On dit ici que identifiant_cours détermine ville ou bien, dans le sens inverse, que ville dépend fonctionnellement de identifiant_cours.

    Si pour des valeurs identiques d’une colonne C on a les mêmes valeurs dans la colonne C2, alors C détermine C2 et nous le noterons C → C2.

    Ici, pour la valeur 1 dans la colonne identifiant_cours nous avons toujours la valeur Avignon en face et pour la valeur 2 dans la colonne identifiant_cours nous avons toujours la valeur Aix. Ce n’est pas le cas de identifiant_enseignant, qui n’a pas les mêmes valeurs de ville en face (regardez l’enseignant 1, il enseigne dans la ville d’Aix et dans la ville d’Avignon).

    Dans notre relation enseigne nous distinguons:

    • des attributs (des colonnes, c’est pareil) qui forment une clé primaire
    • un attribut non clé (ville)

    Ici nous avons identifié une dépendance fonctionnelle entre une partie de la clé (la colonne identifiant_cours) et une colonne non clé (ville). Voilà pourquoi cette relation viole 2NF.

    Pour qu’une relation soit conforme à 2NF, les attributs non clé de cette relation doivent dépendre fonctionnellement de toute la clé et non d’une partie seulement

    Ici nous avions : identifiant_cours → ville. La colonne identifiant_cours est une partie (la moitié pour être parfaitement exact) de la clé. CQFD.

    Comment se mettre en conformité avec 2NF ?

    Nous allons isoler la DF identifiant_cours → ville qui nous pose problème dans sa propre table, que nous pourrions appeler cours par exemple :

    2nf-bis

    Vous noterez que nous avons mis fin à la redondance inutile jusqu’alors présente dans la table (la ville potentiellement répétée N fois); cela nous aide aussi à assurer comme je vous l’ai dit en introduction la cohérence des données (que se serait-il passé avant si nous avions écrit Avignon « Avinion », « Avignom » et « Avignon » ? Nous aurions eu trois villes différentes !). Dorénavant, Avignon figure une fois et une seule…et le nom est écrit comme il faut.

    Que va devenir notre ancienne relation ? Eh bien oui, nous l’avons quelque peu rabotée !

    2nf3

    Nous avons sorti ville mais il nous faut bien garder le lien entre le cours et la ville, voilà pourquoi la source de la DF identifiant_cours → ville, c’est à dire identifiant_cours, RESTE dans la table d’origine. Grâce à identifiant_cours, qui existe maintenant dans les deux tables, nous pouvons effectuer des jointures et ainsi garantir qu’on a maintenu l’information initiale qui était « tel cours se donne dans telle ville ».

    En pratique ici, identifiant_cours sera clé primaire dans la nouvelle table cours et une contrainte d’intégrité référentielle partira depuis identifiant_cours situé dans enseigne vers identifiant_cours situé dans cours.

    3NF

    Tout comme il nous faut être en 1NF pour être en 2NF, il nous faut être en 2NF pour être en 3NF : nous procédons de manière incrémentale !

    Pour être en 3NF, il faut que toute colonne non clé ne dépende que de la clé de la relation

    Revenons un instant à nos enseignants, dont la structure a quelque peu évolué :

    3nf

    Nous avons souhaité pour chaque enseignant dire à quel département il était rattaché et où ce dernier se situait. Dans le cas présent, localisation dépend de departement (noté departement → localisation). Notre attribut departement ne fait pas partie de la clé, pas plus que localisation, dont il dépend. Voilà pourquoi cette relation n’est pas conforme à 3NF !

    Les problèmes posés par cet exemple sont les mêmes que pour la 2NF, à savoir :

    • la localisation du département est répétée dans chaque enregistrement (regardez les deux premiers) et elle peut aussi être orthographiée différemment suite à une erreur de saisie, compromettant ainsi la cohérence des données et donc leur qualité
    • si la localisation du département change, il faut mettre à jour tous les enseignants qui y travaillent
    • Si le département n’a plus d’enseignant, nous perdons l’information de sa localisation

    Comment se mettre en conformité avec 3NF ?

    Comme pour 2NF, il va nous falloir isoler la DF anormale departement → localisation dans sa propre table, comme ceci :

    3nf2

    Là encore vous noterez que nous avons mis fin à la redondance qui jusque là existait dans notre table. Notre relation enseignant va « maigrir » elle aussi mais on doit également y faire exister un lien vers notre nouvelle relation, ce lien ce sera la colonne identifiant_departement :

    3nf3

    Conclusion

    Nous entretenons désormais des relations tout à fait normales !

    En réalité, il existe d’autres formes normales après 3NF, mais quand on a normalisé en 3NF, on a déjà éliminé la grande partie de ces vilaines redondances que nous voulons éviter.

    Pour retenir facilement 2NF et 3NF, les gens qui travaillent sur les bases de données connaissent par cœur cette phrase de feu William Kent :

    « The key, the whole key, and nothing but the key »

    qui reprend de façon assez humoristique le serment que font les témoins devant un tribunal : « Je jure de dire la vérité, toute la vérité, rien que la vérité » (des petits malins on rajouté « So help me Codd » – E.F Codd étant considéré comme le père des bases de données relationnelles – pour remplacer le « so help me God » (Dieu me vienne en aide) qui termine la phrase aux Etats-Unis).

    En effet :

    • En 2NF, tout attribut (ou colonne) non clé doit dépendre de la clé toute entière (the whole key)
    • En 3NF, tout attribut (ou colonne) non clé ne doit dépendre QUE de la clé (nothing but the key)

    Vous voilà maintenant armés pour partir à l’assaut de vos tables et les normaliser ! Bon courage !