Одна из особенностей языка, объявленных еще в PHP 5.6, заключалась в добавлении токена «…» для обозначения того, что функция или метод принимает аргументы переменной длины.

Я редко упоминаю о том, что эту функцию можно комбинировать с подсказками типов для создания типизированных массивов.

Например, у нас может быть класс Movie с методом для установки массива дат выхода в эфир, который принимает только объекты DateTimeImmutable:

Теперь мы можем передать переменное количество отдельных объектов DateTimeImmutable в метод setAirDates ():

Если бы мы передали что-то еще, кроме DateTimeImmutable, например строку, была бы выдана фатальная ошибка:

Если бы вместо этого у нас уже был массив объектов DateTimeImmutable, который мы хотели передать в setAirDates (), мы могли бы снова использовать токен «…», но на этот раз для их распаковки:

Если бы массив содержал значение, не относящееся к ожидаемому типу, мы все равно получили бы фатальную ошибку, упомянутую ранее.

Кроме того, мы можем использовать скалярные типы таким же образом, начиная с PHP 7. Например, мы можем добавить метод для установки списка оценок как плавающих в нашем классе Movie:

Опять же, это гарантирует, что свойство rating всегда будет содержать числа с плавающей запятой, и нам не придется перебирать все содержимое для их проверки. Итак, теперь мы можем легко выполнять над ними некоторые математические операции в getAverageRating (), не беспокоясь о недопустимых типах.

Проблемы с типизированными массивами такого типа

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

Другая проблема заключается в том, что при использовании PHP 7 типы возвращаемых значений наших методов get () все равно должны быть «массивом», что часто бывает слишком общим.

Решение: классы коллекции

Чтобы решить обе проблемы, мы можем просто внедрить наши типизированные массивы в так называемые классы «коллекции». Это также улучшает наше разделение проблем, потому что теперь мы можем переместить метод расчета среднего рейтинга в соответствующий класс коллекции:

Обратите внимание, что мы по-прежнему используем в конструкторе список типизированных аргументов переменной длины, что избавляет нас от необходимости перебирать каждый рейтинг для проверки его типа.

Если бы мы хотели иметь возможность использовать этот класс коллекции в циклах foreach, нам просто нужно было бы реализовать интерфейс IteratorAggregate:

Двигаясь дальше, мы также можем создать коллекцию для нашего списка дат выхода в эфир:

Собрав все части головоломки вместе в классе Movie, мы теперь можем внедрить две отдельно типизированные коллекции в наш конструктор. Кроме того, мы можем определить более конкретные типы возвращаемых значений, чем «массив», в наших методах get:

Использование объектов значений для пользовательской проверки

Если бы мы хотели добавить дополнительную проверку к нашим рейтингам, мы все равно могли бы пойти еще дальше и определить объект значения рейтинга с некоторыми настраиваемыми ограничениями. Например, рейтинг может быть ограничен от 0 до 5:

Вернувшись к нашему классу коллекции Ratings, нам нужно было бы только внести некоторые незначительные изменения, чтобы использовать эти объекты значений вместо чисел с плавающей запятой:

Таким образом мы получаем дополнительную проверку отдельных членов коллекции, без необходимости перебирать каждый внедренный объект.

Преимущества

Ввод этих отдельных классов коллекций и объекта значения может показаться трудоемким, но у них есть несколько преимуществ перед универсальными массивами и скалярными значениями:

  • Простая проверка типов в одном месте. Нам никогда не нужно вручную перебирать массив для проверки типов членов нашей коллекции;
  • Где бы мы ни использовали эти коллекции и объекты значений в нашем приложении, мы знаем, что их значения всегда проверялись при построении. Например, любой рейтинг всегда будет от 0 до 5;
  • Мы можем легко добавить настраиваемую логику для каждой коллекции и / или объекта-значения. Например, метод getAverage (), который мы можем повторно использовать во всем нашем приложении;
  • Мы получаем возможность внедрять несколько типизированных списков в одну функцию или метод, чего мы не можем сделать с помощью токена «…» без предварительной инъекции значений в классы коллекции;
  • Значительно снижается вероятность путаницы аргументов в сигнатурах методов. Например, когда мы хотим ввести и список рейтингов, и список дат выхода в эфир, они могут легко перепутаться случайно при построении при использовании универсальных массивов;

А как насчет правок?

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

Хотя мы могли бы добавить методы для облегчения редактирования, это быстро стало бы громоздким, потому что нам пришлось бы дублировать большинство методов в каждой коллекции, чтобы сохранить преимущество подсказок типов. Например, метод add () в рейтинге должен принимать только объект рейтинга, а метод add () в AirDates должен принимать только объект DateTimeImmutable. Это очень затрудняет сопряжение и / или повторное использование этих методов.

Вместо этого мы могли бы просто сохранить неизменными наши коллекции и объекты значений и преобразовывать их в их примитивные типы, когда нам нужно внести изменения. После внесения изменений мы можем просто воссоздать любые необходимые коллекции или объекты значений с обновленными значениями. После (повторного) построения все типы будут снова проверены вместе с любой дополнительной проверкой, которую мы могли бы определить.

Например, мы могли бы добавить в наши коллекции простой метод toArray () и внести такие изменения:

Таким образом, мы также можем повторно использовать существующие функции массива, такие как array_filter ().

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

Повторное использование общих методов

Как вы могли заметить, мы все еще получаем некоторое дублирование кода в наших классах коллекций, реализуя как toArray (), так и getIterator () для всех из них. К счастью, эти методы достаточно универсальны, чтобы перейти к универсальному родительскому классу, поскольку оба они просто возвращают внедренный массив:

Все, что нам осталось бы в нашем классе коллекции, это проверка типа в конструкторе и любая дополнительная логика, специфичная для этой коллекции, например:

При желании мы могли бы сделать нашу коллекцию окончательной, чтобы никакие дочерние классы не вмешивались в свойство values ​​таким образом, чтобы это могло отменить нашу проверку типа.

Заключение

Несмотря на то, что он все еще далек от совершенства, в последних выпусках PHP становится все проще работать с проверкой типов в коллекциях и объектах значений.

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

Функцией, которая значительно улучшит использование объектов-значений, будет возможность приводить объект к различным примитивным типам в дополнение к строке. Это можно легко реализовать, добавив дополнительные магические методы, сравнимые с __toString (), например __toInt (), __toFloat () и т. Д.

К счастью, есть некоторые RFC для возможной реализации обеих функций в более поздних версиях, так что скрестим пальцы! 🤞

Если вы нашли это руководство полезным, порекомендуйте его другим, нажав кнопку ❤️ ниже.