Introdução:
As classes aninhadas se relacionam com outras classes de maneira um pouco diferente das classes externas. Tomando Java como exemplo:
As classes aninhadas não estáticas têm acesso a outros membros da classe envolvente, mesmo se forem declarados privados. Além disso, as classes aninhadas não estáticas requerem que uma instância da classe pai seja instanciada.
OuterClass outerObj = new OuterClass(arguments);
outerObj.InnerClass innerObj = outerObj.new InnerClass(arguments);
Existem vários motivos convincentes para usá-los:
- É uma maneira de agrupar logicamente as classes que são usadas apenas em um lugar.
Se uma classe é útil para apenas uma outra classe, então é lógico relacioná-la e incorporá-la nessa classe e manter as duas juntas.
- Aumenta o encapsulamento.
Considere duas classes de nível superior, A e B, onde B precisa de acesso aos membros de A que, de outra forma, seriam declarados privados. Ao ocultar a classe B dentro da classe A, os membros de A podem ser declarados privados e B pode acessá-los. Além disso, o próprio B pode ser escondido do mundo exterior.
- As classes aninhadas podem levar a um código mais legível e sustentável.
Uma classe aninhada geralmente se relaciona com sua classe pai e, juntas, formam um "pacote"
Em PHP
Você pode ter um comportamento semelhante em PHP sem classes aninhadas.
Se tudo o que você deseja alcançar é estrutura / organização, como Package.OuterClass.InnerClass, namespaces de PHP podem ser suficientes. Você pode até declarar mais de um namespace no mesmo arquivo (embora, devido aos recursos de carregamento automático padrão, isso possa não ser aconselhável).
namespace;
class OuterClass {}
namespace OuterClass;
class InnerClass {}
Se você deseja emular outras características, como a visibilidade dos membros, é preciso um pouco mais de esforço.
Definindo a classe "pacote"
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()");
}
}
}
}
Caso de uso
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();
}
}
}
Testando
$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)
Resultado:
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
NOTA:
Eu realmente não acho que tentar emular innerClasses em PHP seja uma ideia tão boa. Acho que o código é menos limpo e legível. Além disso, provavelmente existem outras maneiras de obter resultados semelhantes usando um padrão bem estabelecido, como o Observer, Decorator ou COmposition Pattern. Às vezes, mesmo uma simples herança é suficiente.