PHPで複数のクラスを使用してクラスを拡張できますか?


150

必要な機能を持つクラスをいくつか持っているが、整理のために別々に保存したい場合、クラスを拡張して両方を持たせることはできますか?

すなわち class a extends b extends c

編集:私は一度にクラスを拡張する方法を知っていますが、複数の基本クラスを使用してクラスを即座に拡張する方法を探しています-私はこれをPHPで行うことはできませんが、に頼らずにそれを回避する方法があるはずですclass c extends bclass b extends a


集約またはインターフェースを使用します。PHPには多重継承はありません。
フランク

2
私は大規模なクラス階層の大ファンではないので、インターフェースを調べています。しかし、インターフェイスが実際にどのように機能するのかわかりませんか?
atomicharri 2008

インターフェイスを使用すると、関数本体ではなくAPIのみを「継承」できます。クラスaに、インターフェイスbおよびcからのメソッドを実装するように強制します。つまり、振る舞いを継承したい場合は、クラスbとcのメンバーオブジェクトをクラスaに集約する必要があります。
フランク


2
特性を正解と考えてください。
ダニエル

回答:


170

あなたの編集に答える:

多重継承を偽造したい場合は、マジック関数__call()を使用できます。

クラス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");

「abcdef」を出力します


1
私は実際にクラスを拡張するアイデアがとても好きです。この方法で行うことの既知の制限はありますか?
atomicharri 2008

3
私の知る限り、PHPはこのような小さなハックに対して非常に寛容な言語です。:)他の人が指摘したように、それはそれを行う適切なOOP方法ではありません。
フランク

64
保護されたメソッドやプライベートメソッドを使用することはできません。
ワームヒット

1
@wormhit、ただし、プロダクションでの使用はお勧めしません。ReflectionClassを使用してプライベートメソッドと保護されたメソッドにアクセスできます。
デニスV

2
これは、メソッドが実際に存在することを期待する実装では機能せず、複数の基本クラスに同じ名前のメソッドがある場合の動作はほとんど定義されていません。また、ヒントを提供するエディターの機能を台無しにします。
エリック

138

2つの基本クラスを拡張するクラスを持つことはできません。あなたは持つことができませんでした。

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

ただし、次のような階層を持つこともできます...

Matron extends Nurse    
Consultant extends Doctor

Nurse extends HumanEntity
Doctor extends HumanEntity

HumanEntity extends DatabaseTable
DatabaseTable extends AbstractTable

等々。


最初にNurseをMatronに継承し、次にHumanEntityの継承をNurseに宣言することが正しい理由を説明できますか?
Qwerty、2014

7
@Qwerty Matronには看護師の追加の資質があるのに対し、看護師には人間のすべての資質があるからです。したがって、Matronは人間の看護師であり、最終的にMatron機能を備えています
J-Dizzle

58

PHP 5.4から利用できるようになる特性を使用できます。

トレイトは、PHPなどの単一継承言語でコードを再利用するためのメカニズムです。特性は、開発者が異なるクラス階層にあるいくつかの独立したクラスでメソッドのセットを自由に再利用できるようにすることで、単一継承のいくつかの制限を減らすことを目的としています。トレイトとクラスの組み合わせのセマンティクスは、複雑さを軽減し、多重継承とミックスインに関連する一般的な問題を回避する方法で定義されています。

これらは、より良い構成と再利用をサポートする可能性が認められているため、Perl 6、Squeak、Scala、Slate、Fortressなどの新しいバージョンの言語に統合されています。トレイトもJavaとC#に移植されています。

詳細:https : //wiki.php.net/rfc/traits


それらを乱用しないようにしてください。基本的に、これらはオブジェクトに適用する静的関数です。あなたはそれらを乱用することによって混乱を作成することができます。
Nikola Petkanski、2017

2
これが選ばれた答えではないとは信じられません。
ダニエル

18

