Дата публикации: 10.11.2022 в 17:47

Новые функции в PHP 8.0 и 8.1

0 комментария

С момента выпуска в конце 2020 года PHP 8 значительно изменился. В этом руководстве рассмотрим все последние функции на реальных примерах.
Давайте пройдемся по нескольким глобальным нововведениям PHP версии 8.0.


Объявление свойств в конструкторе


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

// До PHP 8.0

class Client
{
    private string $url;
 
    public function __construct(string $url)
    {
        $this->url = $url;
    }
}
// PHP 8.0
class Client
{
    public function __construct(
        private string $url,
    ) {}
}

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

Объединение типов


Еще одна классная функция, которая появилась, — Union Types (объединение типов). Здесь переменная с указанием типа или типа возвращаемого значения может быть одного или нескольких типов сразу. Это помогает при статическом анализе кода, когда у вас может быть условный возврат внутри метода. Давайте посмотрим на пример.


// До PHP 8.0
class PostService
{
   public function all(): mixed
   {
       if (! Auth::check()) {
           return [];
       }
       return Post::query()->get();
   }
}
// PHP 8.0
class PostService
{
   public function all(): array|Collection
   {
       if (! Auth::check()) {
           return [];
       }
       return Post::query()->get();
   }
}


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


Именованные аргументы


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


// До PHP 8.0
class ProcessImage
{
   public static function handle(string $path, int $height, int $width, string $type, int $quality, int $compression): void
   {
       // logic for handling image processing
   }
}
ProcessImage::handle('/path/to/image.jpg', 500, 300, 'jpg', 100, 5);
// PHP 8.0
class ProcessImage
{
   public static function handle(string $path, int $height, int $width, string $type, int $quality, int $compression): void
   {
       // logic for handling image processing
   }
}
ProcessImage::handle(
   path: '/path/to/image.jpg',
   height: 500,
   width: 300,
   type: 'jpg',
   quality: 100,
   compression: 5,
);


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


Match Expressions


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

// До PHP 8.0
switch (string $method) {
   case 'GET':
       $method = 'GET';
       break;
   case 'POST':
       $method = 'POST';
       break;
   default:
       throw new Exception("$method is not supported yet.");
}
// PHP 8.0
match (string $method) {
   'GET' => $method = 'GET',
   'POST' => $method = 'POST',
   default => throw new Exception(
       message: "$method is not supported yet.",
   ),
};


Оператор match допускает более компактный и читабельный синтаксис. Я ничего не могу сказать об улучшениях в производительности, по сравнению со switch,, но я знаю, что с match работать заметно проще.


Использование ::class для объектов


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

//До PHP 8.0
$commandBus->dispatch(get_class($event), $payload);
// PHP 8.0
$commandBus->dispatch(
   event: $event::class,
   payload: $payload,
);


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


Нет захвата блоков catch


Иногда при создании приложения вам не нужен доступ к объекту исключения, которое может быть инициализировано. Хотя это и редко бывает, но у вас может возникнуть такая ситуация.. Давайте посмотрим на пример.


// До PHP 8.0
try {
   $response = $this->sendRequest();
} catch (RequestException $exception) {
   Log::error('API request failed to send.');
}
// PHP 8.0
try {
   $response = $this->sendRequest();
} catch (RequestException) {
   Log::error('API request failed to send.');
}


Нам не нужно инициализировать объект исключения, поскольку в данном случае мы его не используем. Если мы хотим использовать сообщение из исключения, то убедитесь, что вы поймали это исключение. Как я уже сказал, это не то, что я использую, так как обычно мне нужен объект сгенерированного исключения.
Мы все можем согласиться с тем, что PHP 8.0 был реально крутым выпуском, которого мы все ждали. Но как насчет PHP 8.1? Что он нам принес? Конечно же, лучше быть не может, правда? Как бы не так.


Enums


Прекрасный Enums (перечисления), спаситель от бессмысленных таблиц баз данных и гуляющих в коде констант. Enums быстро стал одной из моих любимых функций PHP 8.1 — теперь я могу помещать свои роли в перечисления вместо того, чтобы хранить их в таблице, которая никогда не меняется. Теперь я с легкостью могу установить HTTP методы в Enums вместо констант или публичных статических свойств класса, которые я никогда не хотел использовать. Давайте посмотрим.


// До PHP 8.1
class Method
{
   public const GET = 'GET';
   public const POST = 'POST';
   public const PUT = 'PUT';
   public const PATCH = 'PATCH';
   public const DELETE = 'DELETE';
}
// PHP 8.1
enum Method: string
{
    case GET = 'GET';
    case POST = 'POST';
    case PUT = 'PUT';
    case PATCH = 'PATCH';
    case DELETE = 'DELETE';
}

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


