Пролог

Руководство по обновлению

Обновление до 5.0 с 4.x

Примерное время обновления: 30 минут

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

Руководство дополняется и будет расширено до выхода версии 5.0. Для обновления с 3.x сначала выполните руководство по обновлению 4.x.

Обновление зависимостей

Вероятность влияния: высокая

Для MoonShine 5.0 требуются PHP 8.4 или новее и Laravel 11, 12 или 13. Laravel 10 больше не поддерживается.

Обновите следующие зависимости в composer.json вашего приложения:

composer update
composer update

Для MoonShine 5.0 требуются symfony/http-foundation и symfony/psr-http-message-bridge версии 7.4 или 8.1. Если Composer не может их установить из-за зафиксированных старых версий, обновите пакет вместе с зависимостями:

composer update moonshine/moonshine -W
composer update moonshine/moonshine -W

Ресурсы

Фильтры, теги запросов и обработчики

Вероятность влияния: высокая

Трейты HasFilters, HasQueryTags и HasHandlers удалены из ModelResource. Фильтры, теги запросов и обработчики настраиваются только в IndexPage.

Перенесите следующие методы из ресурса на его индексную страницу:

  • filters(),
  • queryTags(),
  • handlers(),
  • getIgnoredFields().
 namespaces
namespace App\MoonShine\Resources\Post\Pages;
 
use MoonShine\Laravel\Pages\Crud\IndexPage;
use MoonShine\Laravel\QueryTags\QueryTag;
use MoonShine\UI\Fields\Text;
 
class PostIndexPage extends IndexPage
{
protected function filters(): iterable
{
return [
Text::make('Title', 'title'),
];
}
 
protected function queryTags(): array
{
return [
QueryTag::make(
'Published',
static fn ($query) => $query->where('is_published', true)
),
];
}
}
 namespaces
namespace App\MoonShine\Resources\Post\Pages;
 
use MoonShine\Laravel\Pages\Crud\IndexPage;
use MoonShine\Laravel\QueryTags\QueryTag;
use MoonShine\UI\Fields\Text;
 
class PostIndexPage extends IndexPage
{
protected function filters(): iterable
{
return [
Text::make('Title', 'title'),
];
}
 
protected function queryTags(): array
{
return [
QueryTag::make(
'Published',
static fn ($query) => $query->where('is_published', true)
),
];
}
}

Методы ресурса hasFilters(), getFilters(), hasQueryTags(), getQueryTags(), hasHandlers() и getHandlers() также удалены. Вызывайте их у индексной страницы.

-$resource->getFilters();
+$resource->getIndexPage()?->getFilters();
-$resource->getFilters();
+$resource->getIndexPage()?->getFilters();

Значения фильтров и сохраненное состояние запроса по-прежнему читаются из ресурса, например через getFilterParams().

Подробнее в разделах Фильтры, Теги запросов и Обработчики.

Импорт и экспорт

Вероятность влияния: средняя

MoonShine 5.0 работает с moonshine/import-export 3.x.

Трейт ImportExportConcern и интерфейс HasImportExportContract остаются в ресурсе вместе с полями импорта и экспорта. Кнопки теперь являются обработчиками индексной страницы, поэтому добавьте в нее трейт ImportExportHandlersConcern.

 namespaces
namespace App\MoonShine\Resources\Category\Pages;
 
use MoonShine\ImportExport\Traits\ImportExportHandlersConcern;
use MoonShine\Laravel\Pages\Crud\IndexPage;
 
class CategoryIndexPage extends IndexPage
{
use ImportExportHandlersConcern;
}
 namespaces
namespace App\MoonShine\Resources\Category\Pages;
 
use MoonShine\ImportExport\Traits\ImportExportHandlersConcern;
use MoonShine\Laravel\Pages\Crud\IndexPage;
 
class CategoryIndexPage extends IndexPage
{
use ImportExportHandlersConcern;
}

Перенесите переопределения методов import(), export(), isExportToCsv() и handlers() из ресурса на индексную страницу. Внутри страницы ресурс доступен через getResource().

Экранирование

Экранирование отображаемого текста

Вероятность влияния: высокая

Метки, подсказки, префиксы, суффиксы и строки, возвращаемые callback-функциями полей beforeRender() и afterRender(), теперь экранируются по умолчанию. Метка вида <strong>Name</strong> отображается как текст.

Добавьте соответствующий метод unescape…() там, где нужен HTML.

 namespaces
use MoonShine\UI\Fields\Text;
 
Text::make('<strong>Name</strong>', 'name')
+ ->unescapeLabel()
->hint('<a href="/help">Help</a>')
+ ->unescapeHint();
 namespaces
use MoonShine\UI\Fields\Text;
 
Text::make('<strong>Name</strong>', 'name')
+ ->unescapeLabel()
->hint('<a href="/help">Help</a>')
+ ->unescapeHint();

Значения по умолчанию можно изменить в секции escapes конфигурации.

