Puis-je étendre une classe en utilisant plus d'une classe en PHP?

150

Si j'ai plusieurs classes avec des fonctions dont j'ai besoin mais que je souhaite stocker séparément pour l'organisation, puis-je étendre une classe pour avoir les deux?

c'est à dire class a extends b extends c

edit: Je sais comment étendre les classes une à la fois, mais je recherche une méthode pour étendre instantanément une classe en utilisant plusieurs classes de base - AFAIK vous ne pouvez pas faire cela en PHP mais il devrait y avoir des moyens de contourner cela sans recourir à class c extends b,class b extends a

atomicharri
la source
Utilisez l'agrégation ou les interfaces. L'héritage multiple n'existe pas en PHP.
Franck
2
Je me penche sur les interfaces car je ne suis pas un grand fan des grandes hiérarchies de classes. Mais je ne vois pas comment les interfaces font quoi que ce soit?
atomicharri
Les interfaces vous permettent "d'hériter" de l'API uniquement, pas des corps de fonction. Cela force la classe a à implémenter les méthodes des interfaces b et c. Cela signifie que si vous souhaitez hériter d'un comportement, vous devez agréger les objets membres des classes b et c dans votre classe a.
Franck
2
Pensez à utiliser Decorators sourcemaking.com/design_patterns/decorator ou Strategies sourcemaking.com/design_patterns/strategy
Gordon
2
Veuillez considérer les traits comme la bonne réponse.
Daniel

Réponses:

171

Répondre à votre modification:

Si vous voulez vraiment simuler l'héritage multiple, vous pouvez utiliser la fonction magique __call ().

C'est moche bien que cela fonctionne du point de vue de l'utilisateur de classe 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"

Franck
la source
1
En fait, j'aime assez l'idée d'étendre la classe. Y a-t-il des limites connues à le faire de cette façon?
atomicharri
3
Aucune limitation pour autant que je sache, PHP est un langage très permissif pour les petits hacks comme celui-ci. :) Comme d'autres l'ont souligné, ce n'est pas la bonne façon de le faire.
Franck
64
Vous ne pourrez pas utiliser de méthodes protégées ou privées.
wormhit
1
@wormhit, cependant, je ne le recommanderais pas pour une utilisation en production, on peut utiliser ReflectionClass pour accéder aux méthodes privées et protégées.
Denis V
2
Cela ne fonctionnera pas pour Implements, qui s'attend à ce que la méthode existe réellement et le comportement est pratiquement indéfini lorsque plusieurs classes de base ont une méthode avec le même nom. Cela nuira également à la capacité de votre éditeur à vous donner des indices.
Erik
138

Vous ne pouvez pas avoir une classe qui étend deux classes de base. Vous ne pourriez pas avoir.

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

Vous pourriez cependant avoir une hiérarchie comme suit ...

Matron extends Nurse    
Consultant extends Doctor

Nurse extends HumanEntity
Doctor extends HumanEntity

HumanEntity extends DatabaseTable
DatabaseTable extends AbstractTable

etc.

Adam
la source
Pouvez-vous expliquer pourquoi il est juste d'hériter d'abord de Nurse en Matron, puis de déclarer l'héritage de HumanEntity en Nurse?
Qwerty
7
@Qwerty Parce que Matron a des qualités supplémentaires d'infirmière, tandis qu'une infirmière a toutes les qualités d'un humain. Par conséquent, Matron est une infirmière humaine et a enfin des capacités de Matron
J-Dizzle
58

Vous pouvez utiliser des traits qui, espérons-le, seront disponibles à partir de PHP 5.4.

Traits est un mécanisme de réutilisation de code dans des langages d'héritage uniques tels que PHP. Un trait est destiné à réduire certaines limitations de l'héritage unique en permettant à un développeur de réutiliser librement des ensembles de méthodes dans plusieurs classes indépendantes vivant dans des hiérarchies de classes différentes. La sémantique de la combinaison de Traits et de classes est définie de manière à réduire la complexité et à éviter les problèmes typiques associés à l'héritage multiple et aux Mixins.

Ils sont reconnus pour leur potentiel à soutenir une meilleure composition et réutilisation, d'où leur intégration dans les nouvelles versions de langages tels que Perl 6, Squeak, Scala, Slate et Fortress. Les traits ont également été portés vers Java et C #.

Plus d'informations: https://wiki.php.net/rfc/traits

Nikola Petkanski
la source
Assurez-vous de ne pas en abuser. Ce sont essentiellement des fonctions statiques que vous appliquez aux objets. Vous pourriez créer un désordre en les abusant.
Nikola Petkanski
2
Je ne peux pas croire que ce ne soit pas la réponse choisie.
Daniel
18

