Protected vs private ou le testament des classes

Au risque de paraître désagréable, si vous vous posez cette question, protected vs private, peut être devriez vous vous intéresser un peu plus à l’architecture logicielle.

En premier lieu, La vocation et le devenir d’un attribut ou d’une méthode en terme de portée ne devraient pas souffrir d’incertitude mais plutôt jouir d’une totale limpidité. Pourquoi?

il est bon de rappeler que l’héritage n’est pas une fin en soi mais la conséquence heureuse de l’abstraction. Je développe ce point dans cet article.
Partant de la démarche d’abstraction, le statut « protected » découle du caractère naturellement héritable des éléments qui sont factorisés dans la super-classe pour le compte des sous-classes.
Dans l’exemple ci-dessous, la super-classe « Logger » tient à disposition de ses sous-classes un objet user procurant les informations nécessaires sur l’utilisateur courant afin qu’elles mènent à bien leur tâche de générer du log :

derivation
derivation

A contrario les paramètres d’envoi du mail de la sous-classe « LoggerMail » relèvent de sa seule cuisine interne et mettre du protected ici n’aurait aucun sens.

Mais je repose la question, comment peut-on ne pas savoir si un membre d’une classe a vocation à être partagé à des sous-classes, c’est à dire répondre à la question « protected » vs « private »?

A l’évidence, si vous vous posez cette question à propos d’un attribut ou une méthode, c’est que ce membre n’est pas encore utilisé par une sous-classe. Alors pourquoi diable se poser cette question?
Le bon sens ne vous invite-t-il pas au pari pascalien? Est-il plus facile de passer, sans régression, un membre de classe de « private » à protégé seulement au moment où vous en aurez besoin, ou de « protégé » à « private » quand des héritiers plus ou moins légitimes ou auto-déclarés auront çà et là déjà fait valoir leurs droits?

N’essaimez pas partout des points d’extension de votre système gratuitement et sans aucune considération architecturale.
Vous devez maîtriser vos points d’extension. C’est aussi ça le rôle de l’architecture.
De préférence, dans un système ouvert-fermé, l’extension devrait y être horizontale, ajout de classes soeurs, et non pas verticale, héritage par création d’une petite filles, sur ce point encore je vous recommande la lecture de l’article cité plus haut.

Dites vous bien une chose, quand on met « protected » devant un membre de classe alors que les circonstances ne le demandent pas encore, cela veut dire « À qui veut le lire, héritez moi », comme un étrange testament en forme de chèque en blanc.

SRP et ISP, les faux-jumeaux des principes S.O.L.I.D

Cet article a 2 objectifs :

  • le principal est d’expliciter la nuance entre 2 principes d’architecture logicielle que l’on a tendance à confondre, Single Responsibility Principle et Interface Seggregation Principle, respectivement le S et le I de S.O.L.I.D
  • Comparer 2 patrons de conception au regard de ces 2 principes, l’association DAO-DTO, que j’appelle également DAO-POPO en PHP, et Active Record

Single Responsibility Principle

Ce principe énonce que chaque classe ne devrait avoir qu’une seule responsabilité, et donc une seule raison d’évoluer dans le temps

Interface Segregation Principle

Ce principe énonce que les clients d’une classe ne devraient pas être exposés à d’autres méthodes que celles qui les intéressent, et que mieux vaut des petites interfaces simples qu’une grande interface omnisciente.

SRP <=> ISP?

Ce qui les fait ressembler en un sens, c’est l’exigence de simplicité et de concision du rôle dévolu à un composant logiciel.
Mais il n’y a pas pour autant redondance.
L’un, SRP, porte plus sur la stratégie d’encapsulation du détail d’implémentation.
L’autre, ISP, porte plus sur la stratégie de regroupement et d’isolation des contrats d’interface, c’est à dire des méthodes publiques.
Mais illustrons.

Le match

A ma gauche, l’association DAO-DTO :

DAO POPO
DAO POPO

Je ne vais pas vous refaire ici l’article sur les DAO, quant aux DTO (Data Transfert Object) ils correspondent ni plus ni moins au patron de conception qui se cache derrière nos fameux POPO.

A ma droite Active Record, quel est ce patron de conception? Et bien c’est à la fois un DAO et un DTO regroupés dans une seule classe :

Active Record
Active Record

En gros les fonctionnalités de récupération et de sauvegarde dans la source de données se cumulent avec la gestion de l’état de l’entité métier. Voici un exemple de classe avec une variante dans l’interface :

class VoitureActiveRecord

// partie entité métier
public function setId()
{

}

public function getId()
{

}

public function setMarque()
{

}

public function getMarque()
{

}

// [...]
// partie persitance des données
public function find()
{

}

public function save()
{

}

// [...]

 

En quoi Active Record viole-t-il SRP?

