¿Puedo extender una clase usando más de 1 clase en PHP?


150

Si tengo varias clases con funciones que necesito pero quiero almacenar por separado para la organización, ¿puedo extender una clase para tener ambas?

es decir class a extends b extends c

editar: Sé cómo extender las clases una a la vez, pero estoy buscando un método para extender instantáneamente una clase usando múltiples clases base: AFAIK no puedes hacer esto en PHP, pero debería haber formas de evitarlo sin recurrir a class c extends b,class b extends a


Use agregación o interfaces. La herencia múltiple no existe en PHP.
Franck

2
Estoy buscando interfaces ya que no soy un gran admirador de las grandes jerarquías de clases. ¿Pero no puedo ver cómo las interfaces realmente hacen algo?
atomicharri

Las interfaces le permiten "heredar" solo la API, no los cuerpos de funciones. Obliga a la clase a a implementar métodos de la interfaz by c. Significa que si desea heredar el comportamiento, debe agregar objetos miembros de las clases byc en su clase a.
Franck


2
Considere los rasgos como la respuesta correcta.
Daniel

Respuestas:


170

Respondiendo tu edición:

Si realmente quieres falsificar una herencia múltiple, puedes usar la función mágica __call ().

Esto es feo aunque funciona desde el punto de vista del usuario de clase A:

class B {
    public function method_from_b($s) {
        echo $s;
    }
}

class C {
    public function method_from_c($s) {
        echo $s;
    }
}

class A extends B
{
  private $c;

  public function __construct()
  {
    $this->c = new C;
  }

  // fake "extends C" using magic function
  public function __call($method, $args)
  {
    $this->c->$method($args[0]);
  }
}


$a = new A;
$a->method_from_b("abc");
$a->method_from_c("def");

Imprime "abcdef"


1
De hecho, me gusta mucho la idea de extender la clase ¿Hay alguna limitación conocida de hacerlo de esta manera?
atomicharri

3
Hasta donde yo sé, no hay limitaciones, PHP es un lenguaje muy permisivo para pequeños hacks como este. :) Como otros han señalado, sin embargo, no es la forma correcta de hacerlo.
Franck

64
No podrá utilizar métodos protegidos o privados.
gusano

1
@wormhit, sin embargo, no lo recomendaría para su uso en producción, uno puede usar ReflectionClass para acceder a métodos privados y protegidos.
Denis V

2
Esto no funcionará para Implements, que espera que el método exista realmente y el comportamiento es bastante indefinido cuando varias clases base tienen un método con el mismo nombre. También arruinará la capacidad de su editor para darle pistas.
Erik

138

No puede tener una clase que extienda dos clases base. No podrías haberlo hecho.

// this is NOT allowed (for all you google speeders)
Matron extends Nurse, HumanEntity

Sin embargo, podría tener una jerarquía de la siguiente manera ...

Matron extends Nurse    
Consultant extends Doctor

Nurse extends HumanEntity
Doctor extends HumanEntity

HumanEntity extends DatabaseTable
DatabaseTable extends AbstractTable

y así.


¿Puede explicar por qué es correcto primero heredar a Nurse en Matron y luego declarar la herencia de HumanEntity en Nurse?
Qwerty

77
@Qwerty Porque Matron tiene cualidades adicionales de una Enfermera, mientras que una enfermera tiene todas las cualidades de un humano. Por lo tanto, Matron es una enfermera humana y finalmente tiene capacidades de Matron
J-Dizzle

58

Podrías usar rasgos que, con suerte, estarán disponibles desde PHP 5.4.

Rasgos es un mecanismo para la reutilización de código en lenguajes de herencia única como PHP. Un rasgo está destinado a reducir algunas limitaciones de la herencia única al permitir que un desarrollador reutilice conjuntos de métodos libremente en varias clases independientes que viven en diferentes jerarquías de clases. La semántica de la combinación de Rasgos y clases se define de una manera que reduce la complejidad y evita los problemas típicos asociados con la herencia múltiple y los Mixins.

Son reconocidos por su potencial para apoyar una mejor composición y reutilización, de ahí su integración en versiones más nuevas de lenguajes como Perl 6, Squeak, Scala, Slate y Fortress. Los rasgos también se han portado a Java y C #.

Más información: https://wiki.php.net/rfc/traits


Asegúrate de no abusar de ellos. Esencialmente son funciones estáticas que aplicas a los objetos. Podrías crear un desastre al abusar de ellos.
Nikola Petkanski

2
No puedo creer que esta no sea la respuesta seleccionada.
Daniel

18