クラスは、単なるメソッドのコレクションではありません。クラスは、状態(フィールド)と状態を変更する動作(メソッド)の両方で、抽象的な概念を表すことになっています。継承を使用して目的の動作を取得するだけでオブジェクト指向の設計が悪いように聞こえ、正確に多くの言語が多重継承を許可しない理由:「スパゲッティ継承」を防ぐために、つまり、それぞれに必要なメソッドがあるため3つのクラスを拡張し、 100のメソッドと20のフィールドを継承するクラスですが、そのうちの5つしか使用しません。


1
OPのリクエストが「悪いOO設計」を構成するというあなたの主張に問題を取り上げます 。Mixinsの出現を見て、複数のソースからクラスにメソッドを追加することは、構造的には良いアイデアであるという立場をサポートします。ただし、PHPが最適な「設計」を実現するための最適な言語機能セットを提供していないことをお伝えしますが、これは、概算に使用できる機能を使用することが必ずしも悪い考えであることを意味するものではありません。@Franckの答えを見てください。
MikeSchinkel 2013年

15

近いうちにミックスインを追加する計画があると思います。

しかし、それまでは、受け入れられた答えを使用してください。あなたはそれを少し抽象化して「拡張可能な」クラスを作ることができます:

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

このモデルでは、「OtherClass」が$ fooについて「知る」ことに注意してください。OtherClassには、この関係を設定するための「setExtendee」というパブリック関数が必要です。次に、そのメソッドが$ fooから呼び出された場合、内部で$ fooにアクセスできます。ただし、実際の拡張クラスのように、プライベート/保護されたメソッド/変数にはアクセスできません。


12

基本クラスとして特性を使用します。次に、それらを親クラスで使用します。それを拡張します。

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

ビジネスの女性が論理的に受け継がれたビジネスと人間の両方を今見てください。


私はphp 5.3で立ち往生しています。特性のサポートはありません:D
Nishchal Gautam '20

BusinessWoman代わりにトレイトを使用しないのはなぜBusinessPersonですか?その後、実際の多重継承を行うことができます。
SOFe

@ SOFe、BusinessManも拡張できるため。したがって、ビジネスをしている人は誰でもビジネスの人を伸ばすことができ、特性を自動的に取得します
アリス

これを行う方法は、BusinessPersonがヒューマンと呼ばれる抽象クラスを拡張する単一継承で可能なことと違いはありません。私のポイントは、あなたの例は本当に多重継承を必要としないということです。
SOFe

BusinessPersonが必要になるまで、BusinessWomanは他の特性を直接継承することができました。しかし、ここで試したのは、両方の特性をメディエータークラスに保持し、必要に応じてそれを使用することです。そのため、別の場所で再度必要になった場合は、両方の特性を含めることを忘れることができます(BusinessManについて説明したとおり)。それはすべてコードスタイルに関するものです。クラスに直接含めたい場合。あなたはそれを進めることができます-あなたはそれについて完全に正しいので。
アリス

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

4
回答には、コードの説明と、コードが問題を解決する方法の説明を含める必要があります。
AbcAeffchen 2014年

1
それはほとんど自明ですが、コードに沿ってコメントがあると素晴らしいでしょう。
ヒーローセロヒム2016年

これで、Ext1とExt2の両方に同じ名前の関数がある場合、常にExt1が呼び出され、パラメーターにまったく依存しなくなります。
アリス

8

@Franckによる現在受け入れられている回答は機能しますが、実際には多重継承ではなく、スコープ外で定義されたクラスの子インスタンスです。__call()省略形 もあります。$this->childInstance->method(args)ます。「拡張」クラスでExternalClassクラスメソッドが必要な場所することをください。

正確な答え

キーワードマニュアルに次のextendsように記載されています

拡張クラスは常に単一の基本クラスに依存しています。つまり、多重継承はサポートされていません。

本当の答え

ただし、@ adamが正しく提案したように、これは複数の階層継承を使用することを禁止しません。

クラスを拡張したり、別のクラスを拡張したりできます。

これに関する非常に単純な例は次のようになります。

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

重要な注意点

お気づきかもしれませんが、プロセスに含まれるすべてのクラスを制御できる場合にのみ、階層ごとに複数(2+)の積分を実行できます。実行できます。つまり、このソリューションは、たとえば組み込みクラスや、単に編集することはできません。編集したい場合は、@ Franckソリューションの子インスタンスが残ります。

