PHP Orientado a Objetos for Beginners III

Balu1 Agosto 2010 - 7:54am 9 comentarios
Enviar por Email Imprimir

PHP Orientado a Objetos for Beginners III

Tercera y última parte del genial artículo PHP Orientado a Objetos for Beginners. En este artículo vamos a conocer sobre la visibilidad de los métodos y propiedades, cómo utilizar DocBlocks para documentar nuestros scripts y vamos a comparar la programación orientada a objetos con la procedimental, todo con ejemplos sencillos y fáciles de entender...

Importante: Si no tienes claro los conceptos de OOP con PHP, dirígete a la Parte 1 de este artículo. Allí comentamos los conceptos generales y hacemos una introducción a la Programación Orientada a Objetos con PHP. Para conocer más sobre Constructores y destructores, el uso de los métodos mágicos y las herencias, puedes acudir a la Parte 2 de esta serie.

Asignando la visibilidad de propiedades y métodos

Para un mayor control de nuestros objetos, métodos y propiedades hay que definir su visibilidad. Esto controla como y desde las propiedades y métodos pueden ser accedidas. Hay tres palabras claves sobre visibilidad: public, protected y private. Además de su visibilidad, un método o una propiedad puede declararse como estática, lo que nos permite acceder a ellas sin necesidad de instanciar la clase.

Nota: La visibilidad es una característica a partir de PHP 5. Puedes obtener más información sobre la compatibilidad de OOP con PHP4, leyendo esta página del manual de PHP.

Propiedades y métodos públicos

Todos los métodos y propiedades que hemos utilizado hasta ahora, han sido públicos. Esto significa que pueden accederse a ellos, desde cualquier lugar, tanto dentro como fuera de la clase.

Propiedades y métodos protegidos

Cuando una propiedad o un método se declara como protegido, sólo se puede acceder a él dentro de la misma clase o en clases descendientes (clases que extienden a la clase que contiene el método protegido).

Declara al método getProperty() en MyClass como protegido y trata de acceder a él desde fuera de la clase:

<?php
class MyClass
{
    public
$prop1 = "¡Soy la propiedad de una clase!";

    public function
__construct()
    {
        echo
'¡La clase "', __CLASS__, '" fue iniciada!<br />';
    }

    public function
__destruct()
    {
        echo
'¡La clase "', __CLASS__, '" fue destruida!<br />';
    }

    public function
__toString()
    {
        echo
"Usando el método toString: ";
        return
$this->getProperty();
    }

    public function
setProperty($newval)
    {
       
$this->prop1 = $newval;
    }

    protected function
getProperty()
    {
        return
$this->prop1 . "<br />";
    }
}

class
MyOtherClass extends MyClass
{
    public function
__construct()
    {
       
parent::__construct();
        echo
"Un nuevo constructor en " . __CLASS__ . ".<br />";
    }

    public function
newMethod()
    {
        echo
"Desde un nuevo método en " . __CLASS__ . ".<br />";
    }
}

// Crea un nuevo objeto
$newobj = new MyOtherClass;

// Falla al usar el método protegido
echo $newobj->getProperty();
?>

Al intentar ejecutar este script, nos aparecerá el siguiente error:

¡La clase "MyClass" fue iniciada!
Un nuevo constructor en MyOtherClass.

Fatal error: Call to protected method MyClass::getProperty() from context '' in /Applications/XAMPP/xamppfiles/htdocs/testing/test.php on line 55

Ahora, crea un nuevo método en MyOtherClass para llamar al método getProperty():

<?php
class MyClass
{
    public
$prop1 = "¡Soy la propiedad de una clase!";

    public function
__construct()
    {
        echo
'¡La clase "', __CLASS__, '" fue iniciada!<br />';
    }

    public function
__destruct()
    {
        echo
'¡La clase "', __CLASS__, '" fue destruida!<br />';
    }

    public function
__toString()
    {
        echo
"Usando el método toString: ";
        return
$this->getProperty();
    }

