Типы данных в PHP: self и parent

Начиная с PHP 5.0, мы можем указывать типы данных аргументов возможности, а с выходом новых версий PHP число возможных type hints увеличилось. Краткий ликбез в PHP self:

<?phppublic function foo( // Since 5.0 self $self, parent $parent, FooInterface $foo, // Since 5.1 array $array, // Since 5.4 callable $callable, // Since 7.0 bool $bool, float $float, int $int, string $string) {...}

В PHP 5.0 мы можем указывать типы настроек при определении возможности, а начиная с PHP 7.0, можем использовать для этого скалярные типы данных. Также, начиная с PHP 7.0, можно без проблем указывать типы возвращаемых функцией значений. Давайте подробнее рассмотрим типы self и parent. Они были доступны, но мы редко видим их использование. Почему так?

self

PHP self означает объект того же типа(текущего класса или подкласса). Для каждой переменной должно выполняться условие instanceof по отношению к текущему классу.

Использование в виде аргумента возможности

<?phpinterface Person {  public function addSibling(self $sibling)    {        //...    }}

Бывает надо спроектировать связь между объектами одного типа. В данном случае тип self может быть полезен. Когда мы создаем реализацию этого интерфейса(или рефакторим код без соблюдения SOLID принципов) следует заменить тип self оригинальным названием класса или интерфейса:

<?phpuse Person as PersonContract;class Person implements PersonContract {    public function addSibling(PersonContract $sibling)    {        //...    }}

Использование в виде типа возвращаемого значения

Давайте разберемся, в каких ситуациях можно без проблем использовать new self PHP в виде типа возвращаемого значения.

Сеттеры

Один из самых очевидных примеров использования — это сеттеры(или мутаторы) с функциею сцепления(chaining):

<?php$foo->setBar($bar)->setBaz($baz);

Когда вы используете self, тип возвращаемого значения не важен, если способ возвращает клонированный объект(это тот случай, когда вы имеете дело с неизменяемыми объектами). Возвращаемый объект того же типа, что и объект, способ которого был вызван:

<?phpinterface Foo {    /**     * @param Bar $bar     * @return self     */    public function setBar(Bar $bar): self;}

При расширении или реализации способа в дочернем классе надо явно указать тип, чтобы объявление было совместимым:

<?phpuse Foo as FooContract;class Foo implements FooContract{    /**     * {@inheritdoc}     */    public function setBar(Bar $bar): FooContract    {        //...        return $this;    }}

Если вы будете использовать этот объект, то он сообщит IDE, что все, что возвращается, будет иметь тип Foo(интерфейс). В итоге он не будет автоматом заполнять данным способом имена в реализации класса Foo:

<?php/* @var Foo $foo */$foo->setBar(new Bar())->setBaz(new Baz());

В приведенном выше примере, если setBaz не будет способом интерфейса Foo, он не будет признан IDE. Таким образом, для построения цепочки способов тип возвращаемого значения self PHP class не особенно полезен.

До PHP 7.0 мы обычно объявляли только типы возвращаемых значений в DocBlock comments с @return $this, @return self или @return static. Для цепочечных способов, которые я до сих пор использую, @return static и @return $this в docblocks.

Фабричные способы

Нелегко найти пример способа, который не будет сеттером и возвращает объект того же класса. Но вот он:

<?phpinterface Foo{    public function wrapWithLogDecorator(PsrLogLoggerInterface $logger) : self;}

Реализация будет выглядеть так:

<?phptrait LoggerWrapping{    public function wrapWithLogDecorator(PsrLogLoggerInterface $logger) : Foo    {         return new LogWrapper($this);    }}

Я не говорю, что это хороший пример. Этот способ не указан явно в интерфейсе. Есть, конечно, фабричные способы и лучше. К примеру, способы, которые могут возвращать тип PHP self . Просто на данный момент я не могу придумать практичный пример.

parent

Документация PHP говорит, что parent допустимый тип данных. Давайте разберемся, что он из себя представляет:

<?phpclass Foo extends Bar {    public function setBar(parent $bar)    {        // ...    }}

parent указывает на класс, который вы расширяете. Для примера выше: Bar или один из его дочерних классов. То есть передаваемый объект может быть иным дочерним от Bar .

В данном случае parent не может указывать на интерфейс. И схема работы примерна такая же, когда вы вызываете способ родительского класса ( parent::setBar к примеру). Иными словами, parent можно легко использовать только тогда, когда текущий класс расширяет какой-то другой класс.

Есть пару причин, почему тип parent не используется:

  • Когда при разработке кода соблюдаются SOLID принципы, то почти все — это либо интерфейс, либо класс реализации (конечный узел в дереве наследования). Число абстрактных классов мало, а расширенных классов почти нет. Таким образом, использование типа parent бесполезно за исключением некоторых редких случаев;
  • Использование интерфейсов или реализующих классов (PHP self) для type hints предпочтительней, чему соответствуют разные принципы и методики программирования — SOLID , принцип подстановки Барбары Лисков (объекты должны быть заменяемы объектами подтипов), принцип разделения интерфейсов и принцип инверсии зависимостей (зависимость от абстракций, в данном случае интерфейсов).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *