• Время чтения ~12 мин
  • 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 позволяет вам сделать все это всего за одну команду:

laravel new project --jet --stack=livewire

После того, как строительные леса завершены, нам нужно запустить их:

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

Если все успешно, мы должны увидеть домашнюю страницу по умолчанию со ссылками Login / Register в правом верхнем углу.

Когда вы нажмете Зарегистрироваться, вы должны перейти к регистрационной форме.

После заполнения пользователь регистрируется и попадает на панель мониторинга.

Если вы видите то же самое на экране, поздравляем, установка части успешно завершена.


Список задач: Структура БД и часть, не относящаяся к Jetstream

Далее нам нужно создать серверную структуру для нашей таблицы задач БД. Вот где мы вообще уйдем от Jetstream, на некоторое время. И это одна из главных вещей, которые разработчики должны понимать о Jetstream: вы не полагаетесь на стартовый комплект для своего дальнейшего кода. Вы можете писать код так, как вы обычно делаете без Jetstream, возможно, просто повторно используя некоторые из его компонентов.

Итак, что нам нужно сделать на этом этапе:

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

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

Затем, что нам нужно включить в Jetstream, это следующие действия:

  • Добавьте пункт меню «Задачи» в навигацию
  • Jetstream Create Blade Views for CRUD

Итак, эти две вещи будут зависеть от структуры внешнего интерфейса Jestream, мы рассмотрим это ниже.

Во-первых, часть, не относящаяся к Jestream:

php artisan make:model Task -mcrR

Эти дополнительные флаги будут генерировать связанные файлы:

  • -m сгенерирует класс
  • -cr миграции, сгенерирует контроллер с методами Resource: индексировать, создавать, хранить, показывать, редактировать, обновлять, уничтожать-R
  • сгенерирует два класса Form Request: StoreTaskRequest и UpdateTaskRequest

Далее мы заполняем миграцию только одним полем, для этого простого примера.

database/migrations/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 для контроллера.

app/Http/Controllers/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 промежуточного ПО.

routes/web.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, поэтому у нас есть контроллер, но у нас пока нет view. Мы указали, что это будет resources/views/tasks/index.blade.php, поэтому давайте создадим именно этот файл.

Если вы хотите, чтобы он имел тот же дизайн Jetstream, вы можете использовать существующий файл Dashboard и просто заменить его внутреннюю часть.

resources/views/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 /> просто статическим текстом «Скоро», пока.

resources/views/tasks/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. Мы просто копируем и вставляем ссылку Dashboard и изменяем текст/маршруты.

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 с TAILWIND CSS

Теперь давайте заменим наш текст "Скоро" фактической таблицей.

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

Существуют различные источники, где вы можете получить компонент для таблицы на основе Tailwind, я выбрал бесплатную версию Flowbite здесь. Поэтому я скопирую и вставлю код оттуда и заполню детали циклом @forelse списка задач.

resources/views/tasks/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, и это именно то, что нам нужно, но только в виде ссылки, а не кнопки. Поэтому я просто копирую и вставляю все эти классы в новый компонент, который я создам:

resources/views/components/link.blade.php:

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

Затем, в resources/views/tasks/index.blade мы можем сделать это:

<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 Controller и загружает представление tasks/create.blade.php.

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

resources/views/tasks/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-validation-errors будет ли список всех ошибок проверки формы из сеанса той же кнопкой отправкиx-jet-button
  • , которую вы видели ранее, в качестве «вдохновения» для нашего x-link компонента

Визуальный результат таков:

Сохранение данных должно работать, потому что мы создали код контроллера для этого, помните? Еще одна вещь, которую нам нужно добавить, это правило проверки.

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'
        ];
    }
}

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

Теперь форма Изменить практически идентична форме Создать. Итак, мы можем открыть , do File -> Save as... и сохранить его с минимальными изменениями:resources/views/tasks/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>

create.blade.php Мы будем использовать новый компонент Jetstream с именем x-jet-danger-button. При этом давайте также заменим ссылку «Редактировать», чтобы она больше походила на кнопку.

resources/views/tasks/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>

Visual result:


Roles and Permissions

Для этой части мы будем использовать известный популярный пакет под названием Laravel Permission by Spatie.

Как вы увидите, использование не зависит от Jetstream, это то же самое, что вы использовали бы его в любом другом проекте Laravel. Другими словами, Jetstream дает вам базовую аутентификацию с формами входа / регистрации и управлением профилем, но все, что вы добавляете сверху, в основном не «заботится» о Jetstream.

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

Итак, как сказано в документации пакета, мы запускаем:

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

Затем, нам нужно включить разрешения в модели User.

app/Models/User.php:

use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;

По умолчанию ни один зарегистрированный пользователь не будет иметь ролей или разрешений. Мы создадим отдельный класс сеялки, чтобы создать администратора с разрешениями.

php artisan make:seeder AdminUserSeeder

database/seeders/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 блока.

resources/views/tasks/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() в контроллере.

app/Http/Controllers/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