Подсказка типа 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 тоже возможно.

"Ложный" псевдотип

Также можно использовать falsetype как часть объявления типа 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
}
Другие вопросы по тегам