Intro:
Les classes imbriquées se rapportent aux autres classes un peu différemment des classes externes. Prenant Java comme exemple:
Les classes imbriquées non statiques ont accès aux autres membres de la classe englobante, même si elles sont déclarées privées. En outre, les classes imbriquées non statiques nécessitent une instance de la classe parente pour être instanciée.
OuterClass outerObj = new OuterClass(arguments);
outerObj.InnerClass innerObj = outerObj.new InnerClass(arguments);
Il existe plusieurs raisons impérieuses de les utiliser:
- C'est une façon de regrouper logiquement des classes qui ne sont utilisées qu'à un seul endroit.
Si une classe n'est utile qu'à une seule autre classe, il est logique de la relier et de l'intégrer dans cette classe et de garder les deux ensemble.
- Cela augmente l'encapsulation.
Considérez deux classes de niveau supérieur, A et B, où B a besoin d'accéder aux membres de A qui seraient autrement déclarés privés. En masquant la classe B dans la classe A, les membres de A peuvent être déclarés privés et B peut y accéder. De plus, B lui-même peut être caché du monde extérieur.
- Les classes imbriquées peuvent conduire à un code plus lisible et maintenable.
Une classe imbriquée se rapporte généralement à sa classe parente et forme ensemble un "package"
En PHP
Vous pouvez avoir un comportement similaire en PHP sans classes imbriquées.
Si tout ce que vous voulez réaliser est la structure / organisation, comme Package.OuterClass.InnerClass, les espaces de noms PHP peuvent suffire. Vous pouvez même déclarer plusieurs espaces de noms dans le même fichier (bien que, en raison des fonctionnalités de chargement automatique standard, cela ne soit pas conseillé).
namespace;
class OuterClass {}
namespace OuterClass;
class InnerClass {}
Si vous souhaitez émuler d'autres caractéristiques, telles que la visibilité des membres, cela demande un peu plus d'efforts.
Définition de la classe "package"
namespace {
class Package {
/* protect constructor so that objects can't be instantiated from outside
* Since all classes inherit from Package class, they can instantiate eachother
* simulating protected InnerClasses
*/
protected function __construct() {}
/* This magic method is called everytime an inaccessible method is called
* (either by visibility contrains or it doesn't exist)
* Here we are simulating shared protected methods across "package" classes
* This method is inherited by all child classes of Package
*/
public function __call($method, $args) {
//class name
$class = get_class($this);
/* we check if a method exists, if not we throw an exception
* similar to the default error
*/
if (method_exists($this, $method)) {
/* The method exists so now we want to know if the
* caller is a child of our Package class. If not we throw an exception
* Note: This is a kind of a dirty way of finding out who's
* calling the method by using debug_backtrace and reflection
*/
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
if (isset($trace[2])) {
$ref = new ReflectionClass($trace[2]['class']);
if ($ref->isSubclassOf(__CLASS__)) {
return $this->$method($args);
}
}
throw new \Exception("Call to private method $class::$method()");
} else {
throw new \Exception("Call to undefined method $class::$method()");
}
}
}
}
Cas d'utilisation
namespace Package {
class MyParent extends \Package {
public $publicChild;
protected $protectedChild;
public function __construct() {
//instantiate public child inside parent
$this->publicChild = new \Package\MyParent\PublicChild();
//instantiate protected child inside parent
$this->protectedChild = new \Package\MyParent\ProtectedChild();
}
public function test() {
echo "Call from parent -> ";
$this->publicChild->protectedMethod();
$this->protectedChild->protectedMethod();
echo "<br>Siblings<br>";
$this->publicChild->callSibling($this->protectedChild);
}
}
}
namespace Package\MyParent
{
class PublicChild extends \Package {
//Makes the constructor public, hence callable from outside
public function __construct() {}
protected function protectedMethod() {
echo "I'm ".get_class($this)." protected method<br>";
}
protected function callSibling($sibling) {
echo "Call from " . get_class($this) . " -> ";
$sibling->protectedMethod();
}
}
class ProtectedChild extends \Package {
protected function protectedMethod() {
echo "I'm ".get_class($this)." protected method<br>";
}
protected function callSibling($sibling) {
echo "Call from " . get_class($this) . " -> ";
$sibling->protectedMethod();
}
}
}
Essai
$parent = new Package\MyParent();
$parent->test();
$pubChild = new Package\MyParent\PublicChild();//create new public child (possible)
$protChild = new Package\MyParent\ProtectedChild(); //create new protected child (ERROR)
Production:
Call from parent -> I'm Package protected method
I'm Package protected method
Siblings
Call from Package -> I'm Package protected method
Fatal error: Call to protected Package::__construct() from invalid context
REMARQUE:
Je ne pense vraiment pas qu'essayer d'émuler les innerClasses en PHP soit une si bonne idée. Je pense que le code est moins propre et lisible. De plus, il existe probablement d'autres moyens d'obtenir des résultats similaires en utilisant un modèle bien établi tel que le modèle Observer, Decorator ou COmposition. Parfois, même un simple héritage suffit.