иллюстрация © GOLTS
иллюстрация © GOLTS

Я сразу, как только вышла новость о релизе, решил, что нужно посмотреть, пощупать и разобраться, чего же изменилось. Да-да, на днях, а именно 8 февраля 2022, вышел официальный релиз Laravel 9, который включает довольно много новых улучшений. Для тех же из нас, кто не боится таких слов, как alpha и beta, девятая версия фреймворка давно не новость и уже в работе.

Теперь эта версия будет поддерживаться дольше (LTS), и разработчики фреймворка пришли к решению не выпускать новые версии каждые 6 месяцев, а делать это раз в год - в феврале. Судя из расписания, эта версия останется актуальной год, а обновления безопасности будут выпускаться вплоть до 2025 года.

Версия

Язык

Дата релиза

Выпуск багфиксов

Выпуск патчей безопасности

6 (LTS)

7.2 - 8.0

3 сен 2019

25 янв 2022

6 сен 2022

7

7.2 - 8.0

3 марта 2020

6 окт 2020

3 марта 2021

8

7.3 - 8.1

8 сен 2020

26 июля 2022

24 янв 2023

9 (LTS)

8.0 - 8.1

8 фев 2022

8 фев 2024

8 фев 2025

10

8.0 - 8.1

7 фев 2023

7 авг 2024

7 фев 2025

Версия языка

Новый Laravel работает только с php 8.0 и выше. Почему это так? Как мы увидим далее, разработчики воплотили в девятом фреймворке немало фишек последней версии языка, а значит, что использование php7 означало бы потерю именно этих нововведений.

composer create-project laravel/laravel example-app

Команда создала директорию, наполнила ее файлами нового проекта и установила зависимости, среди которых основным является laravel/framework версии v9.0.2. Как видим, релизную версию уже патчат.

Новые помощники

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

Добавленная функция str создает объект класса Illuminate\Support\Stringable для переданной строки. Это позволяет применять к тексту все методы манипуляции строкой, доступные данному классу. Такая возможность существовала и в 8 версии фреймворка и реализовывалась через Str::of, но теперь получается немного лаконичнее.

<?php

Route::get('/', function () {
    return str('Hello, ') // Создаем класс из строки
      ->append('Max')     // Метод добавления в конец строки
      ->upper();          // Метод делает все буквы строки заглавными
});

Функция to_route создает редирект на существующий роут по его имени. Мы можем воспользоваться ей как в наших роутах, так и вернуть ее результат из метода контроллера.

<?php

// Главная страница
Route::get('/', function () {
    return view('welcome');
})->name('home'); // Имя роута для главной страницы

// Старый способ редиректа по имени роута
Route::get('/test', function () {
    return redirect()->route('home');
});

// Новый способ редиректа с помощью функции to_route
Route::get('/new_test', function () {
    return to_route('home');
});

А еще мы можем передавать в новую функцию параметры роута, статус HTTP и дополнительные заголовки:

<?php

Route::get('/', function () {
	return to_route('users.show',    // Наименование роута
  	['user' => 1],                 // Параметры роута
    302,                           // Код статуса редиректа
    ['X-Framework' => 'Laravel']); // Дополнительные заголовки
});

Также благодаря базированию на языке PHP 8, в классе \Illuminate\Support\Str добавилась поддержка таких функций для работы со строкой как str_contains(), str_starts_with() и str_ends_with().

Страница исключений

Встроенная в фреймворк страница сообщения об исключении была и раньше крайне удобной, информативной и симпатичной. Теперь в ней изменилось оформление. Добавлены возможность переключения темы на светлую/темную, настройка функционала "открыть в редакторе" и др.

Темное оформление мне понравилось даже больше, чем Dracula в IDE
Темное оформление мне понравилось даже больше, чем Dracula в IDE

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

Тема меняется очень красиво
Тема меняется очень красиво

Анонимные миграции

Возможность создавать миграции в виде анонимных классов появилось чуть ранее в Laravel 8.37. А затем в 9 - это стало своего рода стандартом, и при вызове консольной команды php artisan make:migration создается класс без названия, расширяющий Migration. Довольно таки небольшое изменение, но мне показалось достаточно интересным, чтобы отметить и его.

Миграция, добавляющаяся в database/migrations при создании проекта:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('password_resets', function (Blueprint $table) {
            $table->string('email')->index();
            $table->string('token');
            $table->timestamp('created_at')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('password_resets');
    }
};

Полнотекстовая индексация

Если мы пользуемся такими реляционными системами БД как MySQL или PostgreSQL, то можем прям в миграции добавить для текстовых полей полнотекстовую индексацию. При чем это делается всего лишь вызовом одного метода:

<?php

