Подсказка типа php Могу ли я разрешить два типа?
Могу ли я разрешить два разных типа, используя тип подсказки? Например, параметр 1 может быть одним из двух классов
function log (User|File $requester) {
}
4 ответа
Академически это называется союзом типов.
Объединение типов в PHP
Вы можете обманывать, создавая интерфейсы, родительские типы и т. Д., Как упоминалось в других ответах, но какой смысл, кроме добавления сложности и LoC в ваш проект? Кроме того, это не может работать для скалярных типов, так как вы не можете расширить / реализовать скалярный тип.
Вместо того, чтобы сделать код более читабельным, вы получите обратное. За исключением случаев, когда эти классы / интерфейсы уже существуют, и они находятся здесь из-за ООП, а не для решения проблемы хинтинга типов.
обходные
Канонический ответ в PHP... ну, просто не указывайте подсказку типа. Считалось, что язык не имеет сложной и мощной системы типов, и попытка обойти недостатки языка не является хорошим ответом.
Вместо этого, документируйте свою функцию правильно:
/**
* Description of what the function does.
*
* @param User|File $multiTypeArgument Description of the argument.
*
* @return string[] Description of the function's return value.
*/
function myFunction($multiTypeArgument)
{
Это как минимум обеспечит поддержку IDE для автозаполнения и статического анализа кода. Достаточно хорошо при работе над частным проектом, сайтом и т. Д.
При разработке общедоступного API (библиотеки PHP и т. Д.) Иногда вам может потребоваться быть более осторожным в отношении входных данных пользователей API.
Тогда ответ @tilz0R - это путь:
function log($message) {
if (!is_string($message) && !$message instanceof Message) {
throw new \InvalidArgumentException('$message must be a string or a Message object.');
}
// code ...
}
В тот день у PHP (почти) были типы объединения
14 февраля 2015 года было предложено объединение типов PHP RFC для PHP 7.1. После обсуждения и голосования он был отклонен, 18 "нет" против 11 "да".
Если бы RFC был принят, в PHP были бы типы объединения, точно такие, как вы показали (User|File
).
RFC имел некоторые недостатки, но главная причина, по которой он был отклонен, заключается в том, что избиратели, поддерживающие поддержку, весьма сопротивляются изменениям, особенно когда речь идет о строгости типов и других парадигмах программирования (например, "зачем нам нужны объединения типов, когда по умолчанию все типы значений " и " это не хорошо для производительности ").
Начиная с версии PHP 8.0, которая будет выпущена в ноябре 2020 года, это станет возможным с включением типов объединения.
За предложение проголосовали 61 голос против 5, и реализация готова к работе.
Это был предыдущий RFC, который предлагал это, упомянутый в другом ответе, но в итоге он был отклонен.
Он будет работать точно так же, как пример в вашем вопросе:
class F
{
public function foo (File|Resource $f) : int|float { /** implement this**// }
}
Которое значит что F::foo()
ожидает либо File
или ресурс и вернет int
или float
.
Пара дополнительных моментов:
Обнуляемость
Кроме того, вы можете объявить союз с null
. A|null
эквивалентно ?A
, но более сложное объявление как A|B|null
тоже возможно.
"Ложный" псевдотип
Также можно использовать false
type как часть объявления типа union. Напримерint|false
. Это включено в основном по историческим причинам, поскольку некоторые внутренние функции возвращаютfalse
о некоторых типах ошибок. См. Например, strpos().
Более современные функции, вероятно, должны вернуть null
или вызвать исключение в этих ситуациях, но эта альтернатива включена для учета устаревшего кода.
Добавление и удаление части типа объединения при наследовании
Допустимо добавлять типы объединения для подсказок типа параметра (таким образом, делая функцию менее ограничивающей) и удалять типы объединения для подсказок типа возвращаемого значения (делая тип возвращаемого значения более конкретным).
Учитывая класс F
сверху это допустимо:
class G extends F
{
public function foo(File|Resource|string $f) : int { /** **/ }
}
Но это не так:
class H extends F
{
public function foo(File $f) : int|float|bool { /** **/ }
}
В настоящее время это невозможно в PHP. Однако вы можете иметь interface
и реализовать его для User
а также File
, а затем использовать этот интерфейс в качестве подсказки типа в log()
:
<?php
interface UserFile {
}
class User implements UserFile {
}
class File implements UserFile {
}
// snip
public function log (UserFile $requester) {
}
Вы можете проверить тип внутри функции.
function log ($requester) {
if ($requester instanceof User || $requester instanceof File) {
//Do your job
}
}
Или вы можете иметь 2 метода для каждого и один динамический метод:
function logUser($requester)
{
//
}
function logFile($requester)
{
//
}
И динамический
function log($requester)
{
if ($requester instanceof File) {
return $this->logFile($requester);
}
if ($requester instanceof User) {
return $this->logUser($requester);
}
throw new LogMethodException();
}
Вы можете создать родительский класс для этой пары:
abstract class Parent_class {}
class User extends Parent_class {
// ...
}
class File extends Parent_class {
// ...
}
И использовать его в функции
function log (Parent_class $requester) {
// ... code
}