- PVSM.RU - https://www.pvsm.ru -
Я начинал работать с php, когда еще не потерял популярность его 4 выпуск, с тех пор произошли огромные изменения. На мой взгляд, последние несколько лет преобразили разработку на нем. Кстати php продолжает быть серьезно востребованным, например, сейчас “Рексофт” развивает на нем несколько проектов. В одном из них работаю и я. Но перейдем к делу. Делюсь с вами самыми интересными изменениями в php, прошедшими с 5 до версии 8.1.
Одним из самых распространенных классов в веб-разработке я бы назвал сущность User, предназначенную для работы с пользователями. В парадигме MVC ее можно назвать моделью пользователя, которая хранит данные (свойства) и предоставляет доступ к ряду методов работы с ними, а также бизнес-логику, связанную с областью применения класса. На примере такого класса я и хочу посмотреть сам и показать тебе, читатель, как изменился язык php за последнии годы.
Для удобного изменения кода, и чтобы в дальнейшем проще было отслеживать, что в каком порядке менялось, я создал репозиторий на гитхаб. Ссылка на репозиторий: https://github.com/ZhukMax/php-evo [1]. Отразил в коммитах каждый шаг, который ниже постараюсь подробно описать.
Изначальную версию кода пишем на 5.6, последней стабильной версии php5. Можно было бы рассмотреть и более ранние версии, но это уже скорее история, чем практическая необходимость. Весь код, написанный на php до 5.5, который я встречал в своей практике, скорее требовал серьезного рефакторинга, чем обновления интерпретатора. А порой и полной замены.
Итак, первым делом создаем класс пользователя и для начала добавляем всего пару свойств: “имя” и “день рождения”, делаем это с обозначением типа. Поля будут приватными, назначаем значение им только в конструкторе, а получить можно через методы-геттеры. Имя и день рождения обычно у человека не изменяется в течение жизни, или, по крайней мере, такое происходит довольно редко, поэтому пойдем по пути, когда их назначить можно только однажды. К тому же, практика использования геттеров и сеттеров и до 2014 считалась хорошим решением.
<?php
class User
{
/** @var string */
private $name;
/** @var DateTimeImmutable|null */
private $birthday;
/**
* @param string $name
* @param DateTimeImmutable|null $birthday
*/
public function __construct($name, $birthday = null)
{
$this->name = isset($name) ? $name : '';
$this->birthday = $birthday;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return DateTimeImmutable|null
*/
public function getBirthday()
{
return $this->birthday;
}
}
Пользователь где-то живет, поэтому добавим ему еще свойство “город”, но прежде создаем класс City и затем добавим атрибут в основной класс пользователя, который будет хранить объект нового класса.
<?php
class City
{
/** @var string */
public $title;
/**
* @param string $title
*/
public function __construct($title)
{
$this->title = $title;
}
}
Свойство города у класса пользователя и сеттер с геттером для него. Даем некую свободу при использовании нашего класса пользователя, обозначить город можно как через создание объекта класса City, так и просто записав в свойство название текстом. На практике такой подход может оказаться не лучшим решением, но здесь мы скорее экспериментируем с возможностями и историей языка.
<?php
/** @var City|string */
private $city;
/**
* @return City|string
*/
public function getCity()
{
return $this->city;
}
/**
* @param City|string $city
*/
public function setCity($city)
{
$this->city = $city;
}
Scalar types and return types
Одним из самых заметных изменений для меня в новой на тот момент версии языка php стала возможность строгой типизации в классах через реализацию скалярных типов и типов, возвращаемых методом данных. Конечно, это не является строгой типизацией в настоящем ее смысле, но все же дает ряд преимуществ, при правильном использовании.
Теперь можно не указывать эти данных в комментариях к методах, потому что они и так обозначены.
* @param string $name - больше не обязательно писать, из кода и так отлично виден тип данных параметра
<?php
/**
* @param City|string $city
* @param DateTimeImmutable|null $birthday
*/
public function __construct(string $name, $city, $birthday = null)
{
$this->name = isset($name) ? $name : '';
$this->city = $city;
$this->birthday = $birthday;
}
Тоже сделаем и с методами, возвращающими данные. Перемещаем тип возвращаемых данных из комментария.
* @return string
<?php
public function getName(): string
Все изменения, связанные с типами, можно увидеть в коммите 7.0: Use scalar types and return types [2].
Null coalescing operator
Также в версии 7.0 удобным и приятным стал "синтаксический сахар" проверки переменной на пустоту через двойной вопросительный знак. Теперь мы можем первую строку ниже заменить на вторую:
<?php
$this->name = isset($name) ? $name : 'Неизвестно';
$this->name = $name ?? 'Неизвестно';
Anonymous classes
Также появилась возможность создания и использования анонимных классов. Это позволяет создать объект, реализующий нужный нам интерфейс, без создания отдельного файла класса. В примере ниже я покажу, как это можно использовать в User.
Изначально создадим метод, который принимает на вход только объект с интерфейсом LoggerInterface и без возможности использования какого-то класса по умолчанию.
<?php
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
Исправляем это и позволяем вызывать метод setLogger без передачи в него параметров. В таком случае пусть свойству $this->logger присваивается объект дефолного класса, имплементирующего нужный нам интерфейс LoggerInterface.
<?php
/**
* @param LoggerInterface|null $logger
*/
public function setLogger($logger = null)
{
$this->logger = $logger ?? new class implements LoggerInterface {
public function log($message)
{
echo $message;
}
};
}
Все изменения, связанные со скалярными типами, анонимными классами и типом возвращаемого значения, можно посмотреть в коммите 7.0: Null coalescing operator and Anonymous classes [3].
Обнуляемые типы
Я удалил комментарии с описание типа параметра не у всех методов, так как в некоторых случаях метод может иметь необязательное поле или даже поле, имеющее несколько допустимых типов. Так, например, в метод setLogger свойство $logger может передаваться как реализация интерфейса LoggerInterface, а может не передаваться вообще.
Но вот наконец-то в версии языка 7.1 появились так называемые обнуляемые типы или другими словами возможность передавать и возвращать данные определенного типа или NULL. Крайне удобно, так как часто в коде возникает потребность в необязательных параметрах.
* @return DateTimeImmutable|null
<?php
public function getBirthday(): ?DateTimeImmutable
{
return $this->birthday;
}
Метод setLogger теперь не требует блока комментария с информацией о типе, его можно удалить:
<?php
public function setLogger(?LoggerInterface $logger = null)
Остальные правки с этим нововведением в коммите 7.1: Nullable types [4]
Отсутствие возврата и видимость констант класса
Теперь можно ограничивать видимость констант класса. Лично я являюсь сторонником указания видимости в том числе для публичных констант, это улучшает читаемость кода. В общем-то, как и явное указание, что метод не возвращает данных с помощью типа void.
<?php
public const CUSTOMER = 'customer';
public const ADMIN = 'admin';
public function setCity($city): void
Коммит 7.1: Void functions and Class constant visibility [5].
Далее я пропущу версии 7.2 и 7.3, потому что не вижу в них достаточно интересных изменений синтаксиса языка, которые стоило бы упомянуть.
Типизированные свойства
Тут все просто, но крайне удобно, начиная с версии 7.4, можно указывать тип свойства класса, и тем самым ограничивать динамическую типизацию. Функция уже даже напоминает старших братьев по цеху типа Java с их строгостью типа переменной. Теперь совершенно не обязательно указывать тип в комментарии, а можно оставить его (комментарий) для прямого назначения - описания свойства класса.
/** @var string */
<?php
private string $name;
Стрелочные функции
Это сокращенная форма записи функции с одной строкой кода, результат которой возвращается. Мне они напоминают javascript'овые лямбда функции. Интересным их свойством является видимость внутри тела стрелочной функции переменных из родительской.
В коде класса пользователей я не использовал их, и поэтому ниже покажу пример, которого нет в репозитории. Сначала код на php до 7.4.
<?php
$y = 1;
$fn = function ($x) use ($y) {
return $x + $y;
};
Используя стрелочную функцию перепишем код.
<?php
$y = 1;
$fn = fn($x) => $x + $y;
Присваивающий оператор объединения с null
Тут все довольно просто, теперь можно писать проверку на NULL и присваивание еще короче.
<?php
$this->role = $this->role ?? $role;
Заменим на:
<?php
$this->role ??= $role;
Вариация типов
Иногда требуется, чтобы переменная могла быть не строго одного типа. В классе пользователя я сделал возможность назначать свойство города как объект класса City, так и просто строку с названием. Те, кто пришли из строго типизированных языков, могут сказать, что это плохая идея, но динамические возможности php так прельщают. В общем, я рад появлению возможности объявлять переменную и свойство класса объединенными типами.
* @param City|string $city
<?php
public function setCity(City|string $city): void
Объявление свойств в конструкторе
Ранее мы создали класс города, напомню его:
<?php
class City
{
/** @var string */
public $title;
/**
* @param string $title
*/
public function __construct($title)
{
$this->title = $title;
}
}
В версии языка 8.0 мы можем сократить этот код, перенеся объявление свойства класса в конструктор.
<?php
class City
{
public function __construct(public string $title) {}
}
Важное замечание: именно обозначение видимости является для конструктора ключевым фактором создавать ли передаваемое значение свойством класса или локальной для метода переменной. Пример выше создает свойство класса, которое будет доступно через $this->title. Ниже я покажу, как передать в конструктор данные доступные только внутри него.
<?php
class City
{
public function __construct(string $title)
{
echo $title; // Вывод строки
}
public function test()
{
// Вызовет ошибку интерпретатора, свойство не объявлено
echo $this->title;
}
}
Оператор Nullsafe
Очень классная штука, позволяющая сильно упрощать написание цепочки обращений к свойствам и методам класса. Пример из коммита 8.0: Union Types and Match Expression [6] объяснит всю суть нововведения лучше любых слов.
<?php
if ($this->logger) {
$this->logger->log('Someone get my name');
}
Так мы должны были проверять не пустое ли свойство, прежде чем вызывать один из методов его интерфейса. Теперь эта конструкция заменяется одним символом вопроса.
<?php
$this->logger?->log('Someone get my name');
Выражение Match
У нас есть метод getAvatar, который занимается тем, что отдает определенную строку в зависимости от роли пользователя. Ранее я реализовал его с помощью switch, но начиная с 8.0 у нас есть более подходящая конструкция для этого - выражение match.
Так метод выглядит сейчас:
<?php
public function getAvatar(): string
{
switch ($this->role) {
case self::CUSTOMER:
return '/images/customer.png';
case self::ADMIN:
return '/images/admin.png';
default:
return '/images/avatar.png';
}
}
Это можно заменить на:
<?php
public function getAvatar(): string
{
return match ($this->role) {
self::CUSTOMER => '/images/customer.png',
self::ADMIN => '/images/admin.png',
default => '/images/avatar.png',
};
}
Совсем недавно произошло важное событие - 8.1 перешла в статус стабильной версии. Я отметил для себя пару довольно интересных новшеств.
Enums (Перечисления)
Во многих языках программирования существуют перечисления. Мы можем их использовать в С++, Golang, Java и других. Наконец, они появились и в php [7]. Здесь они являются особым видом объектов.
Предлагаю реализовать роли пользователя через перечисление, для этого создадим enum Role с дополнительным методом определения аватарки в зависимости от роли. И будем его использовать в классе пользователя.
<?php
enum Role: string
{
case CUSTOMER = 'customer';
case ADMIN = 'admin';
public function getAvatar(): string
{
return match ($this) {
self::CUSTOMER => '/images/customer.png',
self::ADMIN => '/images/admin.png'
};
}
}
Теперь можно заменить код в методе getAvatar на использование только что созданного перечисления, отдающего путь к аватарке.
<?php
public function getAvatar(): string
{
return $this->role->getAvatar() ?? '/images/avatar.png';
}
Это очень интересная тема, и тут можно приводить много примеров, но совсем недавно на Хабре Сергей Пантелеев уже публиковал об этом отличную статью [8].
Readonly свойства класса
Использование модификатора только для чтения (readonly) позволяет добавлять свойства класса, которые недоступны для изменения после инициализации.
<?php
public function __construct(
private string $name,
private City|string $city,
private ?DateTimeImmutable $birthday = null
) {}
Если изменить свойство $birthday на публичное предназначенное только для чтения, тогда нам больше не понадобится геттер. Свойство будет доступно на установку значения только в конструкторе, другими словами - только во время создания объекта.
<?php
public function __construct(
private string $name,
private City|string $city,
public readonly ?DateTimeImmutable $birthday = null
) {}
Атрибуты
В код можно добавлять машиночитаемые, структурированные метаданные о классах, методах, функциях, свойствах и константах класса. В каком-то смысле, атрибут - это своеобразный интерфейс, который могут реализовывать не только классы. В Python подобная концепция реализуется с помощью декораторов.
В документации на php.net [9] описан пример создания и использования собственного атрибута. Но также существуют уже созданные, которые можно использовать как для работы кода, так и для улучшения пользовательского опыта при работе с кодом через редактор. К примеру, у IDE PhpStorm есть поддержка ряда существующих атрибутов или созданных нами, помогающих с рефакторингом, подсветкой, автодополнением кода, поиском использований и др.
Я решил, а точнее IDE подсказал, что к одному из методов в User можно добавить атрибут чистой функции #[Pure]. Это значит, что ее можно безопасно удалять, если она нигде не используется. Также редактор будет следить, чтобы мы не нарушали ее чистоты.
<?php
#[Pure]
public function getAvatar(): string
Правки, относящиеся к версии языка 8.1 закоммитил в 8.1: Readonly properties and Enums, attributes [10].
Язык развивается, порой интерпретатор переписывается с нуля несмотря на хейт и "пророчества" близкой смерти и забвения. Оставаясь неплохим решением для быстрого создания веб-приложений, php приобретает лучшие особенности других языков. При этом важно понимать его сильные и слабые стороны, не пытаться использовать его не по назначению.
Автор: Жук Макс
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/370337
Ссылки в тексте:
[1] https://github.com/ZhukMax/php-evo: https://github.com/ZhukMax/php-evo
[2] 7.0: Use scalar types and return types: https://github.com/ZhukMax/php-evo/commit/ab882059f93c5af88d651add55c8115477f04446
[3] 7.0: Null coalescing operator and Anonymous classes: https://github.com/ZhukMax/php-evo/commit/1f3bd4f8913033243c75affc02b07283b9da5306
[4] 7.1: Nullable types: https://github.com/ZhukMax/php-evo/commit/8d58b48d8c730fece17f4206efc9612792ad81e3
[5] 7.1: Void functions and Class constant visibility: https://github.com/ZhukMax/php-evo/commit/7b446cf7cf03aeef2509323880ff5d4872f6e0ae
[6] 8.0: Union Types and Match Expression: https://github.com/ZhukMax/php-evo/commit/4e9be230e13aad3998a005ddf4cd8c3adf10c530
[7] появились и в php: https://www.php.net/manual/ru/language.enumerations.overview.php
[8] отличную статью: https://habr.com/ru/post/570508/
[9] документации на php.net: https://www.php.net/manual/ru/language.attributes.overview.php
[10] 8.1: Readonly properties and Enums, attributes: https://github.com/ZhukMax/php-evo/commit/b7d64253f3c6a59f2b1ea2fdf2b0d9910289d6b7
[11] Источник: https://habr.com/ru/post/593731/?utm_source=habrahabr&utm_medium=rss&utm_campaign=593731
Нажмите здесь для печати.