php return type static
Новый PHP, часть 1: Return types
Каждый мажорный релиз PHP добавляет ряд новых возможностей, некоторые из которых действительно имеют значение. Для PHP 5.3 — это были пространства имен и анонимные функции. Для PHP 5.4 — трейты. Для PHP 5.5 — генераторы. Для 5.6 — списки аргументов переменной длины.
PHP 7 имеет большое количество новшеств и улучшений, делающих жизнь разработчика легче. Но я считаю, что самым важным и долгосрочным изменением является работа с типами. Совокупность новых фич изменит взгляд на PHP разработку в лучшую сторону.
Почему поддержка строгой типизации так важна? Она предоставляет программе — компилятору или рантайму и другим разработчикам ценную информацию о том, что вы пытались сделать, без необходимости исполнять код. Это дает три типа преимуществ:
Возвращаемые типы
Первым дополнением к уже существующей системе типизации будет поддержка возвращаемых типов. Теперь можно указывать в функциях и методах тип возвращаемого значения в явном виде. Рассмотрим следующий пример:
Постфиксный синтаксис для возвращаемых типов может показаться странным для разработчиков, привыкших к C/C++ или Java. Однако, на практике подход с префиксным объявлением не подходит для PHP, т.к. перед именем функции может идти множество ключевых слов. Во избежание проблем с парсером PHP выбрал путь схожий с Go, Rust и Scala.
А теперь создадим наш репозиторий. (В качестве заглушки добавим фиктивные данные прямо в конструктор, но на практике, конечно же, необходимо иметь источник данных для нашего репозитория).
Большинство читателей быстро заметит, что `findById()` имеет баг, т.к. в случае, если мы попросим несуществующий идентификатор сотрудника PHP будет возвращать `null` и наш вызов `getAddress()` умрет с ошибкой «method called on non-object». Но на самом деле ошибка не там. Она заключается в том, что `findById()` должен возвращать сотрудника. Мы указываем возвращаемый тип `Employee`, чтобы было ясно чья это ошибка.
Что же делать, если действительно нет такого сотрудника? Есть два варианта: первый — исключение; если мы не можем вернуть то, что мы обещаем — это повод для особой обработки за пределами нормального течения кода. Другой — указание интерфейса, имплементация которого и будет возвращена (в том числе и «пустая»). Таким образом, оставшаяся часть кода будет работать и мы сможем контролировать происходящее в «пустых» случаях.
Выбор подхода зависит от варианта использования и также определяется рамками недопустимости последствий в случае невозвращения правильного типа. В случае репозитория, я бы поспорил с выбором в пользу исключений, поскольку шансы попасть именно в такую ситуацию минимальны, а работа через исключения является довольно дорогостоящей по производительности. Если бы мы имели дело со скалярной переменной, то обработка «пустого значения» было бы приемлемым выбором. Модифицируем наш код соответственно (для краткости показаны только измененные части):
Теперь getStreet() будет отдавать хорошее пустое значение.
Одно важное примечание о возвращаемых типах: при наследовании тип не может быть изменен, даже нельзя сделать его более конкретным (подклассом, например). Причина в особенностях ленивой загрузки PHP.
Возвращаемые типы являются большой, но далеко не единственной новой особенностью, расширяющей систему типов PHP. Во второй части мы рассмотрим другое, пожалуй даже более важное изменение: декларирование скалярных типов.
PHP 8.0: static return type for class methods
PHP 8.0 allows static as a return type for class methods.
PHP class methods can return self and parent in previous versions, but static was not allowed in PHP versions prior to 8.0. The newly allowed static return type allows to narrow down the return type to the called class.
Without the static type allowed in return types, one would have to use self as the return type, which may not be the ideal one. PHP DocBlock already allowed @return static in its DocBlocks to indicate that the methods return the object itself, or an instance of the same class.
With PHP 8.0’s static return type support, it is now possible to replace DocBlock @return static statements with a return type declaration.
Variance
static return type follows Liskov Substitution Principle. A child class method can return a narrower class object than the parent methods return type.
For example, the entirety of the following inheritance is valid:
Excessive inheritance chains are almost always a bad idea, and leads to unmanageable code. The snippet below is just for demonstration.
The snippet above uses other PHP 8.0 features
In fact, it is also not allowed to replace the static return type with the class name of the child class or parent class either.
Return type only
static return type is only allowed as a return type. It is not allowed as a property type or a parameter type.
This is because the static return type is always narrowing the scope, which is not allowed in typed properties and parameter types.
Not allowed outside class context
Only class methods can declare static return type. Standard functions or closures are not allowed to declare static return type.
Backwards Compatibility Impact
Code with static return type will not be backwards-compatible with older PHP versions prior to 8.0. Doing so will result in a parse error:
It is not possible to back-port this feature to older PHP versions either.
PHP RFC: Static return type
Introduction
The static special class name in PHP refers to the class a method was actually called on, even if the method is inherited. This is known as “late static binding” (LSB). This RFC proposes to make static also usable as a return type (next to the already usable self and parent types).
There are a number of typical use-cases where static return types appear (currently in the form of @return static ).
One are named constructors:
Another are withXXX() style interfaces for mutating immutable objects:
Finally, the likely most common use case are fluent methods:
Here we actually have a stronger contract than in the previous two cases, in that we require not just an object of the same class to be returned, but exactly the same object. However, from the type system perspective, the important property we need is that the return value is an instance of the same class, not a parent class.
Proposal
Allowed positions
To understand why static cannot be used as a parameter type (apart from the fact that this just makes little sense from a practical perspective), consider the following example:
More generally, static is only sound in covariant contexts, which at present are only return types.
For property types, we have the additional problem that the static type conflicts with the static modifier:
For this reason, we disallow static types in properties/parameters already at the grammar level, rather than emitting a nicer error message in the compiler.
Variance and Subtyping
The converse replacement shown in the following is not legal:
It should be noted that self here refers to the resolved type of the current class, it does not have to be spelled as self in particular. For example, the following is also legal:
Reflection
Backward Incompatible Changes
There are no backwards incompatible changes in this proposal.
Future Scope
Voting started 2020-01-28 and ends 2020-02-11.
Революция PHP7: Типы возвращаемых значений и удаление артефактов
Планируемая дата выпуска PHP7 стремительно приближается, внутренняя группа усиленно работает, пытаясь исправить наш любимый язык, сделать его как можно лучше, будут удалены артефакты прошлых версий и добавлено несколько столь желанных фич. Есть много RFC, которые можно изучить и обсудить, но в этом посте я хотел бы сосредоточиться на трех самых важных.
PHP 5.7 vs. PHP7
Также необходимо предупредить о некоторых ключевых словах, которые будут зарезервированы в PHP7, чтобы люди могли быстро привести свой код в соответствие с помощью какой-нибудь «автоматической» проверки совместимости версий PHP. Однако, как я писал в рассылке, большинство людей, которые достаточно компетентны, чтобы соблюдать совместимость своего кода с последней версией PHP, на самом деле и не используют конструкции, которые может сломать PHP7.
Обратите внимание, что пока голосование неоднозначно, это говорит о том, что идея окончательно не похоронена. Участники высказываются против 5.7 из-за отсутствия значительных изменений, но с учетом новых голосов на других RFC, они вполне могут изменить свое решение. Давайте посмотрим что произойдет.
Типы возвращаемого значения (Return Types)
С подавляющим большинство голосов «за», PHP, наконец, получил типы возвращаемых значений. Результаты голосования еще свежи, но определенны. Начиная с PHP7, мы, наконец, сможем указать правильный тип возвращаемых значений функции:
Улучшение? Однозначно. Но идеальное ли? К сожалению, нет:
Некоторые люди также жаловались на объявления типа после закрывающей скобки списка аргументов, а не перед именем функции, но для меня это придирки. Популярные языки, такие как современный C++, используют “пост”-синтаксис, при этом сохраняется возможность поиска «function foo» без какой-либо необходимости прибегать к regex-модификациям. Более того, это согласуется с тем, что использует HHVM, таким образом получается непредвиденный бонус в совместимости.
Другие жаловались на «строгость» PHP, но, как заявил один из комментаторов, вы действительно должны знать что хотите получить еще до начала кодирования, через интерфейсы и наследование чужого кода. Кроме того, пока указание возвращаемых типов необязательно, и его наличие никак не влияет на общую производительность или стабильность PHP, не нужно раздувать из этого проблему. Жаловаться на такую фичу, все равно что жаловаться на наличие ООП в PHP в те времена, когда процедурные спагетти были хорошим способом решить задачу. Языки эволюционируют, и это один из шагов в правильном направлении.
Удаление артефактов
Пост очень хорошо написан и, несмотря на очевидный гнев, заставляет меня задаться вопросом — если у вас такая кодовая база живет настолько долго, то стоит ли вообще обновляться до PHP7? И если уж настолько хочется, то стоит ли тащить все это с собой, разве не проще выявить поломанные классы и исправить их конструкторы? Вы даже можете делегировать эту задачу джуниорам, при условии достаточного покрытия кода тестами вы всегда сможете убедиться, что ничего не сломалось. И даже если тестов нет, если ваше приложение — это кусок кода непонятного качества, то вы действительно надеетесь получить выгоду от переезда на PHP7? Не лучше ли обновить ваше приложение в первую очередь?
Приговор «код, который я написал 10 лет назад, по-прежнему должен запускаться сегодня и должна быть возможность выполнить его через 10 лет» — для меня безумие — вы, безусловно, не должны ожидать подобного ни от одного популярного языка в рамках перехода от одной основной его версии к другой. Проведите параллель с реальным миром, вы не должны ожидать разрешения иметь рабов сегодня, только потому, что когда-то давно закон это разрешал. Да, был кровавый переход, но когда большинство сторонников рабства либо покаялись либо умерли, настал мир.
Стоит отдать должное Тони, он прав в том, что отстаивает свое мнение и прилагает большое количество усилий, чтобы его твердое «нет» было услышано. Но в долгосрочной перспективе это займет больше коллективных усилий по устранению проблем с конструкторами, чем если просто избавиться от проблемы прямо сейчас.
Мой совет тем, кто опасается PHP7 — стоп. Если вы не хотите обновляться, то и не делайте этого. Если вы сидели на 5.3 или 5.2 так долго (привет, любители CodeIgniter), то посидите на 5.6 еще десяток лет, не мешайте PHP быть современным. Оставьте прогресс для тех, кто готов принять его.
Что думаете вы по этому поводу? Вся эта затея с удалением артефактов бред или действительно нужный шаг?
В сторону: изменения Extension API
В качестве интересной заметки на полях, есть некоторые изменения в PHP7, которые могут стать причиной небольшой задержки с портированием расширений на версию 7. API для создания расширений попало под рефакторинг (чистку) и все может поменяться, тем не менее этот провакационный твит от Sara Golemon собрал немного внимания.
Damn. There are some serious surprises in the PHP7 Extension API changes. Not for nothin’, but it’s a good time to switch to HHVM.
— SaraMG (@SaraMG) January 3, 2015
Она в основном говорит о том, что изменения в создании расширений при переходе от 5.6 до 7 будет настолько большими, что проще узнать как делать расширения под HHVM. А затем она продолжает развивать тему серией статей на тему как создать HHVM extension.
Вы разрабатываете расширения? Изучали ли вы изменения и как вы относитесь к всему этому, не слишком ли рано сейчас говорить о будущем эффекте?
Edit: Мое внимание обратили на то, что Сара уже после всего этого начала документировать новое API расширений, совместимое и с HHVM и с PHP7. Похоже, можно делать расширения, совместимые с обоими рантаймами!
Вывод
Как обычно, недостатка в драме PHP-мир не испытывает. Как и во всех крупных революциях, на протяжении всей истории PHP7 также будет проливаться кровь, прежде чем произойдет что-то удивительное. PHP7 еще далек, так что даже если вы попали под перекрестный огонь мушкетных выстрелов, есть достаточно времени, чтобы добраться до выхода. Если ты спишь под гусеницей танка, то обе стороны могут мало что сделать, чтобы помочь тебе.
Что вы думаете об этих RFC? Как вы относитесь PHP7 в целом? Движется ли он в правильном направлении? Дайте нам знать — мы хотим услышать ваши мысли!
Готовимся к собеседованию по PHP: ключевое слово «static»
Не секрет, что на собеседованиях любят задавать каверзные вопросы. Не всегда адекватные, не всегда имеющие отношение к реальности, но факт остается фактом — задают. Конечно, вопрос вопросу рознь, и иногда вопрос, на первый взгляд кажущийся вам дурацким, на самом деле направлен на проверку того, насколько хорошо вы знаете язык, на котором пишете.
Попробуем разобрать «по косточкам» один из таких вопросов — что значит слово «static» в PHP и зачем оно применяется?
Ключевое слово static имеет в PHP три различных значения. Разберем их в хронологическом порядке, как они появлялись в языке.
Значение первое — статическая локальная переменная
Однако всё меняется, если мы перед присваиванием поставим ключевое слово static:
Подводные камни статических переменных
Разумеется, как всегда в PHP, не обходится без «подводных камней».
Камень первый — статической переменной присваивать можно только константы или константные выражения. Вот такой код:
с неизбежностью приведет к ошибке парсера. К счастью, начиная с версии 5.6 стало допустимым присвоение не только констант, но и константных выражений (например — «1+2» или «[1, 2, 3]»), то есть таких выражений, которые не зависят от другого кода и могут быть вычислены на этапе компиляции
Камень второй — методы существуют в единственном экземпляре.
Тут всё чуть сложнее. Для понимания сути приведу код:
Такое поведение может быть неожиданным для неподготовленного к нему разработчика и послужить источником ошибок. Нужно заметить, что наследование класса (и метода) приводит к тому, что всё-таки создается новый метод:
Вывод: динамические методы в PHP существуют в контексте классов, а не объектов. И только лишь в рантайме происходит подстановка «$this = текущий_объект»
Значение второе — статические свойства и методы классов
В объектной модели PHP существует возможность задавать свойства и методы не только для объектов — экземпляров класса, но и для класса в целом. Для этого тоже служит ключевое слово static:
Для доступа к таким свойствам и методам используются конструкции с двойным двоеточием («Paamayim Nekudotayim»), такие как ИМЯ_КЛАССА::$имяПеременной и ИМЯ_КЛАССА:: имяМетода().
Само собой разумеется, что у статических свойств и статических методов есть свои особенности и свои «подводные камни», которые нужно знать.
Особенность вторая — static не аксиома!
Обратное не совсем верно:
И кстати, всё написанное выше относится только к методам. Использование статического свойства через «->» невозможно и ведет к фатальной ошибке.
Значение третье, кажущееся самым сложным — позднее статическое связывание
Разработчики языка PHP не остановились на двух значениях ключевого слова «static» и в версии 5.3 добавили еще одну «фичу» языка, которая реализована тем же самым словом! Она называется «позднее статическое связывание» или LSB (Late Static Binding).
Понять суть LSB проще всего на несложных примерах:
Ключевое слово self в PHP всегда значит «имя класса, где это слово написано». В данном случае self заменяется на класс Model, а self::$table — на Model::$table.
Такая языковая возможность называется «ранним статическим связыванием». Почему ранним? Потому что связывание self и конкретного имени класса происходит не в рантайме, а на более ранних этапах — парсинга и компиляции кода. Ну а «статическое» — потому что речь идет о статических свойствах и методах.
Немного изменим наш код:
Теперь вы понимаете, почему PHP ведёт себя в этой ситуации неинтуитивно. self был связан с классом Model тогда, когда о классе User еще ничего не было известно, поэтому и указывает на Model.
Для решения этой дилеммы был придуман механизм связывания «позднего», на этапе рантайма. Работает он очень просто — достаточно вместо слова «self» написать «static» и связь будет установлена с тем классом, который вызывает данный код, а не с тем, где он написан:
Это и есть загадочное «позднее статическое связывание».
Нужно отметить, что для большего удобства в PHP кроме слова «static» есть еще специальная функция get_called_class(), которая сообщит вам — в контексте какого класса в данный момент работает ваш код.