    public function
setProperty($newval)
    {
       
$this->prop1 = $newval;
    }

    protected function
getProperty()
    {
        return
$this->prop1 . "<br />";
    }
}

class
MyOtherClass extends MyClass
{
    public function
__construct()
    {
       
parent::__construct();
        echo
"Un nuevo constructor en " . __CLASS__ . ".<br />";
    }

    public function
newMethod()
    {
        echo
"Desde un nuevo método en " . __CLASS__ . ".<br />";
    }

    public function
callProtected()
    {
        return
$this->getProperty();
    }
}

// Crea un nuevo objeto
$newobj = new MyOtherClass;

// Usa el método protegido desde un método público
echo $newobj->callProtected();
?>

Esto generará el siguiente resultado:

¡La clase "MyClass" fue iniciada!
Un nuevo constructor en MyOtherClass.
¡Soy la propiedad de una clase!
¡La clase "MyClass" fue destruida!

Propiedades y métodos privados

Una propiedad o método que es declarado como privado sólo es accesible desde dentro de la clase que lo define. Esto significa que incluso si una nueva clase extiende la clase que define la propiedad o método privado, este no estará disponible dentro de la clase hija.

Para demostrarlo, declara getProperty() como privado en MyClass, y trata de llamar a callProtected() desde MyOtherClass:

<?php
class MyClass
{
    public
$prop1 = "¡Soy la propiedad de una clase!";

    public function
__construct()
    {
        echo
'¡La clase "', __CLASS__, '" fue iniciada!<br />';
    }

    public function
__destruct()
    {
        echo
'¡La clase "', __CLASS__, '" fue destruida!<br />';
    }

    public function
__toString()
    {
        echo
"Usando el método toString: ";
        return
$this->getProperty();
    }

    public function
setProperty($newval)
    {
       
$this->prop1 = $newval;
    }

    private function
getProperty()
    {
        return
$this->prop1 . "<br />";
    }
}

class
MyOtherClass extends MyClass
{
    public function
__construct()
    {
       
parent::__construct();
        echo
"Un nuevo constructor en " . __CLASS__ . ".<br />";
    }

    public function
newMethod()
    {
        echo
"Desde un nuevo método en " . __CLASS__ . ".<br />";
    }

    public function
callProtected()
    {
        return
$this->getProperty();
    }
}

// Crea un nuevo objeto
$newobj = new MyOtherClass;

// Usa el método protegido desde un método público
echo $newobj->callProtected();
?>

Ahora actualiza tu navegador y aparecerá el siguiente error:

¡La clase "MyClass" fue iniciada!
Un nuevo constructor en MyOtherClass.

Fatal error: Call to private method MyClass::getProperty() from context 'MyOtherClass' in /Applications/XAMPP/xamppfiles/htdocs/testing/test.php on line 49

Propiedades y métodos estáticos

Un método o propiedad declarado como estático puede ser accesible sin crear instancias de la clase; simplemente basta con proporcionar el nombre de la clase, el operador de resolución de alcance y el nombre de la propiedad o método.

Una de las principales ventajas de utilizar propiedades estáticas es que mantienen sus valores almacenados durante la ejecución de todo el script.

Para demostrar esto, agrega una propiedad estática llamada $count y un método estático llamado plusOne() a MyClass. Luego, configura un bucle do… while para imprimir el valor incrementado de $count siempre y cuando su valor sea menor a 10:

<?php
class MyClass
{
    public
$prop1 = "¡Soy la propiedad de una clase!";

    public static
$count = 0;

    public function
__construct()
    {
        echo
'¡La clase "', __CLASS__, '" fue iniciada!<br />';
    }

    public function
__destruct()
    {
        echo
'¡La clase "', __CLASS__, '" fue destruida!<br />';
    }

    public function
__toString()
    {
        echo
"Usando el método toString: ";
        return
$this->getProperty();
    }

    public function
setProperty($newval)
    {
       
$this->prop1 = $newval;
    }

    private function
getProperty()
    {
        return
$this->prop1 . "<br />";
    }

    public static function
plusOne()
    {
        return
"La cuenta es " . ++self::$count . ".<br />";
    }
}

class
MyOtherClass extends MyClass
{
    public function
__construct()
    {
       
parent::__construct();
        echo
"Un nuevo constructor en " . __CLASS__ . ".<br />";
    }

    public function
newMethod()
    {
        echo
"Desde un nuevo método en " . __CLASS__ . ".<br />";
    }

    public function
callProtected()
    {
        return
$this->getProperty();
    }
}

do
{
   
// Llama al método plusOne sin instanciar MyClass
   
echo MyClass::plusOne();
} while (
MyClass::$count < 10 );
?>

Nota: Cuando se acceden a las propiedades estáticas, el signo de dólar($) va después del operador de resolución de alcance.

Cuando cargues el script en el navegador, verás lo siguiente:

La cuenta es 1.
La cuenta es 2.
La cuenta es 3.
La cuenta es 4.
La cuenta es 5.
La cuenta es 6.
La cuenta es 7.
La cuenta es 8.
La cuenta es 9.
La cuenta es 10.

Comentando con DocBlocks

Aunque no es parte oficial de la POO, el comentar al estilo DocBlock es un método de documentación ampliamente aceptado para documentar clases. Además de proporcionar un estándar a los desarrolladores que escriben código, muchos programadas de desarrollo (SDKs) han adoptado esta metodología, tal es el caso de Eclipse o NetBeans, y es utilizado para generar code hints o sugerencias de código.

Un DockBlock se define mediante el uso de un bloque de comentario que empieza con un asterisco adicional:

El poder real de DocBlock viene con la posibilidad de usar tags, que comienzan con el símbolo del arroba (@) seguido inmediatamente del nombre del tag y el valor del mismo. Las etiquetas o tag en DocBlock permiten a los desarrolladores definir al autor de un archivo, la licencia de una clase, la información de la propiedad o método y otra información útil.

Las etiquetas más utilizadas son las siguientes:

<?php
/**
* Este es un DocBlock muy básico
*/
?>

@author: El autor del elemento actual (que podría ser una clase, un archivo, un método o cualquier otro trozo de código) se enumera con esta etiqueta. Múltiples etiquetas de autor pueden ser usadas en el mismo DocBlock si hay más de un autor. El formato para el nombre de autor es John Doe <john.doe@email.com>.

@copyright: Esto significa el año del copyright y el nombre del titular del copyright del elemento actual. El formato es 2010 Titular del Copyright.

@license: Esto enlaza a la licencia del elemento actual. El formato para la información de la licencia es http://www.example.com/path/to/license.txt Nombre de la licencia.

@var: Esto define el tipo y descripción de una variable o propiedad de una clase. El formato es tipo y descripción del elemento.

@param: Este tag muestra el tipo y descripción del parámetro de una función o método. El formato es el tipo $nombre_del_elemento y descripción del elemento.

@return: El tipo y descripción del valor de retorno de una función o método es provisto por este tag. El formato es el tipo y descripción del elemento.

Una clase de ejemplo comentada con DocBlock podría tener este aspecto:

<?php
/**
* Una clase sencilla
*
* Esta es una larga descripción de la clase,
* la cual ocupará tantas lineas como sea necesario. Esta
* no será necesaria, mientras la descripción corta sea
* necesaria.
*
* Esta descripción puede dividirse en varios párrafos si la
* descripción merece mucha verborrea.
*
* @author Jason Lengstorf <jason.lengstorf@ennuidesign.com>
* @copyright 2010 Ennui Design
* @license http://www.php.net/license/3_01.txt PHP License 3.01
*/
class SimpleClass
{
   
/**
     * Una variable pública
     *
     * @var string almacena data para la clase
     */
   
public $foo;

   
/**
     * Define $foo con un nuevo valor al instanciarse la clase
     *
     * @param string $val un valor requerido por la clase
     * @return void
     */
   
public function __construct($val)
    {
       
$this->foo = $val;
    }

   
/**
     * Multiplica dos números enteros
     *
     * Acepta un par de enteros y devuelve el
     * producto de ambos.
     *
     * @param int $bat un número a ser multiplicado
     * @param int $baz un número a ser multiplicado
     * @return int el propudcto de los dos parámetros
     */
   
public function bar($bat, $baz)
    {
        return
$bat * $baz;
    }
}
?>

