Имитировать универсальный класс в PHP

#php #generics #phpdoc

#php #универсальные классы #phpdoc

Вопрос:

Я пытаюсь реализовать класс результатов, который обрабатывает запросы. Итак, проще говоря, у вас были бы такие функции, как:

 function all();
function first();
function paginate(int $perPage, int $pageNo = 1);
  

Это работает довольно хорошо, проблема в том, что IDE не имеет возможности узнать тип возвращаемого значения, когда этот же класс результатов используется в нескольких разных классах запросов. Пример:

UserQuery->results()->all() вернет массив пользовательских объектов.

UserQuery->results()->first() вернет единственную пользовательскую сущность.

В некоторых языках у вас есть общие классы, что означает, что я мог бы просто использовать Results<User> в классе UserQuery, и тогда мой класс результатов мог бы возвращать T[] и T соответственно.

Одна из моих идей заключалась в том, чтобы передать пустую сущность в качестве конструктора классу Results, а затем попытаться использовать это свойство в качестве возвращаемого типа, но я не смог этого понять. Есть ли какой-либо обходной путь для этого? Основная проблема, которую я пытаюсь решить, — это автозаполнение и анализ IDE, поэтому чистое решение PHPDoc идеально подходит для моего варианта использования.

Единственное другое решение, которое я могу придумать, — это написать отдельный класс результатов для каждого типа объекта, что оказалось бы утомительным.

Комментарии:

1. В Eclipse и PHP storm вы можете вручную создать подсказку типа, подобную этой, /* @var $foo Users */ например, прямо перед циклом, чтобы as $foo часть имела автозаполнение.

2. Да, это то, что я обычно делаю, когда IDE не определяет правильный тип. К сожалению, это приводит к написанию почти такого же объема кода, как и в последнем варианте, поскольку мне приходится документировать возвращаемое значение при каждом использовании, а не только там, где оно определено. Это также не очень удобно для рефакторинга.

Ответ №1:

Я не думаю, что есть способ сделать именно то, что вы описали, но в подобных случаях я бы предложил использовать прокси-класс для каждого типа результатов и документировать правильные типы возвращаемых данных, используя @метод phpDocumentor. Это решение имеет дополнительную ценность в виде наличия отличного места для любых модификаций и расширений результатов, специфичных для конкретного типа. Вот пример:

 abstract class Results
{
    function all(): array
    {
    }

    function first()
    {
    }

    function paginate(int $perPage, int $pageNo = 1): array
    {
    }
}

class User { }

/**
 * @method User[] all()
 * @method User first()
 * @method User[] paginate(int $perPage, int $pageNo = 1)
 */
class UserResults extends Results { }

class UserQuery
{
    /**
     * @var UserResults
     */
    private $results;

    public function __construct()
    {
        $this->results = new UserResults();
    }

    public function results(): UserResults
    {
        return $this->results;
    }
}

$userQuery = new UserQuery();
$test = $userQuery->results()->all();
  

PhpStorm правильно распознает возвращаемый тип

Комментарии:

1. Только сейчас я заметил ваш комментарий о «отдельном классе результатов для каждого типа объекта, который оказался бы утомительным», но имхо в предлагаемой форме эти классы реализации результатов могут быть легко сгенерированы автоматически и не будут стоить никакой дополнительной работы.

2. Да, проблема в том, что когда изменяется абстрактный класс или вводится новый метод, мне пришлось бы переписать PHPDoc для ~ 30 дочерних классов, спасибо, что собрали это вместе, я уверен, что это может помочь другим, которые могут столкнуться с этим.