• Czas czytania ~11 min
  • 18.06.2022

Laravel Jetstream to zestaw startowy, który pomaga nie tylko w rusztowaniach Auth, ale także w dodatkowych funkcjach, takich jak Teams lub uwierzytelnianie dwuskładnikowe. Ale widzę, że wiele osób zmaga się z dostosowaniem Jetstream po instalacji i dodaniem większej funkcjonalności. W tym artykule dodajmy prosty CRUD z rolami / uprawnieniami na Jetstream.

Instalacja

Jetstream Stworzymy nowy projekt Laravel, aby zarządzać listą zadań do wykonania. Istnieje wiele sposobów instalacji Laravel + Jetstream, użyję instalatora Laravel.

Możesz zrobić:Uwaga:

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

Jetstream ma dwie opcje - Livewire i Inertia. W tym artykule użyję stosu Livewire, ale to nie ma znaczenia. Livewire/Inertia jest używany do kodu rusztowania, ale po zainstalowaniu Jetstream możesz kontynuować pisanie kodu bez żadnego z tych dodatkowych narzędzi, z czystym kodem Laravel + Blade MVC.

Tak więc instrukcje instalacji są powyżej, ale tutaj dam ci wskazówkę dotyczącą krótszej instalacji. Instalator Laravel pozwala zrobić to wszystko w jednym poleceniu:Po zakończeniu rusztowania musimy uruchomić te:

laravel new project --jet --stack=livewire

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

Jeśli wszystko się powiedzie, powinniśmy zobaczyć domyślną stronę główną, z linkami Zaloguj / Zarejestruj się w prawym górnym rogu.

Po kliknięciu przycisku Zarejestruj powinieneś wylądować w formularzu rejestracyjnym.

Po wypełnieniu użytkownik jest rejestrowany i ląduje na pulpicie nawigacyjnym.

Jeśli widzisz to samo na ekranie, gratulacje, część instalacyjna została zakończona pomyślnie.


Lista zadań: Struktura DB i część

niezwiązana z Jetstream Następnie musimy utworzyć strukturę zaplecza dla naszej tabeli zadań DB. Oto, gdzie w ogóle uciekniemy od Jetstream, na jakiś czas. I to jest jedna z głównych rzeczy, które programiści powinni zrozumieć na temat Jetstream: nie polegasz na zestawie startowym do dalszego kodu. Możesz pisać kod tak, jak zwykle robisz to bez Jetstream, może po prostu ponownie wykorzystując niektóre z jego komponentów.

Co więc musimy zrobić w tym momencie:

  • Tworzenie modelu zadań Tworzenie migracji
  • baz danych Tworzenie
  • kontrolera zadań zasobów i wypełnianie operacji
  • CRUD Dodawanie trasy do tego nowego kontrolera
  • Tworzenie klas żądań formularzy do sprawdzania poprawności

Są to działania potrzebne bez znaczenia dla zestawu startowego, niezależnie od tego, czy jest to Jetstream, Breeze czy jakikolwiek inny.

Następnie, co musimy włączyć do Jetstream, to następujące działania:

  • Dodaj element menu "Zadania" do nawigacji
  • Jetstream Tworzenie widoków bloków dla operacji

CRUD Tak więc te dwie rzeczy będą zależeć od struktury front-end Jestream, przyjrzymy się temu poniżej.

Po pierwsze, część nie-Jestream:Te dodatkowe flagi wygenerują powiązane pliki: wygeneruje klasę migracji wygeneruje kontroler z metodami zasobów: index, create, store, show, edit, update, destroy

php artisan make:model Task -mcrR

-R wygeneruje dwie klasy Form Request:

  • -m StoreTaskRequest i UpdateTaskRequest
  • Następnie wypełniamy migrację tylko jednym polem, dla tego prostego przykładu.

-cr 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();
        });
    }

Celowo nie pokazuję down() metody, ponieważ nie tworzę tej metody od 2017 roku, kiedy Taylor Otwell ją zatwierdził.

Uruchommy php artisan migrateteż , aby ta tabela została utworzona.

