Herencia de objetos en PHP 7

El paradigma OOP coloca los objetos en el corazón del diseño de la aplicación, donde los objetos pueden considerarse como unidades que contienen varias propiedades y métodos. La interacción entre estas propiedades y métodos define el estado interno de un objeto. Cada objeto se construye a partir de un plano llamado clase. No hay tal cosa como un objeto sin la clase, al menos no en una OOP basada en la clase.

Diferenciamos OOP basado en clases (PHP, Java, C #, …) y OOP basado en prototipos (ECMAScript / JavaScript, Lua, …). En la OOP basada en clases, los objetos se crean a partir de clases; en la OOP basada en prototipos, los objetos se crean a partir de otros objetos.

El proceso de construir o crear nuevos objetos se llama instanciación. En PHP, como muchos otros lenguajes, usamos la palabra clave new para instanciar un objeto de una clase dada. Echemos un vistazo al siguiente ejemplo:

<?php
class JsonOutput
{
protected $content;
public function setContent($content)
{
$this->content = $content;
}
public function render()
{
return json_encode($this->content);
}
}
class SerializedOutput
{
protected $content;
public function setContent($content)
{
$this->content = $content;
}
public function render()
{
return serialize($this->content);
}
}
$users = [
['user' => 'John', 'age' => 34],
['user' => 'Alice', 'age' => 33],
];
$json = new JsonOutput();
$json->setContent($users);
echo $json->render();
$ser = new SerializedOutput();
$ser->setContent($users);
echo $ser->render();

Aquí, estamos definiendo dos clases simples, JsonOutput y SerializedOutput. Decimos simple simplemente porque tienen una sola propiedad y dos métodos. Estas dos clases son casi idénticas: solo difieren en una sola línea de código dentro del método render(). La clase convierte el contenido dado en JSON, mientras que el otro lo convierte en una cadena serializada.
Justo después de nuestras declaraciones de clase, definimos una matriz ficticia de $users que luego alimentamos a las instancias de las clases JsonOutput y SerializedOutput, es decir, los objetos $json y $ser.
Si bien esto está lejos de ser un diseño de clase ideal, sirve como una buena introducción a la herencia.

La herencia permite que las clases y, por lo tanto, los objetos hereden propiedades y métodos de otra clase. Términos como superclase, clase base o clase padre se utilizan para marcar la clase utilizada como base para la herencia. Los términos como subclase, clase derivada o clase secundaria se utilizan para marcar la clase heredada.
La palabra clave PHP extends se utiliza para habilitar la herencia. La herencia tiene sus límites. Solo podemos extendernos desde una sola clase a la vez, ya que PHP no admite la herencia múltiple.
Sin embargo, tener una cadena de herencia es perfectamente válido:

// valid
class A {}
class B extends A {}
class C extends B {}
// invalid
class A {}
class B {}
class C extends A, B {}

La clase C que se muestra en el ejemplo válido terminará heredando todas las propiedades y métodos permitidos de las clases B y A. Cuando decimos permitido, nos referimos a la propiedad y la visibilidad del método, es decir, modificadores de acceso:

<?php
error_reporting(E_ALL);
class A
{
public $x = 10;
protected $y = 20;
private $z = 30;
public function x()
{
return $this->x;
}
protected function y()
{
return $this->y;
}
private function z()
{
return $this->z;
}
}
class B extends A
{
}
$obj = new B();
var_dump($obj->x); // 10
var_dump($obj->y); // Uncaught Error: Cannot access protected property
B::$y
var_dump($obj->z); // Notice: Undefined property: B::$z
var_dump($obj->x()); // 10
var_dump($obj->y()); // Uncaught Error: Call to protected method A::y()
from context
var_dump($obj->z()); // Uncaught Error: Call to private method A::z() from
context

En el contexto del objeto, los modificadores de acceso se comportan como en el ejemplo anterior, que es más o menos como esperaríamos. El objeto exhibiría el mismo comportamiento, ya sea una instancia de clase A o clase B. Observemos el comportamiento de acceso a modificadores en el funcionamiento interno de la clase secundaria:

class B extends A
{
public function test()
{
var_dump($this->x); // 10
var_dump($this->y); // 20
var_dump($this->z); // Notice: Undefined property: B::$z
var_dump($this->x()); // 10
var_dump($this->y()); // 20
var_dump($this->z()); // Uncaught Error: Call to private method
A::z() from context 'B'
}
}
$obj = new B();
$obj->test();

Podemos ver que se puede acceder a los miembros public y protected (propiedad o método) desde las clases secundarias, mientras que los miembros privados no pueden hacerlo: solo son accesibles desde la clase que los define.

La palabra clave extends también es aplicable a las interfaces:

<?php
interface User {}
interface Employee extends User {}

Ser capaz de heredar las propiedades y métodos de la clase y la interfaz lo convierte en un poderoso mecanismo general de herencia de objetos.
Conociendo estas simples reglas de herencia, veamos cómo podemos reescribir nuestras clases JsonOutput y SerializedOutput en una forma más conveniente usando la herencia:

<?php
class Output
{
protected $content;
public function setContent($content)
{
$this->content = $content;
}
public function render()
{
return $this->content;
}
}
class JsonOutput extends Output
{
public function render()
{
return json_encode($this->content);
}
}
class SerializedOutput extends Output
{
public function render()
{
return serialize($this->content);
}
}

Comenzamos definiendo una clase de Salida con el contenido casi idéntico a las clases JsonOutput y SerializedOutput anteriores, simplemente cambiando su método render() para devolver simplemente el contenido tal como está. Luego reescribimos las clases JsonOutput y SerializedOutput de tal manera que ambas extiendan la clase Output. En esta configuración, una clase de Salida se convierte en una clase principal, mientras que JsonOutput y SerializedOutput se convierten en clases secundarias. Las clases secundarias redefinen el método render(), anulando así la implementación de la clase principal. La palabra clave $this tiene acceso a todos los modificadores públicos y protegidos, lo que facilita el acceso a la propiedad $content.
Si bien la herencia puede ser una forma rápida y poderosa de estructurar nuestro código en cadenas convenientes de relaciones padre/hijo, uno debe evitar el peligro de mal uso o uso excesivo. Esto puede ser especialmente complicado con sistemas más grandes donde podríamos terminar gastando más abordar una gran jerarquía de clases que mantener las interfaces del subsistema.
Por lo tanto, debemos usarlo con cuidado.

Comparte