Les classes ne sont pas destinées à être simplement des collections de méthodes. Une classe est censée représenter un concept abstrait, avec à la fois un état (champs) et un comportement (méthodes) qui change l'état. Utiliser l'héritage juste pour obtenir un comportement souhaité ressemble à une mauvaise conception OO, et c'est exactement la raison pour laquelle de nombreux langages interdisent l'héritage multiple: afin d'éviter "l'héritage spaghetti", c'est-à-dire étendre 3 classes car chacune a une méthode dont vous avez besoin, et se retrouver avec une classe qui hérite de 100 méthodes et 20 champs, mais n'en utilise que 5.

Michael Borgwardt
la source
1
Je conteste votre affirmation selon laquelle la demande du PO constitue une "mauvaise conception OO" ; il suffit de regarder l'émergence de Mixins pour soutenir la position selon laquelle l'ajout de méthodes à une classe à partir de plusieurs sources est une bonne idée architecturale. Je vais cependant vous dire que PHP ne fournit pas un ensemble optimal de fonctionnalités de langage pour réaliser un "design" optimal mais cela ne signifie pas que l'utilisation des fonctionnalités disponibles pour se rapprocher est forcément une mauvaise idée; regardez la réponse de @ Franck.
MikeSchinkel
15

Il est prévu d'ajouter bientôt des mix-ins, je crois.

Mais jusque-là, allez avec la réponse acceptée. Vous pouvez faire un résumé de cela pour créer une classe "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();

Notez que dans ce modèle, "OtherClass" apprend à "connaître" $ foo. OtherClass doit avoir une fonction publique appelée "setExtendee" pour configurer cette relation. Ensuite, si ses méthodes sont appelées à partir de $ foo, il peut accéder à $ foo en interne. Cependant, il n'aura pas accès à des méthodes / variables privées / protégées comme le ferait une vraie classe étendue.

Sam
la source
12

Utilisez les traits comme classes de base. Ensuite, utilisez-les dans une classe parent. Prolongez-le.

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();

Voir maintenant femme d'affaires logiquement héritée des affaires et humaine à la fois;

Alice
la source
Je suis coincé avec php 5.3, il n'y a pas de support de trait: D
Nishchal Gautam
Pourquoi ne pas utiliser le trait au BusinessWomanlieu de BusinessPerson? Ensuite, vous pouvez avoir un héritage multiple réel.
SOFe
@SOFe, car BusinessMan peut également l'étendre. Ainsi, quiconque fait des affaires peut étendre ses affaires et obtenir automatiquement des traits
Alice
La façon dont vous procédez ne diffère pas de ce qui est possible dans l'héritage unique où BusinessPerson étend une classe abstraite appelée human. Mon point est que votre exemple n'a pas vraiment besoin d'héritage multiple.
SOFe
Je crois que jusqu'à ce que BusinessPerson soit nécessaire, BusinessWoman aurait pu hériter directement d'autres traits. Mais ce que j'ai essayé ici, c'est de garder les deux traits dans une classe de médiateur et de l'utiliser ensuite, là où c'est nécessaire. Donc, à je peux oublier d'inclure les deux traits si besoin à nouveau ailleurs (comme je l'ai décrit BusinessMan). Tout est question de styles de code. Si vous souhaitez inclure directement dans votre classe. vous pouvez aller de l'avant avec cela - car vous avez tout à fait raison à ce sujet.
Alice le
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;
?>
Mehdi Homeily
la source
4
Votre réponse doit contenir une explication de votre code et une description de la manière dont il résout le problème.
AbcAeffchen
1
C'est presque explicite, mais ce serait bien d'avoir des commentaires le long du code.
Heroselohim
maintenant, si Ext1 et Ext2 ont tous deux une fonction avec le même nom, toujours Ext1 cwill sera appelé, cela ne dépendra pas du tout des paramètres.
Alice le
8

La réponse actuellement acceptée par @Franck fonctionnera mais ce n'est pas en fait un héritage multiple mais une instance enfant de classe définie hors de portée, il y a aussi le __call()raccourci - envisagez d'utiliser $this->childInstance->method(args)n'importe où vous avez besoin de la méthode de classe ExternalClass dans la classe "étendue".

Réponse exacte

Non, vous ne pouvez pas, respectivement, pas vraiment, comme leextends dit le manuel du mot - clé :

Une classe étendue dépend toujours d'une seule classe de base, c'est-à-dire que l'héritage multiple n'est pas pris en charge.

Vraie réponse

Cependant, comme @adam l'a suggéré correctement, cela ne vous interdit PAS d'utiliser l'héritage hiérarchique multiple.

Vous POUVEZ étendre une classe, avec une autre et une autre avec une autre et ainsi de suite ...

Un exemple assez simple à ce sujet serait:

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

Note importante

Comme vous l'avez peut-être remarqué, vous ne pouvez faire plusieurs intégrations (2+) par hiérarchie que si vous avez le contrôle sur toutes les classes incluses dans le processus - cela signifie que vous ne pouvez pas appliquer cette solution, par exemple avec des classes intégrées ou avec des classes que vous ne peut tout simplement pas modifier - si vous voulez faire cela, vous vous retrouvez avec la solution @Franck - les instances enfants.