$table->text('bio')->fulltext();

По таким полям мы можем искать специальными методами полнотекстового поиска whereFullText и orWhereFullText, добавляя их как условие where. Эти методы преобразуются в SQL запросы поиска по индексу текста.

<?php

$users = DB::table('users')
           ->whereFullText('bio', 'php developer')
           ->get();

Общий контроллер

Используя методы роутера controller и group, появилась возможность объединять несколько роутов в одну группу с общим для них контроллером. Это довольно удобно и позволяет экономить на количестве кода, не указывая одно и тоже в нескольких строках. Если раньше нам пришлось бы писать как-то так:

<?php
use \App\Http\Controllers\UsersController;

Route::get('/users', [UsersController::class, 'index']);
Route::get('/users/{id}', [UsersController::class, 'showOne']);

То теперь можно это сгруппировать:

<?php
use \App\Http\Controllers\UsersController;

Route::controller(UsersController::class)->group(function () {
  Route::get('/users', 'index');
  Route::get('/users/{id}', 'showOne');
});

Scoped Bindings в роутерах

Благодаря усилиям разработчика из Амстердама Клаудио Деккера (Claudio Dekker) была реализована и внедрена удобная возможность связывать параметры строки запроса между собой. Лично я не сразу смог понять, что за связанность такая, но на примере кода все становится "ясно-понятно".

Предположим, что мы создали таблицы с пользователями и статьями, причем каждая статья относится к определенному пользователю, как многое-к-одному. То есть у каждого пользователя есть статьи, которые он написал. И вот мы сделали url для вывода списка статей: /users/{user}/posts. Отсюда логично построить url для вывода отдельной статьи конкретного пользователя по следующей ссылке: /users/{user}/posts/{post}.

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

<?php

use App\Models\Post;
use App\Models\User;

Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
    return $post;
})->scopeBindings();

Теперь, если мы попытаемся открыть статью несоответствующую пользователю (связь по внешнему ключу в таблице с другим пользователем), то получим страницу 404 ошибки:

Страница ошибка с оформлением по умолчанию
Страница ошибка с оформлением по умолчанию

Также есть возможность объединить в одну группу роуты, для которых важно соблюдение связывания параметров:

<?php

Route::scopeBindings()->group(function () {
    Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
        return $post;
    });
  
    Route::get('/users/{user}/comments/{comment}', function (User $user, Comment $comment
        return $comment;
    });
});

Laravel Scout

Полнотекстовый поиск по отдельным параметрам Eloquent моделей, основанный на драйвере базы данных - вот, что представляет из себя этот Scout. Он отлично подходит для поиска по тексту в небольших и даже среднего размера базах. Если наша база вполне умещается на одном сервере, тогда мы можем не использовать такие драйверы как Algolia или MeiliSerach, а воспользоваться простым и удобным Scout с драйвером MySQL/PostgreSQL.

Для начала нужно установить Laravel Scout и выбрать его провайдер-класс для полнотекстового поиска:

composer require laravel/scout
php artisan vendor:publish

Which provider or tag's files would you like to publish?:
  [0 ] Publish files from all providers and tags listed below
  [1 ] Provider: Fruitcake\Cors\CorsServiceProvider
  [2 ] Provider: Illuminate\Foundation\Providers\FoundationServiceProvider
  [3 ] Provider: Illuminate\Mail\MailServiceProvider
  [4 ] Provider: Illuminate\Notifications\NotificationServiceProvider
  [5 ] Provider: Illuminate\Pagination\PaginationServiceProvider
  [6 ] Provider: Laravel\Sail\SailServiceProvider
  [7 ] Provider: Laravel\Sanctum\SanctumServiceProvider
  [8 ] Provider: Laravel\Scout\ScoutServiceProvider
  [9 ] Provider: Laravel\Tinker\TinkerServiceProvider
  [10] Provider: Spatie\LaravelIgnition\IgnitionServiceProvider
  [11] Tag: cors
  [12] Tag: laravel-errors
  [13] Tag: laravel-mail
  [14] Tag: laravel-notifications
  [15] Tag: laravel-pagination
  [16] Tag: sail
  [17] Tag: sail-bin
  [18] Tag: sail-docker
  [19] Tag: sanctum-config
  [20] Tag: sanctum-migrations
 > 8
 
 Copied File [/vendor/laravel/scout/config/scout.php] To [/config/scout.php]

Был создан конфигурационный файл для Scout. И в нем мы можем установить нужный нам драйвер database.

Настройки посковика Scout
Настройки посковика Scout

Теперь можно настраивать поиск по в классах - моделях, чтобы определить какие модели имеют поддержку полнотекстового поиска и по каким полям. И обязательно добавим трейт Searchable к модели, что позволит использовать все необходимые нам методы поиска. Как видно из скриншота ниже, кроме имени я добавил поиск и к нестандартному полю bio (биография).

