Clases y Objetos (PHP 4)class
Una clase es una colección de variables y funciones que trabajan con
estas variables. Las variables se definen utilizando var y
las funciones utilizando function. Una clase se define empleando la
siguiente sintáxis:
items[$artnr] += $num;
}
// Sacar $num artículos de $artnr fuera del carrito
function remove_item($artnr, $num) {
if ($this->items[$artnr] > $num) {
$this->items[$artnr] -= $num;
return true;
} elseif ($this->items[$artnr] == $num) {
unset($this->items[$artnr]);
return true;
} else {
return false;
}
}
}
?>
]]>
Esto define una clase llamada Cart que consiste de una matriz asociativa
de artículos en el carrito y dos funciones para agregar y quitar
elementos de este carrito.
NO se puede separar una definición de clase en
múltiples ficheros. Tampoco es posible separar una
definición de clase en múltiples bloques PHP, a menos que la división sea dentro
de una declaración de método. Lo siguiente no funcionará:
]]>
Sin embargo, lo siguiente es permitido:
]]>
Las siguientes notas de precaución son válidas para PHP 4.
El nombre stdClass es usado internamente por
Zend y está reservado. No es posible tener una clase llamada
stdClass en PHP.
Los nombres de función __sleep y
__wakeup son mágicos en las clases de PHP. No
se puede tener funciones con estos nombres en ninguna
clase a menos que se desee la funcionalidad mágica asociada
con ellas. Ver abajo para más información.
PHP reserva todos los nombres de función que empiezan con __ como mágicos.
Se recomienda que no se utilicen nombres de función con
__ en PHP a menos que se quiera alguna funcionalidad mágica que esté documentada.
En PHP 4, para variables var solamente se permiten inicializadores constantes.
Para inicializar variables con valores no constantes,
se requiere una función de inicialización que es invocada automáticamente
cuando un objeto se está construyendo a partir de la clase.
Tal función es llamada un constructor (ver más abajo).
todays_date = date("Y-m-d");
$this->name = $GLOBALS['firstname'];
/* etc. . . */
}
}
?>
]]>
Las clases son tipos, es decir, son planos para las variables
de verdad. Se tiene que crear una variable del tipo deseado con el
operador new.
add_item("10", 1);
$another_cart = new Cart;
$another_cart->add_item("0815", 3);
?>
]]>
Esto crea los objetos $cart y
$another_cart, ambos de la clase Cart. La función
add_item() del objeto $cart es invocada para agregar 1
elemento del artículo 10 a $cart. 3 elementos del
artículo número 0815 son agregados a $another_cart.
Ambos, $cart y $another_cart, tienen
las funciones add_item(), remove_item()
y una variable items. Estas son funciones y variables distintas. Se puede
pensar en los objetos como algo semejante a directorios en un sistema de ficheros.
En un sistema de ficheros se pueden tener dos diferentes ficheros llamados
README.TXT, siempre y cuando éstos, estén en directorios distintos.
Tal como con los directorios donde se tiene que teclear la trayectoria de nombre completa para
poder alcanzar cada fichero desde el directorio de nivel máximo, se tiene que especificar
el nombre completo de la función que se desea invocar: en términos de PHP, el directorio de
nivel máximo sería el espacio de nombres global, y el separador en las trayectorias de nombre
sería ->. Así, los nombres
$cart->items y
$another_cart->items nombran a dos variables distintas.
Nótese que la variable se llama $cart->items, y no
$cart->$items, es decir, un nombre de variable en PHP tiene
únicamente un sólo signo de dólar ($).
items = array("10" => 1);
// inválido, porque $cart->$items se vuelve $cart->""
$cart->$items = array("10" => 1);
// correcto, pero podría ser o no lo que se pretendía:
// $cart->$myvar se vuelve $cart->items
$myvar = 'items';
$cart->$myvar = array("10" => 1);
?>
]]>
Dentro de una definición de clase, no se conoce bajo que nombre el objeto será
accesible dentro del programa: al momento en que la clase Cart fue
escrita, se desconocía si el objeto sería nombrado
$cart, $another_cart, o algo
distinto posteriormente. Siendo así,
no se puede escribir $cart->items dentro de la clase Cart
propiamente. En cambio, para ser capáz de acceder a sus propias funciones y
variables desde el interior de una clase, es posible utilizar la pseudovariable
$this que puede ser leída como 'lo mío' u 'objeto
actual'. Así, '$this->items[$artnr] +=
$num' puede leerse como 'agregar $num al
contador $artnr de mi propia matriz de artículos' o 'agregar
$num al contador $artnr de la matriz de
artículos dentro del objeto actual'.
La pseudovariable $this normalmente no está definida si
el método en el que se encuentra alojada es invocado de manera estática. Pero,
ésta, no es una regla estricta: $this está definida si un método es
invocado estáticamente desde el interior de otro objeto. En este caso, el valor de
$this será aquel del objeto que realiza la invocación. Esto se
ilustra en el siguiente ejemplo:
foo();
A::foo();
$b = new B();
$b->bar();
B::bar();
?>
]]>
&example.outputs;
Existen algunas funciones apropiadas para manejar clases y objetos. Quizas se quiera
echar un vistazo al las Funciones
Clase/Objeto.
extends
Frecuentemente son necesarias clases con variables y funciones semejantes
a los de otra clase ya existente. De hecho, es una buena práctica
definir una clase genérica que pueda ser utilizada en todos sus
proyectos y adaptar esta clase a las necesidades de cada uno de sus
proyectos específicos. Para facilitar esto, las clases pueden ser
extensiones de otras clases. La clase extendida o derivada
tiene todas las variables y funciones de la clase base (esto es
llamado 'herencia' a pesar del hecho de que nadie ha muerto) y aquello que
se agregue en la definición extendida. No es posible
disminuir una clase, es decir, remover la definición de cualquier función o
variable existente. Una clase extendida siempre es dependiente de
una sóla clase base, es decir, la herencia múltiple no
está soportada. Las clases son extendidas utilizando la palabra clave 'extends'.
owner = $name;
}
}
?>
]]>
Esto define una clase llamada Named_Cart que contiene todas las variables y funciones de
Cart más una variable adicional $owner y una
función adicional set_owner(). Se crea un carrito con nombre de la manera habitual y
ahora se puede indicar y obtener el dueño del carrito. Aún se pueden utilizar las funciones de
carrito normal en los carritos con nombre:
set_owner("kris"); // Nombrar el carrito
print $ncart->owner; // imprimir el nombre del dueño del carrito
$ncart->add_item("10", 1); // (funcionalidad heredada de cart)
?>
]]>
Esto también es llamado una relación "padre-hijo". Se crea una clase,
el padre, y se utiliza extends para crear una nueva clase
basada en la clase padre: la clase hijo. Es posible
incluso utilizar esta nueva clase hijo y crear otra clase basada en esta clase
hijo.
¡Las clases deben de estar definidas antes de ser utilizadas! Si se desea que la clase
Named_Cart extienda a la clase
Cart, se tendrá que definir primero la clase
Cart. Si se quiere crear otra clase llamada
Yellow_named_cart basada en la clase
Named_Cart se tiene que definir primero
Named_Cart. Para resumir: el orden en que las
clases se definen es importante.
Constructores
Los constructores son funciones en una clase que son invocadas
automáticamente cuando se crea una nueva instancia de una clase con
new. Una función se vuelve un constructor, cuando
tiene el mismo nombre que la clase. Si una clase
no tiene constructor, el constructor de la clase base será
invocado, si es que existe.
add_item("10", 1);
}
}
?>
]]>
Esto define una clase Auto_Cart que es una Cart más un constructor
que inicializa el carrito con un elemento del artículo número "10"
en cada ocasión que un nuevo Auto_Cart es creado con "new".
Los constructores pueden llevar argumentos y estos argumentos pueden ser opcionales, lo que
los hace mucho más útiles. Para poder seguir utilizando la clase
sin parámetros, todos los parámetros para los constructores deben hacerse
opcionales proporcionando los valores por omisión.
add_item ($item, $num);
}
}
// Comprar las mismas cosas aburridas de siempre.
$default_cart = new Constructor_Cart;
// Comprar en serio...
$different_cart = new Constructor_Cart("20", 17);
?>
]]>
También se puede utilizar el operador @ para
callar los errores que ocurren en el constructor, para ejemplo
@new.
\n";
}
function B()
{
echo "I am a regular function named B in class A. \n";
echo "I am not a constructor in A. \n";
}
}
class B extends A
{
}
// Esto invocará a B() como un constructor
$b = new B;
?>
]]>
La función B() en la clase A repentinamente se volverá un
constructor en la clase B, aunque nunca fue la intención que lo fuera.
A PHP 4 no le importa si la función ha sido definida
en la clase B, o si ha sido heredada.
PHP no llama automáticamente a los constructores de la clase base
desde un constructor de una clase derivada. Es su responsabilidad
propagar la llamada a los constructores en la jerarquía superior
cuando sea apropiado.
Los destructores son funciones que son invocadas automáticamente
cuando un objeto es destruido, ya sea con unset
o simplemente al salir del alcance. No existen destructores
en PHP. En cambio, se puede utilizar register_shutdown_function
para simular la mayoría de los efectos de los destructores.
Operador de Resolución de Alcance (::)
Lo siguiente es válido para PHP 4 y posteriores únicamente.
Algunas veces es útil hacer referencia a funciones y variables
en las clases base o hacer referencia a funciones que se encuentran en clases que
aún no tienen ninguna instancia. El operador ::
es utilizado para realizar esto.
\n";
}
}
class B extends A {
function example() {
echo "I am the redefined function B::example(). \n";
A::example();
}
}
// no hay objetos de la clase A.
// esto imprimirá
// I am the original function A::example().
A::example();
// crear un objeto de clase B.
$b = new B;
// esto imprimirá
// I am the redefined function B::example().
// I am the original function A::example().
$b->example();
?>
]]>
El ejemplo de arriba invoca a la función example() en
la clase A, pero no hay un objeto de la clase A, así que
no es posible escribir $a->example() o algo semejante. En vez de ello
se invoca example() como una 'función de clase', es decir, como una
función de la clase misma, no de un objeto de esa
clase.
Existen funciones de clase, pero no existen variables de clase.
De hecho, no existe objeto alguno en el momento de la invocación.
Siendo así, una función de clase no puede utilizar ninguna variable de objeto (pero
puede utilizar variables locales y globales), y no puede utilizar
$this para nada.
En el ejemplo anterior, la clase B vuelve a definir la función
example(). La definición original en la clase A queda desplazada
y ya no se encuentra disponible, a menos que se haga referencia específicamente
a la implementación de example() en la clase A empleando el
operador ::. Se escribe A::example() para realizar esto (de hecho, se
debe escribir parent::example(), tal como se muestra en la siguiente
sección).
En este contexto, existe un objeto actual y puede tener variables de
objeto. Así, cuando se usen desde el INTERIOR de una función de objeto, se puede utilizar
$this y las variables de objeto.
parent
Se puede encontrar que se escribe código que hace referencia a
variables y funciones en las clases base. Esto es particularmente
cierto si la clase derivada es un refinamiento
o especialización de código en la clase base.
En vez de utilizar el nombre de la clase base en el
código, debe utilizarse el nombre especial
parent, que hace referencia al nombre de la
clase base tal como se especifica en la declaración extends
de la clase. Al hacer esto, se evita utilizar el
nombre de la clase base en más de una ubicación. Si el
árbol de herencia llegase a cambiar durante la implementación, el
cambio puede llevarse a cabo con facilidad simplemente cambiando la
declaración extends de la clase.
\n";
}
}
class B extends A {
function example() {
echo "I am B::example() and provide additional functionality. \n";
parent::example();
}
}
$b = new B;
// Este ejemplo invocará a B::example(), que a su vez invocará a A::example().
$b->example();
?>
]]>
Serialización de objetos - objetos en las sesionesserialize devuelve un string conteniendo una
representación de flujo de bytes de cualquier valor que pueda ser almacenado en
PHP. unserialize puede utilizar este string para
recrear los valores originales de la variable. Al utilizar serialize para
guardar un objeto, se guardarán todas las variables en el objeto. Las
las funciones en un objeto no serán guardadas, solamente el nombre de
la clase.
Para poder utilizar unserialize en un objeto, la
clase de este objeto debe estar definida. Es decir, si se tiene un objeto
$a de la clase A en page1.php y se serializa, se
obtiene un string que hace referencia a la clase A y que contiene todos los valores de las variables
contenidas en $a. Si se desea ser capáz de revertir la serialización
de esto en page2.php, recreando $a de la clase A, la
definición de la clase A debe estar presente en page2.php.
Esto puede hacerse por ejemplo al colocar la definición de clase de la clase A
en un fichero de include e incluyendo este fichero en ambas páginas
page1.php y en page2.php.
one;
}
}
// page1.php:
include("classa.inc");
$a = new A;
$s = serialize($a);
// almacenar $s en algún lado donde page2.php pueda encontrarle.
$fp = fopen("store", "w");
fwrite($fp, $s);
fclose($fp);
// page2.php:
// esto es necesario para que unserialize funcione apropiadamente.
include("classa.inc");
$s = implode("", @file("store"));
$a = unserialize($s);
// ahora utilizar la función show_one() del objeto $a.
$a->show_one();
?>
]]>
Si se están utilizando sesiones y se emplea session_register
para registrar objetos, estos objetos son serializados automáticamente
al final de cada página de PHP, y la serialización les es revertida automáticamente en
en cada una de las páginas siguientes. Esto significa básicamente que estos objetos
pueden aparecer en cualquiera de las páginas una vez que se vuelven parte de la
sesión.
Se recomienda enfáticamente que se incluyan las definiciones
de clase de semejantes objetos en todas las
páginas, aun si no se usan estas clases en todas las
páginas. Si no se hace y a un objeto se le revierte la
serialización sin que su definición de clase esté presente,
perderá su asociacón de clase y se volverá un objeto de la clase
__PHP_Incomplete_Class_Name sin ninguna función disponible
por completo, es decir, se volverá bastante inútil.
Así que si en el ejemplo anterior $a se volviese parte de una sesión
al ejecutar session_register("a"), se debería incluir el
fichero classa.inc en todas las páginas, no sólo en page1.php
y en page2.php.
Las funciones mágicas __sleep y __wakeupserialize revisa si la clase tiene una función con
el nombre mágico __sleep. Si es así, esa función es
ejecutada antes de cualquier serialización. Puede limpiar el objeto
y se supone que devuelva una matriz con los nombres de todas las variables
de ese objeto que deberán ser serializadas.
Si el método no devuelve nada, entonces &null; es serializado y
E_NOTICE es emitida.
La intención de utilizar __sleep es asentar datos
pendientes o realizar tareas similares de limpieza. También la función es
útil si se tienen objetos muy grandes que no necesitan ser
guardados completamente.
De manera correspondiente, unserialize revisa la
presencia de una función con el nombre mágico de
__wakeup. Si está presente, esta función puede
reconstruir cualesquiera recursos que el objeto pueda tener.
El propósito de utilizar __wakeup es
reestablecer cualquier conexión a bases de datos que se pudiese haber perdido
durante la serialización y realizar otras tareas de
reinicialización.
Referencias dentro del constructor
Crear referencias dentro del cosntructor puede llevar a resultados
confusos. Esta sección a manera de guía ayuda a evitar problemas.
setName($name);
// y mostrarlo
$this->echoName();
}
function echoName() {
echo " ", $this->name;
}
function setName($name) {
$this->name = $name;
}
}
?>
]]>
Revisar si hay alguna diferencia entre
$bar1 que ha sido creada usando el
operador de copiado = y
$bar2 que ha sido creada utilzando el
operador de referencia =&...
echoName();
$globalref[0]->echoName();
/* salida:
set in constructor
set in constructor
set in constructor */
$bar2 =& new Foo('set in constructor');
$bar2->echoName();
$globalref[1]->echoName();
/* salida:
set in constructor
set in constructor
set in constructor */
?>
]]>
Aparentemente no hay diferencia, pero de hecho existe una
muy significativa: $bar1 y
$globalref[0] _NO_ están referenciadas, NO
son la misma variable. Esto es porque "new" no
devuelve una referencia por omisión, en vez de ello devuelve una copia.
No hay pérdida de desempeño (ya que PHP 4 y superiores utilizan conteo
de referencias) al devolver copias en vez de referencias. Al contrario
la mayor parte del tiempo es mejor simplemente trabajar con copias
en vez de referencias, porque crear referencias toma algo de
tiempo mientras que crear copias virtualmente no toma tiempo (a menos que una
de ellas sea un objeto o matriz grande y una de ellas es cambiada
y la(las) otra(s) subsecuentemente también, entonces sería sabio
usar referencias para cambiarlas a todas concurrentemente).
Para probar lo que esta escrito aquí arriba observar el código siguiente.
setName('set from outside');
// como se mencionó antes este no es el caso.
$bar1->echoName();
$globalref[0]->echoName();
/* salida:
set from outside
set in constructor */
// veamos que es diferente con $bar2 y $globalref[1]
$bar2->setName('set from outside');
// afortunadamente no sólo son iguales, son la misma variable
// así $bar2->name y $globalref[1]->name son las mismas también
$bar2->echoName();
$globalref[1]->echoName();
/* salida:
set from outside
set from outside */
?>
]]>
Otro ejemplo final, intente comprenderlo.
value = $i;
// intente entender por qué no es necesaria una referencia aquí
$this->b = new B($this);
}
function createRef() {
$this->c = new B($this);
}
function echoValue() {
echo " ","class ",get_class($this),': ',$this->value;
}
}
class B {
function B(&$a) {
$this->a = &$a;
}
function echoValue() {
echo " ","class ",get_class($this),': ',$this->a->value;
}
}
// intente entender por qué usar una copia simple aquí terminaría
// en un resultado no deseado en la línea marcada con *
$a =& new A(10);
$a->createRef();
$a->echoValue();
$a->b->echoValue();
$a->c->echoValue();
$a->value = 11;
$a->echoValue();
$a->b->echoValue(); // *
$a->c->echoValue();
?>
]]>
&example.outputs;
Comparando objects
En PHP 4, los objetos se comparan de una manera muy sencilla, entiéndase: Dos instancias
de objeto son iguales si tienen los mismos atributos y valores, y son
instancias de la misma clase. Reglas semejantes se aplican cuando se comparan dos
objetos utilizando el operador de identidad (===).
Si se fuese a ejecutar el código en el ejemplo siguiente:
Ejemplo de comparación de objetos en PHP 4
flag = $flag;
}
}
class SwitchableFlag extends Flag {
function turnOn() {
$this->flag = true;
}
function turnOff() {
$this->flag = false;
}
}
$o = new Flag();
$p = new Flag(false);
$q = new Flag();
$r = new SwitchableFlag();
echo "Compare instances created with the same parameters\n";
compareObjects($o, $q);
echo "\nCompare instances created with different parameters\n";
compareObjects($o, $p);
echo "\nCompare an instance of a parent class with one from a subclass\n";
compareObjects($o, $r);
?>
]]>
&example.outputs;
El cual es el resultado que se esperaría obtener dadas las reglas de comparación
anteriores. Unicamente instancias con los mismos valores para sus atributos
y de la misma clase son consideradas como iguales e idénticas.
Aun en casos en donde se tiene composición de objetos, se aplican las mismas reglas
de comparación. En el ejemplo siguiente se crea una clase contenedora que almacena
una matriz asociativa de objetos Flag.
Comparaciones de objetos compuestos en PHP 4
set = $flagArr;
}
function addFlag($name, $flag) {
$this->set[$name] = $flag;
}
function removeFlag($name) {
if (array_key_exists($name, $this->set)) {
unset($this->set[$name]);
}
}
}
$u = new FlagSet();
$u->addFlag('flag1', $o);
$u->addFlag('flag2', $p);
$v = new FlagSet(array('flag1'=>$q, 'flag2'=>$p));
$w = new FlagSet(array('flag1'=>$q));
echo "\nComposite objects u(o,p) and v(q,p)\n";
compareObjects($u, $v);
echo "\nu(o,p) and w(q)\n";
compareObjects($u, $w);
?>
]]>
&example.outputs;