- Основы
- Создание
- Базовые свойства
- Объявление в системе
- Автозагрузка
- Сортировка
- Пагинация
- Асинхронный режим
- Добавление в меню
- Alias
- Текущий элемент/модель
- Модальные окна
- Редиректы
- Активные действия
- Жизненный цикл
- Assets
- Response модификаторы
- Обработчики CRUD-операций
Основы
ModelResource расширяет CrudResource и предоставляет функциональность для работы с моделями Eloquent.
Он обеспечивает основу для создания ресурсов, связанных с моделями базы данных.
ModelResource предоставляет методы для выполнения CRUD-операций, управления отношениями, применения фильтров и многое другое.
Вы также можете ознакомиться с разделом CrudResource.
CrudResource - это абстрактный класс предоставляющий базовый интерфейс для CRUD операций без привязки к хранилищу и типу данных.
Под капотом, ModelResource расширяет CrudResource и сразу включает возможность работы с Eloquent.
Если углубляться в детали MoonShine, то вы увидите все те же стандартные Controller, Model и Blade views.
Если бы вы разрабатывали самостоятельно, то создать ресурс контроллеры и ресурс маршруты можно следующим образом:
php artisan make:controller Controller --resourcephp artisan make:controller Controller --resource
use Illuminate\Support\Facades\Route;Route::resource('resources', Controller::class);
use Illuminate\Support\Facades\Route;Route::resource('resources', Controller::class);
Но эту работу можно поручить админ-панели MoonShine, которая будет их генерировать и объявлять самостоятельно.
ModelResource является основным компонентом для создания раздела в админ-панели при работе с базой данных.
Создание
php artisan moonshine:resource Postphp artisan moonshine:resource Post
Для более подробной информации обратитесь к разделу Команды.
Базовые свойства
Базовые параметры, которые можно менять у ресурса, чтобы кастомизировать его работу.
namespace App\MoonShine\Resources;use App\Models\Post;use MoonShine\Laravel\Resources\ModelResource;/*** @extends ModelResource<Post>*/class PostResource extends ModelResource{// Модельprotected string $model = Post::class;// Заголовок разделаprotected string $title = 'Posts';// Eager loadprotected array $with = ['category'];// Поле для отображения значений в связях и хлебных крошкахprotected string $column = 'id';// ...}
namespace App\MoonShine\Resources;use App\Models\Post;use MoonShine\Laravel\Resources\ModelResource;/*** @extends ModelResource<Post>*/class PostResource extends ModelResource{// Модельprotected string $model = Post::class;// Заголовок разделаprotected string $title = 'Posts';// Eager loadprotected array $with = ['category'];// Поле для отображения значений в связях и хлебных крошкахprotected string $column = 'id';// ...}

Объявление в системе
Ресурс автоматически регистрируется в MoonShineServiceProvider при вызове команды php artisan moonshine:resource.
Но если вы создаете раздел вручную, то вам необходимо самостоятельно его объявить в системе в MoonShineServiceProvider.
namespace App\Providers;use App\MoonShine\Resources\ArticleResource;use Illuminate\Support\ServiceProvider;use MoonShine\Contracts\Core\DependencyInjection\CoreContract;use MoonShine\Laravel\DependencyInjection\ConfiguratorContract;use MoonShine\Laravel\DependencyInjection\MoonShine;use MoonShine\Laravel\DependencyInjection\MoonShineConfigurator;class MoonShineServiceProvider extends ServiceProvider{/*** @param MoonShine $core* @param MoonShineConfigurator $config**/public function boot(CoreContract $core,ConfiguratorContract $config,): void{$core->resources([MoonShineUserResource::class,MoonShineUserRoleResource::class,ArticleResource::class,// ...])->pages([...$config->getPages(),]);}}
namespace App\Providers;use App\MoonShine\Resources\ArticleResource;use Illuminate\Support\ServiceProvider;use MoonShine\Contracts\Core\DependencyInjection\CoreContract;use MoonShine\Laravel\DependencyInjection\ConfiguratorContract;use MoonShine\Laravel\DependencyInjection\MoonShine;use MoonShine\Laravel\DependencyInjection\MoonShineConfigurator;class MoonShineServiceProvider extends ServiceProvider{/*** @param MoonShine $core* @param MoonShineConfigurator $config**/public function boot(CoreContract $core,ConfiguratorContract $config,): void{$core->resources([MoonShineUserResource::class,MoonShineUserRoleResource::class,ArticleResource::class,// ...])->pages([...$config->getPages(),]);}}
Автозагрузка
В MoonShine также доступна автозагрузка страниц и ресурсов.
Она выключена по-умолчанию и для активации нужно вызвать метод autoload() в MoonShineServiceProvider вместо указания ссылок на страницы и ресурсы.
namespace App\Providers;use App\MoonShine\Resources\ArticleResource;use Illuminate\Support\ServiceProvider;use MoonShine\Contracts\Core\DependencyInjection\CoreContract;use MoonShine\Laravel\DependencyInjection\ConfiguratorContract;class MoonShineServiceProvider extends ServiceProvider{public function boot(CoreContract $core,ConfiguratorContract $config,): void{$core->autoload();}}
namespace App\Providers;use App\MoonShine\Resources\ArticleResource;use Illuminate\Support\ServiceProvider;use MoonShine\Contracts\Core\DependencyInjection\CoreContract;use MoonShine\Laravel\DependencyInjection\ConfiguratorContract;class MoonShineServiceProvider extends ServiceProvider{public function boot(CoreContract $core,ConfiguratorContract $config,): void{$core->autoload();}}
При деплое проекта на продакшен в Laravel 11+ рекомендуется вызывать консольную команду php artisan optimize.
Помимо её основных функций, она также выполнит кэширование ресурсов MoonShine.
При использовании Laravel 10 необходимо вручную вызывать консольную команду php artisan moonshine:optimize для оптимизации процесса инициализации админ панели.
Очистить кэш панели можно как командой php artisan optimize:clear в Laravel 11,
так и прямым вызовом консольной команды php artisan moonshine:optimize-clear.
Если после создания классов приложение их не видит - обновите кэш композера командой composer dump-autoload.
Сортировка
По умолчанию записи в таблице ресурса сортируются по полю id в порядке убывания.
Вы можете изменить сортировку с помощью свойств $sortColumn и $sortDirection.
protected string $sortColumn = 'created_at';protected string $sortDirection = 'DESC';protected string $sortColumn = 'created_at';protected string $sortDirection = 'DESC';
Пагинация
По умолчанию MoonShine использует стандартную пагинацию Laravel.
Вы можете переключиться на cursor pagination или simple pagination с помощью свойств $cursorPaginate и $simplePaginate.
protected bool $cursorPaginate = true;protected bool $cursorPaginate = true;
protected bool $simplePaginate = true;protected bool $simplePaginate = true;
Подробнее о типах пагинации можно узнать в документации Laravel.
Асинхронный режим
По умолчанию в ресурсе включен "Асинхронный режим".
Чтобы его выключить, переопределите свойство $isAsync в ресурсе или на отдельных CRUD страницах.
protected bool $isAsync = false;protected bool $isAsync = false;
Подробнее об асинхронной загрузке таблицы можно узнать в разделе TableBuilder.
Подробнее об асинхронной отправке форм можно узнать в разделе FormBuilder.
Добавление в меню
Все страницы в MoonShine имеют Layout и у каждой страницы он может быть свой.
По умолчанию при установке MoonShine добавляет базовый MoonShineLayout в директорию app/MoonShine/Layouts.
В Layout кастомизируется всё, что отвечает за внешний вид ваших страниц и это касается также и навигации.
Чтобы добавить раздел в меню, необходимо объявить его через метод menu() в Layout.
namespace App\MoonShine\Layouts;use App\MoonShine\Resources\PostResource;use MoonShine\Laravel\Layouts\AppLayout;use MoonShine\Laravel\Resources\MoonShineUserResource;use MoonShine\Laravel\Resources\MoonShineUserRoleResource;use MoonShine\MenuManager\MenuGroup;use MoonShine\MenuManager\MenuItem;final class MoonShineLayout extends AppLayout{// ...protected function menu(): array{return [MenuGroup::make(__('moonshine::ui.resource.system'), [MenuItem::make(MoonShineUserResource::class),MenuItem::make(MoonShineUserRoleResource::class),]),MenuItem::make(PostResource::class),// ...];}}
namespace App\MoonShine\Layouts;use App\MoonShine\Resources\PostResource;use MoonShine\Laravel\Layouts\AppLayout;use MoonShine\Laravel\Resources\MoonShineUserResource;use MoonShine\Laravel\Resources\MoonShineUserRoleResource;use MoonShine\MenuManager\MenuGroup;use MoonShine\MenuManager\MenuItem;final class MoonShineLayout extends AppLayout{// ...protected function menu(): array{return [MenuGroup::make(__('moonshine::ui.resource.system'), [MenuItem::make(MoonShineUserResource::class),MenuItem::make(MoonShineUserRoleResource::class),]),MenuItem::make(PostResource::class),// ...];}}
О расширенных настройках Layout можно узнать в разделе Layout.
О расширенных настройках MenuManager можно узнать в разделе Menu.
Alias
По умолчанию alias ресурса, который используется в url, генерируется на основе наименования класс в kebab-case, например:
MoonShineUserResource -> moon-shine-user-resource.
Для того чтобы изменить alias, можно воспользоваться свойством ресурса $alias или методом getAlias().
namespace App\MoonShine\Resources;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{protected ?string $alias = 'custom-alias';// ...}
namespace App\MoonShine\Resources;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{protected ?string $alias = 'custom-alias';// ...}
namespace App\MoonShine\Resources;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{public function getAlias(): ?string{return 'custom-alias';}}
namespace App\MoonShine\Resources;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{public function getAlias(): ?string{return 'custom-alias';}}
Текущий элемент/модель
Если в url детальной страницы или страницы редактирования присутствует параметр resourceItem, то в ресурсе вы можете получить доступ к текущему элементу через метод getItem().
$this->getItem();$this->getItem();
Через метод getModel() можно получить доступ к модели.
$this->getModel();$this->getModel();
Модальные окна
Вы можете добавлять, редактировать и просматривать записи прямо на странице со списком в модальном окне.
namespace App\MoonShine\Resources;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{protected bool $createInModal = true;protected bool $editInModal = true;protected bool $detailInModal = true;// ...}
namespace App\MoonShine\Resources;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{protected bool $createInModal = true;protected bool $editInModal = true;protected bool $detailInModal = true;// ...}
Редиректы
По умолчанию при создании и редактировании записи осуществляется редирект на страницу с формой, но это поведение можно контролировать.
Через свойство в ресурсе:
use MoonShine\Support\Enums\PageType;protected ?PageType $redirectAfterSave = PageType::FORM;
use MoonShine\Support\Enums\PageType;protected ?PageType $redirectAfterSave = PageType::FORM;
Через метод:
public function getRedirectAfterSave(): string{return '/';}public function getRedirectAfterSave(): string{return '/';}
Также доступен редирект после удаления:
public function getRedirectAfterDelete(): string{return $this->getIndexPageUrl();}public function getRedirectAfterDelete(): string{return $this->getIndexPageUrl();}
Активные действия
Часто бывает, что необходимо создать ресурс, в котором будет исключена возможность удалять, или добавлять, или редактировать.
И здесь речь не об авторизации, а о глобальном исключении этих разделов.
Делается это крайне просто за счет метода activeActions() в ресурсе.
namespace App\MoonShine\Resources;use MoonShine\Support\ListOf;use MoonShine\Support\Enums\Action;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{// ...protected function activeActions(): ListOf{return parent::activeActions()->except(Action::VIEW, Action::MASS_DELETE)// ->only(Action::VIEW);}}
namespace App\MoonShine\Resources;use MoonShine\Support\ListOf;use MoonShine\Support\Enums\Action;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{// ...protected function activeActions(): ListOf{return parent::activeActions()->except(Action::VIEW, Action::MASS_DELETE)// ->only(Action::VIEW);}}
Также можно просто создать новый список, например:
use MoonShine\Support\Enums\Action;use MoonShine\Support\ListOf;protected function activeActions(): ListOf{return new ListOf(Action::class, [Action::VIEW, Action::UPDATE]);}
use MoonShine\Support\Enums\Action;use MoonShine\Support\ListOf;protected function activeActions(): ListOf{return new ListOf(Action::class, [Action::VIEW, Action::UPDATE]);}
Жизненный цикл
Resource имеет несколько различных методов подключения к различным частям своего жизненного цикла. Давайте пройдемся по ним:
Активный ресурс
Метод onLoad() дает возможность интегрироваться в момент когда ресурс загружен и в данный момент является активным.
namespace App\MoonShine\Resources;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{// ...protected function onLoad(): void{// ...}}
namespace App\MoonShine\Resources;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{// ...protected function onLoad(): void{// ...}}
Рецепт: Изменение breadcrumbs из ресурса.
Вы также можете подключить trait к ресурсу и внутри trait добавить метод согласно конвенции наименований - load{TraitName} и через трейт обратиться к onLoad ресурса.
namespace App\MoonShine\Resources;use App\Traits\WithPermissions;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{use WithPermissions;// ...}
namespace App\MoonShine\Resources;use App\Traits\WithPermissions;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{use WithPermissions;// ...}
use MoonShine\Support\Enums\Layer;use MoonShine\Support\Enums\PageType;trait WithPermissions{protected function loadWithPermissions(): void{$this->getPages()->findByUri(PageType::FORM->value)->pushToLayer(layer: Layer::BOTTOM,component: Permissions::make(label: 'Permissions',resource: $this,));}}
use MoonShine\Support\Enums\Layer;use MoonShine\Support\Enums\PageType;trait WithPermissions{protected function loadWithPermissions(): void{$this->getPages()->findByUri(PageType::FORM->value)->pushToLayer(layer: Layer::BOTTOM,component: Permissions::make(label: 'Permissions',resource: $this,));}}
Создание экземпляра
Метод onBoot() дает возможность интегрироваться в момент когда MoonShine создает экземпляр ресурса в системе.
namespace App\MoonShine\Resources;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{// ...protected function onBoot(): void{// ...}}
namespace App\MoonShine\Resources;use MoonShine\Laravel\Resources\ModelResource;class PostResource extends ModelResource{// ...protected function onBoot(): void{// ...}}
Вы также можете подключить trait к ресурсу и внутри trait добавить метод согласно конвенции наименований - boot{TraitName} и через трейт обратиться к onBoot() ресурса.
Assets
use MoonShine\AssetManager\Css;use MoonShine\AssetManager\Js;protected function onLoad(): void{$this->getAssetManager()->add(Css::make('/css/app.css'))->append(Js::make('/js/app.js'));}
use MoonShine\AssetManager\Css;use MoonShine\AssetManager\Js;protected function onLoad(): void{$this->getAssetManager()->add(Css::make('/css/app.css'))->append(Js::make('/js/app.js'));}
Response модификаторы
Если ресурс в режиме "async", то вы можете модифицировать ответ:
use Symfony\Component\HttpFoundation\Response;use MoonShine\Crud\JsonResponse;public function modifyDestroyResponse(JsonResponse $response): JsonResponse{return $response;}public function modifyMassDeleteResponse(JsonResponse $response): JsonResponse{return $response;}public function modifySaveResponse(JsonResponse $response): JsonResponse{return $response;}public function modifyErrorResponse(Response $response, Throwable $exception): Response{return $response;}use Symfony\Component\HttpFoundation\Response;use MoonShine\Crud\JsonResponse;public function modifyDestroyResponse(JsonResponse $response): JsonResponse{return $response;}public function modifyMassDeleteResponse(JsonResponse $response): JsonResponse{return $response;}public function modifySaveResponse(JsonResponse $response): JsonResponse{return $response;}public function modifyErrorResponse(Response $response, Throwable $exception): Response{return $response;}
Обработчики CRUD-операций
Вы можете изменить логику операций сохранения, удаления и массового удаления записей в ModelResource с помощью своих обработчиков и атрибутов SaveHandler, DestroyHandler и MassDestroyHandler.
В обработчик операции сохранения прокидывается массив $data, который уже прошёл через метод apply() у полей формы.
Пример использования:
use MoonShine\Laravel\Resources\ModelResource;use MoonShine\Crud\Attributes\DestroyHandler;use MoonShine\Crud\Attributes\MassDestroyHandler;use MoonShine\Crud\Attributes\SaveHandler;#[DestroyHandler(MoonShineUserRoleHandlers::class, 'destroy')]#[MassDestroyHandler(MoonShineUserRoleHandlers::class, 'massDestroy')]#[SaveHandler(MoonShineUserRoleHandlers::class, 'save')]class MoonShineUserRoleResource extends ModelResource{//...}use MoonShine\Laravel\Resources\ModelResource;use MoonShine\Crud\Attributes\DestroyHandler;use MoonShine\Crud\Attributes\MassDestroyHandler;use MoonShine\Crud\Attributes\SaveHandler;#[DestroyHandler(MoonShineUserRoleHandlers::class, 'destroy')]#[MassDestroyHandler(MoonShineUserRoleHandlers::class, 'massDestroy')]#[SaveHandler(MoonShineUserRoleHandlers::class, 'save')]class MoonShineUserRoleResource extends ModelResource{//...}
Класс с методами для обработки операций может выглядеть так:
final readonly class MoonShineUserRoleHandlers{public function save(MoonshineUserRole $model, array $data): MoonshineUserRole{$model->fill($data);$model->save();return $model;}public function destroy(MoonshineUserRole $model): bool{return $model->delete();}public function massDestroy(array $ids): void{foreach ($ids as $id) {MoonshineUserRole::query()->whereKey($id)->delete();}}}final readonly class MoonShineUserRoleHandlers{public function save(MoonshineUserRole $model, array $data): MoonshineUserRole{$model->fill($data);$model->save();return $model;}public function destroy(MoonshineUserRole $model): bool{return $model->delete();}public function massDestroy(array $ids): void{foreach ($ids as $id) {MoonshineUserRole::query()->whereKey($id)->delete();}}}
Вы также можете использовать классы-обработчики вместо методов — в этом случае они должны реализовывать метод __invoke():
use MoonShine\Laravel\Resources\ModelResource;use MoonShine\Crud\Attributes\DestroyHandler;use MoonShine\Crud\Attributes\MassDestroyHandler;use MoonShine\Crud\Attributes\SaveHandler;#[SaveHandler(MoonShineUserRoleSaveHandler::class)]#[DestroyHandler(MoonShineUserRoleDestroyHandler::class)]#[MassDestroyHandler(MoonShineUserRoleMassDestroyHandler::class)]class MoonShineUserRoleResource extends ModelResource{//..}use MoonShine\Laravel\Resources\ModelResource;use MoonShine\Crud\Attributes\DestroyHandler;use MoonShine\Crud\Attributes\MassDestroyHandler;use MoonShine\Crud\Attributes\SaveHandler;#[SaveHandler(MoonShineUserRoleSaveHandler::class)]#[DestroyHandler(MoonShineUserRoleDestroyHandler::class)]#[MassDestroyHandler(MoonShineUserRoleMassDestroyHandler::class)]class MoonShineUserRoleResource extends ModelResource{//..}
final readonly class MoonShineUserRoleSaveHandler{public function __invoke(MoonshineUserRole $model, array $data): MoonshineUserRole{$model->fill($data);$model->save();return $model;}}final readonly class MoonShineUserRoleDestroyHandler{public function __invoke(MoonshineUserRole $model): bool{return $model->delete();}}final readonly class MoonShineUserRoleMassDestroyHandler{public function __invoke(array $ids): void{foreach ($ids as $id) {MoonshineUserRole::query()->whereKey($id)->delete();}}}final readonly class MoonShineUserRoleSaveHandler{public function __invoke(MoonshineUserRole $model, array $data): MoonshineUserRole{$model->fill($data);$model->save();return $model;}}final readonly class MoonShineUserRoleDestroyHandler{public function __invoke(MoonshineUserRole $model): bool{return $model->delete();}}final readonly class MoonShineUserRoleMassDestroyHandler{public function __invoke(array $ids): void{foreach ($ids as $id) {MoonshineUserRole::query()->whereKey($id)->delete();}}}