PHPの抽象プロパティ


126

PHPで抽象クラスプロパティを定義する方法はありますか?

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}

回答:


154

プロパティを定義するようなものはありません。

プロパティは、初期化時にメモリに予約されるデータのコンテナーであるため、宣言できるのはプロパティのみです。

一方、関数は定義せずに(型、名前、パラメーター)宣言でき(関数本体が欠落)、したがって抽象化できます。

「抽象」は、何かが宣言されたが定義されていないことを示しているだけなので、それを使用する前に、定義する必要があるか、役に立たなくなります。


58
静的プロパティで「抽象」という単語を使用できなかった明確な理由はありませんが、意味が少し異なります。たとえば、サブクラスがプロパティの値を提供する必要があることを示すことができます。
frodeborli 2014

2
TypeScriptには、抽象プロパティとアクセサがあります。PHPでそれが不可能であることは悲しいです。
ИльяЗеленько

52

いいえ、コンパイラを使用して、$tablename変数に対してランタイムチェック(たとえば、コンストラクタで)を使用する必要があることを強制する方法はありません。例:

class Foo_Abstract {
  public final function __construct(/*whatever*/) {
    if(!isset($this->tablename))
      throw new LogicException(get_class($this) . ' must have a $tablename');
  }
}

これをFoo_Abstractのすべての派生クラスに適用するには、Foo_Abstractのコンストラクターを作成してfinal、オーバーライドを防止する必要があります。

代わりに抽象ゲッターを宣言することができます:

abstract class Foo_Abstract {
  abstract public function get_tablename();
}

class Foo extends Foo_Abstract {
  protected $tablename = 'tablename';
  public function get_tablename() {
    return $this->tablename;
  }
}

素晴らしい機能です。抽象プロパティを実装する方法が好きです。
Mathieu Dumoulin、2011年

4
これには、抽象基本クラスのコンストラクタをfinalにする必要があります。
11

3
いくつかの説明:コンストラクター内でチェックを行い、それが必須である必要がある場合、すべてのインスタンスのインスタンス化で確実に実行されるようにする必要があります。したがって、たとえばクラスを拡張してコンストラクタを置き換えるなどして、削除されないようにする必要があります。最後のキーワードが、あなたはそうすることができるようになります。
11

1
「抽象ゲッター」ソリューションが好きです。クラスの抽象で関数を宣言するときは、クラス自体を抽象で宣言する必要があります。つまり、拡張されて完全に実装されない限り、クラスは使用できません。そのクラスを拡張するときは、「getter」関数の実装を提供する必要があります。つまり、関数は何かを返す必要があるため、拡張クラス内に関連プロパティも作成する必要があります。このパターンに従うと、抽象プロパティを宣言した場合と同じ結果が得られますが、これもクリーンで明確なアプローチです。それが実際に行われている方法です。
Salivan

1
抽象ゲッターを使用すると、定数値を返すのではなく、値を生成することによって実装することもできます。抽象プロパティ、特に静的プロパティではそうすることはできません。
トビア

27

プロパティのコンテキストに応じて、子オブジェクトで抽象オブジェクトプロパティの宣言を強制したい場合staticは、抽象オブジェクトコンストラクターまたはセッター/ゲッターメソッドのプロパティのキーワードで定数を使用します。オプションでを使用finalして、拡張クラスでメソッドがオーバーライドされないようにすることができます。

それ以外では、子オブジェクトは、再定義された場合、親オブジェクトのプロパティとメソッドをオーバーライドします。たとえば、プロパティがprotected親のように宣言さpublicれ、子のように再定義された場合、結果のプロパティはパブリックになります。ただし、プロパティがprivate親で宣言されている場合、プロパティはそのまま残りprivate、子は使用できません。

http://www.php.net//manual/en/language.oop5.static.php

abstract class AbstractFoo
{
    public $bar;

    final public function __construct()
    {
       $this->bar = static::BAR;
    }
}

class Foo extends AbstractFoo
{
    //const BAR = 'foobar';
}

$foo = new Foo; //Fatal Error: Undefined class constant 'BAR' (uncomment const BAR = 'foobar';)
echo $foo->bar;

4
ここで最もエレガントなソリューション
Jannie Theunissen 2017年

24

上記のように、そのような正確な定義はありません。ただし、私はこの簡単な回避策を使用して、子クラスに「抽象」プロパティを定義させます。

abstract class Father 
{
  public $name;
  abstract protected function setName(); // now every child class must declare this 
                                      // function and thus declare the property

  public function __construct() 
  {
    $this->setName();
  }
}

class Son extends Father
{
  protected function setName()
  {
    $this->name = "son";
  }

  function __construct(){
    parent::__construct();
  }
}

エレガントですが、staticプロパティの問題は解決しません。
Robbert 14

1
抽象メソッドをプライベートにすることはできないと思います。
Zorji、2015年

@ Phate01私が理解しているように、コメント自体には記載されていますがthe only "safe" methods to have in a constructor are private and/or final ones、私の回避策はそのような場合ではありませんか?

4
これは見栄えは良いですが、子クラスに実際の設定を強制するものではありません$namesetName()実際に設定せずに関数を実装できます$name
JohnWE、2016年

3
getName代わりにを使用した$name方がうまくいくと思います。abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
Hamid 2016

7

今日も同じ質問をしたので、2セントを加算します。

abstractプロパティが必要な理由は、サブクラスがそれらを定義し、そうでない場合に例外をスローすることを確認するためです。私の特定のケースでは、static同盟国と連携できる何かが必要でした。

理想的には私はこのようなものを望みます:

abstract class A {
    abstract protected static $prop;
}

class B extends A {
    protected static $prop = 'B prop'; // $prop defined, B loads successfully
}

class C extends A {
    // throws an exception when loading C for the first time because $prop
    // is not defined.
}

私はこの実装で終わった

abstract class A
{
    // no $prop definition in A!

    public static final function getProp()
    {
        return static::$prop;
    }
}

class B extends A
{
    protected static $prop = 'B prop';
}

class C extends A
{
}

ご覧のとおり、ではA定義していません$propが、staticゲッターで使用しています。したがって、次のコードは機能します

B::getProp();
// => 'B prop'

$b = new B();
$b->getProp();
// => 'B prop'

ではC、他の一方で、私は定義していない$prop、私は例外を取得するので、:

C::getProp();
// => Exception!

$c = new C();
$c->getProp();
// => Exception!

getProp() 例外を取得するにはメソッドを呼び出す必要がありますが、クラスの読み込み時に取得することはできませんが、少なくとも私の場合は、望ましい動作に非常に近いです。

私は、ある賢い人(別名6か月で私自身)がやる気を起こさせないように定義getProp()していますfinal

class D extends A {
    public static function getProp() {
        // really smart
    }
}

D::getProp();
// => no exception...

これは非常に独創的なハックです。うまくいけば、これは将来行う必要はありません。
CMCDragonkai 2017

6

あなたのコードをテストするだけであなたが知ることができるように:

致命的なエラー:3行目の...ではプロパティを抽象として宣言できません

いいえ、ありません。PHPでは、プロパティを抽象として宣言することはできません。

ただし、ゲッター/セッター関数の抽象を実装することはできますが、これはあなたが探しているものかもしれません。

プロパティは実装されておらず(特にパブリックプロパティ)、存在する(または存在しない)だけです。

$foo = new Foo;
$foo->publicProperty = 'Bar';

6

抽象プロパティの必要性は、設計の問題を示している可能性があります。多くの回答は一種のテンプレートメソッドパターンを実装して機能しますが、常に奇妙に見えます。

元の例を見てみましょう:

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}

何かをマークすることabstractは、それが必須のことであることを示すことです。さて、(この場合)必須のは必須の依存関係であるため、インスタンス化中にコンストラクタに渡す必要があります

class Table
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function name(): string
    {
        return $this->name;
    }
}

次に、より具体的な名前付きクラスが実際に必要な場合は、次のように継承できます。

final class UsersTable extends Table
{
    public function __construct()
    {
        parent::__construct('users');
    }
}

これは、DIコンテナーを使用していて、異なるオブジェクトに異なるテーブルを渡す必要がある場合に役立ちます。


3

PHP 7では、抽象的な「プロパティ」を作成するのがかなり簡単になっています。上記と同様に、抽象関数を作成してそれらを作成しますが、PHP 7ではその関数の戻り値の型を定義できるため、誰でも拡張できる基本クラスを構築するときに非常に簡単になります。

<?php

abstract class FooBase {

  abstract public function FooProp(): string;
  abstract public function BarProp(): BarClass;

  public function foo() {
    return $this->FooProp();
  }

  public function bar() {
    return $this->BarProp()->name();
  }

}

class BarClass {

  public function name() {
    return 'Bar!';
  }

}

class FooClass extends FooBase {

  public function FooProp(): string {
    return 'Foo!';
  }

  public function BarProp(): BarClass {
    // This would not work:
    // return 'not working';
    // But this will!
    return new BarClass();
  }

}

$test = new FooClass();
echo $test->foo() . PHP_EOL;
echo $test->bar() . PHP_EOL;

1

オブジェクトの存続期間中にtablenameの値が変更されない場合、以下は単純でありながら安全な実装になります。

abstract class Foo_Abstract {
    abstract protected function getTablename();

    public function showTableName()
    {
        echo 'my table name is '.$this->getTablename();
    }
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' getTablename()
    protected function getTablename()
    {
        return 'users';
    }
}

ここで重要なのは、文字列値 'users'が指定され、子クラス実装のgetTablename()に直接返されることです。この関数は「読み取り専用」プロパティを模倣します。

これは、追加の変数を使用する前述のソリューションとかなり似ています。マルコのソリューションも好きですが、少し複雑になる可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.