javascript

Fusion — php стал ещё ближе к javascript

  • вторник, 25 февраля 2025 г. в 00:00:06
https://habr.com/ru/companies/first/articles/885098/

Каждый PHP-разработчик, работая с современными JavaScript-фреймворками, наверняка задумывался о том, как было бы здорово передавать данные напрямую в компоненты без танцев вокруг контроллеров и типов данных. Арон Френсис, видимо, тоже терзал себя этим вопросом, поэтому 4 февраля 2025 года на своём ютуб-канале представил новую веху развития фронтенда на Laravel — Fusion.  В этом статье мы рассмотрим базовые принципы работы библиотеки и основные нюансы работы с ней.

Концепт

У Laravel уже есть Inertia, которая позволяет использовать vue шаблоны для рендеринга, напрямую передавая туда данные из php, но всё-равно требовалось писать контроллеры и вручную передавать данные в шаблоны. Fusion решает эту проблему

Что делает Fusion:

  • Использует Vite для извлечения PHP-блоков из JavaScript-файлов и сохраняет их на диск.

  • В процессе трансляции использует Vite для добавления информации в JavaScript-файлы.

  • Запускает PHP на бэкенде, а JavaScript — на фронтенде.

  • Превращает PHP-блоки в подобие контроллеров.

  • Использует стандартный жизненный цикл запроса/ответа Laravel, включая маршрутизацию, аутентификацию, middleware и т. д.

  • Позволяет вам управлять синхронизацией состояния frontend/backend.

Можно представить <php>-блок в вашем файле как контроллер, к которому автоматически подключается состояние и добавляются вызовы методов.

Fusion поддерживает два стиля написания php-кода в SFC (single file components) — процедурный и классовый.

Процедурный:

<php>
  // Определяем пропс в PHP
  $name = prop(Auth::user()->name);
</php>

<template>
  <!-- Используем его во Vue! -->
  Hello {{ name }}!
</template>

Class-based (классовый): 

<php>
  new class {
  // Определяем пропс в PHP
    public string $name;

    public function mount()
    {
      $this->name = Auth::user()->name;
    }
  }
</php>

<template>
<!-- Используем его во Vue! -->
  Hello {{ name }}!
</template>

Код выше позволяет передать переменную $name в ваш шаблон Vue в качестве name. Она будет передана приложению при первой загрузке. Вам не нужно определять какие-либо пропсы на стороне Vue, библиотека позаботится об этом вместо вас.

Как работает под капотом

Теперь, чтобы разобраться с магией, мы рассмотрим жизненный цикл Fusion. Он состоит из четырёх этапов. 

Запуск сборки

Когда вы выполняете npm run dev или npm run build, запускается Vite. Fusion добавляет в процесс плагин, который перехватывает работу над Vue-компонентами до того, как их обработает Vue-плагин.

Обработка PHP-блоков

Fusion-плагин ищет <php>-блоки. Если они найдены, код извлекается и передается Artisan-команде:

php artisan fusion:conform

Она, в свою очередь, делает так, что:

  • Код проходит серию парсеров, чтобы соответствовать стандарту Fusion.

  • PHP-файл записывается на диск.

Этот процесс выполняется на этапе сборки, а не во время выполнения скрипта (для экономии ресурсов).

Генерация Shim-файла

После валидации PHP-блоков выполняется команда:

php artisan fusion:shim

Она запускает следующие процессы:

  • Создается небольшой JavaScript-файл с информацией о свойствах состояния и доступных методах.

  • В этом файле находится функция useFusion. Для каждого компонента создается своя useFusion.

Стоит заметить, что вы не обязаны импортировать useFusion, Fusion автоматически подключит ее в Vue-компонент. Однако, если хотите вручную управлять состоянием и методами, можете использовать useFusion в script или script setup.

Запросы через Laravel

После генерации PHP-класса Fusion использует стандартный жизненный цикл Laravel запросов и ответов:

  • Ходящие запросы обрабатывает FusionController, который направляет их в соответствующий PHP-класс (из Vue-шаблона).

  • Если запрос — страничный (открытие страницы), возвращается Inertia-ответ с названием нужного компонента.

  • Если отправляется запрос действия (например, вызов метода), ответ передается фронтенду для обработки.

Установка

Fusion поддерживает только Laravel приложения, использующие Inertia, но в будущем это, по словам авторов, может измениться.

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

composer require fusionphp/fusion

После — запустить скрипт установки:

php artisan fusion:install

Скрипт сделает следующее:

  • опубликует конфиг (config/fusion.php),

  • создаст нужные директорий в storage,

  • добавит Vue-пакет в package.json,

  • изменит postinstall-скрипт и добавит fusion:install,

  • подключит Vue-плагин в resources/js/app.js,

  • подключит Vite-плагина в vite.config.js,

  • добавит post-update-cmd в composer.json,

  • выполнит миграцию внутренней SQLite-базы Fusion.

Кроме этого, Fusion создает резервные файлы ([original].backup) на случай ошибок. Копии можно удалить, если всё работает корректно.

Запуск и сборка происходит с помощью Vite, собственно, командами npm run dev и npm run build.

Маршрутизация

Fusion представляет на выбор 2 пути для маршрутизации:  file-based или индивидуальное назначение маршрутов для компонентов.

File-based routing

Удобный способ автоматической маршрутизации, когда структура URL отражает структуру файлов в вашем проекте.

Для включения файлового роутинга в web.php используется метод Fusion::pages():

use Fusion\Fusion;

