- Типы данных в PHP: self и parent
- self
- Использование в качестве аргумента функции
- Использование в качестве типа возвращаемого значения
- Сеттеры
- Фабричные методы
- parent
- Возвращаемый тип self и static в интерфейсах PHP
- Проблема с self
- Возвращаемый тип static
- Когда использовать static?
- Текучие интерфейсы
- Неизменяемые классы
- Порождающие статические методы
- Интерфейсы для «одиночек»
- Заключение
- PHP type hints: self and parent
- Extreme Autonomy — the heart of Free Range Management
- The Fractional CTO explained
- How to do a software rewrite
- Get all the latest posts delivered straight to your inbox.
- self
- Function argument type
- Return type
- Mutators
- Factory methods
- parent
- Other interesting reads:
Типы данных в PHP: self и parent
Начиная с PHP 5.0 , мы можем указывать типы данных аргументов функции, а с выходом новых версий PHP количество возможных type hints увеличилось. Краткий ликбез в PHP self :
В PHP 5.0 мы можем указывать типы параметров при определении функции , а начиная с PHP 7.0 , можем использовать для этого скалярные типы данных. Также, начиная с PHP 7.0 , можно указывать типы возвращаемых функцией значений . Давайте подробнее рассмотрим типы self и parent . Они всегда были доступны, но мы редко видим их использование. Почему так?
self
PHP self означает объект того же типа ( текущего класса или подкласса ). Для каждой переменной должно выполняться условие instanceof по отношению к текущему классу.
Использование в качестве аргумента функции
Бывает необходимо спроектировать связь между объектами одного типа. В данном случае тип self может быть полезен.
Когда мы создаем реализацию этого интерфейса ( или рефакторим код без соблюдения SOLID принципов ) следует заменить тип self оригинальным названием класса или интерфейса:
Использование в качестве типа возвращаемого значения
Давайте разберемся, в каких ситуациях можно использовать new self PHP в качестве типа возвращаемого значения.
Сеттеры
Один из самых очевидных примеров использования — это сеттеры ( или мутаторы ) с возможностью сцепления ( chaining ):
Когда вы используете self , тип возвращаемого значения не важен, если метод возвращает клонированный объект ( это тот случай, когда вы имеете дело с неизменяемыми объектами ). Возвращаемый объект того же типа, что и объект, метод которого был вызван:
При расширении или реализации метода в дочернем классе необходимо явно указать тип, чтобы объявление было совместимым:
*/ public function setBar(Bar $bar) : FooContract < // . return $this; >>
Если вы будете использовать этот объект, то он сообщит IDE , что все, что возвращается, будет иметь тип 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 .
Фабричные методы
Нелегко найти пример метода, который не является сеттером и возвращает объект того же класса. Но вот он:
Реализация будет выглядеть так:
Я не говорю, что это хороший пример. Этот метод не указан явно в интерфейсе. Есть, конечно, фабричные методы и лучше. Например, методы, которые могут возвращать тип PHP self . Просто на данный момент я не могу придумать практичный пример.
parent
Документация PHP говорит, что parent допустимый тип данных. Давайте разберемся, что он из себя представляет:
parent всегда указывает на класс, который вы расширяете. Для примера выше: Bar или один из его дочерних классов. То есть передаваемый объект может быть другим дочерним от Bar .
В данном случае parent не может указывать на интерфейс. И схема работы примерна такая же, когда вы вызываете метод родительского класса ( parent::setBar например ). Другими словами, parent можно использовать только тогда, когда текущий класс расширяет какой-то другой класс.
Есть несколько причин, почему тип parent не используется:
- Когда при разработке кода соблюдаются SOLID принципы, то почти все — это либо интерфейс, либо класс реализации ( конечный узел в дереве наследования ). Количество абстрактных классов очень мало, а расширенных классов почти нет. Таким образом, использование типа parent бесполезно за исключением некоторых очень редких случаев;
- Использование интерфейсов или реализующих классов ( PHP self ) для type hints предпочтительней, чему соответствуют различные принципы и методики программирования — SOLID , принцип подстановки Барбары Лисков ( объекты должны быть заменяемы объектами подтипов ), принцип разделения интерфейсов и принцип инверсии зависимостей ( зависимость от абстракций, в данном случае интерфейсов ).
ВЛ Виктория Лебедева автор-переводчик статьи «
Возвращаемый тип self и static в интерфейсах PHP
Очень популярная история в PHP, когда метод объекта возвращает сам объект или новый экземпляр того же самого объекта. В этом случае мы указываем тип результата self и всё отлично работает. Например:
final class Car < public function __construct( private ?string $color = null, ) < >public function withColor(string $color): self < $new = clone $this; $new->color = $color; return $new; > >
С финальными классами это действительно гарантированно работает. Но в случае с интерфейсами (а также с не финальными классами, абстрактными классами и трейтами, но в этой статьей рассмотрим только интерфейсы) появляется не очевидная на первый взгляд проблема.
Проблема с self
Допустим у нас есть два интерфейса. Объекты, которые их реализуют, позволяют указать цвет и имя соответственно:
interface ColorableInterface < public function setColor(string $color): self; >interface NameableInterface
И мы решили сделать некий конфигуратор для объектов, реализующих оба интерфейса:
final class Configurator < public function configure( ColorableInterface&NameableInterface $object, string $color, string $name ): void < $object->setColor($color)->setName($name); > >
И вроде бы всё хорошо, но если прогнать этот код, к примеру, через статический анализатор Psalm, то мы получим ошибку «Method ColorableInterface::setName does not exist» («Метод ColorableInterface::setName не существует»).
И действительно, Psalm не ошибся. Интерфейс ColorableInterface гарантирует, что метод setColor() вернёт self , то есть объект, реализующий ColorableInterface , и ничего более, а метода setName() в интерфейсе ColorableInterface нет.
Пример объекта, реализующего оба интерфейса, но при этом не готового для использования в конфигураторе из предыдущего примера:
final class CustomColor implements ColorableInterface < private string $color = ''; public function setColor(string $color): self < $this->color = $color; return $this; > > final class CustomName implements NameableInterface < private string $name = ''; public function setName(string $name): self < $this->name = $name; return $this; > > final class StrangeObject implements ColorableInterface, NameableInterface < public function setColor(string $color): ColorableInterface < return new CustomColor(); >public function setName(string $name): NameableInterface < return new CustomName(); >>
Возвращаемый тип static
В PHP 8.0 появилась возможность указать в качестве результата выполнения метода тип static , который на уровне языка гарантирует, что метод вернёт экземпляр того же самого класса, в котором он вызван.
Теперь мы можем переписать интерфейсы следующим образом:
interface ColorableInterface < public function setColor(string $color): static; >interface NameableInterface
И это решит описанную выше проблему.
Когда использовать static?
Как правило, static используется в следующих ситуациях.
Текучие интерфейсы
Текучий интерфейс — структурный шаблон проектирования, позволяющий создавать более читабельный код. Фактически, это возможность вызова методов «цепочкой».
Неизменяемые классы
Неизменяемый (иммутабельный) класс — это класс, который после создания не меняет своего состояния, то есть он не содержит сеттеров и публичных изменяемых свойств. Но такие классы могут содержать методы (обычно они имеют префикс with ), позволяющие получить клон объекта с изменённым состоянием. Как раз для таких методов в интерфейсах необходимо использовать тип static .
interface ReadableInterface
Порождающие статические методы
Статические методы, позволяющие создать экземпляр объекта текущего класса.
Интерфейсы для «одиночек»
Интерфейс, определяющий метод для реализации шаблона проектирования «Одиночка» (метод предоставляющий доступ к единственному экземпляру объекта в приложении и запрещающий повторное создание этого объекта).
Заключение
Таким образом, при проектировании интерфейсов в PHP мы можем сразу заложить, что ожидаем увидеть в качестве результата выполнения метода:
- self — любой объект, который реализует данный интерфейс;
- static — экземпляр объекта того же класса, что и объект в котором вызван метод.
PHP type hints: self and parent
Extreme Autonomy — the heart of Free Range Management
The Fractional CTO explained
How to do a software rewrite
Get all the latest posts delivered straight to your inbox.
Processing your application Please check your inbox and click the link to confirm your subscription. There was an error sending the email
In PHP we can type hint function arguments since version 5.0. Over the years and with newer versions of PHP the number of possible type hints have increased. Here’s an overview:
Since PHP 5.0 we’ve been able to type hint function arguments and since PHP 7.0 we’re aso able to type hint scalar values. The entire set of type hints can be used for function return type hinting since version 7.0 as well. Let’s take a closer look at self and parent . They’ve been around for a long time
but we don’t see them very often. Why is that?
self
Self means: an object of the same type (the current class or a subclass). Every variable passed must be an instanceof the same class.
Function argument type
When modelling relations between nodes/objects/…, it happens that a relationship needs to be modelled between two objects of the same type. In this case it’s useful to use the self type hint.
When you create an implementation of this interface (or when overwriting this in case of less SOLID designed code) it will be necessary to replace the self type hint with the original class/interface name.
Return type
Let’s take a look in what situations we can use self as a return type.
Mutators
One of the more obvious use case is setters (or mutators) that allow method chaining.
If you use self return type it doesn’t matter if the method returns a cloned version of the object (this is the case when you’re dealing with immutable objects). The object that is returned is of the same type as the object on which the method is called.
When extending or implementing a method in a subclass you have to make type hint explicit so that declaration is compatible.
*/ public function setBar(Bar $bar) : FooContract < // . return $this; >>
If you use this object it will tell your IDE that whatever is returned will be of type Foo (the interface). So practically it won’t/shouldn’t autocomplete method names of the Foo implementing class anymore.
setBar(new Bar())->setBaz(new Baz());
In the example above, if setBaz is not a method of the interface Foo , it will not be recognized by the IDE. Thus for chaining methods, the return type self isn’t so useful.
Prior to PHP 7.0 we usually only declared return types in the docblocks with @return $this , @return self or @return static .
For chained methods I still use @return static and @return $this in the docblocks.
Factory methods
It’s not easy to come up with examples of non-setter methods that still return objects of the same class. Here’s one:
An implementation would be:
I’m not saying this is good design, though. This method clearly doesn’t belong in this interface. There are better factory-like methods that can have a self return type hint. I just can’t come up with a practical one at this moment. Feel free to suggest an example in the comments below.
parent
I noticed from the PHP docs that parent is a valid type hint. Let me explain what parent means in the context of type hinting:
parent always refers to the class that you are extending. Thus in the example above it means: Bar, or one of its subclasses. The object that gets passed here could be of a sibling class of Foo, for example.
The type hint can not refer to an interface. It’s exactly the same as when you would call a method on a parent class ( parent::__construct for example). You can only use that when the current class actually extends a parent class.
There are a couple of reasons why the parent type hint isn’t used much:
When your code’s design respects the SOLID principles, it should strike you that almost everything is either an interface or an implementing class (leaf node in inheritance tree). You’ll see that the number of abstract classes will be very small and almost no classes get extended. So using the parent type hint is not that useful, except in some very rare cases.
Secondly, it is generally better to type hint interfaces. This again is part of the SOLID design principles: Liskov Substitution Principle (objects should be replaceable with objects of subtypes), Interface Segregation Principle and Dependency Inversion Principle (Depend upon abstractions(interfaces)). Thus, again: the amount of type hints that are class type hints and not interface type hints is very small.
If you’re having questions or would like to add something, feel free to leave a comment below.