• Час читання ~11 хв
  • 18.06.2022

Laravel Jetstream — це стартовий набір, який допоможе вам не тільки з риштуванням Auth, але й з додатковими функціями, такими як Teams або двофакторна аутентифікація. Але я бачу, що багато людей борються з налаштуванням Jetstream після встановлення та з додаванням більшої функціональності. Отже, у цій статті давайте додамо простий CRUD з ролями/дозволами, поверх Jetstream.

Jetstream Installation

Ми створимо свіжий проект Laravel для управління списком справ. Є кілька способів встановити Laravel + Jetstream, я буду використовувати інсталятор Laravel.

Ви можете зробити:Зверніть увагу:

laravel new project
cd project
composer require laravel/jetstream
php artisan jetstream:install livewire

Jetstream поставляється з двома варіантами - Livewire та Inertia. У цій статті я буду використовувати стек Livewire, але це не біда. Livewire/Inertia використовується для коду риштування, але після встановлення Jetstream ви можете продовжити написання коду без будь-якого з цих додаткових інструментів, з чистим кодом Laravel + Blade MVC.

Отже, інструкція по установці вище, але тут я дам вам підказку для більш короткої установки. Laravel installer дозволяє зробити все це лише за одну команду:

laravel new project --jet --stack=livewire

Після завершення риштування нам потрібно запустити їх:

cd project
php artisan migrate
npm install && npm run dev

Якщо все успішно, ми повинні побачити домашню сторінку за замовчуванням із посиланнями для входу/реєстрації у верхньому правому куті.

Коли ви натискаєте Зареєструватися, ви повинні потрапити на реєстраційну форму.

Після його заповнення користувач реєструється та потрапляє на інформаційну панель.

Якщо ви бачите те ж саме на своєму екрані, вітаємо, інсталяційна частина завершена успішно.


Список завдань: Структура БД і нереактивна частина

Далі нам потрібно створити внутрішню структуру для нашої таблиці завдань БД. Ось де ми взагалі на деякий час відійдемо від Jetstream. І це одна з головних речей, які розробники повинні розуміти про Jetstream: ви не покладаєтеся на стартовий набір для вашого подальшого коду. Ви можете написати код, як зазвичай, без Jetstream, можливо, просто повторно використовуючи деякі його компоненти.

Отже, що нам потрібно зробити на цьому етапі:

  • Створити модель
  • завдання Створити міграцію
  • БД Створити ресурс TaskController і заповнити операції
  • CRUD Додати маршрут до цього нового контролера
  • Створити класи запитів форми для перевірки

Це дії, необхідні не мають відношення до стартового комплекту, будь то Jetstream, Breeze або будь-який інший.

Потім, що нам потрібно включити в Jetstream, це ці дії:

  • Додайте пункт меню «Завдання» до навігації
  • Jetstream Створіть перегляди лопатей для операцій

CRUD Отже, ці дві речі будуть залежати від передньої структури Jestream, ми розглянемо це нижче.

По-перше, частина, що не належить Jestream:Ці додаткові прапорці генеруватимуть пов'язані файли: згенерує клас-cr міграції згенерує Контролер із методами ресурсів: індексувати, створювати, зберігати, показувати, редагувати, оновлювати, знищувати-R

php artisan make:model Task -mcrR

генеруватиме два класи запиту форми:-m

  • StoreTaskRequest та UpdateTaskRequest
  • Далі ми заповнюємо міграцію лише одним полем, для цього простого прикладу.

база даних/міграції/2022_04_19_131435_create_tasks_table.php:

eturn new class extends Migration
{
    public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
    }

Я навмисно не показую down() метод, тому що я не створюю цей метод з 2017 року, коли Тейлор Отвелл схвалив його.

Давайте також запустимо php artisan migrate, щоб ця таблиця була створена.

Потім у Model я додаю одне заповнене поле:

app/Models/Task.php