// До PHP 8.1
trait SendsRequests
{
   public function send(string $method, string $uri, array $options = []): Response
   {
       if (! in_array($method, ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'])) {
           throw new InvalidArgumentException(
               message: "Method [$method] is not supported.",
           );
       }
       return $this->buildRequest()->send(
           method: $method,
           uri: $uri,
           options: $options,
       );
   }
}
// PHP 8.1
trait SendsRequests
{
    public function send(Method $method, string $uri, array $options = []): Response
    {
        return $this->buildRequest()->send(
            method: $method->value,
            uri: $uri,
            options: $options,
        );
    }
}


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


Распаковка массивов со строковыми ключами


Я не был уверен, что мне понадобиться эта функция, пока не попробовал. Раньше нам всегда приходилось копировать что-то или объединять в массивы, чтобы получить то, что нам нужно. Теперь мы можем просто распаковать массив, и поведение будет таким же. Я часто использую DTO в своем коде, и у всех них есть метод toArray, который позволяет мне легко преобразовать DTO во что-то, что Eloquent будет обрабатывать за меня. Давайте посмотрим на пример.


// До PHP 8.1
final class CreateNewClient implements CreateNewClientContract
{
   public function handle(DataObjectContract $client, int $account): Model|Client
   {
       return Client::query()->create(
           attributes: array_merge(
               $client->toArray(),
               [
                   'account_id' => $account,
               ],
           ),
       );
   }
}
// PHP 8.1
final class CreateNewClient implements CreateNewClientContract
{
    public function handle(DataObjectContract $client, int $account): Model|Client
    {
        return Client::query()->create(
            attributes: [
                ...$client->toArray(),
                'account_id' => $account,
            ],
        );
    }
}


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


Новое в конструкторах


Что я могу сказать нового о конструкторах в PHP, чего вы еще не знаете? Не очень много, но попробую. До PHP 8.1 иногда вы могли не передавать новый экземпляр класса конструктору по разным причинам, а иногда и так. Это создало ситуацию, когда вы никогда не можете быть уверенным, нужно ли вам передать экземпляр или нет. При возникновении такого случая - я просто передам null и посмотрю, что произойдет - надеясь на лучшее. Спасибо, PHP 8.1, за то, что предоставил нам некоторую защиту от таких поспешных решений. Не правда ли? Давайте посмотрим на пример.


// До PHP 8.1
class BuyerWorkflow
{
   public function __construct(
       private null|WorkflowStepContract $step = null
   ) {
       $this->step = new InitialBuyerStep();
   }
}
// PHP 8.1
class BuyerWorkflow
{
    public function __construct(
        private WorkflowStepContract $step = new InitialBuyerStep(),
    ) {}
}


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


Свойства только для чтения


Не буду лукавить, я влюбился в это с первого взгляда! Это было большим изменением для меня. Данное нововведение позволяет мне легко программировать без необходимости изменения области видимости свойства. Раньше мне приходилось изменять public свойства на protected или private, а это означало, что теперь мне нужно добавлять геттеры в класс, что вызывало дублирование шаблонного кода. Давайте посмотрим на пример.


// До PHP 8.1
class Post
{
    public function __construct() {
        protected string $title,
        protected string $content,
    }
 
    public function getTitle(): string
    {
        return $this->title;
    }
 
    public function getContent(): string
    {
        return $this->content;
    }
}
// PHP 8.1
class Post
{
    public function __construct() {
        public readonly string $title,
        public readonly string $content,
    }
}


Глядя на этот пример кода, улучшения, которые вы можете добавить благодаря этой новой языковой функции, впечатляют. Ясно видно преимущество, которое могут дать вам свойства только для чтения — ваш код становится менее подробным, и вы можете ослабить видимость и доступ, сохраняя при этом структуру кода.
Это, конечно, не исчерпывающий список — это всего лишь несколько ключевых моментов, которые выделяют релизы. В PHP 8.0 и 8.1 было добавлено много других вещей, о которых я здесь не упомянул. Если вы хотите более подробно ознакомиться со всеми добавленными вещами, я настоятельно рекомендую посетить Stitcher от Брента Руса, который усердно публикует обновления, связанные с языковыми обновлениями.


Я не буду вдаваться в PHP 8.2 в этой статье, так как он еще не выпущен, поэтому я еще не сформулировал свое мнение о новых функциях, но следите за этим пространством, поскольку оно будет появляться. Не говоря уже об улучшениях, уже запланированных для PHP 8.3!


Оригинал статьи.

ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай