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

Разберемся с типами self и parent, которые доступны в PHP с версии 5.0, однако не так широко используемые.

В PHP начиная с версии 5.0 мы может указывать типы данных аргументов функций. С выходом новых версий количество возможных тайпхинтов только растет. Краткий ликбез:

public function foo(
    // начиная с версии 5.0
    self $self,
    parent $parent,
    FooInterface $foo,
    // начиная с версии 5.1
    array $array,
    // начиная с версии 5.4
    callable $callable,
    // начиная с версии 7.0
    bool $bool,
    float $float,
    int $int,
    string $string
) {}

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

self

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

Использование в качестве аргумента функции

interface Person {
    public function addSibling(self $sibling) {
        // ...
    }
}

При проектировании связей между объектами бывает необходимым спроектировать связь между объектами одного типа. В данном случае тип self может быть полезным.

Когда мы создаем реализацию этого интерфейса необходимо заменить тип self оригинальным названием класса или интерфейса.

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

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

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

Сеттеры

Один из самых очевидных примеров использования это сеттеры с возможностью chaining.

$foo->setBar($bar)->setBaz($baz);

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

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

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

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

Фабричные методы

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

interface Foo
{
    public function wrapWithLogDecorator(\Psr\Log\LoggerInterface $logger) : self;
}

И реализация:

trait LoggerWrapping
{
    public function wrapWithLogDecorator(\Psr\Log\LoggerInterface $logger) : Foo
    {
         return new LogWrapper($this);
    }
}

parent

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

class Foo extends Bar {
    public function setBar(parent $bar)
    {
        // ...
    }
}

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

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

Но все же использование интерфейсов или реализующих классов для тайпхинтинга предпочтительней, за что ратуют различные принципы и методики программирования — SOLID, принцип подстановки Барбары Лисков (объекты должны быть заменяемы объектами подтипов), принцип разделения интерфейсов и принцип инверсии зависимостей (зависимость от абстракций, в данном случае интерфейсов).