class Task extends Model
{
    protected $fillable = ['name'];
}

Потім код CRUD для контролера.

додаток/Http/Контролери/TaskController.php:

use App\Http\Requests\StoreTaskRequest;
use App\Http\Requests\UpdateTaskRequest;
use App\Models\Task;

class TaskController extends Controller
{
    public function index()
    {
        $tasks = Task::all();

        return view('tasks.index', compact('tasks'));
    }
    public function create()
    {
        return view('tasks.create');
    }
    public function store(StoreTaskRequest $request)
    {
        Task::create($request->validated());

        return redirect()->route('tasks.index');
    }
    public function edit(Task $task)
    {
        return view('tasks.edit', compact('task'));
    }
    public function update(UpdateTaskRequest $request, Task $task)
    {
        $task->update($request->validated());

        return redirect()->route('tasks.index');
    }
    public function destroy(Task $task)
    {
        $task->delete();

        return redirect()->route('tasks.index');
    }
}

Нарешті, ми призначаємо цей контролер маршруту з auth проміжним програмним забезпеченням.

маршрути/веб.php:

Route::get('/', function () {
    return view('welcome');
});
Route::middleware([
    'auth:sanctum',
    config('jetstream.auth_session'),
    'verified'
])->group(function () {
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard');

    // This is our new line
    Route::resource('tasks', \App\Http\Controllers\TaskController::class);
});

Це перший раз, коли ми бачимо щось від Jetstream: згенерований маршрут інформаційної панелі з проміжними програмами за замовчуванням, як auth:sanctum та інші. Наше завдання тут полягає лише в тому, щоб додати наш маршрут до цієї групи.


Список завдань: Нова сторінка з макетом Jetstream Ok

, тому у нас є контролер, але у нас ще немає перегляду. Ми вказали його як , resources/views/tasks/index.blade.phpтому давайте створимо саме цей файл.

Якщо ви хочете, щоб він мав той самий дизайн Jetstream, ви можете використовувати наявний файл інформаційної панелі та просто замінити внутрішню його частину.

ресурси/перегляди/dashboard.blade.php:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Dashboard') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                <x-jet-welcome />
            </div>
        </div>
    </div>
</x-app-layout>

Отже, ми робимо Файл -> Зберегти як... в нашій IDE, і поставте її як resources/views/tasks/index.blade.php, потім замініть заголовок на "Список завдань" і <x-jet-welcome /> просто статичним текстом "Скоро буде", поки що.

ресурси/перегляди/завдання/index.blade.php:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Tasks list') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                Coming soon.
            </div>
        </div>
    </div>
</x-app-layout>

Тепер, якщо ми запустимо /tasks URL-адресу в браузері, це те, що ми повинні побачити:

Якщо ви не знайомі з синтаксисом, наприклад x-app-layout або x-slot, прочитайте про макети з використанням компонентів Blade.

Якщо ви не знайомі з методом__(), читайте про переклади в Laravel.

Нарешті, ми додаємо меню зверху, у двох місцях, з компонентами x-jet-nav-link Jetstream і x-jet-responsive-nav-link. Ми просто копіюємо-вставляємо посилання на інформаційну панель і змінюємо текст / маршрути.

resources/views/navigation-menu.blade.php:

<x-jet-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')">
    {{ __('Dashboard') }}
</x-jet-nav-link>
<x-jet-nav-link href="{{ route('tasks.index') }}" :active="request()->routeIs('tasks.*')">
    {{ __('Tasks') }}
</x-jet-nav-link>

<x-jet-responsive-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')">
    {{ __('Dashboard') }}
</x-jet-responsive-nav-link>
<x-jet-responsive-nav-link href="{{ route('tasks.index') }}" :active="request()->routeIs('tasks.*')">
    {{ __('Tasks') }}
</x-jet-responsive-nav-link>

Список завдань: Таблиця Jetstream з попутним вітром CSS