Una vez que observes la clase anterior, los beneficios de DocBlock te parecerán evidentes: todo está definido claramente para que el siguiente desarrollador pueda captar el código y nunca tener que preguntarse lo que un fragmento de código hace o lo que debe contener.

Comparando la programación orientada a objetos y la procedimental

Realmente no hay una manera correcta o incorrecta de escribir código. Dicho esto, esta sección presenta un argumento de peso para adoptar un enfoque orientado a objetos en el desarrollo de software, especialmente en aplicaciones de gran tamaño.

Razón 1: Fácil de implementar

Si bien puede se un poco desalentador al principio, la programación orientada a objetos en realidad ofrece un enfoque más fácil para manejar datos. Debido a que un objeto puede almacenar datos internos, no se necesita pasar variables de una función a otra función para que trabajen apropiadamente.

Además, dado que varias instancias de la misma clase pueden existir simultáneamente, tratar con grandes conjuntos de datos es infinitamente más sencillo. Por ejemplo, imagina que tienes información de dos personas que están siendo procesados en un mismo archivo. Se necesitan los nombres, ocupaciones y edades.

El enfoque procedimental

Este es el enfoque procedimental de nuestro ejemplo:

Cuando los ejecutes, el código nos mostrará lo siguiente:

<?php
function changeJob($person, $newjob)
{
   
$person['job'] = $newjob; // Cambiar el trabajo de la persona
   
return $person;
}

function
happyBirthday($person)
{
    ++
$person['age']; // Añade 1 a la edad de la persona
   
return $person;
}

$person1 = array(
   
'name' => 'Tom',
   
'job' => 'Button-Pusher',
   
'age' => 34
);

$person2 = array(
   
'name' => 'John',
   
'job' => 'Lever-Puller',
   
'age' => 41
);

// Imprime los valores iniciales de las personas
echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";
echo
"<pre>Person 2: ", print_r($person2, TRUE), "</pre>";

// Tom obtiene un ascenso y cumple años
$person1 = changeJob($person1, 'Box-Mover');
$person1 = happyBirthday($person1);

// John sólo cumple años
$person2 = happyBirthday($person2);

// Imprime los nuevos valores de las personas
echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";
echo
"<pre>Person 2: ", print_r($person2, TRUE), "</pre>";
?>

Cuando lo ejecutamos, el código nos imprimirá lo siguiente:

Person 1: Array
(
    [name] => Tom
    [job] => Button-Pusher
    [age] => 34
)
Person 2: Array
(
    [name] => John
    [job] => Lever-Puller
    [age] => 41
)
Person 1: Array
(
    [name] => Tom
    [job] => Box-Mover
    [age] => 35
)
Person 2: Array
(
    [name] => John
    [job] => Lever-Puller
    [age] => 42
)

Aunque este código no sea necesariamente malo, hay que tener varias cosas en cuenta mientras se programa. El array de atributos de la persona afectada deben ser pasados y devueltos en cada llamada a la función, lo que deja un margen de error.

Para limpiar este ejemplo, sería conveniente dejarle menos cosas al desarrollador. Sólo la información esencial para la operación debe necesitar ser transmitida a las funciones.

Aquí es donde la POO avanza y puede ayudarte a limpiar las cosas.

El enfoque POO

Este es el enfoque POO de nuestro ejemplo:

<?php
class Person
{
    private
$_name;
    private
$_job;
    private
$_age;