Następnie w Model dodaję jedno pole do wypełnienia:

aplikacja / modele / zadanie.php

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

Następnie kod CRUD dla kontrolera.

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

Na koniec przypisujemy ten kontroler do trasy, z auth oprogramowaniem pośredniczącym.

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);
});

Po raz pierwszy widzimy coś z Jetstream: wygenerowaną trasę pulpitu nawigacyjnego z domyślnym oprogramowaniem pośredniczącym, takim jak auth:sanctum i inne. Naszym zadaniem jest po prostu dodanie naszej trasy do tej grupy.


Lista zadań: Nowa strona z układem

Jetstream Ok, więc mamy kontroler, ale nie mamy jeszcze widoku. Określiliśmy, że jest resources/views/tasks/index.blade.phpto , więc stwórzmy dokładnie ten plik.

Jeśli chcesz, aby miał ten sam projekt Jetstream, możesz użyć istniejącego pliku pulpitu nawigacyjnego i po prostu zastąpić jego wewnętrzną część.

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>

Więc robimy Plik -> Zapisz jako... w naszym IDE i umieść go jako resources/views/tasks/index.blade.php, a następnie zastąp nagłówek "Lista zadań" i <x-jet-welcome /> tylko statycznym tekstem "Wkrótce", na razie.

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>

Teraz, jeśli uruchomimy /tasks adres URL w przeglądarce, powinniśmy zobaczyć to:

Jeśli nie znasz składni takiej jak x-app-layout lub x-slot, przeczytaj o układach wykorzystujących składniki Blade.

Jeśli nie znasz metody, przeczytaj o tłumaczeniach __() w Laravel.

Na koniec dodajemy menu na górze, w dwóch miejscach, z komponentami x-jet-nav-link Jetstream i x-jet-responsive-nav-link. Po prostu kopiuj-wklej link Dashboard i zmieniamy tekst / trasy.

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>

Lista zadań: Tabela Jetstream z Tailwind CSS

Teraz zastąpmy nasz tekst "Wkrótce" rzeczywistą tabelą.

Projekt wizualny Laravel Jetstream jest oparty na frameworku CSS Tailwind, więc powinniśmy nadal używać go dla innych niestandardowych stron, które tworzymy.

Istnieją różne źródła, w których można uzyskać komponent dla tabeli opartej na Tailwind, wybrałem tutaj darmową wersję Flowbite. Więc skopiuj-wkleję kod stamtąd i wypełnię szczegóły pętlą @forelse listy zadań.

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>

Jeśli tworzę losowe zadania ręcznie w bazie danych, powinno to wyglądać tak:Full CRUD:


Przyciski i formularze

Nad tabelą umieść przycisk, aby dodać nowy rekord. Jetstream ma zestaw komponentów Blade dla różnych elementów interfejsu użytkownika, w tym przycisków. Aby je opublikować, musimy uruchomić:

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

Następnie będziemy mieli wiele elementów wewnątrz resources/views/vendor/jetstream/components, i są one automatycznie włączane przez dostawców usług Jetstream.

Jeśli otworzymy resources/views/vendor/jetstream/components/button.blade.php, zobaczymy to:

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

Wiele innych klas Tailwind jest wymienionych po items-center, i to jest dokładnie to, czego potrzebujemy, ale tylko w formie linku, a nie przycisku. Więc po prostu kopiuję i wklejam wszystkie te klasy do nowego komponentu, który stworzę:Następnie, w resources/views/tasks/index.blade We can do this:

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

So, dodaliśmy ze stylizacją go jako przycisk,

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

    <table class="...">

dodając <x-link> klasę m-4 dla marginesu.

Zauważ, że musisz uruchomić npm run dev ponownie lub mieć npm run watch w tle, więc Tailwind ponownie skompiluje wszystkie klasy używane w plikach Blade, tak jak m-4 w naszym przypadku.

Oto wynik wizualny:

Kiedy klikniemy ten link, trafimy na adres URL /tasks/create, który odpowiada metodzie TaskController@create Controller i ładuje widok tasks/create.blade.php.

