Posso estender uma classe usando mais de uma classe em PHP?

150

Se eu tiver várias classes com funções necessárias, mas desejar armazenar separadamente para a organização, posso estender uma classe para ter as duas?

ie class a extends b extends c

edit: Eu sei como estender as classes uma de cada vez, mas estou procurando um método para estender instantaneamente uma classe usando várias classes base - AFAIK, você não pode fazer isso em PHP, mas deve haver maneiras de contornar isso sem recorrer a class c extends b,class b extends a

atomicharri
fonte
Use agregação ou interfaces. Herança múltipla não existe no PHP.
Franck
2
Estou olhando para interfaces, pois não sou um grande fã de grandes hierarquias de classes. Mas não consigo ver como as interfaces realmente fazem alguma coisa?
Atomicharri
As interfaces permitem "herdar" apenas a API, não os corpos das funções. Força a classe a a implementar métodos da interface bec. Isso significa que, se você deseja herdar o comportamento, deve agregar objetos membros das classes bec em sua classe a.
Franck
2
Por favor, considere os traços como a resposta correta.
Daniel

Respostas:

170

Respondendo à sua edição:

Se você realmente deseja fingir herança múltipla, pode usar a função mágica __call ().

Isso é feio, embora funcione do ponto de vista do usuário da 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
fonte
1
Na verdade, eu gosto bastante da idéia de estender a classe. Existem limitações conhecidas de fazê-lo dessa maneira?
Atomicharri
3
Sem limitações, até onde eu sei, o PHP é uma linguagem muito permissiva para pequenos hacks como este. :) Como outros já apontaram, não é a maneira correta de fazê-lo.
Franck
64
Você não poderá usar métodos protegidos ou privados.
Wormhit
1
@wormhit, no entanto, eu não o recomendaria para uso na produção, pode-se usar o ReflectionClass para acessar métodos privados e protegidos.
Denis V
2
Isso não funcionará para Implements, que espera que o método realmente exista e o comportamento seja praticamente indefinido quando várias classes base tiverem um método com o mesmo nome. Isso também atrapalhará a capacidade do seu editor de fornecer dicas.
Erik
138

Você não pode ter uma classe que estenda duas classes base. Você não poderia ter.

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

No entanto, você pode ter uma hierarquia da seguinte maneira ...

Matron extends Nurse    
Consultant extends Doctor

Nurse extends HumanEntity
Doctor extends HumanEntity

HumanEntity extends DatabaseTable
DatabaseTable extends AbstractTable

e assim por diante.

Adão
fonte
Você pode explicar por que é certo primeiro herdar a Enfermeira na Matrona e depois declarar a herança da HumanEntity na Enfermeira?
Qwerty
7
@ Qwerty Porque a matrona tem qualidades adicionais de enfermeira, enquanto uma enfermeira tem todas as qualidades de um ser humano. Portanto, dama é uma enfermeira humano e, finalmente, tem capacidades Matrona
J-Dizzle
58

Você pode usar características que, esperamos, estarão disponíveis no PHP 5.4.

Traits é um mecanismo para reutilização de código em linguagens de herança única, como PHP. Um Trait visa reduzir algumas limitações de herança única, permitindo que um desenvolvedor reutilize conjuntos de métodos livremente em várias classes independentes que vivem em diferentes hierarquias de classes. A semântica da combinação de características e classes é definida de uma maneira que reduz a complexidade e evita os problemas típicos associados à herança múltipla e aos Mixins.

Eles são reconhecidos por seu potencial em oferecer suporte a melhor composição e reutilização, portanto, sua integração em versões mais recentes de idiomas como Perl 6, Squeak, Scala, Slate e Fortress. Traços também foram portados para Java e C #.

Mais informações: https://wiki.php.net/rfc/traits

Nikola Petkanski
fonte
Certifique-se de não abusar deles. Essencialmente, são funções estáticas que você aplica aos objetos. Você pode criar uma bagunça abusando deles.
Nikola Petkanski
2
Não acredito que essa não é a resposta selecionada.
Daniel
18

Classes não devem ser apenas coleções de métodos. Supõe-se que uma classe represente um conceito abstrato, com o estado (campos) e o comportamento (métodos) que alteram o estado. Usar a herança apenas para obter o comportamento desejado parece um projeto ruim de OO, e exatamente o motivo pelo qual muitas linguagens desaprovam a herança múltipla: para impedir a "herança de espaguete", ou seja, estendendo 3 classes porque cada uma tem um método que você precisa e terminando com uma classe que herda 100 métodos e 20 campos, mas usa apenas 5 deles.