config/moonshine.php
'escapes' => [
'label' => true,
'hint' => true,
'prefix' => true,
'suffix' => true,
'before_render' => true,
'after_render' => true,
],
'escapes' => [
'label' => true,
'hint' => true,
'prefix' => true,
'suffix' => true,
'before_render' => true,
'after_render' => true,
],

Названия ссылок полей, метки связанных записей и заголовки модальных окон экранируются независимо от этих настроек. Чтобы разрешить в них HTML, используйте unescapeLinkName(), unescapeOptionLabels() и unescapeTitle().

Значения, реализующие Illuminate\Contracts\Support\Htmlable, например HtmlString, выводятся как подготовленный HTML.

Разрешайте HTML только для доверенного содержимого. Отключение экранирования не очищает строку.

Все настройки описаны в разделе Экранирование отображаемого текста.

Экранирование значений полей

Вероятность влияния: низкая

Значения полей Text, Textarea и BelongsTo по-прежнему экранируются по умолчанию. Новые настройки escapes.value и escapes.value_on_apply меняют это поведение глобально, поэтому, если оставить их включенными, ничего менять не нужно.

config/moonshine.php
'escapes' => [
// ...
'value' => true,
'value_on_apply' => true,
],
'escapes' => [
// ...
'value' => true,
'value_on_apply' => true,
],

Если ваше поле использует трейт WithEscapedValue и переобъявляет свойство $unescape, измените его тип на ?bool. null означает, что поле следует глобальной настройке.

-protected bool $unescape = false;
+protected ?bool $unescape = null;
-protected bool $unescape = false;
+protected ?bool $unescape = null;

Вызов escapeOnApply() без аргумента теперь всегда включает экранирование при применении, даже если escapes.value_on_apply выключен. Настройки отдельного поля описаны в разделе Text.

Редактирование в popover

Вероятность влияния: очень низкая

Редактирование в popover через updateOnPreview() теперь экранирует значение, если поле не реализует MoonShine\UI\Contracts\HasEscapedValueContract или не вызывает unescape(). Встроенные поля Text, Textarea и BelongsTo реализуют этот контракт. Собственное поле с трейтом WithEscapedValue должно реализовать этот контракт, чтобы в popover сохранялись неэкранированные значения.

Поля и компоненты

Переопределенные представления

Вероятность влияния: средняя

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

Представления, выводящие метки, подсказки и заголовки, теперь выводят подготовленные в PHP значения, например $labelHtml, $hintHtml, $titleHtml и $valueHtml, которые уже учитывают настройки экранирования отображаемого текста. Опубликованная копия с {!! $label !!} обходит экранирование, а копия с {{ $label }} игнорирует unescapeLabel().

  • moonshine::components.card, collapse, field-container, heading, modal, off-canvas, tabs,
  • moonshine::components.form.fieldset, form.hint, form.wrapper, form.input-extensions.ext, form.input-extensions.prefix,
  • moonshine::components.layout.box, layout.divider, layout.footer,
  • moonshine::components.link-button, link-native,
  • moonshine::components.menu.divider, menu.group, menu.item-link,
  • moonshine::components.metrics.value, metrics.wrapped.value,
  • moonshine::components.table.index,
  • moonshine::fields.fieldset, fields.relationships.belongs-to-many.

Несколько часто используемых представлений теперь выводят элементы напрямую, без вложенных Blade-компонентов. Их данные тоже подготавливаются в PHP, например $tagAttributes и $labelHtml в представлении кнопки.

  • moonshine::components.action-button,
  • moonshine::components.form.builder,
  • moonshine::components.icon,
  • moonshine::components.rating,
  • moonshine::fields.hidden.

Строки вертикальных таблиц по умолчанию, например на DetailPage, выводятся новым представлением moonshine::components.table.vertical-field. Представления layout.grid, layout.column и layout.div для них больше не используются, поэтому их переопределения на эти строки не влияют. Переопределяйте moonshine::components.table.vertical-field. Строки, настроенные через замыкания title или value метода vertical(), по-прежнему используют компоненты.

Расширения файлов

Вероятность влияния: низкая

Поля File и Image наследуют новый глобальный список allowed_extensions, если у поля не вызван allowedExtensions(). Значение по умолчанию [] разрешает все расширения, поэтому в существующих приложениях ничего менять не нужно.

Если ваш класс поля наследует File или Image и переобъявляет свойство $allowedExtensions, измените его тип на ?array. null означает, что поле следует глобальному списку.

-protected array $allowedExtensions = ['pdf'];
+protected ?array $allowedExtensions = ['pdf'];
-protected array $allowedExtensions = ['pdf'];
+protected ?array $allowedExtensions = ['pdf'];

Метод allowedExtensions() контракта MoonShine\UI\Contracts\FileableContract теперь принимает array|string. Обновите его сигнатуру в собственных реализациях и переопределениях.