... Et enfin exemple avec une sortie:

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();

Quelles sorties

I am a of A 
I am b of B 
I am c of C 
jave.web
la source
6

J'ai lu plusieurs articles décourageant l'héritage dans les projets (par opposition aux bibliothèques / frameworks), et encourageant à programmer des interfaces agaisnt, non contre les implémentations.
Ils préconisent également l'OO par composition: si vous avez besoin des fonctions des classes a et b, faites en sorte que c ait des membres / champs de ce type:

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();
    }
}
PhiLho
la source
Cela devient effectivement la même chose que montrent Franck et Sam. Bien sûr, si vous choisissez d'utiliser explicitement la composition, vous devriez utiliser l' injection de dépendances .
MikeSchinkel
3

L'héritage multiple semble fonctionner au niveau de l'interface. J'ai fait un test sur php 5.6.1.

Voici un code fonctionnel:

<?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

Je ne pensais pas que c'était possible, mais je suis tombé sur le code source de SwiftMailer, dans la classe Swift_Transport_IoBuffer, qui a la définition suivante:

interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream

Je n'ai pas encore joué avec, mais j'ai pensé que cela pourrait être intéressant à partager.

lingue
la source
1
intéressant et non pertinent.
Yevgeniy Afanasyev
1

Je viens de résoudre mon problème "d'héritage multiple" avec:

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 cette façon, j'ai le pouvoir de manipuler une session à l'intérieur d'une SessionResponse qui étend MyServiceResponsetype tout en étant capable de gérer Session par lui-même.

blomman9
la source
1

Si vous souhaitez vérifier si une fonction est publique, consultez cette rubrique: https://stackoverflow.com/a/4160928/2226755

Et utilisez la méthode call_user_func_array (...) pour plusieurs arguments ou pas.

Comme ça :

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");
A-312
la source
0

PHP ne prend pas encore en charge l'héritage de classes multiples, il prend cependant en charge l'héritage d'interfaces multiples.

Voir http://www.hudzilla.org/php/6_17_0.php pour quelques exemples.

Tader
la source
Encore? Je doute qu'ils le fassent. Les OO modernes découragent l'héritage multiple, cela peut être compliqué.
PhiLho
0

PHP n'autorise pas l'héritage multiple, mais vous pouvez le faire avec l'implémentation de plusieurs interfaces. Si l'implémentation est "lourde", fournissez une implémentation squelettique pour chaque interface dans une classe distincte. Ensuite, vous pouvez déléguer toutes les classes d'interface à ces implémentations squelettiques via le confinement d'objets .

petr k.
la source
0

Ne sachant pas exactement ce que vous essayez de réaliser, je suggérerais d'examiner la possibilité de reconcevoir votre application pour utiliser la composition plutôt que l'héritage dans ce cas.

Joël
la source
0

Une bonne idée est toujours de créer une classe parent, avec des fonctions ... c'est-à-dire d'ajouter toutes ces fonctionnalités au parent.

Et "déplacez" toutes les classes qui l'utilisent de manière hiérarchique vers le bas. J'ai besoin - des fonctions de réécriture, qui sont spécifiques.

publikz.com
la source
0

L'un des problèmes de PHP en tant que langage de programmation est le fait que vous ne pouvez avoir qu'un seul héritage. Cela signifie qu'une classe ne peut hériter que d'une autre classe.

Cependant, la plupart du temps, il serait avantageux d'hériter de plusieurs classes. Par exemple, il peut être souhaitable d'hériter des méthodes de quelques classes différentes afin d'éviter la duplication de code.

Ce problème peut conduire à une classe qui a une longue histoire familiale d'héritage qui n'a souvent pas de sens.

En PHP 5.4, une nouvelle fonctionnalité du langage a été ajoutée, appelée Traits. Un Trait est un peu comme un Mixin en ce qu'il vous permet de mélanger des classes de Trait dans une classe existante. Cela signifie que vous pouvez réduire la duplication de code et bénéficier des avantages tout en évitant les problèmes d'héritage multiple.

Traits

Oussama
la source
-1

Ce n'est pas une vraie réponse, nous avons une classe en double

... mais ça marche :)

class A {
    //do some things
}

class B {
    //do some things
}

la classe copy_B est une copie de toute la classe B

class copy_B extends A {

    //do some things (copy class B)
}

class A_B extends copy_B{}

maintenant

class C_A extends A{}
class C_B extends B{}
class C_A_b extends A_B{}  //extends A & B
Codeur MR
la source
-3

la classe A étend B {}

la classe B étend C {}

Alors A a étendu B et C

Robert DC
la source
2
@rostamiani car cette méthode modifie également la classe B. Ce que nous voulons la plupart du temps, c'est d'avoir deux classes non liées Bet C(probablement de bibliothèques différentes) et d'être héritées simultanément par A, de sorte qu'elles Acontiennent tout des deux Bet C, mais Bne contiennent rien de Cet vice versa.
SOFe