Тепер, давайте замінимо наш текст "Незабаром" на фактичну таблицю.

Візуальний дизайн Laravel Jetstream заснований на фреймворку Tailwind CSS, тому ми повинні продовжувати використовувати його для інших користувацьких сторінок, які ми створимо.

Є різні джерела, де ви можете отримати компонент для таблиці на основі Tailwind, я вибрав тут безкоштовну версію Flowbite. Тому я скопіюю-вставлю код звідти і заповню деталі циклом @forelse списку завдань.

ресурси/перегляди/завдання/index.blade.php:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Tasks list') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                <div class="relative overflow-x-auto shadow-md sm:rounded-lg">
                    <table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
                        <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
                        <tr>
                            <th scope="col" class="px-6 py-3">
                                Task name
                            </th>
                            <th scope="col" class="px-6 py-3">

                            </th>
                        </tr>
                        </thead>
                        <tbody>
                        @forelse ($tasks as $task)
                            <tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
                                <td class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap">
                                    {{ $task->name }}
                                </td>
                                <td class="px-6 py-4">
                                    <a href="{{ route('tasks.edit', $task) }}"
                                       class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Edit</a>
                                </td>
                            </tr>
                        @empty
                            <tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
                                <td colspan="2"
                                    class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap">
                                    {{ __('No tasks found') }}
                                </td>
                            </tr>
                        @endforelse
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

Якщо я створюю деякі випадкові завдання вручну в базі даних, це має виглядати так:Full CRUD:


Кнопки та форми

Над таблицею давайте розмістимо кнопку, щоб додати новий запис. Jetstream має набір компонентів Blade для різних елементів інтерфейсу користувача, включаючи кнопки. Щоб опублікувати їх, нам потрібно запустити:

php artisan vendor:publish --tag=jetstream-views

Тоді у нас буде багато елементів всерединіresources/views/vendor/jetstream/components, і вони автоматично вмикаються через постачальників послуг Jetstream.

Якщо ми відкриємо resources/views/vendor/jetstream/components/button.blade.php, то побачимо це:

<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center ...']) }}>
    {{ $slot }}
</button>

Багато інших класів Tailwind перераховані після items-center, і це саме те, що нам потрібно, але просто у вигляді посилання, а не кнопки. Тому я просто копіюю-вставляю всі ці класи в новий компонент, який я створю

:ресурси/перегляди/компоненти/link.blade.php:Потім, у resources/views/tasks/index.blade ми можемо це зробити:

<a {{ $attributes->merge(['class' => 'inline-flex items-center ...']) }}>
    {{ $slot }}
</a>

<div class="relative ...">
    <x-link href="{{ route('tasks.create') }}" class="m-4">Add new task</x-link>

    <table class="...">

Отже, ми додали <x-link> зі стилізацією його як кнопку, додавши m-4 клас для поля.

Зверніть увагу, що вам потрібно запустити npm run dev знову, або мати npm run watch у фоновому режимі, щоб Tailwind повторно компілював усі класи, які використовуються у файлах Blade, як m-4 у нашому випадку.

Це візуальний результат:

Коли ми натискаємо це посилання, ми потрапляємо на URL-адресу /tasks/create, яка відповідає методу TaskController@create контролера та завантажує вигляд tasks/create.blade.php.

Для цієї форми я буду повторно використовувати HTML-код і класи зі сторінки реєстрації в Jetstream, яка знаходиться в resources/views/auth/register.blade.php. Після деякого копіювання-вставки ось наша нова форма у новоствореному файлі.

ресурси/подання/завдання/create.blade.php:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Add New Task') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                <div class="relative overflow-x-auto shadow-md sm:rounded-lg px-4 py-4">
                    <x-jet-validation-errors class="mb-4" />

                    <form method="POST" action="{{ route('tasks.store') }}">
                        @csrf
                        <div>
                            <x-jet-label for="name" value="{{ __('Name') }}" />
                            <x-jet-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
                        </div>

                        <div class="flex mt-4">
                            <x-jet-button>
                                {{ __('Save Task') }}
                            </x-jet-button>
                        </div>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