    public function
__construct($name, $job, $age)
    {
       
$this->_name = $name;
       
$this->_job = $job;
       
$this->_age = $age;
    }

    public function
changeJob($newjob)
    {
       
$this->_job = $newjob;
    }

    public function
happyBirthday()
    {
        ++
$this->_age;
    }
}

// Crea dos personas nuevas
$person1 = new Person("Tom", "Button-Pusher", 34);
$person2 = new Person("John", "Lever Puller", 41);

// Imprime su punto de inicio
echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";
echo
"<pre>Person 2: ", print_r($person2, TRUE), "</pre>";

// Da a Tom un ascenso y un cumpleaños
$person1->changeJob("Box-Mover");
$person1->happyBirthday();

// John sólo obtiene un año más
$person2->happyBirthday();

// Imprime los valores finales
echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";
echo
"<pre>Person 2: ", print_r($person2, TRUE), "</pre>";
?>

Esto imprimirá lo siguiente en el navegador:

Person 1: Person Object
(
    [_name:private] => Tom
    [_job:private] => Button-Pusher
    [_age:private] => 34
)

Person 2: Person Object
(
    [_name:private] => John
    [_job:private] => Lever Puller
    [_age:private] => 41
)

Person 1: Person Object
(
    [_name:private] => Tom
    [_job:private] => Box-Mover
    [_age:private] => 35
)

Person 2: Person Object
(
    [_name:private] => John
    [_job:private] => Lever Puller
    [_age:private] => 42
)

Hay unos bits adicionales involucrados en el enfoque orientado a objetos, pero luego de que la clase es definida, crear y modificar personas es muy sencillo; la información de la persona no necesitará ser pasada o devuelta de los métodos y sólo la información escencial es transmitida a cada método.

En menor medida, esta diferencia puede no parecer mucha, pero conforme las aplicaciones crezcan en tamaño, POO reducirá significativamente la carga de trabajo, si se aplica correctamente.

Consejo: No todo tiene que ser orientado a objetos. Una función rápida que se ocupa de algo pequeño en un lugar o dentro de una aplicación no necesita tener que estar envuelta en una clase. Utiliza tu juicio para decidir que enfoque utilizar, el orientado a objetos o el procedimental.

Razón 2: Mejor organización

Otro beneficio de la POO es lo bien que se presta para ser empaquetado y catalogado. Cada clase en general se puede mantener en su archivo por separado, y si una convención de nomenclatura uniforme es utilizada, acceder a las clases será extremadamente simple.

Supongamos que tenemos una aplicación con 150 clases que se llaman de forma dinámica a través de un archivo controlador en la raiz de nuestra aplicación. Todas las 150 clases siguen la convención de nomenclatura class.classname.inc.php y residen en la carpeta inc de nuestra aplicación.

El controlador puede implementar la función __autoload() de PHP para cargar de forma dinámica sólo las clases que van a necesitar ser llamadas, en lugar de tener que incluir los 150 archivos en el controlador:

<?php
   
function __autoload($class_name)
    {
        include_once
'inc/class.' . $class_name . '.inc.php';
    }
?>

Tener cada clase en un archivo separado, también permite que el código sea más portátil y más fácil de reutilizar en nuevas aplicaciones, sin necesidad de copiar y pegar.

Razón 3: Un mantenimiento más fácil

Debido a la naturaleza más compacta de la programación orientada a objetos, cuando se aplica correctamente; los cambios en el código son generalmente mucho más fácil de detectar y cambiar que en un código espagueti de implementación procedimental.

Si un array de información obtiene un nuevo atributo, el enfoque procedimental podrá requerir (en el peor de los casos) que el nuevo atributo se añada en cada función que utilice el array.

Una aplicación POO podría actualizarse con la misma facilidad con la que se agrega una nueva propiedad y, a continuación, añade los métodos que tienen que ver con dicha propiedad.

