ModelResource

Основы

Основы

ModelResource расширяет CrudResource и предоставляет функциональность для работы с моделями Eloquent. Он обеспечивает основу для создания ресурсов, связанных с моделями базы данных. ModelResource предоставляет методы для выполнения CRUD-операций, управления отношениями, применения фильтров и многое другое.

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

Под капотом, ModelResource расширяет CrudResource и сразу включает возможность работы с Eloquent. Если углубляться в детали MoonShine, то вы увидите все те же стандартные Controller, Model и Blade views.

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

php artisan make:controller Controller --resource
 namespaces
use Illuminate\Support\Facades\Route;
 
Route::resource('resources', Controller::class);

Но эту работу можно поручить админ-панели MoonShine, которая будет их генерировать и объявлять самостоятельно.

ModelResource является основным компонентом для создания раздела в админ-панели при работе с базой данных.

Создание

php artisan moonshine:resource Post

Для более подробной информации обратитесь к разделу Команды.

Базовые свойства

Базовые параметры, которые можно менять у ресурса, чтобы кастомизировать его работу.

 namespaces
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 load
protected array $with = ['category'];
 
// Поле для отображения значений в связях и хлебных крошках
protected string $column = 'id';
 
// ...
}

resource_paginate resource_paginate_dark

Объявление в системе

Ресурс автоматически регистрируется в MoonShineServiceProvider при вызове команды php artisan moonshine:resource. Но если вы создаете раздел вручную, то вам необходимо самостоятельно его объявить в системе в MoonShineServiceProvider.

 namespaces
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 вместо указания ссылок на страницы и ресурсы.

 namespaces
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.

Добавление в меню

Все страницы в MoonShine имеют Layout и у каждой страницы он может быть свой. По умолчанию при установке MoonShine добавляет базовый MoonShineLayout в директорию app/MoonShine/Layouts. В Layout кастомизируется всё, что отвечает за внешний вид ваших страниц и это касается также и навигации.

Чтобы добавить раздел в меню, необходимо объявить его через метод menu() в Layout.

 namespaces
namespace App\MoonShine\Layouts;
 
use App\MoonShine\Resources\PostResource;
 
use MoonShine\Laravel\Layouts\CompactLayout;
use MoonShine\Laravel\Resources\MoonShineUserResource;
use MoonShine\Laravel\Resources\MoonShineUserRoleResource;
use MoonShine\MenuManager\MenuGroup;
use MoonShine\MenuManager\MenuItem;
 
final class MoonShineLayout extends CompactLayout
{
// ...
 
protected function menu(): array
{
return [
MenuGroup::make(__('moonshine::ui.resource.system'), [
MenuItem::make(
__('moonshine::ui.resource.admins_title'),
MoonShineUserResource::class
),
MenuItem::make(
__('moonshine::ui.resource.role_title'),
MoonShineUserRoleResource::class
),
]),
MenuItem::make('Posts', PostResource::class),
// ...
];
}
}

О расширенных настройках Layout можно узнать в разделе Layout.

О расширенных настройках MenuManager можно узнать в разделе Menu.

Alias

По умолчанию alias ресурса, который используется в url, генерируется на основе наименования класс в kebab-case, например: MoonShineUserResource -> moon-shine-user-resource.

Для того чтобы изменить alias, можно воспользоваться свойством ресурса $alias или методом getAlias().

 namespaces
namespace App\MoonShine\Resources;
 
use MoonShine\Laravel\Resources\ModelResource;
 
class PostResource extends ModelResource
{
protected ?string $alias = 'custom-alias';
 
// ...
}
 namespaces
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();

Через метод getModel() можно получить доступ к модели.

$this->getModel();

Вы можете добавлять, редактировать и просматривать записи прямо на странице со списком в модальном окне.

 namespaces
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;
 
// ...
}

Редиректы

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

Через свойство в ресурсе:

 namespaces
use MoonShine\Support\Enums\PageType;
 
protected ?PageType $redirectAfterSave = PageType::FORM;

Через метод:

public function getRedirectAfterSave(): string
{
return '/';
}

Также доступен редирект после удаления:

public function getRedirectAfterDelete(): string
{
return $this->getIndexPageUrl();
}

Активные действия

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

 namespaces
namespace App\MoonShine\Resources;
 
use MoonShine\Support\ListOf;
use MoonShine\Laravel\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)
;
}
}

Также можно просто создать новый список, например:

 namespaces
use MoonShine\Laravel\Enums\Action;
use MoonShine\Support\ListOf;
 
protected function activeActions(): ListOf
{
return new ListOf(Action::class, [Action::VIEW, Action::UPDATE]);
}

Кнопки

По умолчанию на индексной странице ресурса модели присутствует только кнопка для создания. Метод topButtons() позволяет добавить дополнительные кнопки.

 namespaces
