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

Используемые типы в PHP

В языке PHP предусмотрены следующие типы данных, которые можно использовать для присвоения типов переменных при написании кода:

  • bool - используется для работы с логическими операциями и может принимать только два параметра: true или false (истина или ложь);
  • float - используется для обозначения чисел с плавающей точкой, то есть всех нецелых чисел;
  • int (integer) - присваивается только целым числам без точек (неважно положительным или отрицательным);
  • string - преобразует значение в строку;
  • mixed - способно принимать любое значение;
  • callable - используется для обозначения функций;
  • array - обозначение массива данных;
  • iterable - используется для обозначения массивов или классов, реализованных через интерфейс Traversable, а также при переборе в цикле foreach;
  • Self - присваивается для объекта, представляющего тот же класс. Доступно использование внутри классов;
  • parent - присваивается родительскому классу. Тоже можно использовать внутри классов;
  • null - несуществующий тип, присвоенный переменным без наследуемого типа данных.

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

Стандартная типизация в PHP

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

Вот пример стандартной типизации классов в PHP с пояснениями:

В стандартном PHP можно выполнить преобразование в другой тип, отличный от того, что был определен языком изначально. Делается это таким образом:

Преобразование еще возможно другим образом. Представим, что у нас имеется та же переменная $theVar с тем же значением, но только обозначенная как строка. Изменить ее снова на целочисленное можно простым прибавлением любого целого числа:

Аналогичным образом число можно преобразовать в строковое значение - добавив к нему данные, которые PHP по умолчанию посчитает строкой:

Прибавлять к числу строку, чтобы преобразовать все значение переменной в строку не очень удобно, поэтому иногда удобнее пользоваться присвоением по типу: $название_переменной = (желаемый_тип)$название_переменной.

Неинициализированные типы в PHP

Некоторые переменные не могут быть корректно инициализированы в PHP в силу каких-либо обстоятельств. Таким данным присваивается тип «состояния переменной»: неинициализированный. Если в скрипте есть хоть одна такая переменная, то он не сможет корректно сработать и, скорее всего, завершится ошибкой с текстом:

Fatal error: Uncaught Error: Typed property Foo::$theVar must not be accessed before initialization

Пример скрипта с неинициализированной переменной:

При выполнении данного скрипта возникнет ошибка, так как у переменной невозможно определить тип. В старых версиях PHP переменной $theVar был бы присвоен просто тип null, означающий, что у переменной нет типа. Однако типы можно обнулять, что делает невозможным корректно определить, было ли ранее установлено типизированное свойство обнуляемого типа или оно было забыто в ходе выполнения скрипта. Дабы исправить эту проблему в PHP 7 было добавлено "неинициализированное" состояние.

Исправить приведенный выше код можно установив неинициализированное значение после объекта:

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

Учитывайте следующие моменты при работе с неинициализированными состояниями:

  1. Чтение значений из неинициализированный свойств невозможно, так как это всегда приводит к фатальной ошибке и завершения выполнения скрипта.
  2. Во избежании фатальных ошибок можно создать объект с неинициализированным свойством, даже если его тип не имеет значения NULL. Считать значение будет все-равно невозможно, но это не приведет к фатальным ошибкам.
  3. Также во избежании фатальных ошибок можно записать неинициализированное свойство перед чтением из основного скрипта.
  4. Если вам требуется сделать типизированное свойство неинициализированным, то используйте значение unset. При отключении нетипизированного свойства ему присваивается значение null.

Использование значений по умолчанию и конструкторов

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

Задание значений по умолчанию в скалярных типах:

В рассматриваемом классе задаются только пользовательские значения, присвоенные через знак “=”.

С типизацией функций и ее аргументов в PHP немного сложнее. Как вариант, можно попробовать задать такую типизацию:

В рассмотренном примере использовать тип null можно только в том случае, если тип действительно можно обнулить. Если это не так, то при компиляции кода может появиться ошибка.

Обратите внимание, что задать значения по умолчанию с object типами или классами невозможно. Для обхода данных ограничений как раз используется конструктор, который помогает корректно инициализировать тип у объекта. Пример использования конструктора в PHP:

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

Наследование типов

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

Пример работы с наследованием:

В приведенном примере класс Bar наследует класс Foo, получая доступ ко всем методам и свойствам Foo, которые не являются приватными. Также наследуется и тип переменных в классе. Обратите внимание, что в новом классе изменен модификатор доступа изменен с private на public (допустимо также значение protected). В противном случае вы получите критическую ошибку. Рекомендуется по возможности избавляться от приватных свойств классов, особенно, если планируется использовать их в качестве родителей.

Возможные проблемы

У типизации в PHP есть важная проблема - он не строготипизируемый. Это значит, что корректность установленных типов проверяется непосредственно во время выполнения скрипта, а не компиляции кода. Это усложняет тестирование кода, так как нужно предусмотреть как можно больше сценариев поведения пользователя, чтобы скрипт не “посыпался” в самый ответственный момент.

Вот пара советов по решению возможных проблем с некорректной типизацией в PHP:

  1. Проверяйте код небольшими кусками в рантайме. Чем больше объем скрипта проходит проверку в режиме реального времени, тем больше нагрузка в процессе, следовательно, есть риск пропустить что-то важное.
  2. Потратьте дополнительное время на тестирование уже готового продукта. Даже если небольшие проверки не выявили никаких проблем с типизацией, реальное использование может обнажить проблемы в самый неподходящий момент. Рассмотрите несколько вариантов использования продукта пользователем, чтобы избежать хотя бы самых основных проблем.

Заключение

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

  • снизит риск возникновения ошибок, связанных с типами;
  • по заранее заданным типам проще составлять документацию к проекту;
  • позволяет сделать проектирование функции более продуманным.

Как раз сложности с типизацией мешали PHP стать в один ряд с такими языками как Java и C#. В обновленном PHP 7 появились дополнительные возможности работы с типизацией, в том числе установка типов по умолчанию.