Muchos de los beneficios cubiertos en esta serie de artículos son el producto de la POO en combinación con las prácticas de desarrollo DRY. Definitivamente, es posible crear un código procedimental que no nos de pesadillas; pero, es igualmente posible crear un código orientado a objetos terrible. Por eso, hemos intentado demostrar como combinar los buenos hábitos de programación relacionados con la programación orientada a objetos para generar un código limpio, que sea fácil de leer y mantener.

Conclusiones

En este momento, debes sentirte cómodo con el estilo de la programación orientada a objetos. Aprender POO es una muy buena forma de llevar tu programación al siguiente nivel. Cuando se implementa correctamente, la programación orientada a objetos te ayudará a leer y mantener el código fácilmente; y te salvará (y a los desarrolladores que trabajan contigo) de horas extra de trabajo. ¿Estas metido en algo que no ha sido tratado en esta serie de artículos? ¿Ya utilizas la POO y tienes algunos ejemplos para principiantes? ¡Compártelos en los comentarios!

Comentarios

Imagen de Gerald

Excelente tutorial. Ya era hora de que volvieras con excelentes artículos.

Imagen de angelfqc

Excelentes artículos sobre PHP!
no sabía de la función __autoload
sigue asi!

Imagen de marcos velasquez

Muy buena el tutorial , basico pero eficiente, me ayuda a iniciar en POO y avanzar a los siguentes niveles, he programado en php dede 5 años atras, pero ya es hora de hacerlo con esta metodologia, es bueno unir los dos tipos de programacion POO y como lo llamas la forma procedimental.

gracias por todo y espero la parte IV del tuto.

Imagen de Anonimous
Anonimous

La verdad estan muy buenos los ejemplos y mas aun la traduccion.

gracias.

Imagen de edalex

Gracias por este magnifico tutorial.

Imagen de Anonymous
Anonymous

Excelente articulo !!

Imagen de pikilon

Lo primero, lo he entendido todo (o eso creo) y te doy las infinitas gracias que te mereces.
Tengo un par de preguntas.

1. ¿Puede un objeto contener otros objetos? pongamos que tengo una lista de canciones, la lista es en sí un objeto con unas propiedades (duración, nombre, número de canciones, etc...) cada canción es en sí un objeto también ¿no? No consigo hacerme a la idea de cómo se haría esto.

2. ¿Deben acceder los objetos instanciados a la base de datos recabando sus atributos o deben tener métodos para adquirir esa información de mysql?
Un ejemplo basado en el anterior.
Yo instancio una lista de reproducción y obtengo su id de mysql ¿debo cargar de mysql también el nombre, su duración o incluso sus videos? o es mejor tener un método que lo "cargue" de la bdd cada vez que lo necesite?

muchas gracias

Imagen de Anonymous

CON LA OOP SE PUEDE CREAR UN CAPTCHA ORIENTADO OBJETO?

SI LA RESPUESTA ES SI.. ALGUIEN SABE DONDE ENCUENTRO UNO? PLZZ

BUENOS ARTICULOS PERMITEME EL ABUSO COPIARLOS Y LEERLOS EN CASA CON CALMA =)

Imagen de Marcos Ramiro
Marcos Ramiro

Muy útil, muchas gracias!

Dejar comentario

Tutoriales

Cómo descargar Paper de Facebook aún fuera de EE.UU.
La semana pasada, la famosa red social anunció "Paper", una...
Cómo transferir archivos grandes de PC a PC
Pasar datos de una computadora a otra puede ser bastante sencillo...
Qué hacer antes de descargar iOS 7
A sólo una horas de la llegada del iOS 7, recuerden que antes de...

Artículo Recomendado

Google agota un modelo Google Glass en su único día de ventas abiertas
Google Glass hizo del martes su día. Por primera vez, desde su lanzamiento, cualquiera en EE.UU. pudo adquirir sus gafas inteligentes y la respuesta del público no pudo ser mejor. Así es, en el que podría ser su... más