Fusion::pages();
  • По умолчанию маршрутизируются все файлы из resources/js/Pages (настраивается в config/fusion.php под ключом paths.pages), начиная с корневого URI /.

  • Каждый .vue-файл в этой директории становится страницей, даже без <php>-блока, где название файла преобразуется в рут, например, HelloWorld.vue → /hello-world

Route Model Binding 

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

Route Model Binding используется с файловым роутингом, где параметры маршрута задаются в именах файлов с помощью квадратных скобок, например, Podcasts/[Podcast].vue. При посещении URL вроде /podcasts/1 Fusion может автоматически подтянуть модель вместо передачи строки "1".

Для получения данных в процедурном стиле достаточно использовать метод fromRoute() с указанием класса модели:

<php>
  $podcast = prop()->fromRoute(class: \App\Models\Podcast::class);
</php>

Для классового же укажите тип параметра в методе mount или свойстве:

<php>
  new class {
    public function mount(\App\Models\Podcast $podcast) {
      // $podcast — экземпляр модели
    }
  }
// или же
new class {
    public \App\Models\Podcast $podcast; // Автоматически привязывается
  }
</php>

Если модель не найдена, то вернётся ошибка 404.

Ручной роутинг

Для создания ручных маршрутов в Fusion используется метод Fusion::page() в файле web.php. Этот метод связывает конкретный URI с определённым компонентом. Вот пример:

// web.php
use Fusion\Fusion;

Fusion::page(uri: '/hello-world', component: 'Custom/HelloWorld');

Взаимодействие с JavaScript

Ручное управление состоянием с помощью useFusion

Кроме способа, который мы рассматривали выше, для большего контроля, получить переменную состояния из php можно с помощью композабла useFusion. Она позволяет импортировать состояние и действия в ваши JavaScript-скрипты.

<php>
  new class {
    public string $name = 'Aaron';
    public string $email = 'aaron@example.com';
  }
</php>

<script setup>
  import { useFusion } from '$fusion/Pages/Hello.js';
  const { name } = useFusion(['name']);
</script>

<template>
  Hello {{ name }}, your email is {{ email }}!
</template>

Здесь мы импортировали только name через useFusion, а email продолжает автоматически внедряться в шаблон. Это полезно, если вам нужно работать с конкретными частями состояния в JavaScript.

Если же вы хотите получить доступ ко всему состоянию, можно сделать так:

const data = useFusion();

Actions

Действия (Actions) в Fusion — это удобный механизм для взаимодействия между фронтендом (JavaScript) и бэкендом (PHP) в приложениях на Laravel. Они позволяют вызывать серверные функции из клиентской части, обеспечивая автоматическое управление состоянием, обработку ошибок и валидацию. Давайте разберём, как они работают, как их определять и использовать.

1. Процедурный стиль. В этом подходе действия задаются через функцию expose. Каждое действие передаётся как именованный аргумент в виде анонимной функции:

expose(
    hello: function() {
        return "Привет!";
    },
    anotherAction: function() {
        return "Другое действие";
    }
);

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

new class {
    public function hello() {
        return "Привет!";
    }

    public function anotherAction() {
        return "Другое действие";
    }
};

Если вы хотите, чтобы какой-то метод не был доступен на фронтенде, используйте атрибут #[ServerOnly]:

use Fusion\Attributes\ServerOnly;

new class {
    #[ServerOnly]
    public function internalMethod() {
        // Этот метод доступен только на сервере
    }
};

Синхронизация состояния между бэкендом и фронтендом

Fusion не обновляет состояние автоматически при каждом изменении на фронтенде. Вместо этого синхронизация происходит в двух случаях:

  1. При вызове действий:
    Когда вы вызываете действие (например, через @click="someAction"), текущее состояние фронтенда отправляется на бэкенд, обновляется там, а затем возвращается на фронтенд.

  2. С помощью fusion.sync:
    Это магическая функция, которая позволяет вручную синхронизировать состояние.

Работа с action на клиентской части

Действия, определенные в php, становятся доступны в js как функции. Например, если на бэкенде есть метод favorite, вы можете использовать его так:

<template>
  <button @click="favorite">Favorite</button>
</template>

Каждое действие в Fusion — это не просто функция, а прокси-объект, который содержит информацию о состоянии запроса. Это позволяет отслеживать выполнение действия и реагировать на него в интерфейсе. Доступные свойства:

  • processing: Запрос выполняется.

  • succeeded: Запрос успешно завершён.

  • recentlySucceeded: True, но становится false через 3.5 секунды (удобно для флеш-сообщений).

  • failed: Запрос завершился с ошибкой.

  • recentlyFailed: Выдает сообщение об ошибке, становится false через 3.5 секунды.

  • finished: Запрос завершён (успешно или с ошибкой).

  • recentlyFinished: Запрос завершён, становится false через 3.5 секунды.

  • error: Объект с ошибками (например, ошибки валидации из ответа 422).

  • errors: Массив сообщений об ошибках.

Пример с отслеживанием статуса:

<template>
  <button @click="favorite">Favorite</button>
  <div v-if="favorite.processing">Загрузка...</div>
  <div v-if="favorite.succeeded">Успех!</div>
</template>

В завершение 

Fusion — это мощный инструмент, который упрощает создание современных веб-приложений на Laravel и Vue.js. Благодаря Vite он обеспечивает быструю разработку и оптимизацию, а как дополнение к Inertia.js предлагает более тесную и удобную связь между фронтендом и бэкендом. На мой взгляд, это делает Fusion интересным выбором для тех, кто ищет эволюцию привычных подходов к интеграции в будущем.


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

-15% на заказ любого VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.