namespace App\MoonShine\Resources;
 
use MoonShine\Laravel\Resources\ModelResource;
use MoonShine\Support\AlpineJs;
use MoonShine\Support\Enums\JsEvent;
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
 
class PostResource extends ModelResource
{
// ...
 
protected function topButtons(): ListOf
{
return parent::topButtons()->add(
ActionButton::make('Refresh', '#')
->dispatchEvent(AlpineJs::event(JsEvent::TABLE_UPDATED, $this->getListComponentName()))
);
}
}

Отображение

Вы также можете изменить отображение кнопок, отображать их в линию или же в выпадающем меню для экономии места.

 namespaces
namespace App\MoonShine\Resources;
 
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
 
class PostResource extends ModelResource
{
// ...
 
protected function indexButtons(): ListOf
{
return parent::indexButtons()->prepend(
ActionButton::make('Button 1', '/')
->showInLine(),
ActionButton::make('Button 2', '/')
->showInDropdown(),
);
}
}

Модификаторы

Для модификации основного компонента IndexPage, FormPage или DetailPage страницы из ресурса можно переопределить соответствующие методы modifyListComponent(), modifyFormComponent() и modifyDetailComponent().

 namespaces
use MoonShine\Contracts\UI\ComponentContract;
 
public function modifyListComponent(ComponentContract $component): ComponentContract
{
return parent::modifyListComponent($component)->customAttributes([
'data-my-attr' => 'value'
]);
}
 namespaces
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\UI\Components\FlexibleRender;
 
public function modifyFormComponent(ComponentContract $component): ComponentContract
{
return parent::modifyFormComponent($component)->fields([
FlexibleRender::make('Top'),
...parent::modifyFormComponent($component)->getFields()->toArray(),
FlexibleRender::make('Bottom'),
])->submit('Go');
}
 namespaces
use MoonShine\Contracts\UI\ComponentContract;
 
public function modifyDetailComponent(ComponentContract $component): ComponentContract
{
return parent::modifyDetailComponent($component)->customAttributes([
'data-my-attr' => 'value'
]);
}

Компоненты

Лучший способ изменить компоненты страниц - это опубликовать страницы и взаимодействовать через них. Но, если вы хотите быстро добавить компоненты на страницы, то можете воспользоваться методами ресурса pageComponents(), indexPageComponents(), formPageComponents() и detailPageComponents().

 namespaces
use MoonShine\Core\Collections\Components;
use MoonShine\UI\Components\FormBuilder;
use MoonShine\UI\Components\Modal;
use MoonShine\UI\Fields\Text;
 
// or indexPageComponents/formPageComponents/detailPageComponents
protected function pageComponents(): array
{
return [
Modal::make(
'My Modal'
components: Components::make([
FormBuilder::make()->fields([
Text::make('Title')
])
])
)
->name('demo-modal')
];
}

Компоненты будут добавлены в bottomLayer.

Жизненный цикл

Resource имеет несколько различных методов подключения к различным частям своего жизненного цикла. Давайте пройдемся по ним:

Активный ресурс

Метод onLoad() дает возможность интегрироваться в момент когда ресурс загружен и в данный момент является активным.

 namespaces
namespace App\MoonShine\Resources;
 
use MoonShine\Laravel\Resources\ModelResource;
 
class PostResource extends ModelResource
{
// ...
 
protected function onLoad(): void
{
// ...
}
}

Вы также можете подключить trait к ресурсу и внутри trait добавить метод согласно конвенции наименований - load{TraitName} и через трейт обратиться к onLoad ресурса.

 namespaces
namespace App\MoonShine\Resources;
 
use App\Traits\WithPermissions;
use MoonShine\Laravel\Resources\ModelResource;
 
class PostResource extends ModelResource
{
use WithPermissions;
 
// ...
}
 namespaces
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 создает экземпляр ресурса в системе.

 namespaces
namespace App\MoonShine\Resources;
 
use MoonShine\Laravel\Resources\ModelResource;
 
class PostResource extends ModelResource
{
// ...
 
protected function onBoot(): void
{
// ...
}
}

Вы также можете подключить trait к ресурсу и внутри trait добавить метод согласно конвенции наименований - boot{TraitName} и через трейт обратиться к onBoot() ресурса.

Assets

 namespaces
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\Laravel\Http\Responses\MoonShineJsonResponse;
 
public function modifyDestroyResponse(MoonShineJsonResponse $response): MoonShineJsonResponse
{
return $response;
}
 
public function modifyMassDeleteResponse(MoonShineJsonResponse $response): MoonShineJsonResponse
{
return $response;
}
 
public function modifySaveResponse(MoonShineJsonResponse $response): MoonShineJsonResponse
{
return $response;
}
 
public function modifyErrorResponse(Response $response, Throwable $exception): Response
{
return $response;
}