mrzlanx532/laravel-basic-components
最新稳定版本:1.1.12
Composer 安装命令:
composer require mrzlanx532/laravel-basic-components
包简介
Laravel basic components
README 文档
README
- Установка
- Важно
- Компоненты и как их использовать
- Трейт
UploadImages(Mrzlanx532\LaravelBasicComponents\Traits\Model\UploadImages\UploadImages) - Трейт
UploadFile(Mrzlanx532\LaravelBasicComponents\Traits\Model\UploadFile\UploadFile) - Класс
Service(Mrzlanx532\LaravelBasicComponents\Service) - Класс
Definition(Mrzlanx532\LaravelBasicComponents\Definition\Definition) - Класс
PanelForm(Mrzlanx532\LaravelBasicComponents\PanelForm\PanelForm) - Класс
QueryBuilder(Mrzlanx532\LaravelBasicComponents\QueryBuilder) - Класс
PanelSet(Mrzlanx532\LaravelBasicComponents\PanelSet\PanelSet) - Класс
PanelSetSortable(Mrzlanx532\LaravelBasicComponents\PanelSetSortable\PanelSetSortable) - Класс
BrowserFilterPresetController(Mrzlanx532\LaravelBasicComponents\Controllers\BrowserFilterPresetController)
- Трейт
Установка
- Устанавливаем пакет:
composer require mrzlanx532/laravel-basic-components - Публикуем настройки:
php artisan vendor:publish --tag="laravel-basic-components-config" - Публикуем миграции:
php artisan vendor:publish --tag="laravel-basic-components-migrations" - Если в проекте не используется
browser(компонент, который используется в админке), удаляем миграциюcreate_duotek_browser_filters_presets. - Выполняем команду:
php artisan migrate
Важно
- При установке пакета устанавливается
middlewareдля группыapiMrzlanx532\LaravelBasicComponents\Middleware\ReplaceNullValuesInFormData - Приложение должно работать в
UTC (00:00), соответственно, в конфигеconfig.appдолжно быть:
timezone => 'UTC' - Все даты для
datetimeиtimestampпри сохранении должны приводится кUTC (00:00)
Компоненты и как их использовать
Трейт UploadFile (Mrzlanx532\LaravelBasicComponents\Traits\Model\UploadImages\UploadImages)
Зачем нужен?
- Сохранение\Обновление изображений из параметров
Requestс учетом конфигурации в модели
( в модели определяется нарезать ли картинки и как нарезать ) - При обновлении также удаляются предыдущие файлы с сервера
Как использовать?
- Трейт вставляется в модель, которая имеет изображения в свойствах.
Для правильной конфигурации необходимо в свойство модели$filePropertiesWithSettingsпоместить конфиг по примеру ниже:
... use UploadImages; ... /** * Если есть константа UPLOAD_FILE_TRAIT_DELETING_FILES и она true, * то при удалении модели также будут удаляться связанные с ней изображения (и нарезки) */ const UPLOAD_FILE_TRAIT_DELETING_FILES = true; public static $filePropertiesWithSettings = [ 'picture' => [ '200' => [200, 200, FileHelper::RESIZE_TYPE_SMART], '400' => [400, 400, FileHelper::RESIZE_TYPE_FIT_INTO_AREA_WITH_PROPORTIONS], '600' => [600, 600, FileHelper::RESIZE_TYPE_FIT_INTO_AREA_WITH_PROPORTIONS_AND_COLOR_CANVAS, '#ffffff', 'center'], // Возможные значение 4-го параметра: top-left, top, top-right, left, center, right, bottom-left, bottom, bottom-right ], 'background_picture' => null, // null означает сохранить как есть (без нарезок) ]; ...
- Для корректного вывода в ресурсах делаем так:
... class UserResource extends JsonResource { /* @var $resource User */ public $resource; /** * @throws InvalidFilePropertiesWithSettingsPropertyConfiguration */ public function toArray($request): array { return [ 'id' => $this->resource->id, 'photo' => $this->resource->getFileLinksBySettings('photo'), 'photo2' => $this->resource->getFileLinksBySettings('photo2', 'http://api.tip.ru'), // возможность кастомно задать домен, не смотря на UPLOAD_FILE_DOMAIN ]; } }
- Если нужно чтобы файлы отдавались с абсолютным путем через метод
getFileLinksBySettingsможно:
- Глобально добавить
UPLOAD_FILE_DOMAIN=http://api.example.ruв.envфайл в корне проекта - Локально добавить
->getFileLinksBySettings('photo', 'http://api.example.ru')
- Если нужно в обособленном ресурсе, не привязываясь к классу, вывести изображения по конфигу модели:
... use Mrzlanx532\LaravelBasicComponents\Helpers\FileHelper\FileHelper; ... class UserResource extends JsonResource { /* @var $resource User */ public $resource; /** * @throws InvalidFilePropertiesWithSettingsPropertyConfiguration */ public function toArray($request): array { return [ 'id' => $this->resource->id, 'photo1' => FileHelper::getFileLinksBySettings(User::class, 'photo1', $this->resource->photo1), 'photo2' => FileHelper::getFileLinksBySettings(User::class, 'photo2', $this->resource->photo2), ]; } }
Трейт UploadFile (Mrzlanx532\LaravelBasicComponents\Traits\Model\UploadFile\UploadFile)
Зачем нужен?
Трейт применяется к смежным таблицам. (например: таблица user_files, где есть колонка user_id и file_id)
Позволяет удобно сохранять, обновлять, удалять файлы взаимодействуя с таблицей files.
Также позволяет удобно настраивать конфигурацию сохранения (см. ниже)
При обновление модели удаляет предыдущий файл и заменяет новым.
При удаление модели удаляет файл (если нету трейта SoftDeletes).
При удалении через forceDelete файл тоже физически удаляется.
Как использовать?
- Создаем модель для промежуточной таблицы файлов (например:
user_files)
<?php namespace App\Models; use App\Config\UploadFileConfig; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Http\UploadedFile; use Illuminate\Support\Carbon; use Illuminate\Database\Eloquent\Builder; use App\Traits\UploadFile; /** * \App\Models\UserFile * * @property int id * @property int user_id * @property int file_id * @property Carbon|null deleted_at * * @property UploadedFile|null uploadFile * @property-read File $file; * * @method static Builder|ContactFile query() * @method static ContactFile|null find($id) * @method static ContactFile findOrFail($id) * * @mixin Model */ class UserFile extends Model { use UploadFile; // Добавляем трейт use SoftDeletes; // Добавляем SoftDeletes, если требуется protected $table = 'user_files'; const $timestamps = false; /** * Если настройки по-умолчанию не подходят, то создаем метод getUploadFileConfig */ public function getUploadFileConfig(): UploadFileConfig { return UploadFileConfig::create() ->setAsPublic() ->setForeignKey('file_id'); } }
- Применяем в сервисе.
... $user = new User; $user->save(); if (isset($this->params['files'])) { foreach ($this->params['files'] as $passedFile) { $file = new UserFile; // Свойство `uploadFile` является зарезервированным. // $passedFile является \Illuminate\Http\UploadedFile $file->uploadFile = $passedFile; $files[] = $file; }; $user->files()->saveMany($files); } ...
- Итог
У нас сохранились файлы в
storage/app/private/files/<год>/<месяц>/<день>/<хэш_от_файла>.<расширение_файла>.
Добавились в таблицуfilesи создалась связь в таблицеuser_files
Класс Service (Mrzlanx532\LaravelBasicComponents\Service)
Зачем нужен?
Сервис является обязательным для использования при POST-действиях.
Например: Создание клиента, Изменение статуса заказа, Обновления данных клиента, Удаления клиента
Как использовать?
- Создаем сервис
namespace App\Services\Finance\BalanceInvoice; use App\Models\Finance\BalanceInvoice as FinanceBalanceInvoice; use Mrzlanx532\LaravelBasicComponents\Service\Service; class FinanceBalanceInvoiceCreateService extends Service { public function getRules(): array { return [ 'user_id' => 'required|int|exists:users_users,id', 'base_type_id' => 'required|string', 'offer_id' => 'required|int|exists:market_offers_offers,id', 'state_id' => 'required|string', 'total' => 'required|numeric', ]; } public function handle(): FinanceBalanceInvoice { $balanceInvoice = new FinanceBalanceInvoice(); $balanceInvoice->user_id = $this->params['user_id']; $balanceInvoice->offer_id = $this->params['offer_id']; $balanceInvoice->base_type_id = $this->params['base_type_id']; $balanceInvoice->state_id = $this->params['state_id']; $balanceInvoice->total = $this->params['total']; $balanceInvoice->save(); return $balanceInvoice; } }
- Применяем в контроллере
... public function create(Request $request, FinanceBalanceInvoiceCreateService $financeBalanceInvoiceCreateService): JsonResponse { return response()->json( new FinanceBalanceInvoiceResource( $financeBalanceInvoiceCreateService->setParams($request)->handle() ) ); } ...
Важные нюансы
paramsв методеgetRulesдоступны в чистом виде (всё что приходит сRequestилиarray)paramsв методеhandleочищены от полей, которые отсутствуют вgetRules
Класс Definition (Mrzlanx532\LaravelBasicComponents\Definition\Definition)
Зачем нужен?
Используется для хранения констант в определенном формате.
Имеет множество методов для удобной работы с ними.
Как использовать?
namespace App\Definitions\Finance\Balance; use Mrzlanx532\LaravelBasicComponents\Definition\Definition; class InvoiceBaseDefinition extends Definition { const OFFER = 'OFFER'; public static function items(): array { return [ self::OFFER => [ 'id' => self::OFFER, 'title' => 'Оплата объявления', ], ]; } }
Как локализовать title?
Как заполнить локализацию?
Как вызвать все константы с учётом локализации?
$definition::getItems(withLocale:true) $definition::getItems(withLocale:true, specifiedLocale:'ru')
Как вызвать одну константу с учётом локализации?
$definition::getItemByConst('RUS'); $definition::getItemByConst(const:'RUS', withLocale:true, specifiedLocale:'ru'); $definition::getItemByConst(const:'RUS', withLocale:true);
Класс PanelForm (Mrzlanx532\LaravelBasicComponents\PanelForm\PanelForm)
Зачем нужен?
Используется в качестве соглашения с библиотекой на фронтенде для данных, которые должны быть получены для формы.
Используется исключительно в админках.
Как использовать?
- Создаем
PanelForm
namespace App\PanelForms\Backoffice\Users; use App\Http\Resources\Backoffice\Users\User\UserResource; use App\Models\Users\User; use Mrzlanx532\LaravelBasicComponents\PanelForm\PanelForm; class UserPanelForm extends PanelForm { protected string $model = User::class; protected string|null $resource = UserResource::class; protected function getInputs(): array { return []; } }
- Применяем в контроллере
... public function form(UserPanelForm $userPanelForm): JsonResponse { return response()->json($userPanelForm->get()); } ...
Класс QueryBuilder (Mrzlanx532\LaravelBasicComponents\QueryBuilder)
Зачем нужен?
Нужен для построения сложных запросов с участнием Illuminate\Http\Request и соответственно инкапсуляции логики
Как использовать?
- Создаем
QueryBuilder
namespace App\Http\QueryBuilders; use App\Models\Finance\BalanceTransaction; use Mrzlanx532\LaravelBasicComponents\QueryBuilder\QueryBuilder; use Illuminate\Database\Eloquent\Collection; class FinanceBalanceTransactionQueryBuilder extends QueryBuilder { public function handle(): Collection|array { $balanceTransactionQuery = BalanceTransaction::query(); if ($this->request->has('user_id')) { $balanceTransactionQuery->where('user_id', $this->request->get('user_id')) } return $balanceTransactionQuery->get(); } }
- Используем в контроллере
... public function list(Request $request): JsonResponse { return response()->json( FinanceBalanceTransactionResource::collection( (new FinanceBalanceTransactionQueryBuilder($request))->handle() ) ); } ...
Важные нюансы
paramsв методеgetRulesдоступны в чистом виде (всё что приходит сRequestилиarray)paramsв методеhandleочищены от полей, которые отсутствуют вgetRules
Класс PanelSet (Mrzlanx532\LaravelBasicComponents\PanelSet\PanelSet)
Зачем нужен?
Используется в качестве соглашения с библиотекой на фронтенде для определения интерфейса на фронтенде через бэкенд.
Используется исключительно в админках.
Как использовать?
- Создаем
PanelSet
namespace App\PanelSet; use App\Http\Resources\Web\MarketOffer\MarketOfferResource; use App\Models\Market\Offer\Offer as MarketOffer; use Mrzlanx532\LaravelBasicComponents\PanelSet\Filters\BooleanFilter; use Mrzlanx532\LaravelBasicComponents\PanelSet\Filters\SelectFilter; use Mrzlanx532\LaravelBasicComponents\PanelSet\PanelSet; class MarketOfferPanelSet extends PanelSet { protected string $model = MarketOffer::class; public string $resource = MarketOfferResource::class; public string $browserId = 'market_offers'; /** * Если необходимо передать кастомный способ поиска отличный от LIKE, делаем как в примере 1 */ public array $fieldsForDefaultSearchFilter = ['costs_type_id', 'payment_type_id']; protected array $defaultOrderBy = [ 'created_at' => 'desc', // Пример с мапингом: принимаем `created_at`, а в запрос кладем `tips_codes.created_at` // 'created_at as tips_codes.created_at' => 'desc' // Для кастомизации смотри "Пример 2" ]; public array $availableOrderBy = [ 'created_at', // Пример с мапингом: принимаем `created_at`, а в запрос кладем `tips_codes.created_at` // 'created_at as tips_codes.created_at' // Для кастомизации смотри "Пример 3" ]; public function __construct() { parent::__construct(); /** * Пример 1 */ $this->fieldsForDefaultSearchFilter = [ 'costs_type_id', 'payment_type_id', function (Builder $query, string|null $searchString) { $query->OrWhere('id', '=', $searchString); } ]; /** * Пример 2 */ $this->defaultOrderBy = [ 'created_at' => function (\Illuminate\Database\Eloquent\Builder $queryBuilder) { // Пример кастомизации: здесь пишем как обработать поле 'updated_at' $queryBuilder->orderByRaw('<RAW SQL QUERY>'); } ]; /** * Пример 3 */ $this->availableOrderBy = [ 'created_at' => function (\Illuminate\Database\Eloquent\Builder $queryBuilder, $field, $direction) { // Пример кастомизации: здесь пишем как обработать поле 'updated_at' $queryBuilder->orderByRaw('<RAW SQL QUERY>'); } ]; } protected function setFilters() { $this->filtersManager->add(SelectFilter::class, 'product_id', 'Продукт', function (SelectFilter $selectFilter) { $exampleOptions = [ 0 => [ 'id' => 1, 'title' => 'Продукт1' ], 1 => [ 'id' => 2, 'title' => 'Продукт2' ], ]; $selectFilter->setOptions($exampleOptions); }); $this->filtersManager->add(BooleanFilter::class, 'is_price_per_one_is_set', 'Тест', function (BooleanFilter $booleanFilter) { // В запрос передаем вместо 'is_price_per_one_is_set' => 'is_price_per_one_is' $booleanFilter->setFilterParamName('is_price_per_one_is'); // Скрываем из интерфейса, но обрабатываем всё равно $booleanFilter->hidden(); // Делаем фильтр обязательным $booleanFilter->required(); }); // Пример кастомного запроса $this->filtersManager->add(SelectFilter::class, 'randomValue(ни на что не влияет в случае кастома)', 'Участник', function (SelectFilter $selectFilter) { $selectFilter ->setFilterParamName('user_id'); ->setCustomQueryClosure(function (\Illuminate\Database\Eloquent\Builder $builder, $values) { $builder ->where('market_deals.requester_user_id', $values[0]) ->orWhere('market_deals.responser_user_id', $values[0]); }); }); } }
- Используем в контроллере
... /** * @throws InvalidJsonFormatForFiltersParameterException * @throws InvalidPanelSetConfigurationException */ public function browse(MarketOfferPanelSet $marketOfferPanelSet): JsonResponse { return response()->json($marketOfferPanelSet->handle()); } ...
Дополнительно:
Пример из POSTMAN как управляться с фильтрами:
Класс PanelSetSortable (Mrzlanx532\LaravelBasicComponents\PanelSetSortable\PanelSetSortable)
Зачем нужен?
Используется в качестве соглашения с библиотекой на фронтенде для определения интерфейса на фронтенде через бэкенд.
Нужен для компонента на фронте, который имеет Drag'n'Drop
Компонент определяет порядок записей
и может вкладывать одну запись в другую (опционально)
Как использовать?
- Создаем
PanelSetSortable
<?php namespace App\PanelSets\Backoffice\Suppliers\Levels; use App\Http\Resources\Backoffice\Suppliers\Levels\LevelListResource; use App\Models\Suppliers\Levels\Level; use Mrzlanx532\LaravelBasicComponents\PanelSetSortable\PanelSetSortable; class LevelPanelSetSortable extends PanelSetSortable { protected string $model = Level::class; public string $resource = LevelListResource::class; /** [Опционально] Определяем нужна ли поддержка вложенности */ protected bool $isNested = false; /** [Опционально] Колонка в таблице, которое будет отвечать за порядок */ protected string $orderField = 'order_index'; /** [Опционально] Колонка в таблице, которая определяет родителя при включенной вкложенности */ protected string $parentField = 'parent_id'; /** [Опционально] Колонка, содержащая в себе первичный ключ таблицы */ protected string $identifierField = 'id'; }
- Используем в контроллере
use App\PanelSets\Backoffice\Suppliers\Levels\LevelPanelSetSortable; use Illuminate\Http\JsonResponse; /** * GET * suppliers/levels/browse-sortable * [backoffice-api] * Браузер уровней поставщиков сортированный * * @param LevelPanelSetSortable $levelPanelSetSortable * @return JsonResponse */ public function browseSortable(LevelPanelSetSortable $levelPanelSetSortable): JsonResponse { return response()->json($levelPanelSetSortable->handle()); }
- Добавляем метод обновления сортировки\вложенности в контроллер
use Mrzlanx532\LaravelBasicComponents\Service\PanelSetSortableUpdateService\PanelSetSortableUpdateService; use App\PanelSets\Backoffice\Suppliers\Levels\LevelPanelSetSortable; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; /** * POST * suppliers/levels/browse-sortable/update * [backoffice-api] * Обновить сортировку браузера * * @bodyParam items object[] required * @bodyParam items[].id int required * @bodyParam items[].parent int required (если вложенность отключена, убираем из доки) * * @param Request $request * @return JsonResponse * @throws ValidationException */ public function browseSortableUpdate(Request $request): JsonResponse { (new PanelSetSortableUpdateService(LevelPanelSetSortable::class))->setParams($request)->handle(); return response()->json(['status' => true]); }
Класс BrowserFilterPresetController (Mrzlanx532\LaravelBasicComponents\Controllers\BrowserFilterPresetController)
Зачем нужен?
Используется в качестве соглашения между фронтендом и бэкэндом.
Содержит в себе контроллер с методами для сохранения\изменения\удаления пользовательских пресетов.
Как использовать?
Вставляем машруты в файл с роутами (там где необходимо).
Используется исключительно в админках, вместе с компонентом
PanelSet
... Route::post('browser/preset/create', [BrowserFilterPresetController::class, 'create']); Route::post('browser/preset/update', [BrowserFilterPresetController::class, 'update']); Route::post('browser/preset/delete', [BrowserFilterPresetController::class, 'delete']); ...
统计信息
- 总下载量: 44
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2024-12-18