Comme je l’ai dit, dans une classe Active Record sont contenues 2 responsabilités bien distinctes :

  • La gestion de l’état d’une entité (accesseurs-mutateurs)
  • La gestion de la persistance d’une entité (find(), etc.

Si je veux gérer une 2eme source de donnée avec les DAO, je peux abstraire et passer de ça :

DAO POPO
DAO POPO

A ça :

DAO abstrait
DAO abstrait

Pourquoi c’est possible? parce que la responsabilité « persistance » est isolée dans les classes de la couche DAO.

Dans Active Record vous pouvez abstraire l’une des 2 responsabilités de la même façon et passer de ça :

Active Record
Active Record

A ça :

Active Record abstrait
Active Record abstrait

Les méthodes de la partie « persistance » sont déclarées abstraites et implémentées dans les sous-classes.

Mais c’est une chance pour Active Record que la gestion d’état est une responsabilité stable dans le temps et qu’elle ne constitue également pas une raison d’évolution pour votre classe.
Nous n’aurions pas pu abstraire la 2eme responsabilité de la classe car nous avons déjà un super type pour la persistance et c’est tout le coté sclérosant de l’héritage simple.
Si on reprend le schéma plus haut comment combiner les cas BDD et web service avec la déclinaison des cas d’une autre responsabilité que la persistance des données? qui aurait du hériter de quoi?
Sauf si vous aimez les explosions combinatoires absurdes de classes concrètes redondantes.
Vous rêvez de vous rendre compte de visu ce que ça donnerait? Allez petite immersion dans la 5ème dimension et les univers parallèles, attention, à ne pas reproduire chez vous :

active record absurde
active record absurde

Et l’héritage multiple me direz vous, et je vous répondrai , ce qui m’évitera de ne pas trop digresser dans un sujet suffisamment abstrait comme ça.

Bon tout ça c’est quand on maîtrise l’abstraction. Vous savez comme moi que chez la plus part des développeurs PHP, les « complications » dans les classes à multiples responsabilités se règlent à coup de gros « IF » qui tachent donnant des placards imbriqués de 100 lignes dans des méthodes de 500 lignes le tout dans des classes de 3000 lignes.
La maintenance est alors un cauchemar relevant du syndrome de l’artificier qui ne sait quel fil couper devant un Mikado de lignes de codes.

A ce stade, nous dirons que sans écraser son adversaire notre association DAO-POPO mène aux points. Accordons à Active Record que l’extrême sobriété de la responsabilité « gestion d’état » peut à la limite justifier qu’on la fusionne avec la responsabilité « persistance des données ».

Peut être saisissez vous mieux à présent ce qu’on appelle « responsabilité » et l’importance de son unicité dans une classe.

En quoi Active Record viole-t-il ISP?

Raisonnons ISP à présent et plaçons nous non pas à l’intérieur des classes et de leur implémentation mais hors des classes et face à leur interface.
En quoi la séparation DAO-DTO est-elle bien meilleure?

  • Meilleur contrôle de l’accès à la persistance, je peux utiliser des DTO sans exposer ma source de données à des composants qui n’ont pas à y avoir accès.
  • les DTO sont light-weight, alors que les classes active record recèlent de toute l’artillerie nécessaire à la gestion de la persistance (connexion SQL, Entity Manager, etc.). Dans l’Active Record, la partie persistance constitue une gêne indéniable pour cacher ou sérializer des entités. En clair La responsabilité « persistance » pollue et parasite l’interface « gestion d’état ».
  • Les interfaces des DTO sont beaucoup moins couplantes car la gestion d’état seule est simple et n’induit pas de dépendances vers d’autres composants (connexion SQL, Entity Manager, etc.).

Active Record est cette fois incontestablement mis KO, il est clairement plus efficace de séparer les interfaces de persistance (DAO) et de gestion d’état (DTO) conformément à ISP.

On constate ici que pour un même choix architectural, l’impact s’évalue différemment selon que l’on raisonne SRP et ISP.
C’est bien que ces 2 principes couvrent chacun un domaine de préoccupation différent.
L’un, depuis l’intérieur des classes, regarde l’encapsulation et l’évolution contenue du détail d’implémentation, l’autre au contraire, regarde depuis l’extérieur l’accès aux interfaces de programmation et leurs conditions d’utilisation.
Il me semble finalement que ce qui les rapproche surtout, c’est que de la stricte observation de l’un, SRP, découle naturellement des interfaces simples et conformes à ISP. C’est en ce sens que ces 2 principes sont liés, c’est qu’une bonne réflexion sur l’encapsulation des responsabilités influence positivement les interfaces.

L’architecture logicielle justement, n’est-ce pas l’art de distribuer les responsabilités?

Le polymorphisme expliqué à ma grand-mère

Le polymorphisme, ce sont les développeurs qui en parlent le mieux … ou pas.
Expliquer le polymorphisme cela ressemble souvent à cette série de pubs « Darling » des années 80.

Au mieux on se contente d’égrainer les différents polymorphismes possibles mais n’est-il pas plus pertinent et efficace d’expliquer sur quel mécanisme s’appuie le polymorphisme?
Comprendre la source du mécanisme sensibilise de toute façon naturellement au champ applicatif de ce concept.

Donc moi je décide ici de ne pas vous parler de polymorphisme avant de vous avoir parlé de ce qu’on appelle les liaisons tardives (ou liaisons dynamiques).

Qu’est ce?

Prenons une requête vers un objet :

$animal->crier();

Au moment où j’écris ce code, je n’ai pas besoin de savoir qui ou quoi criera effectivement, par exemple, quelle sous-classe de la classe Animal.
Les compilateurs ou les parseurs n’effectuent que des contrôles statiques, et ne s’inquiètent pas de savoir quelle classe concrète est visée et donc quelle implémentation de la méthode crie() est finalement visée.
Le code $animal->;crier(); veut dire, un animal crie, et toute l’ambiguïté littéraire de cette affirmation, tous les possibles sont résumés dans ce simple bout de code.
Ainsi, que les circonstances avèrent qu’un chien, un chat, ou un tamanoir crie, ce petit bout de code reste le même et demeure suffisant. C’est l’implémentation de circonstance, Chien, Chat, Tamanoir, flanquée dans $animal qui décidera du comportement de ce code, seulement au moment de l’exécution du programme.

La résolution d’une telle requête ne se fait donc pas à la compilation (ou au parsing).La résolution de cette requête se fait au runtime c’est à dire au moment de l’exécution de ce code, c’est à dire au moment de crier. Ce qui change tout.

C’est cela que l’on appelle liaison tardive. C’est le fait pour un langage d’autoriser la résolution d’un bout code seulement au moment de l’exécution et non statiquement, et laisser les choses se déterminer dynamiquement, en fonction du contexte d’exécution.

Voila qui donne un peu plus d’éclat à la définition ci-dessous qui sinon pourrait paraître un peu trop prosaïque.
Le polymorphisme c’est la possibilité pour un même bout de code de se comporter différemment en fonction du contexte d’exécution.

Quand on sait ça, il est facile d’en déduire ce qui relève du polymorphisme.
La surcharge d’une fonction ou d’un opérateur par exemple, est un polymorphisme, car c’est au moment de l’exécution et en fonction des arguments passés que sera déterminée l’implémentation à solliciter.
Ainsi dans certains langages, string + string, int + int sont 2 comportements possibles de l’opérateur polymorphe « + ». Dans le premier cas les paramètres seront concaténés, et dans le 2eme additionnés.

Dans le cas de ma requête vers un objet ou dans le cas de la surcharge le bout de code s’en trouve simplifié car je ne suis pas obligé d’évoquer toutes les aternatives de comportement dans une structure de controle de type « if, else if » ou « switch ». Si je dis

$animal->crier();

J’ai tout dit. Ce code peut se comporter d’autant de manière que j’aurai implémenté de sous-classes de la classe Animal, c’est à dire autant de façon possibles de crier dans mon programme. Le comportement dépendra de l’instance concrête qui sera placée dans $animal et le « if else if » est exprimé dans le polymorphisme de ce simple bout de code.

Ainsi le polymorphisme simplifie-t-il le code et sa maintenance et l’ouvre à l’extension.

Comprendre le polymorphisme et son apport c’est donc avant tout comprendre le mécanisme sur lequel il repose.

Bon je confesse, ou plutôt je confirme, ma grand-mère ne maîtrise toujours pas le polymorphisme.

L’abstraction précède l’héritage

L’héritage ne devrait pas être visé comme une fin en soi, mais comme la conséquence heureuse de l’abstraction.
Tout néophyte de l’objet raisonne de haut en bas, je dérive pour hériter, et j’hérite pour réutiliser. D’ailleurs seul l’héritage l’intéresse quand il croit faire de la POO en empilant des colonnes de responsabilités.
Mais jetez un œil aux diagrammes de classes UML.

ClassDiagram

La flèche entre la super-classe et ses sous-classes va vers le haut, et s’appelle « généralisation » et non pas « héritage » qui n’est que la conséquence de la généralisation. Ça n’est pas un hasard, c’est parce qu’on raisonne dans le sens de l’abstraction.

Une classe seule c’est déjà de l’abstraction

Rembobinons l’histoire de ce composant :

ClassDiagram

On imagine qu’au commencement était une classe simple, « Logger » à laquelle on passait des objets « Erreur » et qui écrivait des logs dans un fichier.

Dès la première classe, on abstrait.
La classe simple « Personne » par exemple, est une d’abstraction de toutes la réalité complexe d’une personne réelle (traits de personnalité, rhésus, goûts alimentaires etc.) et pour laquelle on a gommé une infinité de détails inutiles afin d’obtenir quelque chose de simple à manipuler et d’utile à notre application.
Imaginez une flèche « généralisation » partant du réel vers la classe.

reel

Evoluer dans la stabilité

Nous allons toujours du plus concret vers le plus abstrait. L’abstrait s’inspire du concret pour le simplifier. Il n’y a pas de raison pour qu’il en soit autrement s’agissant du rapport entre une classe simple et sa super-classe.

C’est ainsi que par la suite il a fallu ajouter un deuxième moyen de « loguer » tout en maintenant l’interface? Comment?

Reprenons notre premier diagramme.

ClassDiagram

  • Logger est devenue une super-classe
  • l’interface, en l’occurrence La méthode Logger::log(Erreur $erreur) est désormais déclarée abstraite dans la super-classe.
  • L’ancienne implémentation de Logger est inchangée, elle est simplement descendue dans la classe concrète LoggerFile
  • La nouvelle façon de loguer est implémentée dans la sous-classe LoggerMail

Tout les codes clients dépendent toujours de la même interface garantie par le type Logger promu désormais super-type.

Abstraire permet de  décliner au fil du temps et des besoins les façons de couvrir une même responsabilité tout en maintenant une interface de programmation unique et stable.

Au passage les ingrédients sont réunis pour réaliser le « patron de conception strategie« , qui permet de rendre des algorithmes dynamiquement interchangeables. Ici, vous voyez que c’est la façon de loguer qui l’on pourra interchanger.

L’abstraction commence donc dès la première classe simple, c’est pourquoi il est primordial de ne pas trop exposer de détail au niveau de son interface (comprenez ses méthodes publiques), c’est à dire de choisir dès la première classe le juste niveau d’abstraction pour ne pas « griller » la responsabilité.
Ce qui compte c’est loguer, si vous exposer le détail sur la façon de le faire, relatif aux fichiers de log par exemple, vous pour ne pourrez plus abstraire la classe et donc décliner les façons de couvrir une même responsabilité.

Vous voyez prévoir le pire ce n’est pas faire plus de code et de classes non encore utiles,c’est au contraire soigner d’emblée la simplicité de vos classes seules pour les laisser ouvertes à l’abstraction.

L’abstraction faite, le système est ouvert à l’extension. Ainsi pourrez vous ajouter autant de façon de loguer que l’histoire de votre application vous le demandera et seulement au moment où elle le demandera.

ClassDiagram

Notez sur le diagramme ci-dessus qu’il y a bien héritage, que pour mener à bien leur même mission, les 3 classes peuvent par exemple trouver dans l’objet user de la classe mère les informations sur l’utilisateur courant.

Soit dit en passant, les affirmations « LoggerFile est un Logger », « LoggerMail est un Logger » et « LoggeWebService est un Logger » sonnent vrai.

Et si une carotte était un cosinus?

Si toutefois vous cédiez à la tentation d’hériter pour hériter, sous-classer une classe sous prétexte qu’une ou plusieurs de ses méthodes vous intéresse(nt) Sans vraiment vous préoccuper des types et responsabilités en jeu, il y a des chances pour que vous vous surpreniez à postuler que Carotte est un Cosinus, et plus tard que Robinet ne l’est pas moins.

ClassDiagram

L’héritage en première intention, juste pour réutiliser est un poison, une bombe à retardement. Tôt ou tard vous allez scléroser l’évolutivité du système.

Pourquoi faut-il préférer la composition?

L’héritage est un fusil à un coup, une fois que Carotte a décidé d’être Cosinus, elle ne pourra plus réutiliser quelque choses d’une autre classe, de Bidet par exemple, sauf à contraindre Cosinus, qui n’avait rien demandé, à être Bidet pour faire plaisir à Carotte.

ClassDiagram

Sans parler de Robinet qui au départ n’était intéressé que par Cosinus et qui se retrouve couplé à Bidet lui aussi.

j’en vois déjà qui par un trait de génie vont me dire « multi-héritage ! ».Et Comment gérer les dépendances de chaque classes, c’est à dire, leurs constructeurs? Et si on veux abstraire Cosinus ou Bidet ? Ou même seulement une partie de leur implémentation? Car oui il est possible de n’abstraire qu’une partie du comportement d’une classe pour le donner en sous-traitance à une stratégie.

L’héritage pour réutiliser est une impasse d’autant qu’il est tout aussi simple de réutiliser plus souplement et plus finement :

ClassDiagram

  • Carotte a tout ce dont elle a besoin
  • Robinet n’a que ce dont il a besoin
  • Cosinus est à nouveau en paix
  • les couplages sont nettement réduits
  • Chacune des classes peut abstraire tout ou partie de son comportement sans impact sur les classes qui l’utilisent.

Parce qu’il constitue une exposition directe de l’implémentation, l’héritage est le niveau de dépendance et de couplage le plus fort. La composition, elle, permet de ne dépendre que d’interfaces et non d’implémentations.

L’héritage seul emmène l’héritage toujours plus excessif et toxique, et inhibe l’apport des autres concepts cardinaux de la POO que sont l’abstraction, l’encapsulation et le polymorphisme.
L’apparente et trompeuse accessibilité de l’héritage par rapport aux autres concepts de POO fonde et entretient le malentendu souvent facteur d’entropie logicielle.

Tirer un trait sur …

Ou pourquoi les traits sont le prochain grand fléau de la communauté PHP.

Le Trait est de loin la star des fonctionnalités natives apportées par PHP 5.4.

Mais qu’est-ce exactement ?

– bloc de code « héritable » via le mot-clé « use »
– possibilité de déclarer des méthodes abstraites
– possiblité d’invoquer depuis une classe autant de traits que l’on veut.

A priori, ça a la couleur du multi-héritage.

Déjà de l’opportunité d’introduire du multi-héritage il y aurait matière à débat dont je ferai ici l’économie car de multi-héritage est-il réellement question?

Tirer un trait sur le typage fort

Sachez le, l’instance d’une classe qui fait un « use MonTrait », disons $monObjet, n’est pas pour autant un « MonTrait ». Je m’explique, $monObjet instanceOf MonTrait demeure une expression fausse en l’état du comportement des traits en PHP.

Donc le trait n’est pas typant, donc le trait apporte une forme de multi-héritage non typant, donc je me pose cette question,  qu’est ce que c’est que ce monstroplante?

Il est très gênant de ne pouvoir faire :

public function maMethode(MonTrait $objetInjecte)
{
    //[...]

Pourquoi? parce que si le trait étend l’interface et les responsabilités d’une classe, je devrais pouvoir « hinter » ce supplément de contrat d’interface.

Mais, me direz vous, cette responsabilité n’est elle pas incarnée par le type de la classe qui utilise le trait ? ne puis-je pas faire :

public function maMethode(UneClasseClienteDeMonTrait $objetInjecte)
{
    //[...]

Oui mais le trait est partagé par plusieurs « types » différents.

public function maMethode(UneClasseClienteDeMonTrait $objetInjecte)
{
    //[...]
public function uneAutreMethode(UneAutreClasseClienteDeMonTrait $objetInjecte)
{
    //[...]

Par le plus grand des hasards, plusieurs classes qui selon le principe de cohésion, « incarnent » des responsabilités différentes, ont quand même pu utiliser le même bout de code, parfois public.
Ceci suppose que le code de votre trait « s’exprime » différemment en fonction de la classe cliente dont il « épouse » le type.

Accordons le, si finalement le trait n’est qu’un trait, qu’il est façonné par le type de la classe qui l’utilise, cela pourrait donner du sens au fait qu’il ne soit pas typant lui-même.
Ce sont finalement les classes clientes qui le typent et non l’inverse, peut-on alors vraiment parler d’héritage multiple ?

Le trait apporte du code, les classes apportent le typage, voilà en tout cas une bien étrange relation de dépendance.

Tout cela suppose en fait d’avoir la lucidité et l’habileté de faire des traits polymorphes, sauf à redonder partout dans votre système une même responsabilité.
Dans une architecture logicielle il ne faut pas confondre distribuer les responsabilisés et disperser les responsabilités. Mais je vais revenir sur ce problème que suscite cette forme de réutilisation de code.

Creusons d’abord plus avant la toxicité de cette fonctionnalité que la communauté PHP accueille avec gourmandise.

Le trait ne se contente pas de ne pas être typant, il est destructeur de type de surcroit.
Prenez l’exemple le plus rependu sur le net démontrant la grisante panoplie applicative du trait : le singleton.

trait Singleton
{
    private static $instance;

    public static function getInstance()
    {
        if (!(static::$instance instanceof self)) {
            static::$instance = new self;
        }

        return static::$instance;
    }
}

Un getInstance() développé une bonne fois pour toute dans un trait, qui transformera en une invocation votre classe citrouille en un superbe singleton carrosse :

class Connection
{
    use Singleton;

//...
}
$singleConnection = Connection::getInstance();

Très bien essayons de cartoucher notre méta-méthode statique à présent :

trait Singleton
{
   private static $instance;
 
    /**
    * gère l'instance unique de la classe
    * @param void
    * @return mixed
    */

    public static function getInstance()
    {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self;
        }

        return self::$instance;
    }
}

Et qu’a-t-on mis en guise d’@return dans le doc bloc de ce getInstance()? En fait, pas mieux qu’un bon vieux « mixed » aux légendaire troubles de la personnalité en lieu et place d’un @return Connection plein d’assurance.
Ben oui le type retourné par la méthode statique getInstance() dépendra du type de la classe cliente du trait et votre méthode statique poussée à la schizophrénie hérite désormais d’une signature faiblement et pourravement typée.
Les utilisateurs d’IDE genre Eclipse et en particulier de leurs vertus auto-complétives apprécieront la dégradation de leur confort de développement assurément plus couteuse que de se fendre des 3 lignes d’implémentation du getInstance().

Une fausse bonne idée donc qui laisse entrevoir le retour des vieux démons dont le monde PHP peine tant à se débarrasser.

Le typage fort, pilier de la programmation par contrat et que l’on croyait relancé en PHP, a des soucis à se faire.

Tirer Un trait sur l’abstraction 

Dépendre d’interfaces/d’abstractions et non du concret/détail, préférer la composition à l’héritage, le Trait prend sans complexe le contrepied de ces principes d’architecture en encourageant le plus mauvais moyen de faire de la réutilisation.

Je ne vais pas développer à nouveau ici toute la toxicité du recours à l’héritage mal tempéré, il n’en reste pas moins que sur un plan architectural le trait écope des inconvénients de l’héritage (fort couplage) tout en se passant de ses avantages (non typant).
C’est en somme l’héritage dans tous ses abus mais en pire.

Tout le code d’un trait a vocation à n’être et demeurer que de l’implémentation partagée (je n’ose même plus parler d’héritage non typant, douce oxymore).
Une fois vos classes clientes couplées, il n’y a plus de place pour abstraire le code « réutilisé », vos traits ferment donc votre système à l’extension. Par exemple, fini les stratégies et l’injection de dépendance,  cette association de bienfaiteurs permettant d’inter-changer dynamiquement vos algorithmes..

Des pans entiers de l’apport architectural de la POO à la poubelle, à commencer par un certain polymorphisme.

Tirer un trait sur les frameworks dernière génération?

Bon le hook est ici capillotracté mais je voulais m’interroger sur la limitation de l’utilité du trait de par les innovations architecturales apportées par Zend 2 et Symfony2.
L’architecture extrêmement flexible en micro-systèmes « standalone » des applications SF2 (bundles) et ZF2 (modules) genre packages partageables orchestrés par l’excellent Composer tendrait, me semble-t-il, à limiter les périmètres de ré-utilisabilité des traits et donc leur utilité dans l’absolu.

Sauvons le trait

J’essaie désespérément de trouver un cas d’utilisation pour le trait dans une architecture S.O.L.I.D dont il viole à peu près tous les principes allègrement, et j’entrevois une petite place, après et derrière l’abstraction et ses interfaces (et surtout pas à la place de …).
Imaginons 2 sous-classes qui auraient la même implémentation d’une des méthodes de leur interface commune, on peut imaginer éviter la redondance en factorisant en un trait ce bout d’implémentation commune, pour le reste je mets au défit les enthousiastes béats du trait de m’apporter un exemple non toxique de son utilité.

Factoriser de l’implémentation, derrière les interfaces, c’est à dire derrière des types déjà existant, donnerait du sens à son absence de typage mais pour le reste …

En symétrique des interfaces comme outil d’abstraction, le trait envisagé comme outil d’implémentation, pourquoi pas.
Mais que l’on puisse y déclarer des méthodes abstraites sans qu’il ne soit typant est tout à fait déconcertant d’illisibilité quant aux intentions des concepteurs de PHP et de ce véritable Frankenstein du développement.
Le fait est que, loin de se contenter d’apporter des solutions palliatives dans des architectures faibles ou absentes, le trait vient brouiller d’avantage , si besoin était, les pistes qui mènent le développeur aux architectures S.O.L.I.D.
Avec ce satané principe archaïque de « centralisation » relancé sans réelle réflexion sur la cohésion, on est à contre-courant des avancées architecturales et des principes fondamentaux de la POO.
Nul doute, au vu de son accueil dans la communauté PHP, que le Trait alimente la machine à paver l’enfer de bonnes intentions.

La place des ORM dans votre architecture

Bien que l’un n’exclue pas l’autre, si vous me demandiez de choisir entre la couche DAO et un ORM genre Doctrine, je garde sans hésiter mon baril de DAO.

La lecture assidue de l’article sur les DAO vous rappelle peut être votre ORM préféré et vous vous demandez peut être à quoi bon implémenter ce que font très bien les ORM?

Le but de cet article est de distinguer le rôle et l’apport de la couche de DAO de ce qu’apporte ou n’apporte pas un ORM.

 

Open-bar sur votre BDD

Aussi pratiques soient les fonctions d’extraction  génériques ou magiques des ORM genre (findBY[n’importe quel attribut de l’entité],() m’est d’avis qu’il est préférable de s’en tenir à quelques fonctions précises et explicitement requises par le système et ne pas exposer inutilement toute les possibilités d’extraction de données.

Plutôt que de faire journée porte-ouverte sur votre BDD, vous conserverez le contrôle sur l’accès aux tables et saurez au fur et à mesure évaluer le besoin ou non d’un index supplémentaire sur un champ par exemple.

A l’intérieur des DAO cela ne vous empêche pas de bénéficier des facilités apportées par un ORM, disons que par dessus l’ORM, les DAO encapsulent cette permissivité, et n’en exposnt au reste du système que le stricte nécessaire requis par le métier en terme de manipulation de vos données.

Découplage limité

Les ORM comme Doctrine mappent à merveille les propriétés de leur entités avec les champs d’une table, mais les entités elles mêmes sont mappées à quoi? Aux tables pardi, car la structure de vos tables relationnelles et celle de vos entités relationnelles se superposent totalement. c’est une lapalissade que de le dire quand on connait la définition littérale d’un ORM, et c’est là qu’est l’os car on ne peut pas toujours choisir/définir le modèle physique sur lequel repose notre application.

Dans le cadre d’un refactoring, vous n’avez pas toujours le loisir de réinventer votre stratégie de persistance et dans ce cas cette dernière impose sa structure à vos entités et donc à votre structure métier tout entière juste aux plus hautes couches.
Et quand bien même, votre stratégie de persistance peut-elle, doit-elle toujours coller avec vos classes métier? Que vous soyez tenté par le no SQL ou simplement par des patterns de tables du genre metadata, ou encore que votre BDD contienne des vues et procédures stockées, force est de constater que les ORM peinent à vous découpler totalement de votre modèle physique de données.

Et puis il n’y a pas que les BDD dans la vie, peut être avez vous une source de données derrière l’interface REST, SOAP voire freestyle d’un web service. Peu importe les formats de données retournés, votre DAO s’en arrangera pour vous retourner de beaux et stables POPO sur lesquels votre système pourra toujours compter. Ils protégeront votre système contre la vie parfois trépidante d’un web service surtout si ce n’est pas le votre.

Comme c’est souvent le cas dans les architectures distribuées, ce qui est vrai aujourd’hui peut ne plus l’être demain, une partie de votre système d’information peut éclater en nuages de services.
Vos DAO tiendront alors fidèlement leur rôle d’encapsulation des problématiques de source de données.

Vous avez dit entité?

Il est vrai que le couple repository/entity est assez proche du couple DTO / DAO. Mais avez vous essayé de dumper une entité Doctrine? Si par bonheur l’opération n’a pas été stoppée nette par  un dépassement mémoire, vous constaterez avec perplexité (je l’espère) que la dite entité recèle en son for intérieur la cuisine interne de Doctrine en particulier ces mécanismes de proxies aux capacités surpondérantes fabuleuses pour vos entités. Pour info, un dump clean est possible via la méthode Doctrine\Common\Util\Debug::dump()(notez le \Common\Util\ dans le namespace passablement … disons … \fourre-tout\fourre-tout\ ). Bref des entités plombées de bricoles internes, ça fait un peu dégueulasse quand vous voulez sérialiser, loguer, cacher des entités métier ou pire, des collections d’entités.

 

Alors faut-il jeter l’eau du bain dans les orties?

Soyons clair, je ne dis pas qu’il n’ont pas de place dans une architecture. Par exemple ils sont pratiques pour déployer et maintenir vos tables notamment grâce à leurs outils d’administration et de génération en ligne de commande.

Ils soulagent nettement la tâche des DAO par l’apport de leur couche DAL (cf Doctrine-DBAL et ses fameuses méthodes magiques dont je parlais plus haut). Quoique niveau  DBAL à strictment parler, un Zend DB est moins lourd et plus que suffisant.

Pour réaliser le site Sylvie coiffure en 3 jours accordons même qu’ils sauront de façon très industrielle  et sans faille faire le taf en lieu et place du couple DTO/DAO.

Mais à partir d’une certaine échelle de projet, leur place est sous le couche DAO, en sous-traitance de ces derniers. Sinon cela veut dire que dans une architecture MVC vous laissez monter cette mécanique tierce partie dans les contrôleurs.
Il sera alors difficile d’éviter les tentations d’utiliser les fonctionnalités ORM depuis les vues de votre MVC, qui ne manqueront pas par ce biais de se faire leur propres courses dans la BDD. Vous finirez par perdre totalement le contrôle des couplages vers votre couche modèle et somme toute, vers vos tables relationnelles.

 

En conclusion je dirais  que ce n’est pas parce qu’ils sont utiles voire incontournables dans l’industrialisation PHP que les ORM sont suffisants.
/* TODO : réfléchir à une conclu moins baclée ... ou pas */

 

Les DAO, la couche polyglotte de votre architecture

Dans la série on explose notre God object métier, une responsabilité assez évidente peut se dégager et être supportée par la couche DAO : La persistance.

Les DAO (Data Access Object) sont les piliers de votre architecture. Il assurent la convergence de toute source de données vers votre système et vice versa.

En gros il connaissent toutes les langues et dialectes des systèmes tierce partie, BDD, ORM web services etc. et jouent les interprètes pour le reste de votre système qui, rappelons le, ne parle que le POPO.

Ils protègent la stabilité de votre système contre l’instabilité des sources de données (BDD, web services, etc.) dont vous n’avez pas forcément la maîtrise.

Pour me ré-re-reparaphaser, C’est en somme la couche D.A.O. qui absorbent la diversité et les spécificités des sources de données.

A quoi ça ressemble ?

Un DAO implémente souvent une interface de base de type CRUD (create(), read(), update(), delete()). complétée par une interface propre au domaine,  ses attributs et des relations (findByName(), findBy[relation]()).

 

Comment dialoguer avec les DAO ?

Ben avec les POPO, vous savez les petits messagers de votre système, on s’adresse aux DAO en langage POPO, et il nous répondent en langage POPO :

//...
$popoVoiture = new Voiture();
$popoVoiture->setMarque("marque");
$popoVoiture->setModele("modele");
$DaoVoiture->create($popoVoiture);
//...

ou encore

//...
$prototypeVoiture = new Voiture();
$prototypeVoiture->setID("UN_ID");
$popoVoiture = $DaoVoiture->read($prototypeVoiture);
//...

Vous constaterez dans cet exemple que j’ai éludé l’instanciation du DAO, j’aborde la problématique des instanciations et de l’injection de dépendance dans l’article sur les fabriques. Retenez ici  qu’épurés de toute responsabilité, les POPO sont parmi les rares classes à pouvoir être instanciées directement grâce à la stabilité de la signature de leur constructeur .

Voilà pour les DAO, Plus que l’ORM dont les entités ne font que que coller (trop) fidèlement à votre modèle physique, du moins à vos tables (et vues), ils sont les seuls garants d’une vraie inversion de dépendance avec les sources de données qui ne sont que des composants tierce partie.

Et c’est cette inversion de dépendance qui offre aux couches supérieures  de votre application des interfaces métier comme le métier vous le demande et non comme les sources de données le dictent.

Les POPO, la langue universelle de votre architecture

Plain-Old PHP Objects. Bon c’est vrai c’est un peu pompé des POJO du voisin Java. Ils sont surtout inspirés du design pattern DTO.

Nos premiers objets en PHP, surtout si on a connu l’objet avant PHP 5 sont toujours maladroits. Non pas que l’on ne sache pas identifier les domaines du métier, la classe « employe », la classe » utilisateur », la classe « voiture », etc., mais parce qu’on imagine ingénieux de regrouper tout ce qui se rapporte au dit domaine dans cette même classe.

Qui ne s’est pas retrouvé avec une classe « toto » de 3000 lignes , non testable, difficilement maintenable, mais naïvement satisfait d’être certain d’y trouver tout ce qui se rapporte de près ou de loin à toto. L’état de toto, la persistance de toto (CRUD), l’affichage de toto, faisant éventuellement le café de toto, bref un gros God Object (http://fr.wikipedia.org/wiki/God_object).

C’est une étape dans une vie de développeur que de surmonter sa peur irrationnelle d’émietter son code dans des classes simples à forte cohésion. Car en réalité trouver une paire de chaussettes (assorties de préférence) est-il plus accessible dans un seul gros sac de linge ou dans le tiroir réservé aux chaussettes d’ une commode à 10 à tiroirs? D’ailleurs le nombre de tiroirs a-t-il de l’importance si je sais qu’il me suffit de regarder dans celui dédié aux chaussettes?

Non le Bon vieil object PHP se propose de fournir à votre système un service nettoyé de toute responsabilité autre que la transmission de l’état d’une entité métier.
Par entité on entend « voiture » pourvue d’un ID, d’une marque, d’un modèle, ou encore « personne » pourvue d’un nom, d’un prénom, d’un age, bref structure indivise de données liées par un domaine du métier.

Ils consistent donc en des classes toutes simples, uniquement pourvues d’attributs décrivant l’état d’un objet métier, d’accesseurs (ou getters) et de mutateurs (ou setters).

 

Namespace Corp\User;

class User
{
    private $id
    private $name
    private $firstname

    /**
    * @param string $id
    * @return Voiture
    */

    public function setId($id)
    {
    $this->id = $id;

    return $this;
    }

    /**
    * @return string
    */

    public function getId()
    {
        return $this->id;
    }

    /**
    * @param string $name
    * @return Voiture
    */

    public function setName)
    {
        $this->name = $name

        return $this;
    }

    /**
    * @return string
    */

    public function getName
    {
        return $this->name
    }

    /**
    * @param string $firstName
    * @return Voiture
    */

    public function setFirstName)
    {
        $this->name = $firstName

        return $this;
    }

    /**
    * @return string
    */

    public function getFirstName
    {
        return $this->firstName
    }
}

les return $this dans les mutateurs permettent de chainer les appels :

$user   ->setName('NAME')
        ->setFirstName('Firstname');

 

Donc si on résume vos POPO ne sont que des structures de données, fournissant un bus de données stable et normalisé permettant aux composants de votre système de parler de la même chose sous une forme typée (le type ici étant Voiture) c’est à dire contractualisée.

Évitez les échanges de données ou de tableaux de données scalaires ainsi obtiendrez vous grâce aux POPO (ou DTO) des API fortement typées et homogènes.
Si deux composant causent voiture, il utilisent le POPO voiture, l’unique contrat d’interface dans tout votre système pour causer voiture :

use Corp\Voiture\Voiture;

class MonComposant
{
public function maMethode(Voiture $voiture)
{
    $marque = $voiture->getMarque();
//[...]
}

//[...]
}
$monComposant = new Voiture();
$monComposant->setMarque('Marque')
// [...]
$monComposant->maMethode($uneVoiture);

 

Comme mentionné plus haut, ce côté « bus de données » est en soit une responsabilité.
C’est pourquoi il est très important de ne lui en ajouter aucune autre, pas même la gestion de la persistance des données comme on le voit souvent (cf design pattern active record). Ceci garantira la stabilité et la simplicité de leur interface (cf Single Responsibility Principle et Interface Segregation Principle). Ainsi aurez vous l’assurance que vos petits pigeons voyageurs de POPO resteront légers, sérialisables, cachables, logables, et n’auront jamais besoin d’autres objets pour fonctionner.

Vous trouverez dans ce blog une foison d’exemples d’utilisations des POPO à commencer par leur éternelle complicité avec les DAO, la couche justement dévolue à la persistance des données.