W tym formularzu ponownie wykorzystam kod HTML i klasy ze strony rejestracji w Jetstream, która znajduje się w resources/views/auth/register.blade.php. Po pewnym kopiowaniu i wklejaniu oto nasz nowy formularz w nowo utworzonym pliku.

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>

Jak widać, używamy tego samego układu i właśnie dodaliśmy kilka px-4 py-4 klas CSS do wypełnienia.

Ważne jest również, aby zwrócić uwagę na dwa komponenty Jetstream: wyświetli listę wszystkich błędów sprawdzania poprawności formularza z sesji

  • x-jet-button jest tym samym przyciskiem przesyłania, który widziałeś wcześniej, jako "inspiracja" dla naszego x-link komponentu
  • Rezultat wizualny jest następujący:

x-jet-validation-errorsZapisywanie danych powinno działać,

ponieważ stworzyliśmy kod kontrolera do tego, pamiętasz? Jeszcze jedna rzecz, którą musimy dodać, to reguła walidacji.

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

Wygenerowaliśmy również klasę Form Request dla formularza aktualizacji, więc musimy app/Http/Requests/UpdateTaskRequest.php dodać ten sam kod dla authorize() metod i rules() tam. Można dyskutować, czy używać oddzielnych klas Form Request, zwłaszcza jeśli reguły są takie same, ale osobiście preferuję unikanie tej samej klasy, ponieważ nigdy nie wiadomo, kiedy reguły zaczną się różnić.

Teraz formularz Edytuj jest prawie identyczny z formularzem Utwórz. Możemy więc otworzyć create.blade.php, do File -> Save as... i zapisać go z minimalnymi zmianami:

resources / views / tasks / edit.blade.php

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

Na koniec musimy zbudować przycisk Usuń. Użyjemy nowego komponentu Jetstream o nazwie x-jet-danger-button. W ten sposób zastąpmy również link "Edytuj", aby wyglądał bardziej jak przycisk.

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

W tej części użyjemy dobrze znanego popularnego pakietu o nazwie Laravel Permission firmy Spatie.

Jak zobaczysz, użycie jest niezależne od Jetstream, jest takie samo, jak w każdym innym projekcie Laravel. Innymi słowy, Jetstream zapewnia podstawowe uwierzytelnianie z formularzami logowania / rejestracji i zarządzaniem profilami, ale cokolwiek dodasz na górze, w większości nie "obchodzi" Jetstream.

W prostym przykładzie zezwólmy każdemu użytkownikowi na wyświetlanie zadań, ale tylko administrator będzie mógł dodawać/edytować/usuwać zadania.

Tak więc, jak mówi dokumentacja pakietu, uruchamiamy:

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

Następnie musimy włączyć uprawnienia w modelu User.

app/Models/User.php:

use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;

Domyślnie żaden zarejestrowany użytkownik nie będzie miał żadnych ról ani uprawnień. Utworzymy osobną klasę seedera, aby utworzyć administratora z uprawnieniami.

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

A teraz musimy sprawdzić, kto ma uprawnienia do zarządzania zadaniami. Możesz to sprawdzić według nazwy uprawnień lub nazwy roli, to Twoje osobiste preferencje.

Po pierwsze, w pliku Blade zobacz trzy @can ... @endcan bloki.

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>

A potem musimy również zabezpieczyć back-end, więc zobacz $this->authorize() kontrole w kontrolerze.

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

Conclusion

To wszystko, zbudowaliśmy nasz CRUD z rolami / uprawnieniami, na bazie Jetstream. Moim ogólnym celem było pokazanie, że Jetstream to tylko zestaw startowy, ale potem możesz napisać dowolny niestandardowy kod, głównie ignorując Jetstream.

To powiedziawszy, istnieją przydatne komponenty Blade, które można ponownie wykorzystać, dla elementów interfejsu użytkownika, takich jak przyciski, linki i inne.

Możesz przeczytać więcej o Jetstream w oficjalnej dokumentacji.

Repozytorium kodu dla tego projektu demonstracyjnego jest dostępne tutaj.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297