-public function allowedExtensions(array $allowedExtensions): static
+public function allowedExtensions(array|string $allowedExtensions): static
-public function allowedExtensions(array $allowedExtensions): static
+public function allowedExtensions(array|string $allowedExtensions): static

Атрибут accept по умолчанию теперь добавляется при рендере поля, поэтому до рендера getAttribute('accept') возвращает null, если у поля не вызывались accept() или allowedExtensions(). Подробнее в разделе File.

Вероятность влияния: низкая

Компонент Link и ссылки поля Url теперь допускают только схемы http, https, mailto и tel, а также относительные URL и якоря. Ссылки с другими схемами, например javascript: или ftp:, выводятся с href="#". Текст ссылки не меняется.

Скелетон и спиннер таблиц

Вероятность влияния: низкая

Скелетон и спиннер выводятся только для асинхронных таблиц. Методы skeleton() и loader() не влияют на синхронные таблицы.

Вероятность влияния: очень низкая

Экземпляр Popper у выпадающего меню создается при первом открытии. До этого свойство popperInstance Alpine-компонента dropdown равно null. Проверяйте его перед вызовом методов из собственного JavaScript.

Устаревшие API

Удаленные классы

Вероятность влияния: средняя

Классы, помеченные устаревшими в 4.x, удалены.

Класс Замена
MoonShine\Laravel\Handlers\Handler MoonShine\Crud\Handlers\Handler
MoonShine\Laravel\Handlers\Handlers MoonShine\Crud\Handlers\Handlers
MoonShine\Laravel\Notifications\NotificationButton MoonShine\Crud\Notifications\NotificationButton
MoonShine\Laravel\Http\Responses\MoonShineJsonResponse MoonShine\Crud\JsonResponse
MoonShine\Laravel\Http\Requests\Relations\AsyncSearchRequest MoonShine\Laravel\Http\Requests\Relations\RelationModelFieldRequest
MoonShine\Laravel\MoonShineUI Хелпер toast() вместо MoonShineUI::toast()
-use MoonShine\Laravel\Handlers\Handler;
+use MoonShine\Crud\Handlers\Handler;
 
-use MoonShine\Laravel\Http\Responses\MoonShineJsonResponse;
+use MoonShine\Crud\JsonResponse;
 
-MoonShineUI::toast('Saved');
+toast('Saved');
-use MoonShine\Laravel\Handlers\Handler;
+use MoonShine\Crud\Handlers\Handler;
 
-use MoonShine\Laravel\Http\Responses\MoonShineJsonResponse;
+use MoonShine\Crud\JsonResponse;
 
-MoonShineUI::toast('Saved');
+toast('Saved');

Конфигурация

Значения конфигурации

Вероятность влияния: низкая

Значения конфигурации теперь проверяются при чтении, а некорректное значение вызывает TypeError.

  • middleware, auth.pipelines и pages должны быть массивами строк, например имен классов. Замыкания и объекты в auth.pipelines больше не поддерживаются, поэтому регистрируйте каждый pipeline как класс.
  • pages должен содержать классы страниц, layout должен наследовать MoonShine\Crud\Layouts\AbstractLayout, а palette должен реализовывать MoonShine\Contracts\ColorManager\PaletteContract.
config/moonshine.php
'auth' => [
'pipelines' => [
- static fn ($request, $next) => $next($request),
+ \App\MoonShine\AuthPipelines\TwoFactorAuthentication::class,
],
],
'auth' => [
'pipelines' => [
- static fn ($request, $next) => $next($request),
+ \App\MoonShine\AuthPipelines\TwoFactorAuthentication::class,
],
],

Расширение MoonShine

Обертка запроса

Вероятность влияния: очень низкая

Свойство $request класса MoonShine\Core\AbstractRequest теперь имеет тип ServerRequestInterface|Closure. PSR-запрос создается лениво, при чтении данных запроса.

Если ваш класс наследует AbstractRequest и читает $this->request напрямую, используйте метод getRequest().

-$this->request->getQueryParams();
+$this->getRequest()->getQueryParams();
-$this->request->getQueryParams();
+$this->getRequest()->getQueryParams();

Контракты

Вероятность влияния: очень низкая

Если ваши классы реализуют контракты MoonShine напрямую, а не наследуют базовые классы, добавьте новые методы:

  • MoonShine\Contracts\UI\ComponentContract: when() и unless(), например через трейт Illuminate\Support\Traits\Conditionable,
  • MoonShine\Contracts\Core\DependencyInjection\TranslatorContract: getString(),
  • MoonShine\Contracts\Core\HasResourceContract: getResourceOrFail(), его реализует трейт MoonShine\Core\Traits\HasResource,
  • MoonShine\Crud\Contracts\Notifications\NotificationButtonContract: toArray().

RequestContract::getScalar() теперь возвращает string|int|float|bool|null вместо mixed. Обновите тип возврата в собственных реализациях.

Новые контракты экранирования, например HasLabelEscapingContract или HasEscapedValueContract, необязательны.