Метод toSearchableArray возвращает массив полей для поиска
Метод toSearchableArray возвращает массив полей для поиска

Поищем всех пользователей, у которых в биографии есть упоминание обучения в ВУЗе.

<?php

Route::get('/', function () {
    return \App\Models\User::search('институт')->get();
})->name('home');

Покрытие кода тестами

В Laravel 9 мы теперь можем генерировать отчет о покрытии кода тестами с помощью консольной команды php artisan test. Давайте посмотрим, что отобразится, если запустить команду как и раньше.

Тесты успешно выполнились
Тесты успешно выполнились

Если посмотрим подсказку по команде запуска тестов, то увидим 2 новых ключа --coverage и --min[=MIN].

php artisan help test

Usage:
  test [options]

Options:
      --without-tty         Disable output to TTY
      --coverage            Indicates whether code coverage information should be collected
      --min[=MIN]           Indicates the minimum threshold enforcement for code coverage

Coverage - указывает, следует ли собирать информацию о покрытии кода.

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

Я запускаю команду в консоли, но вижу ошибку.

Ошибка запуска команды
Ошибка запуска команды

Не установлен драйвер, но, думаю, это легко починить, установив Xdebug для php. Как несложно было догадаться по скриншотам, у меня MacOS, поэтому воспользуюсь встроенными утилитами для установки и перезапуска сервисов. Предположу, что на другие ОС установка не намного сложнее.

pecl install xdebug
brew services restart php
brew services restart nginx
После установки xdebug и перезагрузки сервисов
После установки xdebug и перезагрузки сервисов

Казалось бы, запускаем команду и радуемся результату, но нет, опять ошибка.