Michael Borgwardt
fonte
1
Aceitarei sua afirmação de que a solicitação do OP constitui "projeto de OO ruim" ; basta olhar para o surgimento de Mixins para apoiar a posição de que adicionar métodos a uma classe de várias fontes é uma boa idéia arquitetonicamente. No entanto, vou dar a você que o PHP não fornece um conjunto ideal de recursos de linguagem para obter um "design" ideal, mas isso não significa que o uso dos recursos disponíveis para aproximá-lo seja necessariamente uma má idéia; basta olhar para a resposta de @ Franck.
MikeSchinkel
15

Existem planos para adicionar mix-ins em breve, acredito.

Mas até então, vá com a resposta aceita. Você pode resumir um pouco isso para criar uma classe "extensível":

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

Observe que, neste modelo, "OtherClass" conhece '$ foo'. OtherClass precisa ter uma função pública chamada "setExtendee" para configurar esse relacionamento. Então, se seus métodos forem chamados a partir de $ foo, ele poderá acessar $ foo internamente. Entretanto, ele não terá acesso a quaisquer métodos / variáveis ​​privados / protegidos, como uma classe estendida real faria.

Sam
fonte
12

Use traços como classes base. Em seguida, use-os em uma classe pai. Estenda.

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

Veja agora a mulher de negócios herdou logicamente os negócios e os humanos;

Alice
fonte
Eu estou preso com PHP 5.3 não há suporte característica: D
Nishchal Gautam
Por que não usar a característica em BusinessWomanvez de BusinessPerson? Então você pode ter uma herança múltipla real.
SOFe 30/01/19
@SOFe, porque o BusinessMan também pode estendê-lo. Então, quem já está fazendo o negócio pode estender busines pessoa, e recebe traços automaticamente
Alice
A maneira como você faz isso não faz diferença do que é possível na herança única, na qual o BusinessPerson estende uma classe abstrata chamada human. O que quero dizer é que o seu exemplo realmente não precisa de herança múltipla.
SOFe 6/02/19
Acredito que, até que o BusinessPerson fosse necessário, a BusinessWoman poderia ter herdado diretamente outras características. Mas o que tentei aqui é manter ambas as características em uma classe de mediador e usá-las, sempre que necessário. Portanto, posso esquecer a inclusão de ambas as características, se necessário novamente em algum outro lugar (conforme descrevi o BusinessMan). É tudo sobre estilos de código. Se você deseja incluir diretamente na sua turma. você pode prosseguir com isso - pois está absolutamente certo sobre isso.
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;
?>
Mehdi Homeily
fonte
4
Sua resposta deve conter uma explicação do seu código e uma descrição de como ele resolve o problema.
AbcAeffchen
1
É quase auto-explicativo, mas seria ótimo ter comentários ao longo do código.
Heroselohim 11/03/16
agora, se Ext1 e Ext2 tiverem uma função com o mesmo nome sempre que Ext1 for chamado, isso não dependerá dos parâmetros.
Alice
8

A resposta atualmente aceita por @Franck funcionará, mas na verdade não é uma herança múltipla, mas uma instância filho da classe definida fora do escopo; também existe uma __call()abreviação - considere usar em $this->childInstance->method(args)qualquer lugar que você precise do método da classe ExternalClass na classe "estendida".

Resposta exata

Não, você não pode, respectivamente, não realmente, como diz o manual da extendspalavra-chave :

Uma classe estendida sempre depende de uma única classe base, ou seja, herança múltipla não é suportada.

Resposta real

No entanto, como o @adam sugeriu corretamente, isso NÃO proíbe o uso de várias heranças hierárquicas.

Você pode estender uma classe, com outra e outra com outra e assim por diante ...

Um exemplo bem simples disso seria:

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

Nota importante

Como você deve ter notado, você só pode fazer várias inter-relações (2+) por hierarquia se tiver controle sobre todas as classes incluídas no processo - ou seja, não poderá aplicar esta solução, por exemplo, com classes internas ou com classes que você simplesmente não pode editar - se você quiser fazer isso, fica com a solução @Franck - instâncias filho.