Las clases no están destinadas a ser solo colecciones de métodos. Se supone que una clase representa un concepto abstracto, con estado (campos) y comportamiento (métodos) que cambia el estado. Usar la herencia solo para obtener un comportamiento deseado suena como un mal diseño de OO, y exactamente la razón por la cual muchos idiomas no permiten la herencia múltiple: para evitar la "herencia de espagueti", es decir, extender 3 clases porque cada una tiene un método que necesita y terminar con una clase que hereda 100 métodos y 20 campos, pero solo usa 5 de ellos.


1
Discutiré con su afirmación de que la solicitud del OP constituye un "mal diseño de OO" ; solo mire la aparición de Mixins para respaldar la posición de que agregar arquitectónicamente a una clase desde múltiples fuentes es una buena idea arquitectónicamente. Sin embargo, le diré que PHP no proporciona un conjunto óptimo de características de lenguaje para lograr un "diseño" óptimo, pero eso no significa que usar las características disponibles para aproximarlo sea necesariamente una mala idea; solo mira la respuesta de @ Franck.
MikeSchinkel

15

Hay planes para agregar mezclas pronto, creo.

Pero hasta entonces, ve con la respuesta aceptada. Puede resumir eso un poco para hacer una clase "extensible":

class Extendable{
  private $extender=array();

  public function addExtender(Extender $obj){
    $this->extenders[] = $obj;
    $obj->setExtendee($this);
  }

  public function __call($name, $params){
    foreach($this->extenders as $extender){
       //do reflection to see if extender has this method with this argument count
       if (method_exists($extender, $name)){
          return call_user_func_array(array($extender, $name), $params);
       }
    }
  }
}


$foo = new Extendable();
$foo->addExtender(new OtherClass());
$foo->other_class_method();

Tenga en cuenta que en este modelo "OtherClass" llega a "saber" acerca de $ foo. OtherClass necesita tener una función pública llamada "setExtendee" para configurar esta relación. Luego, si sus métodos se invocan desde $ foo, puede acceder a $ foo internamente. Sin embargo, no tendrá acceso a ningún método / variable privado / protegido como lo haría una clase extendida real.


12

Usa rasgos como clases base. Luego úselos en una clase para padres. Extiéndelo .

trait business{
  function sell(){

  }

  function buy(){

  }

  function collectMoney(){
  }

}

trait human{

   function think(){

   }

   function speak(){

   }

}

class BusinessPerson{
  use business;
  use human;
  // If you have more traits bring more
}


class BusinessWoman extends BusinessPerson{

   function getPregnant(){

   }

}


$bw = new BusinessWoman();
$bw ->speak();
$bw->getPregnant();

Ver ahora, la mujer de negocios hereda lógicamente los negocios y los humanos;


Estoy atrapado con php 5.3 no hay soporte de rasgos: D
Nishchal Gautam

¿Por qué no usar el rasgo en BusinessWomanlugar de BusinessPerson? Entonces puede tener una herencia múltiple real.
SOFe

@SOFe, porque BusinessMan también puede extenderlo. Por lo que siempre está haciendo negocio puede extender busines persona, y obtiene automáticamente rasgos
Alice

La forma en que lo hace no es diferente de lo que es posible en la herencia única, donde BusinessPerson extiende una clase abstracta llamada humana. Mi punto es que su ejemplo realmente no necesita herencia múltiple.
SOFe

Creo que hasta que se requirió BusinessPerson, BusinessWoman podría haber heredado directamente otros rasgos. Pero lo que probé aquí es mantener ambos rasgos en una clase mediadora y luego usarlos, donde sea necesario. Entonces, puedo olvidar incluir ambos rasgos si nuevamente necesito algún otro lugar (como describí BusinessMan). Se trata de estilos de código. Si te gusta incluirlo directamente en tu clase. puedes seguir adelante con eso, ya que tienes toda la razón al respecto.
Alice

9
<?php
// what if we want to extend more than one class?

abstract class ExtensionBridge
{
    // array containing all the extended classes
    private $_exts = array();
    public $_this;

    function __construct() {$_this = $this;}

    public function addExt($object)
    {
        $this->_exts[]=$object;
    }

    public function __get($varname)
    {
        foreach($this->_exts as $ext)
        {
            if(property_exists($ext,$varname))
            return $ext->$varname;
        }
    }

    public function __call($method,$args)
    {
        foreach($this->_exts as $ext)
        {
            if(method_exists($ext,$method))
            return call_user_method_array($method,$ext,$args);
        }
        throw new Exception("This Method {$method} doesn't exists");
    }


}

class Ext1
{
    private $name="";
    private $id="";
    public function setID($id){$this->id = $id;}
    public function setName($name){$this->name = $name;}
    public function getID(){return $this->id;}
    public function getName(){return $this->name;}
}

class Ext2
{
    private $address="";
    private $country="";
    public function setAddress($address){$this->address = $address;}
    public function setCountry($country){$this->country = $country;}
    public function getAddress(){return $this->address;}
    public function getCountry(){return $this->country;}
}

class Extender extends ExtensionBridge
{
    function __construct()
    {
        parent::addExt(new Ext1());
        parent::addExt(new Ext2());
    }

    public function __toString()
    {
        return $this->getName().', from: '.$this->getCountry();
    }
}

$o = new Extender();
$o->setName("Mahdi");
$o->setCountry("Al-Ahwaz");
echo $o;
?>

44
Su respuesta debe contener una explicación de su código y una descripción de cómo resuelve el problema.
AbcAeffchen

1
Es casi autoexplicativo, pero sería genial tener comentarios a lo largo del código.
Heroselohim

ahora, si Ext1 y Ext2 tienen una función con el mismo nombre, siempre se llamará Ext1, no dependerá en absoluto de los parámetros.
Alice

8

La respuesta actualmente aceptada por @Franck funcionará, pero en realidad no es herencia múltiple sino una instancia secundaria de clase definida fuera de alcance, también existe la __call()taquigrafía: considere usar $this->childInstance->method(args)cualquier lugar donde necesite el método de clase ExternalClass en clase "extendida".

Respuesta exacta

No, no puedes, respectivamente, no realmente, como dice el manual de extendspalabras clave :

Una clase extendida siempre depende de una sola clase base, es decir, no se admite la herencia múltiple.

Respuesta real

Sin embargo, como @adam sugirió correctamente, esto NO le prohíbe usar herencia jerárquica múltiple.

PUEDES extender una clase, con otra y otra con otra y así sucesivamente ...

Entonces, un ejemplo bastante simple sobre esto sería:

class firstInheritance{}
class secondInheritance extends firstInheritance{}
class someFinalClass extends secondInheritance{}
//...and so on...

Nota IMPORTANTE

Como habrás notado, solo puedes hacer una interacción múltiple (2+) por jerarquía si tienes control sobre todas las clases incluidas en el proceso ; eso significa que no puedes aplicar esta solución, por ejemplo, con clases integradas o con clases que simplemente no puede editar; si desea hacerlo, le queda la solución @Franck: instancias secundarias.

... Y finalmente ejemplo con algo de salida:

class A{
  function a_hi(){
    echo "I am a of A".PHP_EOL."<br>".PHP_EOL;  
  }
}

class B extends A{
  function b_hi(){
    echo "I am b of B".PHP_EOL."<br>".PHP_EOL;  
  }
}

class C extends B{
  function c_hi(){
    echo "I am c of C".PHP_EOL."<br>".PHP_EOL;  
  }
}

$myTestInstance = new C();

$myTestInstance->a_hi();
$myTestInstance->b_hi();
$myTestInstance->c_hi();

Que salidas

I am a of A 
I am b of B 
I am c of C 

6

He leído varios artículos que desalientan la herencia en proyectos (a diferencia de las bibliotecas / marcos), y alientan a programar interfaces agaisnt, no en contra de las implementaciones.
También recomiendan la OO por composición: si necesita las funciones en la clase ayb, haga que c tenga miembros / campos de este tipo:

class C
{
    private $a, $b;

    public function __construct($x, $y)
    {
        $this->a = new A(42, $x);
        $this->b = new B($y);
    }

    protected function DoSomething()
    {
        $this->a->Act();
        $this->b->Do();
    }
}

Esto efectivamente se convierte en lo mismo que muestran Franck y Sam. Por supuesto, si elige utilizar explícitamente la composición, debería usar la inyección de dependencia .
MikeSchinkel

3

La herencia múltiple parece funcionar en el nivel de interfaz. Hice una prueba en php 5.6.1.

Aquí hay un código de trabajo:

<?php


interface Animal
{
    public function sayHello();
}


interface HairyThing
{
    public function plush();
}

interface Dog extends Animal, HairyThing
{
    public function bark();
}


class Puppy implements Dog
{
    public function bark()
    {
        echo "ouaf";
    }

    public function sayHello()
    {
        echo "hello";
    }

    public function plush()
    {
        echo "plush";
    }


}


echo PHP_VERSION; // 5.6.1
$o = new Puppy();
$o->bark();
$o->plush();
$o->sayHello(); // displays: 5.6.16ouafplushhello