Xdebug ругается :(
Xdebug ругается :(

Xdebug ругается о том, что не установлен режим покрытия кода тестами. Это мы легко исправляем и видим красивое описание покрытия кода.

Пару слов в конце

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

Комментарии (28)


  1. SerafimArts
    17.02.2022 10:09

    Судя из расписания, эта версия останется актуальной год, а обновления безопасности будут выпускаться вплоть до 2025 года.

    Как раз судя из расписания по вашей же ссылке — 9ка не LTS ;)


    1. MichaelBro
      17.02.2022 10:36
      +2

      Непомню где , но обэтом писали что Тейлор втихаря отменил LTS в Laravel 9


      1. DAGpro
        17.02.2022 21:15

        В твиттере Тейлор писал, что отказались от LTS из-за issue в симфони, чтоб в версии симфони 6.1 поднять требования до php 8.1


  1. MaryRabinovich
    17.02.2022 10:34
    +1

    Добрый день,

    спасибо за статью, познавательно.

    Одно уточнение: я сейчас на восьмёрке пишу, и ->controller(UsersController::class) тут уже есть. Правда, это восьмёрка какая-то из последних, 8.75 (при загрузке версию устанавливала как "8.*")

    Ну и пара вопросов:

    1. индексацию полнотекстовую делает тоже скаут? Или это внутри фреймворка из коробки?

    2. чтобы поиграться, сохраняя возможность работать на прежних версиях, какую версию php вы сейчас используете? Похоже, вы ставите всё на компьютер прямо. А с какими при этом самыми старыми версиями фреймворка вы всё ещё можете работать с той же машины?


    1. MaryRabinovich
      17.02.2022 10:37

      (в продолжение вопроса 2)

      Я запланировала как-то с девяткой перейти в докер. При этом в докере я разбираюсь пока чисто паразитически (девопс настроил, а я могу запустить/остановить), вот книжку взялась читать про него. Вопрос, а тем ли я занята, не бессмысленная ли это трата времени. Насколько это разумный выбор, вместо банальной переустановки локального xampp.


      1. vLachugin
        17.02.2022 10:59
        +1

        1. MaryRabinovich
          17.02.2022 14:57

          Прошла по ссылке... гм. Похоже, девятка рекомендуемо ставится именно через sail. То есть, на инсталляционной странице https://laravel.com/docs/9.x/installation composer идёт уже после sail.

          ... и для восьмёрки уже та же последовательность в документации. Сначала про сейл, потом уже про композер. Я отстала от жизни.


          1. Rukis
            18.02.2022 15:14
            +1

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


      1. zm_llill Автор
        17.02.2022 11:20
        +2

        У меня стоит сразу и 7 и 8 версия языка на компе, я переключаюсь между ними настройкой нужного. Я занимаюсь поддержкой Ларавел 5, Битрикс (посыпаю пеплом за это свою голову) и вот пробую Ларавел 9. Пятая версия и Битрикс идут у меня только на php7.2, поэтому и приходится держать обе. Изучение докера - отличная идея, хотя я предпочел не книги, а гугление и практику. Еще смотрю в сторону podman, потому что докер сжирает слишком много ресурсов.


        1. MaryRabinovich
          17.02.2022 11:54

          А что именно вы делаете, когда "переключаетесь настройкой нужного"? Вписываете в апач каждый раз, какую версию php брать (единственное, что приходит в голову)?

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


          1. zm_llill Автор
            17.02.2022 12:05
            +2

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

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


      1. zm_llill Автор
        17.02.2022 11:20

        Сейчас проверил, Ларавел до версии 7.29.0 не поддерживал версию языка выше 7.2


    1. zm_llill Автор
      17.02.2022 11:13

      Индексация не связана со Скаут, я поставил рядом их потому что тема близкая, теперь вижу, что зря, это немного путает.


      1. IgorAlentyev
        17.02.2022 13:08
        +1

        И кстати полнотекстовый индекс и whereFullText методы появились в 8.79.

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


  1. oxidmod
    17.02.2022 11:55
    +2

    dev зависимости в prod коде так и не убрали
    https://github.com/laravel/framework/blob/9.x/src/Illuminate/Mail/Mailable.php#L20


    1. wendel
      17.02.2022 13:29
      +1

      А на**** надо если можно из phpunit взять ху** х**к и в продакшн! а то что есть альтернативы, я уж не говорю о том что бы выпустить свой пакет - не, не слышали. Особенно обожаю когда в очередном мажорном релизе сделали "более красивое отображние ошибок", без этого версия просто не может быть мажорной!!!


      1. FatalStrike
        17.02.2022 13:33

        О каких альтернативах или своих пакетах идёт речь? Касательно указанного выше кода


        1. oxidmod
          17.02.2022 14:15

          Вот тут есть детали как этот код попал в ларку
          https://habr.com/ru/post/534378/


        1. wendel
          18.02.2022 13:54

          https://github.com/webmozarts/assert как минимум...


      1. zm_llill Автор
        17.02.2022 13:57

        Я не разрабатываю сам фреймворк, поэтому может ошибусь, но разве Illuminate\Mail\Mailable не собственная разработка команды Ларавел?


        1. Layan
          18.02.2022 14:01
          +1

          Illuminate\Mail\Mailable то их разработка. Но претензии к использованию PHPUnit (конкретно PHPUnit\Framework\Assert) в продакшн коде. Зависимость от библиотеки тестирования в прод коде — не очень хорошо.


  1. vfreelancer
    17.02.2022 11:55

    по поводу примера с scopeBindings.

    У вас там в ссылке 2 ошибки: user/1/pots/5 - а должно быть users/1/posts/5 точно 404 без всяких scopeBindings


    1. zm_llill Автор
      17.02.2022 12:06

      Спасибо, хорошее замечание. Сменю скрин по-позже.


  1. kraso4niy
    17.02.2022 22:31
    +5

    А еще мы можем передавать в новую функцию параметры роута, статус HTTP и дополнительные заголовки:

    невероятно, в 2022 году в фреймворке для работы по HTTP проктолу теперь можно передавать заголовки ответа!!!! Браво! Рекомендую забыть этот фреймворк и обратить внимание на symfony, в котором всё что вышло ларе в 2022 году уже было в sf 10 лет назад.


    1. Mellorn
      18.02.2022 08:43
      +1

      Ну, справедливости ради, с отправкой заголовков и до 2022 было всё отлично.
      Просто в статье на этом действительно сделан акцент, из-за чего может сложиться неправильное впечатление.
      А по своей сути, to_route — обычный сахарок, более короткая запись.
      if (! function_exists('to_route')) {
      /**
      * Create a new redirect response to a named route.
      *
      * @param string $route
      * @param mixed $parameters
      * @param int $status
      * @param array $headers
      * @return \Illuminate\Http\RedirectResponse
      */
      function to_route($route, $parameters = [], $status = 302, $headers = [])
      {
      return redirect()->route($route, $parameters, $status, $headers);
      }
      }

      Но принципиально ничего нового этот хелпер не привносит.


      1. wendel
        18.02.2022 13:56
        +2

        Как и новая страница обработки ошибок, как и половина новых фич в каждой новой версии Laravel (:


        1. Mellorn
          18.02.2022 14:48

          Так я же с этим и не спорю. И восьмая версия фактически такая же была.

          Я бы наверное седьмую версию назвал мажорной. А 8 и 9 на мажорные слабо тянут.


          1. Rukis
            18.02.2022 15:17

            Насколько я помню сменился подход к версионированию.