Classe interna ou aninhada em PHP

Estou construindo uma classe de usuáriopara o meu novo site, no entanto, desta vez eu estava pensando em construí-lo um pouco diferente...

eu sei que ++, Java and even Ruby (and probably other programming languages) allows nested/inner classes within the main class which allows to make the code more Object-Oriented and organized.

em PHP, eu gostaria de fazer algo assim:

<?php
    public class User {
        public $userid;
        public $username;
        private $password;

        public class UserProfile {
            // Some code here
        }

        private class UserHistory {
            // Some code here
        }
    }
?>
Isso é possível em PHP? Como posso alcançar aquilo?


Actualizar

Se for impossível, as futuras versões do PHP poderão suportar classes aninhadas?

Author: Lior Elrom, 2013-05-07

9 answers

Introdução:

As classes aninhadas dizem respeito a outras classes um pouco diferente das classes exteriores. Tomando Java como exemplo:

As classes aninhadas não-estáticas têm acesso a outros membros da classe envolvente, mesmo que sejam declaradas privadas. Além disso, classes aninhadas não-estáticas exigem que uma instância da classe-mãe seja instanciada.

OuterClass outerObj = new OuterClass(arguments);
outerObj.InnerClass innerObj = outerObj.new InnerClass(arguments);

Existem várias razões imperiosas para a sua utilização:

    É uma forma de agrupar logicamente classes que são usado apenas num lugar.

Se uma classe é útil para apenas uma outra classe, então é lógico para relacione-o e integre-o nessa aula e mantenha os dois juntos.

    Aumenta a encapsulação.

Considere duas classes de alto nível, A E B, onde B precisa de acesso a membros de A que de outra forma seriam declarados privados. Escondendo a classe B dentro da classe A, os membros de A podem ser declarados privados e B pode acessar o. Além disso, B ela própria pode ser escondida do mundo exterior.

  • As classes aninhadas podem levar a um código mais legível e sustentável.

Uma classe aninhada normalmente relaciona-se com a sua classe-mãe e juntos formam um "pacote"

Em PHP

Você pode ter comportamento similar em PHP sem classes aninhadas.

Se tudo o que você quer alcançar é estrutura/organização, como pacote.Classe exterior.InnerClass, os nomes PHP podem servir. Podes até declare mais de um espaço de nomes no mesmo arquivo (embora, devido às características de autoloading padrão, que pode não ser aconselhável).

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}
Se você deseja imitar outras características, como a visibilidade dos membros, é preciso um pouco mais de esforço.

Definir 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 Utilização

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();
        }
    }
}

Teste

$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:

Não me parece que tentar emular inerclasses em PHP seja tão bom. ideia. Acho que o código é menos limpo e legível. Além disso, existem provavelmente outras maneiras de alcançar resultados semelhantes usando um padrão bem estabelecido, como o observador, decorador ou padrão de composição. Às vezes, até uma simples herança é suficiente.
 112
Author: Tivie, 2014-12-01 12:50:44

Classes aninhadas com public/protected/private a acessibilidade foi proposta em 2013 para o PHP 5.6 como um RFC, mas não chegou a fazê-lo (sem votação ainda, sem atualização desde 2013 - a partir de 2016/12/29):

Https://wiki.php.net/rfc/nested_classes

class foo {
    public class bar {

    }
}

Pelo menos, as classes anónimas transformaram-no em PHP 7

Https://wiki.php.net/rfc/anonymous_classes

A partir desta página da RFC:

Âmbito De Aplicação Futuro

As alterações introduzidas por this patch mean named nested classes are easier to implement (by a tiny bit).

Então, podemos ter aulas aninhadas numa versão futura, mas ainda não está decidido.
 18
Author: Fabian Schmengler, 2016-12-29 15:07:48

Tu não podes fazer isto em PHP. No entanto, existem maneiras funcionais {[3] } de conseguir isso.

Para mais detalhes, por favor verifique esta publicação: Como fazer uma classe aninhada de PHP ou métodos aninhados?

Este modo de implementação é chamado de interface fluente: http://en.wikipedia.org/wiki/Fluent_interface

 8
Author: Sumoanand, 2017-05-23 11:54:59
Não podes fazê-lo em PHP. PHP suporta "include", mas você nem pode fazer isso dentro de uma definição de classe. Não há muitas opções aqui. Isto não responde directamente à sua pergunta, mas pode estar interessado em "Namespaces", uma\sintaxe terrivelmente feia\hacked\on \ top\of PHP OOP: http://www.php.net/manual/en/language.namespaces.rationale.php
 3
Author: dkamins, 2013-05-07 16:43:56