Як бачите, ми використовуємо той самий макет, і просто додали кілька px-4 py-4 класів CSS для заповнення.

Крім того, важливо помітити два компоненти Jetstream: чи перерахує всі помилки перевірки форми з сеансуx-jet-button

  • та сама кнопка надсилання, яку ви бачили раніше, як «натхнення» для нашого x-link компонента
  • Візуальний результат такий:

x-jet-validation-errors

Збереження даних має працювати, оскільки ми створили для цього код контролера, пам'ятаєте? Ще одна річ, яку нам потрібно додати туди, - це правило перевірки.

app/Http/Requests/StoreTaskRequest.php:

class StoreTaskRequest extends FormRequest
{
    public function authorize()
    {
        // default is "false", we need to change to "true"
        return true;
    }
    public function rules()
    {
        return [
            'name' => 'required'
        ];
    }
}

Ми також згенерували клас запиту форми для форми оновлення, тому нам app/Http/Requests/UpdateTaskRequest.php потрібно додати туди той самий код для authorize() та rules() методів. Дискусійним є питання про те, чи використовувати окремі класи Form Request, особливо якщо правила однакові, але моя особиста перевага полягає в тому, щоб уникати одного класу, тому що ви ніколи не знаєте, коли правила почнуть відрізнятися.

Тепер форма редагування майже ідентична формі "Створити". Таким чином, ми можемо відкрити create.blade.php, зробити File -> Save as... і зберегти його з мінімальними змінами:ресурси/перегляди/завдання/edit.blade.php

Нарешті, нам потрібно побудувати кнопку Delete.

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Edit Task') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                <div class="relative overflow-x-auto shadow-md sm:rounded-lg px-4 py-4">
                    <x-jet-validation-errors class="mb-4" />

                    <form method="POST" action="{{ route('tasks.update', $task) }}">
                        @csrf
                        @method('PUT')
                        <div>
                            <x-jet-label for="name" value="{{ __('Name') }}" />
                            <x-jet-input id="name" class="block mt-1 w-full" type="text" name="name" :value="$task->name" required autofocus autocomplete="name" />
                        </div>

                        <div class="flex mt-4">
                            <x-jet-button>
                                {{ __('Save Task') }}
                            </x-jet-button>
                        </div>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

Ми будемо використовувати новий компонент Jetstream під назвою x-jet-danger-button. З огляду на це, давайте також замінимо посилання "Редагувати", щоб воно більше нагадувало кнопку.

ресурси/перегляди/завдання/index.blade.php:

<td class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap">
    {{ $task->name }}
</td>
<td class="px-6 py-4">
    <x-link href="{{ route('tasks.edit', $task) }}">Edit</x-link>
    <form method="POST" action="{{ route('tasks.destroy', $task) }}" class="inline-block">
        @csrf
        @method('DELETE')
        <x-jet-danger-button
            type="submit"
            onclick="return confirm('Are you sure?')">Delete</x-jet-danger-button>
    </form>
</td>

Візуальний результат:


Ролі та дозволи

Для цієї частини ми будемо використовувати відомий популярний пакет під назвою Laravel Permit by Spatie.

Як ви побачите, використання не залежить від Jetstream, це те саме, що ви використовували б його в будь-якому іншому проекті Laravel. Іншими словами, Jetstream дає вам базовий аутфіт з формами входу/реєстрації та управлінням профілями, але що б ви не додали зверху, в основному не «піклується» про Jetstream.

Для простого прикладу давайте дозволимо будь-якому користувачеві переглядати завдання, але додавати / редагувати / видаляти завдання буде дозволено лише користувачеві-адміністратору.

Отже, як сказано в документації пакета, ми запускаємо:

composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate

Потім нам потрібно включити дозволи в моделі користувача.

app/Models/User.php:

use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;

За замовчуванням будь-який зареєстрований користувач не матиме жодних ролей чи дозволів. Ми створимо окремий клас сівалки, для створення адміністратора з дозволами.

php artisan make:seeder AdminUserSeeder

база даних/сідери/AdminUserSeeder.php:

use App\Models\User;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

class AdminUserSeeder extends Seeder
{
    public function run()
    {
        $adminRole = Role::create(['name' => 'Administrator']);
        $permission = Permission::create(['name' => 'manage tasks']);
        $permission->assignRole($adminRole);

        $adminUser = User::factory()->create([
            'email' => '[email protected]',
            'password' => bcrypt('SecurePassword')
        ]);
        $adminUser->assignRole('Administrator');
    }
}

І тепер нам потрібно перевірити, хто має дозвіл на керування завданнями. Ви можете перевірити це за іменем дозволу або за назвою ролі, це ваші особисті переваги.

Спочатку в файлі Blade дивіться три @can ... @endcan блоки.

ресурси/перегляди/завдання/index.blade.php:

@can('manage tasks')
    <x-link href="{{ route('tasks.create') }}" class="m-4">Add new task</x-link>
@endcan
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
    <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
    <tr>
        <th scope="col" class="px-6 py-3">
            Task name
        </th>
        @can('manage tasks')
        <th scope="col" class="px-6 py-3">

        </th>
        @endcan
    </tr>
    </thead>
    <tbody>
    @forelse ($tasks as $task)
        <tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
            <td class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap">
                {{ $task->name }}
            </td>
            @can('manage tasks')
            <td class="px-6 py-4">
                <x-link href="{{ route('tasks.edit', $task) }}">Edit</x-link>
                <form method="POST" action="{{ route('tasks.destroy', $task) }}" class="inline-block">
                    @csrf
                    @method('DELETE')
                    <x-jet-danger-button
                        type="submit"
                        onclick="return confirm('Are you sure?')">Delete</x-jet-danger-button>
                </form>
            </td>
            @endcan
        </tr>
    @empty
        <tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
            <td colspan="2"
                class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap">
                {{ __('No tasks found') }}
            </td>
        </tr>
    @endforelse
    </tbody>
</table>

А потім нам також потрібно захистити серверну частину, тому дивіться $this->authorize() перевірки в Контролері.

додаток/Http/Контролери/TaskController.php:

class TaskController extends Controller
{
    public function index()
    {
        $tasks = Task::all();

        return view('tasks.index', compact('tasks'));
    }
    public function create()
    {
        $this->authorize('manage tasks');

        return view('tasks.create');
    }
    public function store(StoreTaskRequest $request)
    {
        $this->authorize('manage tasks');

        Task::create($request->validated());

        return redirect()->route('tasks.index');
    }
    public function edit(Task $task)
    {
        $this->authorize('manage tasks');

        return view('tasks.edit', compact('task'));
    }
    public function update(UpdateTaskRequest $request, Task $task)
    {
        $this->authorize('manage tasks');

        $task->update($request->validated());

        return redirect()->route('tasks.index');
    }
    public function destroy(Task $task)
    {
        $this->authorize('manage tasks');

        $task->delete();

        return redirect()->route('tasks.index');
    }
}

Висновок

Все, ми створили наш CRUD з ролями/дозволами, поверх Jetstream. Моя загальна мета полягала в тому, щоб показати вам, що Jetstream — це лише стартовий набір, але тоді ви можете написати будь-який користувацький код, який захочете, в основному ігноруючи Jetstream.

Тим не менш, є корисні компоненти Blade, які ви можете використовувати повторно, для елементів інтерфейсу користувача, таких як кнопки, посилання та інші.

Детальніше про Jetstream ви можете прочитати в офіційній документації.

Репозиторій коду для цього демо-проекту тут публічний.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297