No pensé que eso fuera posible, pero me topé con el código fuente de SwiftMailer, en la clase Swift_Transport_IoBuffer, que tiene la siguiente definición:

interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream

Todavía no jugué con él, pero pensé que podría ser interesante compartirlo.


1
Interesante e irrelevante.
Yevgeniy Afanasyev

1

Acabo de resolver mi problema de "herencia múltiple" con:

class Session {
    public $username;
}

class MyServiceResponsetype {
    protected $only_avaliable_in_response;
}

class SessionResponse extends MyServiceResponsetype {
    /** has shared $only_avaliable_in_response */

    public $session;

    public function __construct(Session $session) {
      $this->session = $session;
    }

}

De esta manera, tengo el poder de manipular la sesión dentro de una SessionResponse que extiende MyServiceResponsetype aún siendo capaz de manejar Session por sí mismo.


1

Si desea verificar si una función es pública, vea este tema: https://stackoverflow.com/a/4160928/2226755

Y use el método call_user_func_array (...) para muchos o no argumentos.

Me gusta esto :

class B {
    public function method_from_b($s) {
        echo $s;
    }
}

class C {
    public function method_from_c($l, $l1, $l2) {
        echo $l.$l1.$l2;
    }
}

class A extends B {
    private $c;

    public function __construct() {
        $this->c = new C;
    }

    public function __call($method, $args) {
        if (method_exists($this->c, $method)) {
            $reflection = new ReflectionMethod($this->c, $method);
            if (!$reflection->isPublic()) {
                throw new RuntimeException("Call to not public method ".get_class($this)."::$method()");
            }

            return call_user_func_array(array($this->c, $method), $args);
        } else {
            throw new RuntimeException("Call to undefined method ".get_class($this)."::$method()");
        }
    }
}


$a = new A;
$a->method_from_b("abc");
$a->method_from_c("d", "e", "f");


0

PHP aún no admite la herencia de múltiples clases, sin embargo, admite la herencia de múltiples interfaces.

Consulte http://www.hudzilla.org/php/6_17_0.php para ver algunos ejemplos.


¿Todavía? Dudo que lo hagan. OO moderno desalienta la herencia múltiple, puede ser desordenado.
PhiLho

0

PHP no permite la herencia múltiple, pero puede hacerlo implementando múltiples interfaces. Si la implementación es "pesada", proporcione una implementación esquelética para cada interfaz en una clase separada. Luego, puede delegar toda la clase de interfaz a estas implementaciones esqueléticas a través de la contención de objetos .


0

Sin saber exactamente lo que está tratando de lograr, sugeriría que estudie la posibilidad de rediseñar su aplicación para usar la composición en lugar de la herencia en este caso.


0

Siempre es una buena idea hacer una clase padre, con funciones ... es decir, agregar toda esta funcionalidad a padre.

Y "mueva" todas las clases que usan esto jerárquicamente hacia abajo. Necesito reescribir funciones, que son específicas.


0

Uno de los problemas de PHP como lenguaje de programación es el hecho de que solo puede tener una herencia única. Esto significa que una clase solo puede heredar de otra clase.

Sin embargo, muchas veces sería beneficioso heredar de múltiples clases. Por ejemplo, podría ser deseable heredar métodos de un par de clases diferentes para evitar la duplicación de código.

Este problema puede llevar a una clase que tiene un largo historial familiar de herencia que a menudo no tiene sentido.

En PHP 5.4 se agregó una nueva característica del lenguaje conocida como Rasgos. Un Rasgo es algo así como un Mixin, ya que le permite mezclar clases de Rasgos en una clase existente. Esto significa que puede reducir la duplicación de código y obtener los beneficios mientras evita los problemas de herencia múltiple.

Rasgos


-1

Esta no es una respuesta real, tenemos una clase duplicada

...pero funciona :)

class A {
    //do some things
}

class B {
    //do some things
}

clase copy_B es copiar toda la clase B

class copy_B extends A {

    //do some things (copy class B)
}

class A_B extends copy_B{}

ahora

class C_A extends A{}
class C_B extends B{}
class C_A_b extends A_B{}  //extends A & B

-3

la clase A se extiende B {}

la clase B se extiende C {}

Entonces A ha extendido tanto B como C


2
@rostamiani porque este método también está modificando la clase B. Lo que queremos la mayor parte del tiempo es tener dos clases no relacionadas By C(probablemente de bibliotecas diferentes) y ser heredadas simultáneamente porA , de modo que Acontenga todo de ambos By C, pero Bno contenga nada Cy viceversa.
SOFe
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.