Interfaces
Las interfaces de objetos permiten crear código que especifica qué métodos y propiedades una
clase debe implementar, sin tener que definir cómo se implementan estos métodos o propiedades. Las interfaces comparten un espacio de nombres con las clases, traits y enumeraciones, de modo que no pueden
utilizar el mismo nombre.
Las interfaces se definen de la misma manera que una clase, pero utilizando la palabra clave interface
en lugar de
class
, y sin que ninguno de los métodos tenga su contenido
especificado.
Por la naturaleza misma de una interfaz, todos los métodos declarados en una
interfaz deben ser públicos.
En la práctica, las interfaces sirven dos roles complementarios:
-
Permitir a los desarrolladores crear objetos de clases diferentes
que pueden ser utilizados de manera intercambiable, ya que implementan
la o las mismas interfaces. Un ejemplo común son varios servicios
de acceso a bases de datos, varios gestores de pago o
diferentes estrategias de caché. Diferentes implementaciones pueden ser
intercambiadas sin necesitar cambios en el código que las utiliza.
-
Para permitir que una función o método acepte y opere sobre un
argumento que se ajuste a una interfaz, sin preocuparse de qué más
puede hacer el objeto o cómo está implementado. Estas interfaces suelen
llamarse
Iterable
, Cacheable
, Renderable
,
etc. para describir el significado de su comportamiento.
Las interfaces pueden definir
métodos mágicos para obligar
a las clases que las implementan a implementar estos métodos.
Nota:
Aunque esto es soportado, incluir los
constructores
en las interfaces está fuertemente desaconsejado. Hacerlo reduce
radicalmente la flexibilidad de los objetos que implementan la interfaz.
Además, los constructores no están sujetos a las reglas de herencia,
lo que puede causar incoherencias y comportamientos inesperados.
implements
Para implementar una interfaz, se utiliza el operador implements
.
Todos los métodos de la interfaz deben ser implementados en una
clase; si no es así, se emitirá un error fatal. Las clases pueden
implementar más de una interfaz, separando cada interfaz por una coma.
Advertencia
Una clase que implementa una interfaz puede utilizar nombres diferentes
para sus argumentos que la interfaz. Sin embargo, a partir de PHP 8.0, el
lenguaje soporta los
argumentos nombrados, lo que
significa que el llamador puede depender del nombre del argumento en la interfaz.
Por esta razón, se recomienda encarecidamente que los desarrolladores
utilicen el mismo nombre de argumento que en la interfaz que se implementa.
Nota:
Las interfaces pueden ser extendidas como clases, utilizando el operador
extends
Nota:
La clase que implementa la interfaz debe declarar todos los métodos en
la interfaz con una firma compatible.
Una clase puede implementar dos interfaces que definan un método
con el mismo nombre. En este caso, la implementación debe seguir las
reglas de compatibilidad de firmas
para todas las interfaces. Así,
la covarianza y la contravarianza
pueden ser aplicadas.
Las constantes
Las interfaces pueden contener constantes. Las constantes de interfaces
funcionan exactamente como las
constantes de clase.
Anterior a PHP 8.1.0, no pueden ser redefinidas por una
clase/interfaz que las hereda.
Propiedades
A partir de PHP 8.4.0, las interfaces también pueden declarar propiedades.
Si lo hacen, la declaración debe especificar si la propiedad es legible,
modificable, o ambas.
La declaración de la interfaz se aplica únicamente al acceso en lectura y escritura públicos.
Una clase puede satisfacer una propiedad de interfaz de varias maneras.
Puede definir una propiedad pública.
Puede definir una propiedad pública
virtual
que implemente únicamente el crochet correspondiente.
O una propiedad de lectura puede ser satisfecha por una propiedad readonly
.
Sin embargo, una propiedad de interfaz que es modificable no puede ser readonly
.
Ejemplo #1 Ejemplo de propiedades de interfaz
<?php
interface I
{
// Una clase que implementa esta interfaz DEBE tener una propiedad públicamente legible,
// pero que esta sea o no modificable públicamente no está restringido.
public string $readable { get; }
// Una clase que implementa esta interfaz DEBE tener una propiedad públicamente modificable,
// pero que esta sea o no legible públicamente no está restringido.
public string $writeable { set; }
// Una clase que implementa esta interfaz DEBE tener una propiedad que sea a la vez públicamente
// legible y públicamente modificable.
public string $both { get; set; }
}
// Esta clase implementa las tres propiedades como propiedades tradicionales, sin crochets.
// Esto es completamente válido.
class C1 implements I
{
public string $readable;
public string $writeable;
public string $both;
}
// Esta clase implementa las tres propiedades utilizando únicamente los crochets
// solicitados. Esto también es completamente válido.
class C2 implements I
{
private string $written = '';
private string $all = '';
// Utiliza únicamente un crochet get para crear una propiedad virtual.
// Esto satisface el requisito "get public".
// No es modificable, pero esto no es requerido por la interfaz.
public string $readable { get => strtoupper($this->writeable); }
// La interfaz requiere únicamente que la propiedad sea modificable,
// pero incluir operaciones get también es completamente válido.
// Este ejemplo crea una propiedad virtual, lo cual es aceptable.
public string $writeable {
get => $this->written;
set {
$this->written = $value;
}
}
// Esta propiedad requiere que la lectura y la escritura sean posibles,
// por lo que debemos implementar ambas o permitir el comportamiento por defecto.
public string $both {
get => $this->all;
set {
$this->all = strtoupper($value);
}
}
}
?>
Ejemplos
Ejemplo #2 Ejemplo de interfaz
<?php
// Declaración de la interfaz 'Template'
interface Template
{
public function setVariable($name, $var);
public function getHtml($template);
}
// Implementación de la interfaz
// Esto funcionará
class WorkingTemplate implements Template
{
private $vars = [];
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;
}
}
// Esto no funcionará
// Fatal error: Class BadTemplate contains 1 abstract methods
// and must therefore be declared abstract (Template::getHtml)
class BadTemplate implements Template
{
private $vars = [];
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
}
?>
Ejemplo #3 Las interfaces extensibles
<?php
interface A
{
public function foo();
}
interface B extends A
{
public function baz(Baz $baz);
}
// Esto funcionará
class C implements B
{
public function foo()
{
}
public function baz(Baz $baz)
{
}
}
// Esto no funcionará y resultará en un error fatal
class D implements B
{
public function foo()
{
}
public function baz(Foo $foo)
{
}
}
?>
Ejemplo #4 Compatibilidad de la varianza con múltiples interfaces
<?php
class Foo {}
class Bar extends Foo {}
interface A {
public function myfunc(Foo $arg): Foo;
}
interface B {
public function myfunc(Bar $arg): Bar;
}
class MyClass implements A, B
{
public function myfunc(Foo $arg): Bar
{
return new Bar();
}
}
?>
Ejemplo #5 Herencia de múltiples 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 #6 Interfaces con constantes
<?php
interface A
{
const B = 'Constante de la interfaz';
}
// Muestra: Constante de la interfaz
echo A::B;
// Sin embargo, esto no funcionará, ya que no está permitido
// sobrescribir constantes.
class B implements A
{
const B = 'Constante de clase';
}
// Muestra: Constante de clase
// Anterior a PHP 8.1.0, esto no funcionaría, ya que no estaba permitido
// redefinir constantes.
echo B::B;
?>
Ejemplo #7 Las interfaces con las clases abstractas
<?php
interface A
{
public function foo(string $s): string;
public function bar(int $i): int;
}
// Una clase abstracta puede implementar solo una parte de una interfaz.
// Las clases que extienden la clase abstracta deben implementar el resto.
abstract class B implements A
{
public function foo(string $s): string
{
return $s . PHP_EOL;
}
}
class C extends B
{
public function bar(int $i): int
{
return $i * 2;
}
}
?>
Ejemplo #8 Extendiendo e implementando simultáneamente
<?php
class One
{
/* ... */
}
interface Usable
{
/* ... */
}
interface Updatable
{
/* ... */
}
// El orden de las palabras clave aquí es importante. 'extends' debe ir primero.
class Two extends One implements Usable, Updatable
{
/* ... */
}
?>
Una interfaz, con las declaraciones de tipos, proporciona una buena manera
de asegurarse de que un objeto particular contiene métodos particulares.
Ver el operador instanceof y
las declaraciones de tipo.