Clases y Objetos Introducción A partir de PHP 5, el modelo de objetos ha sido reescrito para permitir un mejor rendimiento y con más características. Este fue un cambio importante a partir de PHP 4. PHP 5 tiene un modelo de objetos completo. Entre las características de PHP 5 están la inclusión de la visibilidad, las clases abstractas y clases y métodos finales, métodos mágicos adicionales, interfaces,clonación y tipos sugeridos. PHP trata los objetos de la misma manera como referencias o manejadores, lo que significa que cada variable contiene una referencia de objeto en lugar de una copia de todo el objeto. Ver objetos y referencias Sugerencia Vea también Guia de Entorno de Usuario para Nombres. Lo básico class La definición básica de clases comienza con la palabra clave class, seguido por un nombre de clase, continuado por un par de llaves que encierran las definiciones de las propiedades y métodos pertenecientes a la clase. El nombre de clase puede ser cualquier etiqueta válida que no sea una palabra reservada de PHP. Un nombre válido de clase comienza con una letra o un guión bajo, seguido de la cantidad de letras, números o guiones bajos que sea. Como una expresión regular, se expresaría de la siguiente forma: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*. Una clase puede tener sus propias constantes, variables (llamadas "propiedades"), y funciones (llamadas "métodos"). Ejemplo #1 Definición simple de una clase <?php class SimpleClass { // Declaración de la propiedad public $var = 'a default value'; // Declaración del método public function displayVar() { echo $this->var; } } ?> La pseudo-variable $this está disponible cuando un método es invocado dentro del contexto de un objeto. $this es una referencia del objeto que invoca (usualmente el objeto al que el método pertenece, pero posiblemente sea otro objeto, si el método es llamado estáticamente desde el contexto de un objeto secundario). Ejemplo #2 Algunos ejemplo de la pseudo-variable $this <?php class A { function foo() { if (isset($this)) { echo '$this está definida ('; echo get_class($this); echo ")\n"; } else { echo "\$this no está definida.\n"; } } } class B { function bar() { // Nota: la siguiente línea arrojará un Warning si E_STRICT está habilitada. A::foo(); } } $a = new A(); $a->foo(); // Nota: la siguiente línea arrojará un Warning si E_STRICT está habilitada. A::foo(); $b = new B(); $b->bar(); // Nota: la siguiente línea arrojará un Warning si E_STRICT está habilitada. B::bar(); ?> El resultado del ejemplo sería: $this $this $this $this está definida (A) no está definida. está definida (B) no está definida. new Para crear una instancia de una clase, la palabra clave new debe ser usada. Un objeto siempre se creará a menos que el objeto tenga un constructor que arroje una excepción en caso de error. Las clases deberían ser definidas antes de la instanciación (y en algunos casos esto es un requerimiento). Si un string que contiene el nombre de una clase se usa con new, una nueva instancia de esa clase será creada. Si la clase está en un espacio de nombres, su nombre completo debe ser usado cuando se hace esto. Ejemplo #3 Creación de una instancia <?php $instance = new SimpleClass(); // Esto también se puede hacer con variables: $className = 'Foo'; $instance = new $className(); // Foo() ?> En el contexto de una clase, es posible crear un nuevo objeto con new self y new parent. Cuando se asigna una instancia de una clase ya creada a una nueva variable, ésta última accederá a la misma instancia como al objeto que le fue asignado. Esta conducta es la misma cuando se pasan instancias a una función. Una copia de un objeto ya creado se puede lograr a través de la clonación de la misma. Ejemplo #4 Asignación de objetos <?php $instance = new SimpleClass(); $assigned $reference = $instance; =& $instance; $instance->var = '$assigned tendrá este valor'; $instance = null; // $instance y $reference se transforman en null var_dump($instance); var_dump($reference); var_dump($assigned); ?> El resultado del ejemplo sería: NULL NULL object(SimpleClass)#1 (1) { ["var"]=> string(30) "$assigned tendrá este valor" } PHP 5.3.0 introdujo un par de nuevas maneras para crear instancias de un objeto: Ejemplo #5 Creando nuevos objetos <?php class Test { static public function getNew() { return new static; } } class Child extends Test {} $obj1 = new Test(); $obj2 = new $obj1; var_dump($obj1 !== $obj2); $obj3 = Test::getNew(); var_dump($obj3 instanceof Test); $obj4 = Child::getNew(); var_dump($obj4 instanceof Child); ?> El resultado del ejemplo sería: bool(true) bool(true) bool(true) extends Una clase puede heredar los métodos y propiedades de otra clase al utilizar la palabra clave extends en la declaración de la clase. No es posible extender múltiples clases; una clase sólo puede heredar de una clase base. Los métodos y propiedades heredados pueden ser sobrescritos con la redeclaración de éstos utilizando el mismo nombre que en la clase parent. Sin embargo, si la clase parent definió un método como final, éste no podrá ser sobrescrito. Es posible acceder a los métodos sobrescritos o propiedades estáticas referenciándolos con parent::. Cuando se sobrescriben métodos, la cantidad y disposición de los parámetros debería ser la misma o PHP generará un error a nivel de E_STRICT. Esto no se aplica a los constructores, que permiten la sobrescritura con diferentes parámetros. Ejemplo #6 Herencia simple de clases <?php class ExtendClass extends SimpleClass { // Redefinición del método parent function displayVar() { echo "Clase extendida\n"; parent::displayVar(); } } $extended = new ExtendClass(); $extended->displayVar(); ?> El resultado del ejemplo sería: Clase extendida un valor por defecto Propiedades Las variables pertenecientes a clases se llaman "propiedades". También se les puede llamar usando otros términos como "atributos" o "campos", pero para los propósitos de esta referencia se va a utilizar "propiedades". Éstas se definen usando una de las palabras clave public, protected, o private, seguido de una declaración normal de variable. Esta declaración puede incluir una inicialización, pero esta inicialización debe ser un valor constante, es decir, debe poder ser evaluada en tiempo de compilación y no debe depender de información en tiempo de ejecución para ser evaluada. Véase Visibilidad para más información sobre el significado de public, protected, y private. Nota: Con el fin de mantener la compatibilidad con PHP 4, PHP 5 continuará aceptando el uso de la palabra clave var en la declaración de propiedades en lugar de (o además de) public, protected, o private. Sin embargo, var ya no es necesaria. Entre las versiones 5.0 y 5.1.3 de PHP, el uso de var fue considerado obsoleto y emitía una advertencia de nivel E_STRICT, pero a partir de PHP 5.1.3 ya no está obsoleta y no emitirá la advertencia. Si se declara una propiedad utilizando var en lugar de public, protected, o private, PHP tratará dicha propiedad como si hubiera sido definida comopublic. Dentro de los métodos de clase, las propiedades, constantes y métodos pueden ser accedidos a través de $this->propiedad (donde propiedad es el nombre de la propiedad; véanse también los nombres de propiedades variables) a menos que el acceso sea a una propiedad estática dentro del contexto de un método de clase estático, en cuyo caso sería accedida usando self::$propiedad. Véase la palabra clave 'static' para más información. La pseudo-variable $this está disponible dentro de cualquier método de clase cuando éste es invocado dentro del contexto de un objeto. $this es una referencia al objeto que se invoca (usualmente el objeto al que pertenece el método, pero posiblemente sea otro objeto, si el método es llamado estáticamentedesde el contexto de un objeto secundario). Ejemplo #1 Declaración de propiedades <?php class ClaseSencilla { // Declaraciones de propiedades inválidas: public $var1 = 'hola ' . 'mundo'; public $var2 = <<<EOD hola mundo EOD; public $var3 = 1+2; public $var4 = self::myStaticMethod(); public $var5 = $myVar; // Declaraciones de propiedades válidas: public $var6 = myConstant; public $var7 = array(true, false); // Esto se permite sólo en PHP 5.3.0 y posteriores. public $var8 = <<<'EOD' hola mundo EOD; } ?> Nota: Existen varias funciones interesantes para manipular clases y objetos. Quizá pueda ser interesante echar un vistazo a las Funciones de clases/objetos. A diferencia de heredocs, nowdocs puede ser utilizado en cualquier contexto de datos estáticos, incluyendo la declaración de propiedades. Ejemplo #2 Ejemplo del uso de nowdoc para inicializar una propiedad <?php class foo { // A partir de PHP 5.3.0 public $bar = <<<'EOT' bar EOT; } ?> Nota: El soporte para nowdoc fue agregado en PHP 5.3.0. Constantes de Clases Es posible definir valores constantes en función de cada clase manteniéndola invariable. Las constantes se diferencian de variables comunes en que no utilizan el símbolo $ al declararlas o usarlas. El valor debe ser una expresión constante, no (por ejemplo) una variable, una propiedad, un resultado de una operación matemática, o una llamada a una función. También es posible para las interfaces tener constantes. Ver la documentación de interfaces para ejemplos. A partir de PHP 5.3.0, es posible hacer referencia a una clase utilizando una variable. El valor de la variable no puede ser una palabra clave (por ej., self, parenty static). Ejemplo #1 Definición y uso de una constante <?php class MyClass { const constant = 'valor constante'; function showConstant() { echo self::constant . "\n"; } } echo MyClass::constant . "\n"; $classname = "MyClass"; echo $classname::constant . "\n"; // A partir de PHP 5.3.0 $class = new MyClass(); $class->showConstant(); echo $class::constant."\n"; // A partir de PHP 5.3.0 ?> Ejemplo #2 Ejemplo de datos estáticos <?php class foo { // A partir de PHP 5.3.0 const bar = <<<'EOT' bar EOT; } ?> A diferencia de heredocs, nowdocs puede ser utilizado en cualquier contexto de datos estáticos. Nota: El soporte nowdoc fue agregado en PHP 5.3.0. Autocarga de clases Muchos desarrolladores que escriben aplicaciones orientadas a objetos crean un fichero fuente PHP para cada definición de clase. Una de las mayores molestias es tener que hacer una larga lista de includes al comienzo de cada script (uno por cada clase). En PHP 5 esto ya no es necesario. Se puede definir una función __autoload() que es automáticamente invocada en caso de que se esté intentando utilizar una clase/interfaz que todavía no haya sido definida. Al invocar a esta función el motor de scripting da una última oportunidad para cargar la clase antes que PHP falle con un error. Sugerencia spl_autoload_register() proporciona una alternativa más flexible para la carga automática de clases. Por esta razón, el uso de__autoload() no se recomienda y puede quedar obsoleta o ser eliminada en el futuro. Nota: Antes de 5.3.0, las excepciones lanzadas en la función __autoload no podían ser capturadas en el bloque catch y resultaba en un error fatal. Desde 5.3.0+ las excepciones lanzadas en la función __autoload pueden ser capturadas en el bloque catch, con una consideración. Si se lanza una excepción personalizada, la clase de dicha excepción debe estar disponible. La función __autoload puede usarse recursivamente para autocargar la clase de la excepción personalizada. Nota: La autocarga no está disponible si se utiliza PHP en el modo interactivo CLI. Nota: Si el nombre de la clase se utiliza, por ejemplo, en call_user_func(), puede contener algunos caracteres peligrosos tales como ../. Se recomienda no utilizar la entrada del usuario en tales funciones, o al menos verificar dicha entrada en __autoload(). Ejemplo #1 Ejemplo de autocarga Este ejemplo intenta cargar las clases MiClase1 y MiClase2 desde los ficheros MiClase1.php y MiClase2.php respectivamente. <?php function __autoload($nombre_clase) { include $nombre_clase . '.php'; } $obj = new MiClase1(); $obj2 = new MiClase2(); ?> Ejemplo #2 Otro ejemplo de autocarga Este ejemplo intenta cargar la interfaz ITest. <?php function __autoload($nombre) { var_dump($nombre); } class Foo implements ITest { } /* string(5) "ITest" Fatal error: Interface 'ITest' not found in ... */ ?> Ejemplo #3 Autocarga con manejo de excepciones para 5.3.0+ Este ejemplo lanza una excepción y demuestra los bloques try/catch. <?php function __autoload($nombre) { echo "Intentando cargar $nombre.\n"; throw new Exception("Imposible cargar $nombre."); } try { $obj = new ClaseNoCargable(); } catch (Exception $e) { echo $e->getMessage(), "\n"; } ?> El resultado del ejemplo sería: Intentando cargar ClaseNoCargable. Imposible cargar ClaseNoCargable. Ejemplo #4 Autocarga con manejo de excepciones para 5.3.0+ - Excepción personalizada ausente Este ejemplo lanza una excepción para una excepción personalizada no cargable. <?php function __autoload($nombre) { echo "Intentando cargar $nombre.\n"; throw new ExcepciónAusente("Imposible cargar $nombre."); } try { $obj = new ClaseNoCargable(); } catch (Exception $e) { echo $e->getMessage(), "\n"; } ?> El resultado del ejemplo sería: Intentando cargar ClaseNoCargable. Intentando cargar ExcepciónAusente. Fatal error: Class 'ExcepciónAusente' not found in testExcepcionAusente.php on line 4 Ver también unserialize() unserialize_callback_func spl_autoload() spl_autoload_register() Constructores y destructores Constructor void __construct ([ mixed $args [, $... ]] ) PHP 5 permite a los desarrolladores declarar métodos constructores para las clases. Aquellas que tengan un método constructor lo invocarán en cada nuevo objeto creado, lo que lo hace idóneo para cualquier inicialización que el objeto pueda necesitar antes de ser usado. Nota: Constructores parent no son llamados implícitamente si la clase child define un constructor. Para ejecutar un constructor parent, se requiere invocar a parent::__construct() desde el constructor child. Ejemplo #1 Utilización de nuevos constructores unificados <?php class BaseClass { function __construct() { print "En el constructor de BaseClass\n"; } } class SubClass extends BaseClass { function __construct() { parent::__construct(); print "En el constructor de SubClass\n"; } } $obj = new BaseClass(); $obj = new SubClass(); ?> Por motivos de compatibilidad, si PHP 5 no puede encontrar una función __construct() para una determinada clase, buscará el viejo estilo de la función constructora, mediante el nombre de la clase. Efectivamente, esto significa que en el único caso en el que se tendría compatibilidad es si la clase tiene un método llamado __construct() que fuese utilizado para diferentes propósitos. A diferencia con otros métodos, PHP no generará un mensaje de error a nivel de E_STRICT cuando __construct() es sobrescrito con diferentes parámetros que los métodos padre __construct() tienen. A partir de PHP 5.3.3, los métodos con el mismo nombre que el último elemento de una clase en un nombre de espacios no serán más tratados como un constructor. Este cambio no afecta a clases sin espacio de nombres. Ejemplo #2 Constructores en clases pertenecientes a un nombre de espacios <?php namespace Foo; class Bar { public function Bar() { // Tratado como constructor en PHP 5.3.0 - 5.3.2 // Tratado como método regular a partir de PHP 5.3.3 } } ?> Destructor void __destruct ( void ) PHP 5 introduce un concepto de destructor similar al de otros lenguajes orientados a objetos, tal como C++. El método destructor será llamado tan pronto como no hayan otras referencias a un objeto determinado, o en cualquier otra circunstancia de finalización. Ejemplo #3 Ejemplo de Destructor <?php class MyDestructableClass { function __construct() { print "En el constructor\n"; $this->name = "MyDestructableClass"; } function __destruct() { print "Destruyendo " . $this->name . "\n"; } } $obj = new MyDestructableClass(); ?> Como los constructores, los destructores padre no serán llamados implícitamente por el motor. Para ejecutar un destructor padre, se deberá llamar explícitamente a parent::__destruct() en el interior del destructor. El destructor será invocado aún si la ejecución del script es detenida usando exit(). Llamar a exit() en un destructor evitará que se ejecuten las rutinas restantes de finalización. Nota: Los destructores invocados durante la finalización del script tienen los headers HTTP ya enviados. El directorio de trabajo en la fase de finalización del script puede ser diferente con algunos SAPIs (por ej., Apache). Nota: Intentar lanzar una excepción desde un destructor (invocado en la finalización del script) causa un error fatal. Visibilidad La visibilidad de una propiedad o método se puede definir anteponiendo una de las palabras claves public, protected o private en la declaración. Miembros de clases declarados como public pueden ser accedidos de cualquier lado. Miembros declarados como protected, sólo de la clase misma, por herencia y clases parent. Aquellos definidos como private, únicamentede la clase que los definió. Visibilidad de Propiedades Las propiedades de clases deben ser definidas como public, private, o protected. Si se declaran usando var, serán definidas como public. Ejemplo #1 Declaración de propiedades <?php /** * Definición de MyClass */ class MyClass { public $public = 'Public'; protected $protected = 'Protected'; private $private = 'Private'; function { echo echo echo } printHello() $this->public; $this->protected; $this->private; } $obj = new MyClass(); echo $obj->public; // Funciona echo $obj->protected; // Error Fatal echo $obj->private; // Error Fatal $obj->printHello(); // Muestra Public, Protected y Private /** * Definición de MyClass2 */ class MyClass2 extends MyClass { // Se puede redeclarar los métodos public y protected, pero no el private protected $protected = 'Protected2'; function { echo echo echo } printHello() $this->public; $this->protected; $this->private; } $obj2 = new MyClass2(); echo $obj2->public; // Funciona echo $obj2->private; // Undefined echo $obj2->protected; // Error Fatal $obj2->printHello(); // Muestra Public, Protected2, Undefined ?> Nota: La forma de declaración de una variable de PHP 4 con la palabra clave var todavía es soportado (como un sinónimo de public) por razones de compatibilidad. En PHP 5 antes de 5.1.3, su uso genera un Warning E_STRICT. Visibilidad de Métodos Los métodos de clases pueden ser definidos como public, private, o protected. Aquellos declarados sin ninguna palabra clave de visibilidad explícita serán definidos como public. Ejemplo #2 Declaración de métodos <?php /** * Definición de MyClass */ class MyClass { // Declaración de un constructor public public function __construct() { } // Declaración de un método public public function MyPublic() { } // Declaración de un método protected protected function MyProtected() { } // Declaración de un método private private function MyPrivate() { } // Esto es public function Foo() { $this->MyPublic(); $this->MyProtected(); $this->MyPrivate(); } } $myclass = new MyClass; $myclass->MyPublic(); // Funciona $myclass->MyProtected(); // Error Fatal $myclass->MyPrivate(); // Error Fatal $myclass->Foo(); // Public, Protected y Private funcionan /** * Definición de MyClass2 */ class MyClass2 extends MyClass { // Esto es public function Foo2() { $this->MyPublic(); $this->MyProtected(); $this->MyPrivate(); // Error Fatal } } $myclass2 = new MyClass2; $myclass2->MyPublic(); // Funciona $myclass2->Foo2(); // Public y Protected funcionan, pero Private no class Bar { public function test() { $this->testPrivate(); $this->testPublic(); } public function testPublic() { echo "Bar::testPublic\n"; } private function testPrivate() { echo "Bar::testPrivate\n"; } } class Foo extends Bar { public function testPublic() { echo "Foo::testPublic\n"; } private function testPrivate() { echo "Foo::testPrivate\n"; } } $myFoo = new foo(); $myFoo->test(); // Bar::testPrivate // Foo::testPublic ?> Visibilidad desde otros objetos Los objetos del mismo tipo tendrán acceso a los miembros private y protected entre ellos aunque no sean de la misma instancia. Esto es porque los detalles específicos de implementación ya se conocen cuando se encuentra dentro de estos objetos. Ejemplo #3 Accediendo a miembros private del mismo tipo de objeto <?php class Test { private $foo; public function __construct($foo) { $this->foo = $foo; } private function bar() { echo 'Método private accedido.'; } public function baz(Test $other) { // Se puede cambiar la propiedad private: $other->foo = 'hola'; var_dump($other->foo); // También se puede invocar al método private: $other->bar(); } } $test = new Test('test'); $test->baz(new Test('other')); ?> El resultado del ejemplo sería: string(5) "hola" Método private accedido. Herencia de Objetos La herencia es un principio de programación bien establecido y PHP hace uso de él en su modelado de objetos. Este principio afectará la manera en que muchas clases y objetos se relacionan unas con otras. Por ejemplo, cuando se extiende una clase, la subclase hereda todos los métodos públicos y protegidos de la clase padre. A menos que una clase sobrescriba esos métodos, mantendrán su funcionalidad original. Esto es útil para la definición y abstracción de la funcionalidad y permite la implementación de funcionalidad adicional en objetos similares sin la necesidad de reimplementar toda la funcionalidad compartida. Nota: A menos que la carga automática sea utilizada, entonces las clases deben ser definidas antes de ser usadas. Si una clase se extiende a otra, entonces la clase padre debe ser declarada antes de la estructura de clase hija. Esta regla se aplica a las clases que heredan de otras clases e interfaces. Ejemplo #1 Ejemplo de herencia <?php class foo { public function printItem($string) { echo 'Foo: ' . $string . PHP_EOL; } public function printPHP() { echo 'PHP is great.' . PHP_EOL; } } class bar extends foo { public function printItem($string) { echo 'Bar: ' . $string . PHP_EOL; } } $foo = new foo(); $bar = new bar(); $foo->printItem('baz'); $foo->printPHP(); $bar->printItem('baz'); $bar->printPHP(); // // // // Salida: Salida: Salida: Salida: 'Foo: baz' 'PHP is great' 'Bar: baz' 'PHP is great' ?> Operador de Resolución de Ámbito (::) El Operador de Resolución de Ámbito (también denominado Paamayim Nekudotayim) o en términos simples, el doble dos-puntos, es un token que permite acceder a elementos estáticos, constantes, y sobrescribir propiedades o métodos de una clase. Cuando se hace referencia a estos items desde el exterior de la definición de la clase, se utiliza el nombre de la clase. A partir de PHP 5.3.0, es posible hacer referencia a una clase usando una variable. El valor de la variable no puede ser una palabra clave (por ej., self, parent ystatic). Paamayim Nekudotayim podría, en un principio, parecer una extraña elección para bautizar a un doble dos-puntos. Sin embargo, mientras se escribía el Zend Engine 0.5 (que utilizó PHP 3), asi es como el equipo Zend decidió bautizarlo. En realidad, significa doble dos-puntos - en Hebreo! Ejemplo #1 :: desde el exterior de la definición de la clase <?php class MyClass { const CONST_VALUE = 'Un valor constante'; } $classname = 'MyClass'; echo $classname::CONST_VALUE; // A partir de PHP 5.3.0 echo MyClass::CONST_VALUE; ?> Las tres palabras claves especiales self, parent y static son utilizadas para acceder a propiedades y métodos desde el interior de la definición de la clase. Ejemplo #2 :: desde el interior de la definición de la clase <?php class OtherClass extends MyClass { public static $my_static = 'variable estática'; public static function doubleColon() { echo parent::CONST_VALUE . "\n"; echo self::$my_static . "\n"; } } $classname = 'OtherClass'; echo $classname::doubleColon(); // A partir de PHP 5.3.0 OtherClass::doubleColon(); ?> Cuando una clase extendida sobrescribe la definición parent de un método, PHP no invocará al método parent. Depende de la clase extendida el hecho de llamar o no al método parent. Esto también se aplica a definiciones de métodos Constructores y Destructores, Sobrecarga, y Mágicos. Ejemplo #3 Invocando a un método parent <?php class MyClass { protected function myFunc() { echo "MyClass::myFunc()\n"; } } class OtherClass extends MyClass { // Sobrescritura de definición parent public function myFunc() { // Pero todavía se puede llamar a la función parent parent::myFunc(); echo "OtherClass::myFunc()\n"; } } $class = new OtherClass(); $class->myFunc(); ?> Véase también algunos ejemplos de trucos de llamadas estáticas . La palabra clave 'static' Sugerencia Esta página describe el uso de la palabra clave static para definir métodos y propiedades estáticos. static también se puede usar paradefinir variables estáticas y para enlaces estáticos en tiempo de ejecución. Por favor, consulte estas páginas para más información sobre estos sifnificados de static. Declarar propiedades o métodos de clases como estáticos los hacen accesibles sin la necesidad de instanciar la clase. Una propiedad declarada como static no puede ser accedida con un objeto de clase instanciado (aunque un método estático sí lo puede hacer). Por motivos de compatibilidad con PHP 4, si no se utiliza ninguna declaración de visibilidad, se tratará a las propiedades o métodos como si hubiesen sido definidos como public. Debido a que los métodos estáticos se pueden invocar sin tener creada una instancia del objeto, la pseudo-variable $this no está disponible dentro de los métodos declarados como estáticos. Las propiedades estáticas no pueden ser accedidas a través del objeto utilizando el operador flecha (>). Invocar métodos no estáticos estáticamente genera una advertencia de nivel E_STRICT. Como cualquier otra variable estática de PHP, las propiedades estáticas sólo pueden ser inicializadas utilizando un string literal o una constante; las expresiones no están permitidas. Por tanto, se puede inicializar una propiedad estática con enteros o arrays (por ejemplo), pero no se puede hacer con otra variable, con el valor de devolución de una función, o con un objeto. A partir de PHP 5.3.0, es posible hacer referencia a una clase usando una variable. El valor de la variable no puede ser una palabra clave (p.ej., self, parent ystatic). Ejemplo #1 Ejemplo de propiedad estática <?php class Foo { public static $mi_static = 'foo'; public function valorStatic() { return self::$mi_static; } } class Bar extends Foo { public function fooStatic() { return parent::$mi_static; } } print Foo::$mi_static . "\n"; $foo = new Foo(); print $foo->valorStatic() . "\n"; print $foo->mi_static . "\n"; // "Propiedad" mi_static no definida print $foo::$mi_static . "\n"; $nombreClase = 'Foo'; print $nombreClase::$mi_static . "\n"; // A partir de PHP 5.3.0 print Bar::$mi_static . "\n"; $bar = new Bar(); print $bar->fooStatic() . "\n"; ?> Ejemplo #2 Ejemplo de método estático <?php class Foo { public static function unMétodoEstático() { // ... } } Foo::unMétodoEstático(); $nombreClase = 'Foo'; $nombreClase::unMétodoEstático(); // A partir de PHP 5.3.0 ?> Abstracción de clases PHP 5 introduce clases y métodos abstractos. Las clases definidas como abstract seguramente no son instanciadas y cualquier clase que contiene almenos un método abstracto debe ser definida como abstract. Los métodos definidos como abstractos simplemente declaran la estructura del método, pero no pueden definir la implementación. Cuando se hereda de una clase abstracta, todos los métodos definidos como abstract en la definición de la clase parent deben ser redefinidos en la clase child; adicionalmente, estos métodos deben ser definidos con la misma visibilidad (o con una menos restrictiva). Por ejemplo, si el método abstracto está definido como protected, la implementación de la función puede ser redefinida como protected o public, pero nunca como private. Por otra parte, las estructuras de los métodos tienen que coincidir, es decir, los (type hinting) tipos sugeridos y el número de argumentos requeridos deben ser los mismos. Esto también aplica a los constructores de PHP 5.4. Antes de PHP 5.4 las estructuras del constructor podían ser diferentes. Ejemplo #1 Ejemplo de clase abstracta <?php abstract class AbstractClass { // Forzando la extensión de clase para definir este método abstract protected function getValue(); abstract protected function prefixValue($prefix); // Método común public function printOut() { print $this->getValue() . "\n"; } } class ConcreteClass1 extends AbstractClass { protected function getValue() { return "ConcreteClass1"; } public function prefixValue($prefix) { return "{$prefix}ConcreteClass1"; } } class ConcreteClass2 extends AbstractClass { public function getValue() { return "ConcreteClass2"; } public function prefixValue($prefix) { return "{$prefix}ConcreteClass2"; } } $class1 = new ConcreteClass1; $class1->printOut(); echo $class1->prefixValue('FOO_') ."\n"; $class2 = new ConcreteClass2; $class2->printOut(); echo $class2->prefixValue('FOO_') ."\n"; ?> El resultado del ejemplo sería: ConcreteClass1 FOO_ConcreteClass1 ConcreteClass2 FOO_ConcreteClass2 Códigos antiguos que no tengan clases o funciones definidas por el usuario llamadas 'abstract' deberían ejecutarse sin modificaciones. Interfaces de objetos Las interfaces de objetos permiten crear código con el cual especificamos qué métodos deben ser implementados por una clase, sin tener que definir cómo estos métodos son manipulados. Las interfaces son definidas utilizando la palabra clave interface, de la misma forma que con clases estándar, pero sin métodos que tengan su contenido definido. Todos los métodos declarados en una interfaz deben ser public, ya que ésta es la naturaleza de una interfaz. implements Para implementar una interfaz, se utiliza el operador implements. Todos los métodos en una interfaz deben ser implementados dentro de la clase; el no cumplir con esta regla resultará en un error fatal. Las clases pueden implementar más de una interfaz si se deseara, separándolas cada una por una coma. Nota: Una clase no puede implementar dos interfaces que compartan nombres de funciones, puesto que esto causaría ambigüedad. Nota: Las interfaces se pueden extender al igual que las clases utilizando el operador extends. Nota: La clase que implemente una interfaz debe utilizar exactamente las mismas estructuras de métodos que fueron definidos en la interfaz. De no cumplir con esta regla, se generará un error fatal. Constantes Es posible tener constantes dentro de las interfaces. Las constantes de interfaces funcionan como las constantes de clases excepto porque no pueden ser sobrescritas por una clase/interfaz que las herede. Ejemplos Ejemplo #1 Ejemplo de interfaz <?php // Declarar la interfaz 'iTemplate' interface iTemplate { public function setVariable($name, $var); public function getHtml($template); } // Implementar la interfaz // Ésto funcionará class Template implements iTemplate { private $vars = array(); public function setVariable($name, $var) { $this->vars[$name] = $var; } public function getHtml($template) { foreach($this->vars as $name => $value) { $template = str_replace('{' . $name . '}', $value, $template); } return $template; } } // Ésto no funcionará // Error fatal: La Clase BadTemplate contiene un método abstracto // y por lo tanto debe declararse como abstracta (iTemplate::getHtml) class BadTemplate implements iTemplate { private $vars = array(); public function setVariable($name, $var) { $this->vars[$name] = $var; } } ?> Ejemplo #2 Interfaces extensibles <?php interface a { public function foo(); } interface b extends a { public function baz(Baz $baz); } // Ésto sí funcionará class c implements b { public function foo() { } public function baz(Baz $baz) { } } // Ésto no funcionará y resultará en un error fatal class d implements b { public function foo() { } public function baz(Foo $foo) { } } ?> Ejemplo #3 Herencia múltiple de interfaces <?php interface a { public function foo(); } interface b { public function bar(); } interface c extends a, b { public function baz(); } class d implements c { public function foo() { } public function bar() { } public function baz() { } } ?> Ejemplo #4 Interfaces con constantes <?php interface a { const b = 'Interface constant'; } // Imprime: Interface constant echo a::b; // Sin embargo ésto no funcionará ya que no está permitido // sobrescribir constantes class b implements a { const b = 'Class constant'; } ?> Una interfaz, junto con type-hinting, proveen una buena forma de asegurarse que determinado objeto contiene métodos particulares. Vea el operadorinstanceof y type hinting. Traits Desde PHP 5.4.0, PHP implementa una metodología de reutilización de código llamada Traits. Los traits (rasgos) son un mecanismo de reutilización de código en lenguajes de herencia simple, como PHP. El objetivo de un trait es el de reducir las limitaciones propias de la herencia simple permitiendo que los desarrolladores reutilicen a voluntad conjuntos de métodos sobre varias clases independientes y pertenecientes a clases jerárquicas distintas. La semántica a la hora combinar Traits y clases se define de tal manera que reduzca su complejidad y se eviten los problemas típicos asociados a la herencia múltiple y a los Mixins. Un Trait es similar a una clase, pero con el único objetivo de agrupar funcionalidades muy específicas y de una manera coherente. No se puede instanciar directamente un Trait. Es por tanto un añadido a la herencia tradicional, y habilita la composición horizontal de comportamientos; es decir, permite combinar miembros de clases sin tener que usar herencia. Ejemplo #1 Ejemplo de Trait <?php trait ezcReflectionReturnInfo { function getReturnType() { /*1*/ } function getReturnDescription() { /*2*/ } } class ezcReflectionMethod extends ReflectionMethod { use ezcReflectionReturnInfo; /* ... */ } class ezcReflectionFunction extends ReflectionFunction { use ezcReflectionReturnInfo; /* ... */ } ?> Precedencia Los miembros heredados de una clase base se sobrescriben cuando se inserta otro miembro homónimo desde un Trait. De acuerdo con el orden de precedencia, los miembros de la clase actual sobrescriben los métodos del Trait, que a su vez sobrescribe los métodos heredados. Ejemplo #2 Ejemplo de Orden de Precedencia Se sobrescribe un miembro de la clase base con el método insertado en MiHolaMundo a partir del Trait DecirMundo. El comportamiento es el mismo para los métodos definidos en la clase MiHolaMundo. Según el orden de precedencia los métodos de la clase actual sobrescriben los métodos del Trait, a la vez que el Trait sobrescribe los métodos de la clase base. <?php class Base { public function decirHola() { echo '¡Hola '; } } trait DecirMundo { public function decirHola() { parent::decirHola(); echo 'Mundo!'; } } class MiHolaMundo extends Base { use DecirMundo; } $o = new MiHolaMundo(); $o->decirHola(); ?> El resultado del ejemplo sería: ¡Hola Mundo! Ejemplo #3 Ejemplo de Orden de Precedencia #2 <?php trait HolaMundo { public function decirHola() { echo '¡Hola Mundo!'; } } class ElMundoNoEsSuficiente { use HolaMundo; public function decirHola() { echo '¡Hola Universo!'; } } $o = new ElMundoNoEsSuficiente(); $o->decirHola(); ?> El resultado del ejemplo sería: ¡Hola Universo! Multiples Traits Se pueden insertar múltiples Traits en una clase, mediante una lista separada por comas en la sentencia use. Ejemplo #4 Uso de Múltiples Traits <?php trait Hola { public function decirHola() { echo 'Hola '; } } trait Mundo { public function decirMundo() { echo 'Mundo'; } } class MiHolaMundo { use Hola, Mundo; public function decirAdmiración() { echo '!'; } } $o = new MiHolaMundo(); $o->decirHola(); $o->decirMundo(); $o->decirAdmiración(); ?> El resultado del ejemplo sería: Hola Mundo! Resolución de Conflictos Si dos Traits insertan un método con el mismo nombre, se produce un error fatal, siempre y cuando no se haya resuelto explicitamente el conflicto. Para resolver los conflictos de nombres entre Traits en una misma clase, se debe usar el operador insteadof para elejir unívocamente uno de los métodos conflictivos. Como esto sólo permite excluir métodos, se puede usar el operador as para permitir incluir uno de los métodos conflictivos bajo otro nombre. Ejemplo #5 Resolución de Conflictos En este ejemplo, Talker utiliza los traits A y B. Como A y B tienen métodos conflictos, se define el uso de la variante de smallTalk del trait B, y la variante de bigTalk del traita A. Aliased_Talker hace uso del operador as para poder usar la implementación de bigTalk de B, bajo el alias adicional talk. <?php trait A { public function smallTalk() { echo 'a'; } public function bigTalk() { echo 'A'; } } trait B { public function smallTalk() { echo 'b'; } public function bigTalk() { echo 'B'; } } class Talker { use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; } } class Aliased_Talker { use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; B::bigTalk as talk; } } ?> Modificando la Visibilidad de los Métodos Al usar el operador as, se puede también ajustar la visibilidad del método en la clase exhibida. Ejemplo #6 Modificar la Visibilidad de un Método <?php trait HolaMundo { public function decirHola() { echo 'Hola Mundo!'; } } // Cambiamos visibilidad de decirHola class MiClase1 { use HolaMundo { decirHola as protected } } // Método alias con visibilidad cambiada // La visibilidad de decirHola no cambia class MiClase2 { use HolaMundo { hacerHolaMundo as private decirHola } } ?> Traits Compuestos de Traits Al igual que las clases, los Traits también pueden hacer uso de otros Traits. Al usar uno o más traits en la definición de un trait, éste puede componerse parcial o completamente de miembros de finidos en esos otros traits. Ejemplo #7 Traits Compuestos de Traits <?php trait Hola { public function decirHola() { echo 'Hola '; } } trait Mundo { public function decirMundo() { echo 'Mundo!'; } } trait HolaMundo { use Hola, Mundo; } class MiHolaMundo { use HolaMundo; } $o = new MiHolaMundo(); $o->decirHola(); $o->decirMundo(); ?> El resultado del ejemplo sería: Hola Mundo! Miembros Abstractos de Traits Los traits soportan el uso de métodos abstractos para imponer requisitos a la clase a la que se exhiban. Ejemplo #8 Expresar Resquisitos con Métodos Abstractos <?php trait Hola { public function decirHolaMundo() { echo 'Hola'.$this->obtenerMundo(); } abstract public function obtenerMundo(); } class MiHolaMundo { private $mundo; use Hola; public function obtenerMundo() { return $this->mundo; } public function asignarMundo($val) { $this->mundo = $val; } } ?> Miembros Estáticos en Traits Los métodos de un trait pueden referenciar a variables estáticas, pero no puede ser definido en el trait. Los traits, sin embargo, pueden definir método estáticos a la clase a la que se exhiben. Ejemplo #9 Variables estáticas <?php trait Contador { public function inc() { static $c = 0; $c = $c + 1; echo "$c\n"; } } class C1 { use Contador; } class C2 { use Contador; } $o = new C1(); $o->inc(); // echo 1 $p = new C2(); $p->inc(); // echo 1 ?> Ejemplo #10 Métodos Estáticos <?php trait EjemploEstatico { public static function hacerAlgo() { return 'Hacer algo'; } } class Ejemplo { use EjemploEstatico; } Ejemplo::hacerAlgo(); ?> Propiedades Los traits también pueden definir propiedades. Ejemplo #11 Definir Propiedades <?php trait PropiedadesTrait { public $x = 1; } class EjemploPropiedades { use PropiedadesTrait; } $ejemplo = new EjemploPropiedades; $ejemplo->x; ?> Si un trait define una propiedad una clase no puede definir una propiedad con el mismo nombre, si no se emitirá un error.Éste de tipo E_STRICT si la definición de la clase es compatible (misma visibilidad y valor inicial) o de otro modo un error fatal. Ejemplo #12 Resolución de Conflictos <?php trait PropiedadesTrait { public $misma = true; public $diferente = false; } class EjemploPropiedades { use PropiedadesTrait; public $misma = true; // Estándares estrictos public $diferente = true; // Error fatal } ?> Sobrecarga La sobrecarga en PHP ofrece los medios para "crear" dinámicamente propiedades y métodos. Estas entidades dinámicas se procesan por los métodos mágicos que se pueden establecer en una clase para diversas acciones. Se invoca a los métodos de sobrecarga cuando se interactúa con propiedades o métodos que no se han declarado o que no son visibles en el ámbito activo. A lo largo de esta sección usaremos los términos "propiedades inaccesibles" y "métodos inaccesibles" para referirnos a esta combinación de declaración y visibilidad. Todos los métodos sobrecargados deben definirse como public. Nota: No se puede pasar ninguno de los parámetros de estos métodos mágicos por referencia. Nota: La interpretación de PHP de "overloading" es distinta de la mayoría de los lenguajes orientados a objetos. La sobrecarga tradicionalmente ofrece la capacidad de tener múltiples métodos con el mismo nombre, pero con un tipo o un número distinto de parámetros. Historial de cambios Versión Descripción 5.3.0 Se añade __callStatic. Se añadieron advertencias para hacer cumplir la visibilidad public e impedir la declaración static. 5.1.0 Añadidos los métodos __isset y __unset. Sobrecarga de propiedades public void __set ( string $name , mixed $value ) public mixed __get ( string $name ) public bool __isset ( string $name ) public void __unset ( string $name ) __set() se ejecuta al escribir datos sobre propiedades inaccesibles. __get() se utiliza para consultar datos a partir de propiedades inaccesibles. __isset() se lanza al llamar a isset() o a empty() sobre propiedades inaccesibles. __unset() se invoca cuando se usa unset() sobre propiedades inaccesibles. El parámetro $name es el nombre de la propiedad con la que se está interactuando. En el método __set() el parámetro $value especifica el valor que se debe asignar a la propiedad $name. La sobrecarga de propiedades sólo funciona en contextos de objetos. Estos métodos mágicos no se lanzarán en contextos estáticos. Por esa razón, no se deben declarar como estáticos. Desde PHP 5.3.0, se emite un aviso si alguno de los métodos de sobrecarga es declarado como static. Nota: Debido a la forma en que PHP procesa el operador de asignación, el valor que devuelve __set() se ignora. Del mismo modo, nunca se llama a __get() al encadenar asignaciones como ésta: $a = $obj->b = 8; Nota: No se puede utilizar una propiedad sobrecargada en ninguna construcción del lenguaje que no sea isset(). Esto significa que si se invoca a empty() en una propiedad sobrecargada, no se llamará al método de sobrecarga. Para solventar esta limitación, se debe copiar la propiedad sobrecargada en una variable de ámbito local que empty() pueda manejar. Ejemplo #1 Sobrecarga de propiedades mediante los métodos __get(), __set(), __isset() y __unset() <?php class PropertyTest { /** Localización de los datos sobrecargados. private $data = array(); */ /** La sobrecarga no se usa en propiedades declaradas. public $declared = 1; */ /** La sobre carga sólo funciona aquí al acceder desde fuera de la clase. private $hidden = 2; public function __set($name, $value) { echo "Estableciendo '$name' a '$value'\n"; $this->data[$name] = $value; } public function __get($name) { echo "Consultando '$name'\n"; if (array_key_exists($name, $this->data)) { return $this->data[$name]; } $trace = debug_backtrace(); */ trigger_error( 'Propiedad indefinida mediante __get(): ' . $name . ' en ' . $trace[0]['file'] . ' en la línea ' . $trace[0]['line'], E_USER_NOTICE); return null; } /** Desde PHP 5.1.0 */ public function __isset($name) { echo "¿Está definido '$name'?\n"; return isset($this->data[$name]); } /** Desde PHP 5.1.0 */ public function __unset($name) { echo "Eliminando '$name'\n"; unset($this->data[$name]); } /** No es un método mágico, esta aquí para completar el ejemplo. public function getHidden() { return $this->hidden; } */ } echo "<pre>\n"; $obj = new PropertyTest; $obj->a = 1; echo $obj->a . "\n\n"; var_dump(isset($obj->a)); unset($obj->a); var_dump(isset($obj->a)); echo "\n"; echo $obj->declared . "\n\n"; echo echo echo echo echo ?> "Vamos a probar con la propiedad privada que se llama 'hidden':\n"; "Las propiedades privadas pueden consultarse en la clase, por lo que no se usa __get()...\n"; $obj->getHidden() . "\n"; "Las propiedades privadas no son visibles fuera de la clase, por lo que se usa __get()...\n"; $obj->hidden . "\n"; El resultado del ejemplo sería: Estableciendo 'a' a '1' Consultando 'a' 1 ¿Está definido 'a'? bool(true) Eliminando 'a' ¿Está definido 'a'? bool(false) 1 Vamos a probar con la propiedad privada que se llama 'hidden': Las propiedades privadas pueden consultarse en la clase, por lo que no se usa __get()... 2 Las propiedades privadas no son visibles fuera de la clase, por lo que se usa __get()... Consultando 'hidden' Notice: Propiedad indefinida mediante __get(): hidden en <file> en la línea 69 in <file>en la línea 28 Sobrecarga de métodos public mixed __call ( string $name , array $arguments ) public static mixed __callStatic ( string $name , array $arguments ) __call() es lanzado al invocar un método inaccesible en un contexto de objeto. __callStatic() es lanzado al invocar un método inaccesible en un contexto estático. El parámetro $name corresponde al nombre del método al que se está llamando. El parámetro $arguments es un array enumerado que contiene los parámetros que se han pasado al método $name. Ejemplo #2 Sobrecarga de métodos mediante los métodos __call() and __callStatic() <?php class MethodTest { public function __call($name, $arguments) { // Nota: el valor $name es sensible a mayúsculas. echo "Llamando al método de objeto '$name' " . implode(', ', $arguments). "\n"; } /** Desde PHP 5.3.0 */ public static function __callStatic($name, $arguments) { // Nota: el valor $name es sensible a mayúsculas. echo "Llamando al método estático '$name' " . implode(', ', $arguments). "\n"; } } $obj = new MethodTest; $obj->runTest('en contexto de objeto'); MethodTest::runTest('en contexto estático'); ?> // Desde PHP 5.3.0 El resultado del ejemplo sería: Llamando al método de objeto 'runTest' en contexto de objeto Llamando al método estático 'runTest' en contexto estático Iteración de objetos PHP 5 ofrece una manera para que los objetos sean definidos por lo que es posible iterar a través de una lista de elementos, con, por ejemplo, una sentenciaforeach. Por defecto, todas las propiedades visibles serán utilizados para la iteración. Ejemplo #1 Iteración simple de objeto <?php class MyClass { public $var1 = 'value 1'; public $var2 = 'value 2'; public $var3 = 'value 3'; protected $protected = 'protected var'; private $private = 'private var'; function iterateVisible() { echo "MyClass::iterateVisible:\n"; foreach($this as $key => $value) { print "$key => $value\n"; } } } $class = new MyClass(); foreach($class as $key => $value) { print "$key => $value\n"; } echo "\n"; $class->iterateVisible(); ?> El resultado del ejemplo sería: var1 => value 1 var2 => value 2 var3 => value 3 MyClass::iterateVisible: var1 => value 1 var2 => value 2 var3 => value 3 protected => protected var private => private var Como se muestra en la salida, el foreach itera a través de todas las propiedades visibles que se puedan acceder. Para dar un paso más, se puede implementar la interfaz Iterator. Esto permite al objeto decidir cómo será iterado y qué valores estarán disponibles en cada iteración. Ejemplo #2 Iteración de objeto implementando Iterator <?php class MyIterator implements Iterator { private $var = array(); public function __construct($array) { if (is_array($array)) { $this->var = $array; } } public function rewind() { echo "rewinding\n"; reset($this->var); } public function current() { $var = current($this->var); echo "current: $var\n"; return $var; } public function key() { $var = key($this->var); echo "key: $var\n"; return $var; } public function next() { $var = next($this->var); echo "next: $var\n"; return $var; } public function valid() { $key = key($this->var); $var = ($key !== NULL && $key !== FALSE); echo "valid: $var\n"; return $var; } } $values = array(1,2,3); $it = new MyIterator($values); foreach ($it as $a => $b) { print "$a: $b\n"; } ?> El resultado del ejemplo sería: rewinding valid: 1 current: 1 key: 0 0: 1 next: 2 valid: 1 current: 2 key: 1 1: 2 next: 3 valid: 1 current: 3 key: 2 2: 3 next: valid: La interface IteratorAggregate se puede usar como alternativa para implementar todos los métodos de Iterator. IteratorAggregate solamente requiere la implementación de un único método, IteratorAggregate::getIterator(), el cual debería devolver una instancia de una clase que implemente Iterator. Ejemplo #3 Iteración de objeto implementando IteratorAggregate <?php class MyCollection implements IteratorAggregate { private $items = array(); private $count = 0; // Se requiere la definición de la interfaz IteratorAggregate public function getIterator() { return new MyIterator($this->items); } public function add($value) { $this->items[$this->count++] = $value; } } $coll = new MyCollection(); $coll->add('value 1'); $coll->add('value 2'); $coll->add('value 3'); foreach ($coll as $key => $val) { echo "key/value: [$key -> $val]\n\n"; } ?> El resultado del ejemplo sería: rewinding current: value 1 valid: 1 current: value 1 key: 0 key/value: [0 -> value 1] next: value 2 current: value 2 valid: 1 current: value 2 key: 1 key/value: [1 -> value 2] next: value 3 current: value 3 valid: 1 current: value 3 key: 2 key/value: [2 -> value 3] next: current: valid: Nota: Para más ejemplos de iteradores, vea la extensión SPL. Métodos mágicos Los nombres de método __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), _ _sleep(), __wakeup(), __toString(),__invoke(), __set_state() y __clone() son mágicos en las clases PHP. No se puede tener métodos con estos nombres en cualquiera de las clases a menos que desee la funcionalidad mágica asociada a estos. Precaución PHP se reserva todos los nombres de los métodos que comienzan con __ como mágicos. Se recomienda que no utilice los nombres de métodos con __ en PHP a menos que desee alguna funcionalidad mágica documentada. __sleep() y __wakeup() public array __sleep ( void ) void __wakeup ( void ) serialize() comprueba si la clase tiene un método con el nombre mágico __sleep(). Si es así, el método se ejecuta antes de cualquier serialización. Se puede limpiar el objeto y se supone que devuelve un array con los nombres de todas las variables de el objeto que se va a serializar. Si el método no devuelve nada, entonces NULL es serializado y un error E_NOTICE es emitido. Nota: No es posible para __sleep() devolver nombres de propiedades privadas en las clases padres. Hacer esto resultaría un nivel de error E_NOTICE. En su lugar, puede utilizar la interfaz Serializable. El uso para el que está destinado __sleep() consiste en confirmar datos pendientes o realizar tareas similares de limpieza. Además, el método es útil si tiene objetos muy grandes que no necesitan guardarse por completo. Por el contrario, unserialize() comprueba la presencia de un método con el nombre mágico __wakeup(). Si está presente, este método puede reconstruir cualquier recurso que el objeto pueda tener. El uso para el que está destinado __wakeup() es restablecer las conexiones de base de datos que se puedan haber perdido durante la serialización y realizar otras tareas de reinicialización. Ejemplo #1 Sleep y wakeup <?php class Connection { protected $link; private $server, $username, $password, $db; public function __construct($server, $username, $password, $db) { $this->server = $server; $this->username = $username; $this->password = $password; $this->db = $db; $this->connect(); } private function connect() { $this->link = mysql_connect($this->server, $this->username, $this->password); mysql_select_db($this->db, $this->link); } public function __sleep() { return array('server', 'username', 'password', 'db'); } public function __wakeup() { $this->connect(); } } ?> __toString() public string __toString ( void ) El método __toString() permite a una clase decidir cómo comportarse cuando se le trata como un string. Por ejemplo, lo que echo $obj; mostraría. Este método debe devolver un string, si no se emitirá un nivel de error fatal E_RECOVERABLE_ERROR. Ejemplo #2 Ejemplo simple <?php // Declarar una clase simple class TestClass { public $foo; public function __construct($foo) { $this->foo = $foo; } public function __toString() { return $this->foo; } } $class = new TestClass('Hola Mundo'); echo $class; ?> El resultado del ejemplo sería: Hola Mundo Antes de PHP 5.2.0 el método __toString() se llama sólo cuando se combina directamente con echo o print. Desde PHP 5.2.0, se le llama en cualquier contexto de string (e.j. en printf() con el modificador %s) pero no en el contexto de otros tipos (e.j. con el modificador %d). Desde PHP 5.2.0, la conversión de los objetos sin el método __toString() a string podría causar E_RECOVERABLE_ERROR. __invoke() mixed __invoke ([ $... ] ) El método __invoke() es llamado cuando un script intenta llamar a un objeto como si fuera una función. Nota: Esta característica está disponible desde PHP 5.3.0. Ejemplo #3 Uso de __invoke() <?php class CallableClass { public function __invoke($x) { var_dump($x); } } $obj = new CallableClass; $obj(5); var_dump(is_callable($obj)); ?> El resultado del ejemplo sería: int(5) bool(true) __set_state() static object __set_state ( array $properties ) Este método static es llamado para las clases exportadas por var_export(), desde PHP 5.1.0. El único parámetro de este método es un array que contiene las propiedades exportadas en la forma array('property' => value, ...). Ejemplo #4 Uso de __set_state() (desde PHP 5.1.0) <?php class A { public $var1; public $var2; public static function __set_state($an_array) // A partir de PHP 5.1.0 { $obj = new A; $obj->var1 = $an_array['var1']; $obj->var2 = $an_array['var2']; return $obj; } } $a = new A; $a->var1 = 5; $a->var2 = 'foo'; eval('$b = ' . var_export($a, true) . ';'); // $b = A::__set_state(array( // 'var1' => 5, // 'var2' => 'foo', // )); var_dump($b); ?> El resultado del ejemplo sería: object(A)#2 (2) { ["var1"]=> int(5) ["var2"]=> string(3) "foo" } Palabra clave Final PHP 5 introduce la nueva palabra clave final, que impide que las clases hijas sobrescriban un método, antecediendo su definición con la palabra final. Si la propia clase se define como final, entonces no se podrá heredar de ella. Ejemplo #1 Ejemplo de métodos Final <?php class BaseClass { public function test() { echo "llamada a BaseClass::test()\n"; } final public function moreTesting() { echo "llamada a BaseClass::moreTesting()\n"; } } class ChildClass extends BaseClass { public function moreTesting() { echo "llamada a ChildClass::moreTesting()\n"; } } // Devuelve un error Fatal: Cannot override final method BaseClass::moreTesting() ?> Ejemplo #2 Ejemplo de clase Final <?php final class BaseClass { public function test() { echo "llamada a BaseClass::test()\n"; } // Aquí no importa si definimos una función como final o no final public function moreTesting() { echo "llamada a BaseClass::moreTesting()\n"; } } class ChildClass extends BaseClass { } // Devuelve un error Fatal: Class ChildClass may not inherit from final class (BaseClass) ?> Nota: Las propiedades no pueden declararse como final. Sólo pueden las clases y los métodos. Clonación de Objetos No siempre se desea crear una copia de un objeto replicando todas sus propiedades completamente. Un buen ejemplo que ilustra la necesidad de contar con un constructor de copias, sería si tuviéramos un objeto que represente una ventana en GTK y el objeto almacene los recursos de esta ventana GTK, de forma que cuando creas un duplicado el comportamiento esperado sería una nueva ventana con las mismas propiedades, y que el nuevo objeto referencie a los recursos de la nueva ventana. Otro ejemplo es si un objeto hace referencia a otro objeto necesario, de forma que cuando se realiza una réplica del objeto principal, se espera que se cree una nueva instancia de este otro objeto, de forma que la réplica tenga su propia copia Para crear una copia de un objeto se utiliza la palabra clave clone (que invoca, si fuera posible, al método __clone() del objeto). No se puede llamar al método__clone() de un objeto directamente. $copia_de_objeto = clone $objeto; Cuando se clona un objeto, PHP5 llevará a cabo una copia superficial de las propiedades del objeto. Las propiedades que sean referencias a otras variables, mantendrán las referencias. void __clone ( void ) Una vez que la clonación ha finalizado, se llamará al método __clone() del nuevo objeto (si el método __clone() estuviera definido), para permitirle realizar los cambios necesarios sobre sus propiedades. Ejemplo #1 Clonación de un objeto <?php class SubObject { static $instances = 0; public $instance; public function __construct() { $this->instance = ++self::$instances; } public function __clone() { $this->instance = ++self::$instances; } } class MyCloneable { public $object1; public $object2; function __clone() { // Forzamos la copia de this->object, si no // hará referencia al mismo objeto. $this->object1 = clone $this->object1; } } $obj = new MyCloneable(); $obj->object1 = new SubObject(); $obj->object2 = new SubObject(); $obj2 = clone $obj; print("Objeto Original:\n"); print_r($obj); print("Objeto Clonado:\n"); print_r($obj2); ?> El resultado del ejemplo sería: Objeto Original: MyCloneable Object ( [object1] => SubObject Object ( [instance] => 1 ) [object2] => SubObject Object ( [instance] => 2 ) ) Objeto Clonado: MyCloneable Object ( [object1] => SubObject Object ( [instance] => 3 ) [object2] => SubObject Object ( [instance] => 2 ) ) Comparación de Objetos En PHP 5, la comparación de objetos es más complicada que en PHP 4, y en mayor concordancia con lo que cabe esperar de un Lenguaje Orientado a Objetos (tenga en cuenta que PHP lo es). Al utilizar el operador de comparación (==), se comparan de una forma sencilla las variables de cada objeto, es decir: Dos instancias de un objeto son iguales si tienen los mismos atributos y valores, y son instancias de la misma clase. Por otra parte, cuando se utiliza el operador identidad (===), las variables de un objeto son idénticas sí y sólo sí hacen referencia a la misma instancia de la misma clase. Un ejemplo aclarará estas reglas. Ejemplo #1 Ejemplo de comparación de objetos en PHP 5 <?php function bool2str($bool) { if ($bool === false) { return 'FALSO'; } else { return 'VERDADERO'; } } function { echo echo echo echo } compararObjetos(&$o1, &$o2) 'o1 'o1 'o1 'o1 == o2 : ' . bool2str($o1 == $o2) . != o2 : ' . bool2str($o1 != $o2) . === o2 : ' . bool2str($o1 === $o2) !== o2 : ' . bool2str($o1 !== $o2) class Bandera { public $bandera; function Bandera($bandera = true) { $this->bandera = $bandera; } } class OtraBandera { public $bandera; function OtraBandera($bandera = true) { $this->bandera = $bandera; } } $o $p $q $r = = = = new Bandera(); new Bandera(); $o; new OtraBandera(); echo "Dos instancias de la misma clase\n"; compararObjetos($o, $p); echo "\nDos referencias a la misma instancia\n"; compararObjetos($o, $q); echo "\nInstancias de dos clases diferentes\n"; "\n"; "\n"; . "\n"; . "\n"; compararObjetos($o, $r); ?> El resultado del ejemplo sería: Dos instancias de la misma clase o1 == o2 : VERDADERO o1 != o2 : FALSO o1 === o2 : FALSO o1 !== o2 : VERDADERO Dos referencias a la misma instancia o1 == o2 : VERDADERO o1 != o2 : FALSO o1 === o2 : VERDADERO o1 !== o2 : FALSO Instancias de dos clases diferentes o1 == o2 : FALSO o1 != o2 : VERDADERO o1 === o2 : FALSO o1 !== o2 : VERDADERO Nota: Las extensiones pueden definir sus propias reglas para la comparación de sus objetos. Implicación de Tipos Desde PHP 5 se incorpora la implicación de tipos. Ahora, las funciones pueden obligar a que sus parámetros sean objetos (especificando el nombre de la clase en el prototipo de la función), interfaces, arrays (desde PHP 5.1) o tipos callable (despe PHP 5.4). Sin embargo, si se usa NULL como el valor predeterminado del parámetro, será permitido como un argumento para llamada posterior. Si se especifica una clase o una interfaz como tipo implicado también se permitirán todos sus hijos o implementaciones too. La implicación de tipos no puede usarse con tipos escalares como int o string. Tampoco están permitidos los Traits. Ejemplo #1 Ejemplos de Implicación de Tipos <?php // Una clase de ejemplo class MiClase { /** * Una función de prueba * * El primer parámetro debe ser un objeto del tipo OtraClase */ public function prueba(OtraClase $otraclase) { echo $otraclase->var; } /** * Otra función de prueba * * El primer parámetro debe ser un array */ public function prueba_array(array $array_entrada) { print_r($array_entrada); } /** * El primer parámetro debe ser un iterador */ public function prueba_interface(Traversable $iterador) { echo get_class($iterador); } /** * El primer parámetro debe ser de tipo callable */ public function prueba_callable(callable $callback, $data) { call_user_func($callback, $data); } } // Otra clase de ejemplo class OtraClase { public $var = 'Hola Mundo'; } ?> Si no se satisface el type hint, se produce un error fatal capturable <?php // Una instancia de cada clase $miclase = new MiClase; $otraclase = new OtraClase; // Error Fatal: El argumento 1 debe ser un objeto de la clase OtraClase $miclase->prueba('hola'); // Error Fatal: El argumento 1 debe ser una instancia de OtraClase $foo = new stdClass; $miclase->prueba($foo); // Error fatal: El argumento 1 no puede ser null $miclase->prueba(null); // Funciona: Imprime en pantalla Hola Mundo $miclase->prueba($otraclase); // Error Fatal: El argumento 1 debe ser un array $miclase->prueba('una cadena'); // Funciona: Imprime en pantalla el array $miclase->prueba(array('a', 'b', 'c')); // Funciona: Imprime en pantalla ArrayObject $miclase->prueba_interface(new ArrayObject(array())); // Funciona: Imprime en pantalla int(1) $miclase->prueba_callable('var_dump', 1); ?> Los tipos sugeridos tambíen se pueden usar con funciones: <?php // Una clase de ejemplo class MiClase { public $var = 'Hola Mundo'; } /** * Una función de prueba * * El primer parámetro debe ser un objeto del tipo MiClase */ function miFunción (MiClase $foo) { echo $foo->var; } // Funciona $miclase = new MiClase; miFunción($miclase); ?> Tipo implicado que acepta valores de tipo NULL: <?php /* Se acepta el valor NULL */ function prueba(stdClass $obj = NULL) { } prueba(NULL); prueba(new stdClass); ?> Enlace estático en tiempo de ejecución Desde PHP 5.3.0, PHP incorpora una nueva funcionalidad llamada enlace estático en tiempo de ejecución que permite hacer referencias a la clase en uso dentro de un contexto de herencia estática. De forma más precisa, un enlace estático en tiempo de ejecución para funcionar almacena el nombre de clase de la última llamada que no tenga "propagación". En el caso de las llamadas a métodos estáticos, se trata de la clase a la que se llamó explícitamente (normalmente, la que precede al operador ::); en los casos de llamadas a métodos que no son estáticos, se resolvería a la clase del objeto. Una "llamada con propagación" es una llamada estática que está precedida porself::, parent::, static::, o, si seguimos la jerarquía de clases, forward_static_call(). La función get_called_class() puede utilizarse para obtener un string con el nombre de la clase que realiza la llamada, y static:: revela cuál es su alcance. Se le ha llamado "enlace estático en tiempo de ejecución" teniendo en cuenta un punto de vista interno. "Enlace en tiempo de ejecución" viene del hecho de questatic:: ya resuelve a la clase en la que se definió el método, sino que en su lugar se resolverá utilizando información en tiempo de ejecución debido a que se puede utilizar (entre otras cosas) para las llamadas de métodos estáticos, se le llamó también "enlace estático". Limitaciones de self:: Las referencias estáticas que hay dentro de la clase en uso, como self:: o __CLASS__, se resuelven empleando el nombre de la clase a la que pertenece la función: Ejemplo #1 Uso de self:: <?php class A { public static function who() { echo __CLASS__; } public static function test() { self::who(); } } class B extends A { public static function who() { echo __CLASS__; } } B::test(); ?> El resultado del ejemplo sería: A Uso de Enlace Estático en Tiempo de ejecución Los enlaces estáticos en tiempo de ejecución tratan de resolver estas limitaciones empleando una palabra clave que haga referencia a la clase que realizó la llamada en tiempo de ejecución. Es decir, una palabra clave que en el ejemplo anterior permita hacer referencia desde test() a B. Se decidió no crear una nueva palabra clave, por lo que en su lugar se ha utilizado la palabra reservada static. Ejemplo #2 Uso básico de static:: <?php class A { public static function who() { echo __CLASS__; } public static function test() { static::who(); // He aquí el enlace estático en tiempo de ejecución } } class B extends A { public static function who() { echo __CLASS__; } } B::test(); ?> El resultado del ejemplo sería: B Nota: En contextos no estáticos, la clase que realiza la llamada será la clase del objeto instanciado. Dado que $this-> tratará de invocar métodos privados en su mismo ámbito, el uso de static:: puede provocar diferents resultados. Otra diferencia es que static:: sólo puede hacer referencia a propiedades estáticas. Ejemplo #3 Uso de static:: en un contexto no estático <?php class A { private function foo() { echo "exito!\n"; } public function test() { $this->foo(); static::foo(); } } class B extends A { /* foo() se copiará en B, por lo tanto su ámbito seguirá siendo A * y la llamada tendrá éxito */ } class C extends A { private function foo() { /* se reemplaza el método original; el ámbito del nuevo es ahora C */ } } $b = new B(); $b->test(); $c = new C(); $c->test(); //falla ?> El resultado del ejemplo sería: exito! exito! exito! Fatal error: Call to private method C::foo() from context 'A' in /tmp/test.php on line 9 Nota: En una llamada que se resuelva como estática, la resolución de enlaces estáticos en tiempo de ejecución se dentendrá sin propagarse. Por otra parte, las llamadas estáticas que utilicen palabras clave como parent:: o self:: sí propagarán la información. Ejemplo #4 Llamadas que propagan y que no propagan <?php class A { public static function foo() { static::who(); } public static function who() { echo __CLASS__."\n"; } } class B extends A { public static function test() { A::foo(); parent::foo(); self::foo(); } public static function who() { echo __CLASS__."\n"; } } class C extends B { public static function who() { echo __CLASS__."\n"; } } C::test(); ?> El resultado del ejemplo sería: A C C Objetos y referencias Uno de los puntos clave de la POO de PHP 5 que a menudo se menciona es que "por omisión los objetos se pasan por referencia". Esto no es completamente cierto. Esta sección rectifica esa creencia general, usando algunos ejemplos. Una referencia en PHP es un alias, que permite a dos variables diferentes escribir sobre un mismo valor. Desde PHP 5, una variable de tipo objeto ya no contiene el objeto en sí como valor. Únicamente contiene un identificador del objeto que le permite localizar al objeto real. Cuando se pasa un objeto como parámetro, o se devuelve como retorno, o se asigna a otra variable, las distintas variables no son alias: guardan una copia del identificador, que apunta al mismo objeto. Ejemplo #1 Referencias y Objetos <?php class A { public $foo = 1; } $a = new A; $b = $a; // $a y $b son copias del mismo identificador // ($a) = ($b) = <id> $b->foo = 2; echo $a->foo."\n"; $c = new A; $d = &$c; // $c y $d son referencias // ($c,$d) = <id> $d->foo = 2; echo $c->foo."\n"; $e = new A; function foo($obj) { // ($obj) = ($e) = <id> $obj->foo = 2; } foo($e); echo $e->foo."\n"; ?> El resultado del ejemplo sería: 2 2 2 Serialización de objetos serialización de objetos - objetos en sesiones La función serialize() devuelve un string que contiene un flujo de bytes que representa cualquier valor que se pueda almacenar en PHP. Por otra parte,unserialize() puede restaurar los valores originales a partir de dicho string. Al utilizar serialize para guardar un objeto, almacenará todas las variables de dicho objeto. En cambio los métodos no se guardarán, sólo el nombre de la clase. Para poder deserializar ( unserialize()) un objeto, debe estar definida la clase de ese objeto. Es decir, si se tiene un objeto de la clase A, y lo serializamos, se obtendrá un string que haga referencia a la clase A y contenga todas las variables que haya en esta clase. Si se desea deseralizar en otro fichero, antes debe estar presente la definición de la clase A. Esto se puede hacer, por ejemplo, escribiendo la definición de la clase A en un fichero, para después o bien incluirlo, o bien hacer uso de la función spl_autoload_register(). <?php // classa.inc: class A { public $one = 1; public function show_one() { echo $this->one; } } // page1.php: include("classa.inc"); $a = new A; $s = serialize($a); // almacenamos $s en algún lugar en el que page2.php puede encontrarlo. file_put_contents('store', $s); // page2.php: // se necesita para que unserialize funcione correctamente. include("classa.inc"); $s = file_get_contents('store'); $a = unserialize($s); // now use the function show_one() of the $a object. $a->show_one(); ?> Si una aplicación está usando sesiones, y utiliza session_register() para registrar objetos, estos objetos se serializarán automáticamente al final de cada página PHP, y se deserializan también automáticamente en cada una de las siguientes peticiones. Esto significa que, una vez que formen parte de la sesión, estos objetos se podrán utilizar en cualquier página de la aplicación. Sin embargo, la función session_register(): ha sido borrada a partir de PHP 5.4.0 Si una aplicación serializa objetos para su uso posterior, se recomienda encarecidamente que se incluya la definición de la clase en toda la aplicación. Si no se hiciera, se deserializaría el objeto sin una definición de clase, lo cual daría como resultado que PHP definiera al objeto con la clase__PHP_Incomplete_Class_Name, que no tiene métodos, haciendo que el objeto no fuera útil. Por tanto, si en el ejemplo anterior $a se guardara en una sesión mediante session_register("a"), sería necesario incluir el fichero classa.inc en todas las páginas, no sólo en page1.php y page2.php. Registro de cambios de POO Aquí se registran los cambios del modelo de POO de PHP 5. Las descripciones y otras notas respecto a estas nuevas funcionalidades están documentadas dentro de la documentación de POO 5. Versión Descripción 5.4.0 Cambiado: Si una clase abstracta define una firma para el constructor ahora se hará cumplir. 5.3.3 Cambiado: Los métodos con el mismo nombre que el último elemento de un nombre de clase perteneciente a un espacio de nombres ya no serán tratado como un constructor. Este cambio no afecta a las clases que no pertenecen a un espacio de nombres. 5.3.0 Cambiado: Ya no es necesario que los valores por omisión de los métodos de una clase que implemente un interfaz coincidan con los valores por omisión de los prototipos de la interfaz. 5.3.0 Cambiado: Ahora es posible hacer referencia a la clase utilizando una variable (p.ej., echo $nombreclase::constante;). La variable no puede contener como valor una palabra clave (p.ej., self, parent o static). 5.3.0 Cambiado: Se emite un error de nivel E_WARNING si al sobrecargar un método mágico se le declara como estático. Además, hace necesario que tengan visibilidad pública. 5.3.0 Cambiado: Antes de 5.3.0, las excepciones lanzadas en la función __autoload() no podían capturarse en el bloque catch, y provocaban un error fatal. Ahora, las excepciones lanzadas dentro de la función __autoload pueden capturarse en el bloque catch, con una única salvedad. Si se lanza una excepción definida por el usuario, entonces esta excepción debería estar disponible. Se puede utilizar recursivamente la función __autoload para cargar automáticamente la clase de la excepción definida por el usuario. 5.3.0 Añadido: El método __callStatic. 5.3.0 Añadido: Soporte heredoc y nowdoc para constantes de clase y definición de propiedades. Nota: los valores heredoc deben seguir las mismas reglas que las cadenas de dobles comillas (p.ej., no contener variables). 5.3.0 Añadido: Enlaces estáticos en tiempo de ejecución. 5.3.0 Añadido: El método __invoke(). 5.2.0 Cambiado: Al método __toString() sólo se le invocaba cuando se le combinaba con echo o con print. Pero ahora, se le invoca en cualquier contexto de strings (p.ej, en printf() con el modificador %s) pero no en contextos de otro tipo (p.ej. con el modificador %d). Desde PHP 5.2.0, convertir objetos a string sin el método __toString emitirá un error de Versión Descripción nivel E_RECOVERABLE_ERROR. 5.1.3 Cambiado: En versiones anteriores de PHP 5, se consideraba obsoleto el uso de var y emitía un error de nivel E_STRICT. Ya no está obsoleto, y por tanto no emite el error. 5.1.0 Cambiado: Ahora se invoca al método estático __set_state() en las clases exportadas por var_export(). 5.1.0 Añadido: Los métodos __isset() y __unset().