Clase Anidada o Interna en PHP


Estoy construyendo una Clase de usuario para mi nuevo sitio web, sin embargo, esta vez estaba pensando en construirlo un poco diferente...

Sé que C++, Java e incluso Ruby (y probablemente otros lenguajes de programación) permite clases anidadas/internas dentro de la clase principal que permite hacer el código más orientado a Objetos y organizado.

En PHP, me gustaría hacer algo así:

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

        public class UserProfile {
            // Some code here
        }

        private class UserHistory {
            // Some code here
        }
    }
?>

¿Es eso posible en PHP? ¿Cómo puedo lograr ¿eso?


UPDATE

Si es imposible, ¿las futuras versiones de PHP podrían soportar clases anidadas?

Author: Lior Elrom, 2013-05-07

9 answers

Introducción:

Las clases anidadas se relacionan con otras clases un poco diferente a las clases externas. Tomando Java como ejemplo:

Las clases anidadas no estáticas tienen acceso a otros miembros de la clase que las encierra, incluso si se declaran privadas. Además, las clases anidadas no estáticas requieren que se cree una instancia de la clase padre.

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

Hay varias razones convincentes para usarlos:

  • Es una forma de agrupar lógicamente clases que son solo se usa en un solo lugar.

Si una clase es útil solo para otra clase, entonces es lógico relacionarlo e incrustarlo en esa clase y mantener los dos juntos.

  • aumenta la encapsulación.

Considere dos clases de alto nivel, A y B, donde B necesita acceso a miembros de A que de otro modo serían declarados privados. Ocultando la clase B dentro de la clase A, los miembros de A pueden ser declarados privados y B puede acceder ellos. Además, B se puede ocultar del mundo exterior.

  • Las clases anidadas pueden conducir a un código más legible y mantenible.

Una clase anidada generalmente se relaciona con su clase padre y juntos forman un "paquete"

En PHP

Puede tener un comportamiento similar en PHP sin clases anidadas.

Si todo lo que desea lograr es estructura/organización, como paquete.Clase externa.InnerClass, los espacios de nombres PHP podrían ser suficientes. Incluso puedes declare más de un espacio de nombres en el mismo archivo (aunque, debido a las características de carga automática estándar, eso podría no ser recomendable).

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

Si desea emular otras características, como la visibilidad de los miembros, se necesita un poco más de esfuerzo.

Definiendo la clase" 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()");
            }
        }
    }
}

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

Pruebas

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

Salida:

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:

Realmente no creo que tratar de emular innerClasses en PHP sea tan bueno idea. Creo que el código es menos limpio y legible. Además, probablemente hay otras formas de lograr resultados similares utilizando un patrón bien establecido, como el Patrón de composición Observer, Decorator ou. A veces, incluso la simple herencia es suficiente.

 112
Author: Tivie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-01 12:50:44

Clases anidadas reales con public/protected/private la accesibilidad se propuso en 2013 para PHP 5.6 como un RFC, pero no lo hizo (Sin votación todavía, no hay actualización desde 2013 - a partir de 2016/12/29):

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

class foo {
    public class bar {

    }
}

Al menos, las clases anónimas lo hicieron en PHP 7

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

De esta página de RFC:

Ámbito de aplicación futuro

Los cambios realizados por este patch mean named anided classes are easier to implement (by a tiny bit).

Entonces, podríamos obtener clases anidadas en alguna versión futura, pero aún no está decidido.

 18
Author: Fabian Schmengler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-29 15:07:48

Usted no puede hacer esto en PHP. Sin embargo, hay formas funcionales de lograr esto.

Para obtener más detalles, consulte esta publicación: ¿Cómo hacer una clase anidada en PHP o métodos anidados?

Esta forma de implementación se llama fluent interface: http://en.wikipedia.org/wiki/Fluent_interface

 8
Author: Sumoanand,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:59

No se puede hacer en PHP. PHP soporta "include", pero ni siquiera puedes hacer eso dentro de una definición de clase. No hay muchas opciones aquí.

Esto no responde a su pregunta directamente, pero puede estar interesado en" Namespaces", una\sintaxis\hackeada\on\top\terriblemente fea de PHP OOP: http://www.php.net/manual/en/language.namespaces.rationale.php

 3
Author: dkamins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-07 16:43:56

Desde la versión 5.4 de PHP puedes forzar la creación de objetos con private constructor a través de la reflexión. Se puede usar para simular clases anidadas en Java. Código de ejemplo:

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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-04 20:23:21

Según el comentario de Xenon a la respuesta de Anıl Özselgin, las clases anónimas se han implementado en PHP 7.0, que es lo más cercano a las clases anidadas que obtendrá en este momento. Aquí están las RFC relevantes:

Clases anidadas (status: withdrawn)

Clases anónimas (estado: implementadas en PHP 7.0)

Un ejemplo de la publicación original, así es como se vería su código:

<?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
            }
        }
    }
?>

Esto, sin embargo, viene con una advertencia muy desagradable. Si utiliza un IDE como PhpStorm o NetBeans, y luego agregue un método como este a la clase User:

public function foo() {
  $this->profile->...
}

...adiós autocompletado. Este es el caso incluso si codifica en interfaces (la I en SOLID), usando un patrón 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 sus únicas llamadas a $this->profile sean del método __construct() (o cualquier método $this->profile esté definido en), entonces no obtendrá ningún tipo de insinuación de tipo. Su propiedad está esencialmente "oculta" a su IDE, haciendo la vida muy difícil si confía en su IDE para autocompletado, olfato de código y refactorización.

 2
Author: e_i_pi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-31 23:14:22

Está a la espera de votar como RFC https://wiki.php.net/rfc/anonymous_classes

 1
Author: Anıl Özselgin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-19 11:53:24

Creo que escribí una solución elegante a este problema mediante el uso de espacios de nombres. En mi caso, la clase interna no necesita conocer su clase padre (como la clase interna estática en Java). Como ejemplo hice una clase llamada ' User 'y una subclase llamada' Type', usada como referencia para los tipos de usuario (ADMIN, OTHERS) en mi ejemplo. Respecto.

Usuario.php (Archivo de clase de usuario)

<?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;
    }
}
?>

Usando.php (Un ejemplo de cómo llamar a la 'subclase')

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

    //calling a subclass reference:
    echo "Value of user type Admin: ".User\Type::ADMIN;
?>
 1
Author: Rogerio Souza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-22 16:25:46

Ponga cada clase en archivos separados y "require" ellos.

Usuario.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
    }

?>

Historia del usuario.php

<?php

    class UserHistory 
    {
        // Some code here
    }

?>
 -2
Author: priyabagus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-24 22:59:28