Desde a versão 5.4 do PHP, você pode forçar a criação de objectos com o construtor privado através da reflexão. Pode ser usado para simular classes aninhadas Java. Código de exemplo:

class OuterClass {
  private $name;

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

  public function getName() {
    return $this->name;
  }

  public function forkInnerObject($name) {
    $class = new ReflectionClass('InnerClass');
    $constructor = $class->getConstructor();
    $constructor->setAccessible(true);
    $innerObject = $class->newInstanceWithoutConstructor(); // This method appeared in PHP 5.4
    $constructor->invoke($innerObject, $this, $name);
    return $innerObject;
  }
}

class InnerClass {
  private $parentObject;
  private $name;

  private function __construct(OuterClass $parentObject, $name) {
    $this->parentObject = $parentObject;
    $this->name = $name;
  }

  public function getName() {
    return $this->name;
  }

  public function getParent() {
    return $this->parentObject;
  }
}

$outerObject = new OuterClass('This is an outer object');
//$innerObject = new InnerClass($outerObject, 'You cannot do it');
$innerObject = $outerObject->forkInnerObject('This is an inner object');
echo $innerObject->getName() . "\n";
echo $innerObject->getParent()->getName() . "\n";
 3
Author: Pascal9x, 2016-01-04 20:23:21
De acordo com o comentário de Xenon à resposta de Anıl Özselgin, classes anônimas foram implementadas em PHP 7.0, que é o mais próximo de classes aninhadas que você vai obter agora. Aqui estão os RFCs relevantes:

Classes aninhadas (estado: retirado)

Classes anónimas (estado: implementado em PHP 7.0)

Um exemplo para o post original, este é o que seu código seria:

<?php
    public class User {
        public $userid;
        public $username;
        private $password;

        public $profile;
        public $history;

        public function __construct() {
            $this->profile = new class {
                // Some code here for user profile
            }

            $this->history = new class {
                // Some code here for user history
            }
        }
    }
?>
Isto, no entanto, vem com uma advertência muito desagradável. Se utilizar um IDE como PHPStorm ou NetBeans, e depois adicionar um método como este à classe User:
public function foo() {
  $this->profile->...
}

...adeus auto-completação. Este é o caso mesmo se você codifica para interfaces( o I no SOLID), usando um padrão como este:

<?php
    public class User {
        public $profile;

        public function __construct() {
            $this->profile = new class implements UserProfileInterface {
                // Some code here for user profile
            }
        }
    }
?>

A menos que as suas únicas chamadas para $this->profile sejam do método __construct() (ou seja qual for o método $this->profile definido em) então você não terá qualquer tipo de insinuação de tipo. Sua propriedade é essencialmente "escondida" para a sua IDE, tornando a vida muito difícil se você confiar em sua IDE para completar automaticamente, cheirar códigos e refactorar.

 2
Author: e_i_pi, 2017-01-31 23:14:22
Está à espera da votação como RFC. https://wiki.php.net/rfc/anonymous_classes
 1
Author: Anıl Özselgin, 2015-03-19 11:53:24
Acho que escrevi uma solução elegante para este problema usando espaços de nomes. No meu caso, a classe interna não precisa de conhecer a sua classe-mãe (como a classe interna estática em Java). Como exemplo, fiz uma classe chamada "Usuário" e uma subclasse chamada "Tipo", usada como referência para os tipos de usuário (ADMIN, outros) no meu exemplo. Cumprimento.

Utilizador.php (ficheiro de classe de utilizador)

<?php
namespace
{   
    class User
    {
        private $type;

        public function getType(){ return $this->type;}
        public function setType($type){ $this->type = $type;}
    }
}

namespace User
{
    class Type
    {
        const ADMIN = 0;
        const OTHERS = 1;
    }
}
?>

A usar.php (um exemplo de como chamar a "subclasse"')

<?php
    require_once("User.php");

    //calling a subclass reference:
    echo "Value of user type Admin: ".User\Type::ADMIN;
?>
 1
Author: Rogerio Souza, 2016-08-22 16:25:46

Coloque cada classe em ficheiros separados e" necessite " deles.

Utilizador.php

<?php

    class User {

        public $userid;
        public $username;
        private $password;
        public $profile;
        public $history;            

        public function __construct() {

            require_once('UserProfile.php');
            require_once('UserHistory.php');

            $this->profile = new UserProfile();
            $this->history = new UserHistory();

        }            

    }

?>

UserProfile.php

<?php

    class UserProfile 
    {
        // Some code here
    }

?>
UserHistory.php
<?php

    class UserHistory 
    {
        // Some code here
    }

?>
 -2
Author: priyabagus, 2017-11-24 22:59:28