В первых версиях 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 было добавлено "неинициализированное" состояние.
Исправить приведенный выше код можно установив неинициализированное значение после объекта:
В таком случае происходит проверка неинициализированного состояния только при чтении значения свойства, плюс, проверка типа будет выполняться и при записи в него. Это позволяет быть уверенным в том, что недопустимый тип не будет установлен в качестве значения свойства.
Учитывайте следующие моменты при работе с неинициализированными состояниями:
- Чтение значений из неинициализированный свойств невозможно, так как это всегда приводит к фатальной ошибке и завершения выполнения скрипта.
- Во избежании фатальных ошибок можно создать объект с неинициализированным свойством, даже если его тип не имеет значения NULL. Считать значение будет все-равно невозможно, но это не приведет к фатальным ошибкам.
- Также во избежании фатальных ошибок можно записать неинициализированное свойство перед чтением из основного скрипта.
- Если вам требуется сделать типизированное свойство неинициализированным, то используйте значение unset. При отключении нетипизированного свойства ему присваивается значение null.
Использование значений по умолчанию и конструкторов
Конструкторы в PHP помогает быстро и корректно инициализировать только созданный объект, присваивая ему как тип по умолчанию, так и пользовательский. Рассмотрим несколько примеров задания типов и аргументов в PHP.
Задание значений по умолчанию в скалярных типах:
В рассматриваемом классе задаются только пользовательские значения, присвоенные через знак “=”.
С типизацией функций и ее аргументов в PHP немного сложнее. Как вариант, можно попробовать задать такую типизацию:
В рассмотренном примере использовать тип null можно только в том случае, если тип действительно можно обнулить. Если это не так, то при компиляции кода может появиться ошибка.
Обратите внимание, что задать значения по умолчанию с object типами или классами невозможно. Для обхода данных ограничений как раз используется конструктор, который помогает корректно инициализировать тип у объекта. Пример использования конструктора в PHP:
Допускается указывать значения в неинициализированное свойство вне конструктора. Неинициализированная проверка не запускается до тех пор, пока из свойства ничего не читается, что помогает избежать ошибок с инициализацией и преждевременного прекращения выполнения кода.
Наследование типов
С помощью наследования можно создавать класс, который будет копировать основные параметры родительского класса, в том числе и тип, но будет считаться отдельным классом. Это полезно в тех случаях, когда нужно расширить функционал определенного элемента, но при этом важно сохранить какой-нибудь исходник с изначальными значениями.
Пример работы с наследованием:
В приведенном примере класс Bar наследует класс Foo, получая доступ ко всем методам и свойствам Foo, которые не являются приватными. Также наследуется и тип переменных в классе. Обратите внимание, что в новом классе изменен модификатор доступа изменен с private на public (допустимо также значение protected). В противном случае вы получите критическую ошибку. Рекомендуется по возможности избавляться от приватных свойств классов, особенно, если планируется использовать их в качестве родителей.
Возможные проблемы
У типизации в PHP есть важная проблема - он не строготипизируемый. Это значит, что корректность установленных типов проверяется непосредственно во время выполнения скрипта, а не компиляции кода. Это усложняет тестирование кода, так как нужно предусмотреть как можно больше сценариев поведения пользователя, чтобы скрипт не “посыпался” в самый ответственный момент.
Вот пара советов по решению возможных проблем с некорректной типизацией в PHP:
- Проверяйте код небольшими кусками в рантайме. Чем больше объем скрипта проходит проверку в режиме реального времени, тем больше нагрузка в процессе, следовательно, есть риск пропустить что-то важное.
- Потратьте дополнительное время на тестирование уже готового продукта. Даже если небольшие проверки не выявили никаких проблем с типизацией, реальное использование может обнажить проблемы в самый неподходящий момент. Рассмотрите несколько вариантов использования продукта пользователем, чтобы избежать хотя бы самых основных проблем.
Заключение
Тот факт, что в PHP можно не указывать типизацию аргументов и свойств с одной стороны позволяет сэкономить время, а с другой увеличивает риски появления ошибок. Рекомендуется использовать строгую типизацию, то есть по возможности прописывать типы в скрипте PHP вручную для тех компонентов, для которых это сделать возможно. Это даст следующие преимущества:
- снизит риск возникновения ошибок, связанных с типами;
- по заранее заданным типам проще составлять документацию к проекту;
- позволяет сделать проектирование функции более продуманным.
Как раз сложности с типизацией мешали PHP стать в один ряд с такими языками как Java и C#. В обновленном PHP 7 появились дополнительные возможности работы с типизацией, в том числе установка типов по умолчанию.