php 7 интерфейсов, подсказки типа возврата и сам

<p>я столкнулся с проблемой использования хинтинга возвращаемого типа в php 7. насколько я понимаю, хинтинг <code>: self</code> означает, что вы намереваетесь, чтобы реализующий класс возвратил сам себя. поэтому я использовал <code>: self</code> в своих интерфейсах, чтобы показать это, но когда я попытался реализовать интерфейс, я получил ошибки совместимости. </p><p>ниже приведена простая демонстрация проблемы, с которой я столкнулся: </p><pre><code>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"); </code></pre><p>ожидаемый результат был: </p><blockquote> <p>фред вильма барни бетти</p> </blockquote><p>что я на самом деле получаю: </p><blockquote> <p>неустранимая ошибка php: объявление foo :: bar (int $ baz): foo должно быть совместимо с ifoo :: bar (int $ baz): ifoo в test.php в строке 7</p> </blockquote><p>дело в том, что foo является реализацией ifoo, поэтому, насколько я могу судить, реализация должна быть полностью совместима с данным интерфейсом. вероятно, я мог бы решить эту проблему, изменив интерфейс или реализующий класс (или оба), чтобы он возвращал подсказку для интерфейса по имени вместо использования <code>self</code>, но я понимаю, что семантически <code>self</code> означает «вернуть экземпляр класса, который вы только что назвали метод на ". поэтому изменение его на интерфейс теоретически означало бы, что я мог бы вернуть любой экземпляр чего-либо, реализующего интерфейс, когда мое намерение заключается в том, чтобы вызванный экземпляр был тем, что будет возвращено. </p><p>это недосмотр в php или это преднамеренное дизайнерское решение? если это первое, есть ли шанс увидеть это исправленным в php 7.1? если нет, то каков правильный способ возврата, указывающий на то, что ваш интерфейс ожидает, что вы вернете экземпляр, для которого вы только что вызвали метод, для создания цепочки? </p>

<p><code>self</code> не ссылается на экземпляр, он ссылается на текущий класс. интерфейс не может указать, что должен быть возвращен тот же самый <em>instance</em> - использование <code>self</code> в том виде, в котором вы пытаетесь, только принудительно указывает, что возвращаемый экземпляр будет того же класса.</p><p>тем не менее, декларации возвращаемого типа в php должны быть инвариантными, а то, что вы пытаетесь, ковариантно.</p><p>ваше использование <code>self</code> эквивалентно:</p><pre><code>interface ifoo { public function bar (string $baz) : ifoo; } class foo implements ifoo { public function bar (string $baz) : foo {...} } </code></pre><p>что не разрешено.</p><hr><p> <a href="https://wiki.php.net/rfc/return\_types">возвращаемые объявления типов rfc</a> has <a href="https://wiki.php.net/rfc/return\_types#variance\_and\_signature\_validation">это сказать</a> :</p><blockquote> <p>применение объявленного типа возврата во время наследования является инвариантным; это означает, что когда подтип переопределяет родительский метод, тогда возвращаемый тип дочернего элемента должен точно соответствовать родительскому методу и не может быть опущен. если родитель не объявляет тип возвращаемого значения, тогда ребенку разрешается объявить его.</p> <p>...</p> <p>этот rfc первоначально предлагал ковариантные типы возврата, но был изменен на инвариантный из-за нескольких проблем. можно добавить ковариантные типы возвращаемых данных в какой-то момент в будущем.</p> </blockquote><hr><p>пока, по крайней мере, лучшее, что вы можете сделать, это:</p><pre><code>interface ifoo { public function bar (string $baz) : ifoo; } class foo implements ifoo { public function bar (string $baz) : ifoo {...} } </code></pre>

<p>это также может быть решением, что вы не определяете явно тип возвращаемого значения в интерфейсе, только в phpdoc, а затем вы можете определить определенный тип возвращаемого значения в реализациях:</p><pre><code>interface ifoo { public function bar (string $baz); } class foo implements ifoo { public function bar (string $baz) : foo {...} } </code></pre>

<p>в случае, если вы хотите принудительно вызвать интерфейс, этот метод вернет объект, но тип объекта будет не типом интерфейса, а самим классом, тогда вы можете написать это так:</p><pre><code>interface ifoo { public function bar (string $baz) : object; } class foo implements ifoo { public function bar (string $baz) : self {...} } </code></pre><p>работает с php 7.2.</p>

<p>это выглядит как ожидаемое поведение для меня.</p><p>просто измените свой метод <code>foo::bar</code>, чтобы он возвращал <code>ifoo</code> вместо <code>self</code>, и покончите с этим.</p><p>объяснение:</p><p><code>self</code>, как используется в интерфейсе, означает «объект типа <code>ifoo</code>.»<br> <code>self</code>, используемая в реализации, означает «объект типа <code>foo</code>.»</p><p>поэтому типы возвращаемых данных в интерфейсе и реализации явно не совпадают.</p><p>в одном из комментариев упоминается java и будет ли у вас эта проблема. ответ - да, у вас возникла бы та же проблема <em>, если бы java позволяла вам писать такой код, чего не происходит.</em> поскольку java требует от вас использовать имя типа вместо ярлыка php <code>self</code>, вы никогда не увидите этого. (см. <a href="http://blog.joda.org/2007/08/java-7-self-types\_1953.html">здесь</a> для обсуждения <em>аналогичной</em> проблемы в java.)</p>

<p>запуская некоторые тесты с php 7.3, я не могу заставить его жаловаться (даже со строгим), когда я делаю это ...</p><pre><code>interface a { function f() {} } interface b { function f():self {} } </code></pre><p>либо мой тест не пройден, либо php изменил некоторые вещи. вообще говоря, если вы уменьшите возможные типы возврата, то<em> стремится </em>не нарушать ооп. как и в любом классе, использующем этот метод, он может обрабатывать любые возвращаемые значения, включая подмножество возвращаемых типов. противоположное - примерно случай для параметров.</p><p>они реализовали это в 7.2.</p>