Alors voilà ... Je me demandais commment on pouvait faire en PHP pour interdire à l'utilisateur de créer lui-même une nouvelle instance de classe, mais par contre rendre cette création possible lors d'un appel à une méthode.
Un exemple concrêt permet de mieux cerner le problème : imaginons une classe qui modélise un noeud XML. Chaque instance de cette classe va posséder une référence vers un objet parent. Et l'objet parent va également posséder une référence vers l'objet fils.
L'idéal serait alors de faire :
$child = new Node();
$child -> setParent($parent);
$parent -> addChild($child);
Cette procédure est parfaite et ne pose aucun problème si l'utilisateur la respecte bien. Mais si un jour il utilise seulement $child = new Node() ... Bah ça ne correspond plus à rien.
Il faudrait plutôt utiliser une méthode de la classe Node qui permet d'automatiser la création de fils :
$child = $parent -> getEmptyChild();
Et empêcher la création d'un nouveau noeud autrement qu'à travers cette méthode ...
C'est la que ma visibilité intervient ...
Source / Exemple :
<?php
/* -------------------- */
class MyOwnClass
{
private $Friends = array();
private function addFriend($name)
{
$friends = &$this -> Friends;
array_push($friends, $name);
}
public function __construct()
{
$this -> addFriend('MyFriendClass');
}
private function isCalledByFriend()
{
$stack = debug_backtrace();
$stack = array_slice($stack, 2, count($stack) - 2);
$friends = &$this -> Friends;
$friend = false;
foreach($stack as $call)
{
if(in_array($call['class'], $friends))
{
$friend = true;
break;
}
}
return $friend;
}
public function methodCalledOnlyByFriends()
{
if(!$this -> isCalledByFriend()) throw new Exception('Impossible d\'appeller cette méthode. ');
}
}
/* -------------------- */
class MyFriendClass
{
public function __construct()
{
}
public function callMethodOfFriend()
{
$instance = new MyOwnClass();
$instance -> methodCalledOnlyByFriends();
}
}
/* -------------------- */
$instance = new MyFriendClass();
$instance -> callMethodOfFriend();
/* -------------------- */
?>
Conclusion :
La manière de procéder est plutôt ... Sale. On récupère la pile des appels pour vérifier que la méthode appellée a bien été appellée par une classe qui se trouve dans la liste des classes dites "amies".
On pourrait améliorer le code en définissant toutes les classes amies comme celles qui se trouvent dans le même répertoire que la classe en question. Qui sait ?
C'est un code un peu experimental et je le mets en initié non-pas parce qu'il est compliqué d'un point-de-vue langage, mais plutôt d'un point-de-vue compréhension.
7 juil. 2007 à 10:33
27 janv. 2008 à 21:40
Sinon, une version d'interface permettant de créer ce type de classes ça serais pas mal. Ensuite, afin de simplifier la source, ce n'est pas possible de créer un constructeur du type __construct(&$parent) puis de vérifier que le parent est bien la classe xxx avec is_a($parent, 'classe....').
Puis dans le construct tu peux executer librement les fonctions dont tu fais référence en tant qu'illustration.
Perso moi je créé systématiquement un fonction evtLoad que je surcharge quand j'en ai besoin et elle est dirrectement appellée dans le __construct.
2 mars 2009 à 16:10
Ton code ne gère que l'accessibilité, au prix d'une vérification couteuse à l'exécution, mais pas du tout la visibilité (ce qui signifie que si une classe interdite est visible par ton procédé, elle masquera et empêchera l'accès à une classe autorisée (avec "package" en Java: il n'y a pas d'interdiction c'est la visibilité qui résoud cela; c'est vrai qu'en plus le ClassLoader de Java vérifie les conditions d'accessibilité lors du chargement des classes souhaitant en utiliser d'autres ou des méthodes de visibilité package, mais on peut encore passer outre par un appel indirect via Réflection, une fonction très utile pour les débogueurs qui font grznd usage de Réflection.)
Aussi le titre de ton source va sembler trompeur: il ne s'agit pas de gérer la "visibilité" package mais seulement "l'accessibilité" puisque ton source ne permet pas du tout de rendre invisible une classe instanciée ou héritée ou une méthode ou un champ de cette instance selon le contexte d'usage.
L'invisibilité c'est autre chose: il s'agit de déterminer si la surcharge d'un élément lexical est autorisée ou non (les élements "finaux" ne peuvent pas être surchargés lorsqu'ils sont visibles, ce qui est indpendant de leut accessibilité) et comment se fait cette surcharge (il n'y a pas virtualisation d'une classe de base si la surcharge est invisible, ce qui fait que la classe de base n'est pas modifiée ou peut être modifiée indépendamment de cette surcharge).
En général la visibilité est un aspect du programme source, déterminé par la compilation, mais Java va plus loin en étendant le concept de visibilité à l'exécution (selon un modèle de sécurité implémenté dans le ClassLoader, mais modifiable, justement pour les débogueurs ou pour certaines classes spéciales construisant dynamiquement des proxies ou des entrées/sorties de flux de persistance: dans ces cas là le coût des vérifications par Reflection est négligeable par rapport au coût du débogage ou monitoring lui-même, ou des entrées-sorties réelles; dans les autres cas, la réflexion est trop couteuse et heureusement Java est nettement plus performant pour se passer de ces vérifications faites une seule fois lors du chargement du programme à liaison quasi-statique).
Le but de la visibilité est avant tout d'offrir une liberté de nommage des éléments internes à une implémentation, et sert aussi de modèle de développement. Pour les langages ne supportant pas la visibilité "package" il semble bien plus simple de nommer les classes ou membres de classes avec un préfixe correspondant au nom du package. C'est pas très pratique en terme d'utilisation, mais ça évite le coût de telles vérifications. Pour la visibilité package des classes cela voudrait dire que ces classes devraient toutes être nommées avec de tels préfixes, ce qui n'est pas possible (en revanche en C++ ou C# on a la notion de namespace qui facilite le travail et fonctionne de façon assez proche, quasi équivalente y compris pour la réflexion).
Tant que PHP n'aura pas de réel support pour les namespaces, on ne peut que recommander de prendre garde au nommage des entités (classes, méthodes membres et/ou évènements, champs et/ou attributs, fonctions statiques, variables de classe, constantes déclarées), en adaoptant une convention de nommage (le seul contrôle efficace de visibilité en PHP est celui des variables, mais il est cassé par les constantes définies).
En PHP on doit aussi faire attention aux constantes globales créées avec "define()" qui échappe à tout modèle (comme le font les macros du C/C++) et peuvent même perturber le fonctionnement des classes utilisées de façon inattendue selon l'ordre difficilement prédictible d'inclusion (que ce soit par "include" mais surtout pour "require"): ces constantes globales devraient pouvoir devenir elles aussi invisibles pour éviter les effets de bord, en leur ajoutant aussi la notion de namespace, au minimum; pour le reste PHP dispose de constantes statiques déclarées dans les classes, ce qui devrait suffire à contrôler leur visibilité.)
Personnellement étant passé par Java (ou même C#) après avoir essayé pendant un temps PHP, j'ai énormément de mal à revenir à PHP, tellement le langage est ambigu et très peu propre, et bourré d'anomalies historiques.
J'ai beaucoup moins de mal à passer à Python en revanche, construit sur des bases bien plus solides et selon un principe aussi facile comme langage de script (Pythin est rapidement devenu bien plus puissant que PHP pour cette raison et dispose de librairies et de compilateurs maintenant aussi efficaces que Java ou C# pour .Net).
Et même Perl est plus propre que PHP! C'est peu dire. PHP reste pour moi un langage de prototypage rapide adapté uniquement à la customisatrion légères de sites web (notamement les formulaires), mais gérer de gros projets avec c'est trop risqué (il y a trop de pièges imprévus et le code est difficile à maintenir), PHP est aujourd'hui à Java (ou même Javascript) ce que le Fortran ou le C sont au Pascal ou Modula.
Sa notation sigillaire est également préhistorique ("$" utilisé seulement pour distinguer constantes et variables, sans aucun rôle pour le typage ni la visibilité), ses types de données sont ambigus.
Et même la quasi-totalité de sa bibliothèque de base est ambigue avec trop de fonctions à retour "mixed" dont les types joints ne sont pas séparables.
Les tableaux associatifs de PHP seraient parfaits s'il n'avait pas ce concept interne de position courante, juste là car il est impossible de les gérer comme des références (PHP n'a pas de support correct des itérateurs, le "foreach" est bogué et sujet à divers effets de bord en plus d'être particulièrement couteux en mémoire pour les collections importantes qu'il va copier). PHP ne gère pas non plus la mutabilité (le droit d'accès en écriture séparé de celui en lecture), ni correctement non plus le cycle de vie des instances (Java le fait de façon allégée sans destructeurs mais avec finaliseurs, la destruction restant automatique après finalisation seulement lorsque le besoin de ressources se fait sentir, les opérations qui demandent une finalisation anticipée selon une chronologie bien définie doivent être programmées par des méthodes appelées explicitement, ce qui n'est pas une véritable gène).
PHP en revanche a certains avantages dans quelques contextes, mais tant qu'à faire JavaScript/ECMASCript le fait de façon plus puissante: c'est celui de la modifications dynamique du contenu des objets (tout objet pouvant être la classe modèle d'un autre, il n'y a pas de séparation entre classes et objets, donc pas de complexité relative aux classes, métaclasses, méta-métaclasses... En Java c'est possible aussi mais il faut créer les instances via un ClassLoader qui va aussi créer explicitement la classe proxy (c'est ce que font maintenant implicitement les moteurs Javascript modernes).
Le défaut est que PHP est comme Javascript trop permissif et, ne propose que le renforcement de la visibilité pour les 3 types de visibilité qu'il connait (contrairement à Javascript qui ne propose rien du tout), mais ne fait rien pour renforcer l'accessibilité (ce qui pose de gros problèmes en terme de protection contre l'injection de code, trop facile dans les programmes PHP). Et le support de la généricité est une horreur à programmer et vérifier en PHP (il suffit de voir les listes impressionnantes d'exceptions et de cavéats dans les commentaires sous la doc officielle de PHP, avec divers solutions plus ou moins compliquées de contournements qui ne résolvent qu'une partie du problème, jusqu'à ce que la librairie soit augmentée d'une nouvelle fonction proche qui apporte encore d'autres problèmes! En plus de cela le calcul est largement bogué en PHP et non conforme aux normes de base IEEE que tout langage devrait respecter)
2 mars 2009 à 16:29
En lisant ton commentaire on aurait envie de bruler tous les codes PHP et de changer de language :))) (lol).
Attention, âmes sensibles s'abstenir !!!
J'ai lu pas mal de remarques de ce type, avantages / inconvénients. La tienne fait sans doute pencher la balance, surtout que tout ce que tu dis est super bien argumenté (ou l'était dans php4).
Merci pour ce commentaire très constructif.
2 mars 2009 à 16:58
Ayant découvert Python tardivement j'ai été surpris par la grande rapidité d'apprentissage de ce langage (même si j'en connaissais déjà de nombreux): je me suis plus vite mis à Python que de me remettre à PHP que je connaissais avant. Et Python reste dans la ligné de Java ou C++ en terme de philosophie générale et de modélisation objet.
Il me semble que comme langage de script il n'y aaujourd'hui pas mieux: c'est aussi rapide à développer que le PHP, aussi souple que le Javascript, aussi solide dans son typage que Java, la librairie de base est aussi riche que celle de Java (sans les multiples variantes incomplètes dans la librairie PHP de base), et s'enrichie plus vite dans ses extensions, sa syntaxe est légère (fini les "$" inutiles dans le code source PHP), et en plus son moteur est maintenant très rapide aussi avec sa machine virtuelle qui génère du bytecode précompilé (comme Java et .Net), mais aussi du code natif profilé à l'exécution (comme java aussi, mais pas encore .Net qui ne fait que du profilage à l'installation et manque de "mobilité").
J'ai bien essayé aussi Ruby, mais sans conviction: les améliorations sont inutilement compliquées, et il lui manque trop de choses dans ses librairies (Ruby est peut-être trop jeune, mais il prend du retard face à Python apparu pas si longtemps que ça avant).
Et Python est déjà près pour le parallélisme massif (le même langage peut servir de langage de kernel pour calcul sur GPUs divers ou sur nuage de coeurs de processeurs locaux ou distribués en réseau, comme sur des threads d'un monocoeur ou des taches sur des serveurs distincts, sans en faire dépendre la granularité arbitrairement par la structure et le découpage de l'application).
Il ne lui manque plus grand chose (dans son bytecode reconnu par sa VM) pour devenir un langage système à part entière: ce qui lui manque c'est un dispositif d'isolation et une architecture de sécurité (comparable à celle dans les ClassLoaders de Java, y compris en terme de vérifiabilité du bytecode et des contrats associés, puisque Python supporte aussi la réflection et une interface simple au code natif de l'hôte).
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.