... E, finalmente, exemplo com alguma saída:

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

Quais saídas

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

Eu li vários artigos desencorajando a herança em projetos (em oposição a bibliotecas / estruturas) e incentivando a programar interfaces contra uma interface, não contra implementações.
Eles também defendem OO por composição: se você precisar das funções nas classes a e b, faça c com membros / campos desse 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();
    }
}
PhiLho
fonte
Isso efetivamente se torna a mesma coisa que Franck e Sam mostram. Obviamente, se você optar por usar explicitamente a composição, deverá usar a injeção de dependência .
21813 MikeSchinkel
3

A herança múltipla parece funcionar no nível da interface. Eu fiz um teste no php 5.6.1.

Aqui está um código de trabalho:

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

Não achei que fosse possível, mas me deparei com o código-fonte SwiftMailer, na classe Swift_Transport_IoBuffer, que tem a seguinte definição:

interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream

Ainda não brinquei com ele, mas achei interessante compartilhar.

ling
fonte
1
interessante e irrelevante.
Yevgeniy Afanasyev
1

Acabei de resolver meu problema de "herança múltipla" com:

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;
    }

}

Dessa forma, eu tenho o poder de manipular a sessão dentro de um SessionResponse que estende o MyServiceResponsetype ainda sendo capaz de lidar com a sessão por si só.

blomman9
fonte
1

Se você deseja verificar se uma função é pública, consulte este tópico: https://stackoverflow.com/a/4160928/2226755

E use o método call_user_func_array (...) para muitos argumentos ou não.

Como isso :

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
fonte
0

O PHP ainda não suporta herança de várias classes; no entanto, suporta herança de múltiplas interfaces.

Veja http://www.hudzilla.org/php/6_17_0.php para alguns exemplos.

Tader
fonte
Ainda? Duvido que eles façam isso. OO moderno desencoraja herança múltipla, pode ser confuso.
PhiLho
0

O PHP não permite herança múltipla, mas é possível implementar várias interfaces. Se a implementação for "pesada", forneça implementação esquelética para cada interface em uma classe separada. Em seguida, você pode delegar toda a classe de interface para essas implementações esqueléticas via contenção de objetos .

petr k.
fonte
0

Sem saber exatamente o que você está tentando alcançar, sugiro examinar a possibilidade de redesenhar seu aplicativo para usar composição em vez de herança neste caso.

Joel
fonte
0

Sempre uma boa idéia é fazer a classe pai, com funções ... ou seja, adicionar toda essa funcionalidade ao pai.

E "mova" todas as classes que usam isso hierarquicamente para baixo. Eu preciso - reescrever funções, que são específicas.

publikz.com
fonte
0

Um dos problemas do PHP como linguagem de programação é o fato de que você pode ter apenas uma herança. Isso significa que uma classe só pode herdar de outra classe.

No entanto, na maioria das vezes, seria benéfico herdar de várias classes. Por exemplo, pode ser desejável herdar métodos de duas classes diferentes para impedir a duplicação de código.

Esse problema pode levar à classe que tem uma longa história familiar de herança que geralmente não faz sentido.

No PHP 5.4, um novo recurso da linguagem foi adicionado, conhecido como Traits. Um Trait é como um Mixin, pois permite misturar classes de Trait em uma classe existente. Isso significa que você pode reduzir a duplicação de código e obter os benefícios, evitando os problemas de herança múltipla.

Traits

Osama
fonte
-1

Esta não é uma resposta real, temos aulas duplicadas

... mas funciona :)

class A {
    //do some things
}

class B {
    //do some things
}

classe copy_B é copiar toda classe B

class copy_B extends A {

    //do some things (copy class B)
}

class A_B extends copy_B{}

agora

class C_A extends A{}
class C_B extends B{}
class C_A_b extends A_B{}  //extends A & B
MR coder
fonte
-3

classe A estende B {}

classe B estende C {}

Então A estendeu B e C

Robert DC
fonte
2
@rostamiani porque esse método também está modificando a classe B. O que queremos na maioria das vezes é ter duas classes não relacionadas Be C(provavelmente de bibliotecas diferentes) e ser herdadas simultaneamente por A, de modo que Acontenha tudo de ambos Be C, mas Bnão contenha nada Ce vice-versa.
SOFe 30/01/19