В 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, принцип подстановки Барбары Лисков (объекты должны быть заменяемы объектами подтипов), принцип разделения интерфейсов и принцип инверсии зависимостей (зависимость от абстракций, в данном случае интерфейсов).