PHP 7で戻り型のヒントを使用すると、問題が発生しました。私の理解では、: self
のヒントは、実装クラスが自分自身を返すことを意味します。そのため、インターフェイスで: self
を使用してそれを示しましたが、実際にインターフェイスを実装しようとしたときに互換性エラーが発生しました。
以下は、私が遭遇した問題の簡単なデモです。
interface iFoo
{
public function bar (string $baz) : self;
}
class Foo implements iFoo
{
public function bar (string $baz) : self
{
echo $baz . PHP_EOL;
return $this;
}
}
(new Foo ()) -> bar ("Fred")
-> bar ("Wilma")
-> bar ("Barney")
-> bar ("Betty");
予想される出力は次のとおりです。
フレッド・ウィルマ・バーニー・ベティ
私が実際に得るものは:
PHP致命的エラー:Foo :: bar(int $ baz)の宣言:FooはiFoo :: bar(int $ baz)と互換性がなければなりません:test.phpの7行目
FooはiFooの実装であるため、特定のインターフェイスと完全に互換性がある必要があると言えます。インターフェイスまたは実装クラス(またはその両方)を変更して、self
を使用する代わりに名前でインターフェイスのヒントを返すことにより、おそらくこの問題を修正できますが、私の理解では、意味的にself
は「メソッドを呼び出したばかりのクラスのインスタンス」。したがって、それをインターフェイスに変更すると、理論的には、返されるのは呼び出されたインスタンスを目的とするときにインターフェイスを実装する何かのインスタンスを返すことができます。
これはPHPの見落としですか、それとも意図的な設計上の決定ですか?前者の場合、PHP 7.1で修正される可能性はありますか?そうでない場合、インターフェイスが、チェーンのためにメソッドを呼び出したばかりのインスタンスを返すことを期待しているというヒントを返す正しい方法は何ですか?
self
はインスタンスを参照せず、現在のクラスを参照します。インターフェイスが同じinstanceを返す必要があることを指定する方法はありません-self
を使用して、返されたインスタンスが同じクラスであることを強制します。
つまり、PHPの戻り値の型宣言は不変である必要がありますが、試行しているものは共変です。
self
の使用は次と同等です:
interface iFoo
{
public function bar (string $baz) : iFoo;
}
class Foo implements iFoo
{
public function bar (string $baz) : Foo {...}
}
許可されていません。
Return Type Declarations RFC には これは言う があります:
継承中に宣言された戻り値の型の適用は不変です。これは、サブタイプが親メソッドをオーバーライドする場合、子の戻り値の型は親と完全に一致する必要があり、省略できないことを意味します。親が戻り値の型を宣言しない場合、子は戻り値の型を宣言できます。
...
このRFCは元々共変の戻り値の型を提案していましたが、いくつかの問題のために不変に変更されました。将来のある時点で、共変の戻り値の型を追加することが可能です。
とりあえず、少なくともあなたができる最善のことは:
interface iFoo
{
public function bar (string $baz) : iFoo;
}
class Foo implements iFoo
{
public function bar (string $baz) : iFoo {...}
}
また、PHPDocでのみ明示的にインターフェイスで戻り値の型を定義せずに、実装で特定の戻り値の型を定義できるという解決策もあります。
interface iFoo
{
public function bar (string $baz);
}
class Foo implements iFoo
{
public function bar (string $baz) : Foo {...}
}
PHP 7.3を使用していくつかのテストを実行すると、これを実行しても(厳密でも)文句を言うことができません...
interface A {
function f() {}
}
interface B {
function f():self {}
}
私のテストが壊れているか、PHPが変更されています。一般的に、可能性のある戻り値の型を減らすと、そのtendsはOOPを中断しません。そのメソッドを使用する他のクラスと同様に、戻り値の型のサブセットを含む任意の戻り値を処理できます。反対は、おおよそパラメーターの場合です。
7.2でこれを実装しました。
インターフェースから強制したい場合、そのメソッドはオブジェクトを返しますが、オブジェクトのタイプはインターフェースのタイプではなく、クラス自体である場合、次のように記述できます。
interface iFoo {
public function bar (string $baz) : object;
}
class Foo implements iFoo {
public function bar (string $baz) : self {...}
}
PHP 7.4以降で機能しています。
これは私にとって予想される動作のように見えます。
iFoo
ではなくself
を返すようにFoo::bar
メソッドを変更するだけで完了です。
説明:
インターフェイスで使用されるself
は、「タイプiFoo
のオブジェクト」を意味します。self
は、「タイプFoo
のオブジェクト」を意味します。
したがって、インターフェイスと実装の戻り値の型は明らかに同じではありません。
コメントの1つには、Javaと、この問題が発生するかどうかが記載されています。答えはイエスです。もしJavaがそのようなコードを書くことを許したなら、同じ問題を持っているでしょう-それはしません。以来Java PHPのself
ショートカットの代わりにタイプの名前を使用する必要がありますが、実際にこれを見ることはありません。 (Javaのsimilar問題の説明については here を参照してください。)