...そして最後にいくつかの出力の例:

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

どの出力

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

6

(ライブラリー/フレームワークではなく)プロジェクトでの継承を阻止し、実装ではなく、インターフェイスのプログラミングを奨励するいくつかの記事を読みました。
また、構成によってオブジェクト指向を推奨しています。クラスaとbの関数が必要な場合は、cにこのタイプのメンバー/フィールドを持たせます。

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

これは事実上、フランクとサムが示すものと同じものになります。もちろん、コンポジションを明示的に使用する場合は、依存性注入を使用する必要があります。
MikeSchinkel 2013年

3

多重継承はインターフェイスレベルで機能するようです。php 5.6.1でテストを行いました。

ここに作業コードがあります:

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

それが可能だとは思いませんでしたが、SwiftMailerのソースコードのSwift_Transport_IoBufferクラスで、次のように定義されていました。

interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream

まだ遊んでいませんが、共有するのは面白いかもしれないと思いました。


1
面白くて無関係です。
Yevgeniy Afanasyev

1

私は私の「多重継承」問題を次のように解決しました:

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

}

このようにして、MyServiceResponsetypeを拡張してSessionResponse内のセッションを操作し、それ自体でSessionを処理できるようにします。


1

関数が公開されているかどうかを確認する場合は、次のトピックを参照してください。https//stackoverflow.com/a/4160928/2226755

そして、多くの引数またはそうでない引数に対してcall_user_func_array(...)メソッドを使用します。

このような :

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はまだ複数のクラスの継承をサポートしていませんが、複数のインターフェイスの継承をサポートしています。

いくつかの例については、http://www.hudzilla.org/php/6_17_0.phpを参照してください


まだ?彼らがそれをするのではないかと思う。現代のオブジェクト指向は多重継承を思いとどまらせます、それは面倒です。
PhiLho 2008

0

PHPでは多重継承は許可されていませんが、複数のインターフェースを実装することで実現できます。実装が「重い」場合は、個別のクラスの各インターフェイスにスケルトン実装を提供します。次に、オブジェクトコンテインメントを介して、すべてのインターフェースクラスをこれらのスケルトン実装に委任できます


0

あなたが達成しようとしていることを正確に知らないので、この場合は継承ではなくコンポジションを使用するようにアプリケーションを再設計する可能性を検討することをお勧めします。


0

常に良いアイデアは、関数を使用して親クラスを作成することです。つまり、このすべての機能を親に追加します。

そして、これを使用するすべてのクラスを階層的に「移動」します。私は必要です-特定の関数を書き換えてください。


0

プログラミング言語としてのPHPの問題の1つは、継承を1つしか持てないことです。つまり、クラスは他の1つのクラスからのみ継承できます。

ただし、多くの場合、複数のクラスから継承することは有益です。たとえば、コードの重複を防ぐために、いくつかの異なるクラスからメソッドを継承することが望ましい場合があります。

この問題は、継承の家族歴が長く、しばしば意味をなさないクラスにつながる可能性があります。

PHP 5.4では、Traitsと呼ばれる言語の新機能が追加されました。特性は、特性クラスを既存のクラスに混在させることができるという点で、ミックスインのようなものです。つまり、多重継承の問題を回避しながら、コードの重複を減らしてメリットを得ることができます。

特性


-1

これは本当の答えではありません。クラスが重複しています

...しかし、それは動作します:)

class A {
    //do some things
}

class B {
    //do some things
}

クラスcopy_BはすべてのクラスBをコピーします

class copy_B extends A {

    //do some things (copy class B)
}

class A_B extends copy_B{}

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

-3

クラスAはB {}を拡張します

クラスBはC {}を拡張します

次に、AはBとCの両方を拡張しました


2
このメソッドもクラスを変更しているため、@ rostamiani B。たいていの場合、2つの無関係なクラスBC(おそらく異なるライブラリからの)クラスをA用意し、Aから同時に継承して、両方BCが、Bから何かが含まれていませんCし、その逆。
SOFe
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.