Pavel Zaněk PavelZanek.com
Vyberte jazyk

Příklad: Jak vytvořit operace CRUD v Laravel 10 (včetně testů)

Návod, který obsahuje proces vytváření CRUD operací v Laravelu. V návodu jsou všechny klíčové kroky, od vytváření migrací a modelů, přes definování akcí a validačních requestů, až po vytváření šablon, továren a testů.

Publikováno 24.06.2023 od Pavel Zaněk

Odhadovaná doba čtení: 33 minut

Example: Příklad: Jak vytvořit operace CRUD v Laravel 10 (včetně testů)

Obsah článku

Vítejte v návodu pro začátečníky pro tvorbu CRUD operací v Laravelu. Laravel je jedním z nejpopulárnějších PHP frameworků a CRUD (Create, Read, Update, Delete) je základním kamenem většiny webových aplikací. V tomto průvodci se naučíte, jak vytvořit základní Laravel CRUD oeprace/aplikaci krok za krokem. Bez ohledu na to, zda jste nováček v Laravelu nebo zkušený vývojář hledající osvěžení, tento průvodce Vám poskytne všechny potřebné informace pro úspěšné vytvoření CRUD operací včetně následného testování.

Co je CRUD?

CRUD je základním konceptem, který je nezbytný pro správu dat v jakékoli webové aplikaci. CRUD je akronym pro Create (Vytvořit), Read (Číst), Update (Aktualizovat) a Delete (Smazat). Tyto čtyři operace tvoří základní funkce, které jsou potřebné pro interakci s databází.

V Laravelu je práce s CRUD usnadněna díky jeho ORM (Object-Relational Mapping) nazývanému Eloquent. Eloquent umožňuje vývojářům pracovat s databázovými tabulkami jako s objekty a poskytuje jednoduché rozhraní (dalo by se říci API) pro běžné databázové operace.

Vytváření (Create) se v Laravelu provádí pomocí metody create(), která vytvoří nový záznam v databázi. Čtení (Read) se provádí pomocí metod jako get(), all(), find() apod., které načtou záznamy z databáze. Aktualizace (Update) se provádí pomocí metody update(), která upraví existující záznam v databázi. A nakonec, mazání (Delete) se provádí pomocí metody delete(), která odstraní záznam z databáze.

Úvod k naší ukázce

Představte si, že chceme vytvořit model ExampleItem, který bude obsahovat tyto atributy:

  • title (string)
  • body (string / text)
  • is_active (boolean)
  • type (string)

Předpokladem je mít nainstalovaný framework Laravel. Jelikož jsem testoval tento kód na již upravené aplikaci, navíc zde najdete lokalilizaci aplikace a omezení přístupu pouze uživateli s rolí superadmin.

Struktura souborů je přizpůsobena pro ukázku, tedy kromě názvu souborů uvádím i složku, ve které se daný soubor nachází. Nicméně použití je pouze na Vás, strukturu si můžete upravit dle svých potřeb.

V rámci testování je pak použit balíček Pest.

Vytvoření migrace

Migrace v Laravelu jsou jako verzovací systém pro vaši databázi. Umožňují vám měnit strukturu databáze pomocí PHP kódu místo ručního psaní SQL. Migrace jsou také skvělým nástrojem pro sdílení a aktualizaci databázové struktury mezi členy týmu.

Vytvoření migrace v Laravelu je jednoduché díky příkazu Artisan. Příkaz "php artisan make:migration" vytvoří nový soubor migrace v adresáři "database/migrations". Název souboru obsahuje datum a čas vytvoření, což Laravelu umožňuje určit, v jakém pořadí by měly být migrace spuštěny.

Každý soubor migrace obsahuje dvě metody: "up" a "down". Metoda "up" je použita k přidání nových tabulek, sloupců nebo indexů do vaší databáze. Metoda "down" by měla provést opačné operace k těm, které jsou provedeny v metodě "up".

Po vytvoření migrace můžete migraci spustit pomocí příkazu "php artisan migrate". Tento příkaz spustí všechny dosud nespustěné migrace v pořadí, v jakém byly vytvořeny.

Migrace (database/migrations):

<?php

use App\Enums\Examples\ExampleItemType;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('example_items', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->mediumText('body')->nullable();
            $table->boolean('is_active')->default(false);
            $table->string('type', 8)->default(ExampleItemType::TYPE1->value);
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('example_items');
    }
};

Vytvoření modelu

Modely jsou jedním z klíčových aspektů frameworku. Jsou to reprezentace databázových tabulek a umožňují vám pracovat s databázovými záznamy jako s objekty. To znamená, že můžete provádět CRUD operace na svých modelech, aniž byste museli psát SQL.

Vytvoření modelu v Laravelu je jednoduché. Použijete Artisan příkaz "php artisan make:model", který vytvoří nový model v adresáři "app/Models". Název modelu by měl být v jednotném čísle a začínat velkým písmenem.

Každý model v Laravelu rozšiřuje třídu "Illuminate\Database\Eloquent\Model" a obsahuje metody, které umožňují interakci s databází. Můžete také definovat vlastní metody a vlastnosti na svých modelech.

Modely také obsahují tzv. Eloquent vztahy, které umožňují definovat vztahy mezi různými modely. Například, pokud máte model "User" a model "Post", můžete definovat vztah, který říká, že jeden uživatel může mít mnoho příspěvků.

Model "ExampleItem" (app\Models\Examples):

<?php

namespace App\Models\Examples;

use App\Enums\Examples\ExampleItemType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class ExampleItem extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $fillable = [
        'title',
        'body',
        'is_active',
        'type',
    ];

    protected $casts = [
        'is_active' => 'boolean',
        'type' => ExampleItemType::class,
    ];
}

Vytvoření akcí

V Laravelu můžete akce definovat jako samostatné třídy, které zastupují jednotlivé operace, které vaše aplikace může provést. Toto je alternativní přístup k tradičnímu modelu, kde jsou akce definovány jako metody v kontrolerech.

Vytvoření akce jako samostatného souboru v Laravelu je jednoduché. Nejprve musíte vytvořit novou třídu v adresáři "app/Actions". Třída například může mít metodu "execute()", která provede požadovanou operaci.

Tento přístup má několik výhod. Jedním z nich je, že vaše akce jsou dobře izolované, snadno rozšiřitelné a testovatelné. Každá akce má jasně definovanou odpovědnost a je snadné ji testovat nezávisle na ostatních částech vaší aplikace.

Akce "CreateExampleItemAction" (app\Actions\Examples\ExampleItems):

<?php

namespace App\Actions\Examples\ExampleItems;

use App\Models\Examples\ExampleItem;

class CreateExampleItemAction
{
    public function execute(array $validatedData): ExampleItem
    {
        return ExampleItem::create($validatedData);
    }
}

Akce "UpdateExampleItemAction" (app\Actions\Examples\ExampleItems):

<?php

namespace App\Actions\Examples\ExampleItems;

use App\Models\Examples\ExampleItem;

class UpdateExampleItemAction
{
    public function execute(ExampleItem $exampleItem, array $validatedData): ExampleItem
    {
        $exampleItem->update($validatedData);

        return $exampleItem;
    }
}

Akce "RemoveExampleItemAction" (app\Actions\Examples\ExampleItems):

<?php

namespace App\Actions\Examples\ExampleItems;

use App\Models\Examples\ExampleItem;

class RemoveExampleItemAction
{
    public function execute(ExampleItem $exampleItem): void
    {
        $exampleItem->delete();
    }
}

Vytvoření Enum

Vytváření enumerátorů (Enums) v Laravelu je silný způsob, jak definovat typ proměnné, který umožňuje mnoho různých, ale specifických hodnot. Tato funkce je obzvláště užitečná, když chcete omezit možnosti hodnoty proměnné tak, aby obsahovala pouze hodnotu z konkrétní sady. V Laravelu nejsou enumerátory vestavěné, ale lze je snadno implementovat pomocí tříd PHP.

Vytváření enumerátorů v Laravelu může být skvělý způsob, jak zpřehlednit a udržovat váš kód, zejména při práci s proměnnými, které by měly mít pouze specifickou sadu hodnot. Mohou být použity v různých částech vaší aplikace Laravel, například v modelech, kontrolerech a dokonce i ve vašich databázových migracích.

Kromě toho, že váš kód je čistší a snadněji pochopitelný, enumerátory také pomáhají snižovat chyby ve vašem kódu. Omezením možných hodnot proměnné můžete zajistit, že bude vždy obsahovat hodnotu, kterou má, čímž se snižuje pravděpodobnost, že neočekávané hodnoty způsobí chyby ve vašem kódu.

Pamatujte, že enumerátory jsou jen jednou z mnoha pokročilých funkcí, které Laravel nabízí, aby usnadnil vaše vývojářské práce v PHP. Ať už vytváříte malý osobní projekt nebo velkou podnikovou aplikaci, Laravel má nástroje a funkce, které potřebujete k vytvoření robustních, škálovatelných a udržitelných PHP aplikací.

"ExampleItemType" Enum (app\Enums\Examples):

<?php

namespace App\Enums\Examples;

use Illuminate\Support\Collection;

enum ExampleItemType: string
{
    case TYPE1 = 'Type 1';
    case TYPE2 = 'Type 2';

    public static function all(): Collection
    {
        return collect(ExampleItemType::cases())->map(
            fn(ExampleItemType $code) => $code->details()
        );
    }

    public function details(): array
    {
        return match($this) 
        {
            self::TYPE1 => [
                'name'  => 'Type Name 1',
                'value' => 'Type 1',
            ],

            self::TYPE2 => [
                'name'  => 'Type Name 2',
                'value' => 'Type 2',
            ],
        };
    }
}

Vytvoření validace / validačních requestů

Validační requesty jsou speciální třídy, které slouží k validaci vstupních dat předtím, než jsou zpracovány vaší aplikací. Pomocí validačních requestů můžete zajistit, že data, která vaše aplikace přijímá, jsou vždy v očekávaném formátu a splňují všechna pravidla, která jste nastavili.

Vytvoření validačního requestu v Laravelu je jednoduché. Nejprve musíte vytvořit validační třídu pomocí Artisan příkazu "php artisan make:request", který vytvoří nový validační request v adresáři "app/Http/Requests".

Validační třída obsahuje dvě metody: "authorize()" a "rules()". Metoda "authorize()" určuje, zda je uživatel oprávněn provést daný požadavek. Metoda "rules()" pak vrací pole validačních pravidel, která se mají použít na vstupní data.

"StoreXYZRequest" a "UpdateXYZRequest" jsou dva běžné typy validačních requestů v Laravelu. "StoreXYZRequest" se používá při vytváření nového záznamu a "UpdateXYZRequest" se používá při aktualizaci existujícího záznamu. Nicméně můžete si například vytvořit pouze jeden validační request, který bude použit při vytváření i úpravě záznamu - což se hodí, pokud jsou validační podmínky pro vytváření i úpravu stejné.

Validační request "StoreExampleItemRequest" (app\Http\Requests\Examples\ExampleItems):

<?php

namespace App\Http\Requests\Examples\ExampleItems;

use App\Enums\Examples\ExampleItemType;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;

class StoreExampleItemRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255',
            'body' => 'nullable|string',
            'is_active' => 'required|boolean',
            'type' => [
                'required',
                'string',
                'max:8',
                new Enum(ExampleItemType::class),
            ],
        ];
    }

    /**
     * Prepare inputs for validation.
     *
     * @return void
     */
    protected function prepareForValidation()
    {
        $this->merge([
            'is_active' => $this->has('is_active') ? true : false,
        ]);
    }
}

Validační request "UpdateExampleItemRequest" (app\Http\Requests\Examples\ExampleItems):

<?php

namespace App\Http\Requests\Examples\ExampleItems;

use App\Enums\Examples\ExampleItemType;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;

class UpdateExampleItemRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255',
            'body' => 'nullable|string',
            'is_active' => 'required|boolean',
            'type' => [
                'required',
                'string',
                'max:8',
                new Enum(ExampleItemType::class),
            ],
        ];
    }

    /**
     * Prepare inputs for validation.
     *
     * @return void
     */
    protected function prepareForValidation()
    {
        $this->merge([
            'is_active' => $this->has('is_active') ? true : false,
        ]);
    }
}

Vytvoření kontroleru

Kontroléry jsou klíčovou součástí jakékoliv aplikace postavené na tomto frameworku. Jsou to třídy, které obsluhují všechny HTTP požadavky, které vaše aplikace přijímá, a řídí, jak má vaše aplikace reagovat.

Vytvoření kontroleru v Laravelu je jednoduché opět díky nástroji Artisan, který je součástí Laravelu. Příkaz "php artisan make:controller" vytvoří nový kontrolér v adresáři "app/Http/Controllers". Název kontroleru by měl být v jednotném čísle a začínat velkým písmenem.

Kontroléry v Laravelu mohou být buď jednoduché, což jsou kontroléry s jednou akcí (využívá metodu "__invoke"), nebo zdrojové, což jsou kontroléry, které obsluhují celý soubor CRUD operací. Zdrojové kontroléry jsou ideální pro práci s databázovými záznamy, jako jsou uživatelé, příspěvky na blogu nebo jakékoliv jiné entity ve vaší aplikaci. Samozřejmě může být i kontroler vytvořený dle Vašich představ, tedy nemusí být ani s jednou metodou, ani však zdrojový.

Kontroler "ExampleItemController" (app\Http\Controllers\Examples):

<?php

namespace App\Http\Controllers\Examples;

use App\Actions\Examples\ExampleItems\CreateExampleItemAction;
use App\Actions\Examples\ExampleItems\RemoveExampleItemAction;
use App\Actions\Examples\ExampleItems\UpdateExampleItemAction;
use App\Enums\Examples\ExampleItemType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Examples\ExampleItems\StoreExampleItemRequest;
use App\Http\Requests\Examples\ExampleItems\UpdateExampleItemRequest;
use App\Models\Examples\ExampleItem;

class ExampleItemController extends Controller
{
    public function index()
    {
        $exampleItems = ExampleItem::orderBy('created_at', 'desc')->paginate(20);

        return view('examples.example-items.index', [
            'exampleItems' => $exampleItems,
        ]);
    }

    public function create()
    {
        return view('examples.example-items.create', [
            'exampleItemTypes' => ExampleItemType::all()
        ]);
    }

    public function store(StoreExampleItemRequest $request)
    {
        (new CreateExampleItemAction())->execute($request->validated());

        return redirect()->route('example-items.index')->with([
            'flashType' => 'success',
            'flashMessage' => __('Example Item was successfully created.'),
        ]);
    }

    public function show(ExampleItem $exampleItem)
    {
        abort_if(!$exampleItem->is_active, 404, "Example Item is not active.");

        return view('examples.example-items.show', [
            'exampleItem' => $exampleItem
        ]);
    }

    public function edit(ExampleItem $exampleItem)
    {
        return view('examples.example-items.edit', [
            'exampleItem' => $exampleItem,
            'exampleItemTypes' => ExampleItemType::all()
        ]);
    }

    public function update(UpdateExampleItemRequest $request, ExampleItem $exampleItem)
    {
        (new UpdateExampleItemAction())->execute($exampleItem, $request->validated());

        return redirect()->route('example-items.index')->with([
            'flashType' => 'success',
            'flashMessage' => __('Example Item was successfully updated.'),
        ]);
    }

    public function destroy(ExampleItem $exampleItem)
    {
        (new RemoveExampleItemAction())->execute($exampleItem);

        return redirect()->route('example-items.index')->with([
            'flashType' => 'success',
            'flashMessage' => __('Example Item was successfully removed.'),
        ]);
    }
}

Vytvoření šablony

Šablony jsou klíčovým nástrojem pro vytváření dynamických webových stránek. Laravel používá šablonovací engine "Blade", který umožňuje vkládání PHP kódu přímo do HTML šablon.

Vytvoření šablony v Laravelu je jednoduché. Nejprve musíte vytvořit soubor šablony v adresáři "resources/views". Soubory šablon mají koncovku ".blade.php".

Šablony mohou obsahovat jakýkoliv platný HTML kód a také speciální Blade direktivy pro vkládání PHP kódu. Například, můžete použít direktivu "{{ $variable }}" pro výpis hodnoty proměnné, nebo direktivy @if, @foreach a další pro řízení toku aplikace.

Kromě toho můžete využít systém dědičnosti šablon, který umožňuje definovat "základní" šablony s obecnou strukturou stránky a pak vytvářet "dědičné" šablony, které tuto strukturu rozšiřují a přidávají specifický obsah.

Šablona "Index" (resources/views/examples/example-items):

<x-app-layout>
    <x-slot name="header">

        <div class="flex justify-between items-center">
            <div>
                <h1 class="font-semibold text-xl text-neutral-800 dark:text-neutral-50 leading-tight">
                    {{ __('Example Items') }}
                </h1>
            </div>
            <div class="ml-2">
                <a href="{{ route('example-items.create') }}"
                    class="transition-all ease-in-out duration-500 px-4 py-2 rounded-md bg-blue-600 text-blue-100 hover:bg-blue-700">
                    {{ __('Create') }}
                </a>
            </div>
        </div>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white rounded-lg shadow dark:bg-gray-800 overflow-hidden shadow-xl sm:rounded-lg">

                <!-- Index Example Items -->
                <div class="relative overflow-x-auto">
                    <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">
                                    {{ __('ID') }}
                                </th>
                                <th scope="col" class="px-6 py-3">
                                    {{ __('Title') }}
                                </th>
                                <th scope="col" class="px-6 py-3">
                                    {{ __('Is active') }}
                                </th>
                                <th scope="col" class="px-6 py-3">
                                    {{ __('Type') }}
                                </th>
                                <th scope="col" class="px-6 py-3">
                                    {{ __('Created at') }}
                                </th>
                                <th scope="col" class="px-6 py-3">
                                    {{ __('Updated at') }}
                                </th>
                                <th scope="col" class="px-6 py-3">
                                    {{ __('Action') }}
                                </th>
                            </tr>
                        </thead>
                        <tbody>
                            @foreach ($exampleItems as $exampleItem)
                                <tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
                                    <th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
                                        {{ $exampleItem->id }}
                                    </th>
                                    <td class="px-6 py-4">
                                        <a href="{{ route('example-items.show', $exampleItem) }}" class="transition-all ease-in-out duration-500 text-blue-700 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
                                            {{ $exampleItem->title }}
                                        </a>
                                    </td>
                                    <td class="px-6 py-4">
                                        {{ $exampleItem->is_active ? __('Yes') : __('No') }}
                                    </td>
                                    <td class="px-6 py-4">
                                        {{ $exampleItem->type->value }}
                                    </td>
                                    <td class="px-6 py-4">
                                        {{ $exampleItem->created_at->diffForHumans() }}
                                    </td>
                                    <td class="px-6 py-4">
                                        {{ $exampleItem->updated_at?->diffForHumans() }}
                                    </td>
                                    <td class="px-6 py-4">
                                        <div class="flex justify-start items-center gap-2">
                                            <a href="{{ route('example-items.edit', $exampleItem->id) }}"
                                                class="transition-all ease-in-out duration-500 text-gray-400 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300">
                                                <i class="fas fa-edit"></i>
                                            </a>
                                            <form action="{{ route('example-items.destroy', $exampleItem->id) }}" method="POST" onsubmit="return confirm('{{ __('Are You Sure?') }}');">
                                                <input type="hidden" name="_method" value="DELETE">
                                                <input type="hidden" name="_token" value="{{ csrf_token() }}">
                                                <button type="submit" class="flex items-center">
                                                    <i class="transition-all ease-in-out duration-500 fas fa-trash text-red-600 hover:text-red-900 dark:hover:text-red-400"></i>
                                                </button>
                                            </form>
                                        </div>
                                    </td>
                                </tr>
                            @endforeach
                        </tbody>
                    </table>
                </div>

                <div class="mt-5 mb-2 mx-3">
                    {!! $exampleItems->withQueryString()->links() !!}
                </div>

            </div>
        </div>
    </div>
</x-app-layout>

Šablona "Create" (resources/views/examples/example-items):

<x-app-layout>
    <x-slot name="header">
        <h1 class="font-semibold text-xl text-neutral-800 dark:text-neutral-50 leading-tight">
            {{ __('Create Example Item') }}
        </h1>
    </x-slot>

    <!-- Create Example Item -->
    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white rounded-lg shadow dark:bg-gray-800 overflow-hidden shadow-xl sm:rounded-lg">

                <div class="w-full px-6 py-4">
                    <form method="POST" action="{{ route('example-items.store') }}">
                        @csrf

                        <!-- Title -->
                        <div>
                            <label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" for="title">
                                {{ __('Title') }}
                            </label>

                            <input
                                id="title"
                                class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5  dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                                type="text" name="title" value="{{old('title')}}">

                            @error('title')
                                <p class="mt-2 text-sm text-red-600 dark:text-red-500">{{ $message }}</p>
                            @enderror
                        </div>

                        <!-- Body -->
                        <div class="mt-4">
                            <label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" for="body">
                                {{ __('Body') }}
                            </label>

                            <textarea name="body"
                                id="body"
                                class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                                rows="4">{{old('body')}}</textarea>

                            @error('body')
                                <p class="mt-2 text-sm text-red-600 dark:text-red-500">{{ $message }}</p>
                            @enderror
                        </div>

                        <!-- Is active -->
                        <div class="mt-4">
                            <div class="flex items-center mb-4">
                                <input name="is_active" id="is-active" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" >
                                <label for="is-active" class="ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">{{ __('Active') }}</label>
                            </div>

                            @error('is_active')
                                <p class="mt-2 text-sm text-red-600 dark:text-red-500">{{ $message }}</p>
                            @enderror
                        </div>

                        <!-- Type -->
                        <div class="mt-4">
                            <label for="type" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{ __('Select an option') }}</label>
                            <select name="type" id="type" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
                                <option selected>{{ __('Choose a type') }}</option>
                                @foreach ($exampleItemTypes as $exampleItemType)
                                    <option value="{{ $exampleItemType['value'] }}">
                                        {{ $exampleItemType['name'] }}
                                    </option>
                                @endforeach
                            </select>

                            @error('type')
                                <p class="mt-2 text-sm text-red-600 dark:text-red-500">{{ $message }}</p>
                            @enderror
                        </div>

                        <div class="flex items-center justify-start mt-4">
                            <button type="submit"
                                class="transition-all ease-in-out duration-500 inline-flex items-center px-6 py-2 text-sm font-semibold rounded-md text-blue-100 bg-blue-600 hover:bg-blue-700 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300">
                                {{ __('Save') }}
                            </button>
                        </div>

                    </form>
                </div>

            </div>
        </div>
    </div>
</x-app-layout>

Šablona "Edit" (resources/views/examples/example-items):

<x-app-layout>
    <x-slot name="header">
        <h1 class="font-semibold text-xl text-neutral-800 dark:text-neutral-50 leading-tight">
            {{ __('Edit Example Item') }}
        </h1>
    </x-slot>

    <!-- Edit Example Item -->
    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white rounded-lg shadow dark:bg-gray-800 overflow-hidden shadow-xl sm:rounded-lg">

                <div class="w-full px-6 py-4">
                    <form method="POST" action="{{ route('example-items.update', $exampleItem->id) }}">
                        @csrf
                        @method('PUT')

                        <!-- Title -->
                        <div>
                            <label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" for="title">
                                {{ __('Title') }}
                            </label>

                            <input
                                id="title"
                                class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5  dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                                type="text" name="title" value="{{ old('title', $exampleItem->title) }}">

                            @error('title')
                                <p class="mt-2 text-sm text-red-600 dark:text-red-500">{{ $message }}</p>
                            @enderror
                        </div>

                        <!-- Body -->
                        <div class="mt-4">
                            <label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" for="body">
                                {{ __('Body') }}
                            </label>

                            <textarea name="body"
                                id="body"
                                class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                                rows="4">{{ old('body', $exampleItem->body) }}</textarea>

                            @error('body')
                                <p class="mt-2 text-sm text-red-600 dark:text-red-500">{{ $message }}</p>
                            @enderror
                        </div>

                        <!-- Is active -->
                        <div class="mt-4">
                            <div class="flex items-center mb-4">
                                <input {{ $exampleItem->is_active ? 'checked' : ''}} name="is_active" id="is-active" type="checkbox" value="{{ $exampleItem->is_active }}" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" >
                                <label for="is-active" class="ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">{{ __('Active') }}</label>
                            </div>

                            @error('is_active')
                                <p class="mt-2 text-sm text-red-600 dark:text-red-500">{{ $message }}</p>
                            @enderror
                        </div>

                        <!-- Type -->
                        <div class="mt-4"><label for="type" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{ __('Select an option') }}</label>
                            <select name="type" id="type" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
                                <option selected>{{ __('Choose a type') }}</option>
                                @foreach ($exampleItemTypes as $exampleItemType)
                                    <option value="{{ $exampleItemType['value'] }}" {{ $exampleItemType['value'] == $exampleItem->type->value ? 'selected' : '' }}>
                                        {{ $exampleItemType['name'] }}
                                    </option>
                                @endforeach
                            </select>

                            @error('type')
                                <p class="mt-2 text-sm text-red-600 dark:text-red-500">{{ $message }}</p>
                            @enderror
                        </div>

                        <div class="flex items-center justify-start mt-4">
                            <button type="submit"
                                class="transition-all ease-in-out duration-500 inline-flex items-center px-6 py-2 text-sm font-semibold rounded-md text-blue-100 bg-blue-600 hover:bg-blue-700 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300">
                                {{ __('Update') }}
                            </button>
                        </div>

                    </form>
                </div>

            </div>
        </div>
    </div>
</x-app-layout>

Šablona "Show" (resources/views/examples/example-items):

<x-app-layout>
    <x-slot name="header">
        <h1 class="font-semibold text-xl text-neutral-800 dark:text-neutral-50 leading-tight">
            {{ $exampleItem->title }}
        </h1>
    </x-slot>

    <!-- Show Example Item -->
    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white rounded-lg shadow dark:bg-gray-800 overflow-hidden shadow-xl sm:rounded-lg">

                <div class="sm:p-6 lg:p-8 text-gray-900 dark:text-gray-200">
                    <div class="mb-4">
                        {{ $exampleItem->body }}
                    </div>
                    <div>
                        {{ __('Type') }}: {{ $exampleItem->type->details()['name'] }} ({{ $exampleItem->type->value }})
                    </div>
                </div>

            </div>
        </div>
    </div>
</x-app-layout>

Trasování (routing)

Trasování je jedním z nejdůležitějších aspektů jakékoliv webové aplikace a Laravel poskytuje silný a flexibilní systém trasování, který usnadňuje definování tras pro vaši aplikaci.

Trasy v Laravelu jsou definovány v souborech tras, které se nacházejí v adresáři "routes" vaší aplikace. Laravel poskytuje několik různých souborů tras pro různé typy tras, včetně webových tras, API tras a konzolových tras.

Každá trasa je definována pomocí HTTP metody (jako je GET, POST, PUT nebo DELETE), cesty a akce, která se má provést, když je trasa vyvolána. Akce může být buď funkce (closure funkce), nebo odkaz na metodu kontroleru.

Laravel také podporuje pokročilé funkce trasování, jako je trasování se zástupnými znaky, trasování s názvy, trasování s middlewarem, trasování s omezeními parametrů, apod.

Trasování u "web" (routes):

...
Route::group([
    'prefix' => "{language}", 
    'where' => ['language' => '[a-zA-Z]{2}']
    ], function () {

    // Admin
    Route::group(['middleware' => ['role:superadmin']], function () {
        ...
        Route::resource('/examples/example-items', ExampleItemController::class);
    });

});
...

Vytvoření továrny

Továrny jsou nástroje, které usnadňují generování testovacích dat pro vaši databázi. Továrny umožňují definovat "šablony" (nepleťte si se blade šablonou) pro vytváření nových instancí vašich modelů s náhodnými nebo předdefinovanými daty.

Vytvoření továrny v Laravelu je jednoduché opět díky Artisanu. Příkaz "php artisan make:factory" vytvoří novou továrnu v adresáři "database/factories". Název továrny by měl odpovídat názvu modelu, pro který je továrna určena.

Každá továrna obsahuje metodu "definition()", která vrací pole atributů, které se mají použít při vytváření nové instance modelu. Můžete použít Laravel funkce jako "faker" pro generování náhodných dat.

Továrny jsou ideální pro vytváření velkého množství dat pro testování nebo pro naplnění vaší databáze před vývojem nebo při vývoji.

Továrna "ExampleItemFactory" (database/factories/Examples):

<?php

namespace Database\Factories\Examples;

use App\Enums\Examples\ExampleItemType;
use App\Models\Examples\ExampleItem;
use Illuminate\Database\Eloquent\Factories\Factory;

class ExampleItemFactory extends Factory
{
    protected $model = ExampleItem::class;

    public function definition()
    {
        return [
            'title' => $this->faker->sentence(4),
            'body' => $this->faker->realText(500),
            'is_active' => $this->faker->boolean(),
            'type' => $this->faker->boolean() ? ExampleItemType::TYPE1 : ExampleItemType::TYPE2,
        ];
    }
}

Vytvoření testů

Testování je zásadní součástí vývoje jakékoliv aplikace a Laravel poskytuje vynikající nástroje pro automatizované testování vašeho kódu. Laravel Vám umožňuje snadno vytvářet a spouštět testy pro vaši aplikaci.

Vytvoření testu v Laravelu je jednoduché, jak můžete očekávat, díky nástroji Artisan. Příkaz "php artisan make:test" vytvoří nový test v adresáři "tests/Feature" nebo "tests/Unit", v závislosti na tom, jaký typ testu chcete vytvořit.

Každý test je třída, která obsahuje jednu nebo více testovacích metod. Tyto metody obsahují přiřazování, které ověřují, že váš kód funguje tak, jak má. Laravel poskytuje širokou škálu přiřazování pro ověření různých aspektů vaší aplikace, včetně odpovědí HTTP, interakcí s databází, a mnoho dalšího.

Testy můžete spustit pomocí příkazu "php artisan test", který spustí všechny testy ve vaší aplikaci a vypíše výsledky do konzole.

Testy pro kontroler

Test "ExampleItemControllerTest" (tests/Feature/App/Controllers/Examples):

<?php

use App\Enums\Examples\ExampleItemType;
use App\Models\Examples\ExampleItem;
use App\Models\User;
use Spatie\Permission\Models\Role;

beforeEach(function () {
    $user = User::factory()
        ->withPersonalTeam()
        ->create();

    $superadminRole = Role::updateOrCreate(['name' => 'superadmin'], ['name' => 'superadmin']);
    $user->assignRole($superadminRole);

    $this->actingAs($user);
});

test('example item list can be rendered', function () {
    ExampleItem::factory()
        ->count(5)
        ->create();

    $this->get(route('example-items.index', ['language' => 'en']))
        ->assertStatus(200);
});

test('example item create view can be rendered', function () {
    $this->get(route('example-items.create', ['language' => 'en']))
        ->assertStatus(200);
});

test('example item can be created', function () {
    $data = [
        'title' => 'Example Title',
        'body' => 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Phasellus rhoncus. Aenean vel massa quis mauris vehicula lacinia. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?',
        'is_active' => true,
        'type' => ExampleItemType::TYPE1->value,
    ];

    $this->post(route('example-items.store', ['language' => 'en']), $data)
        ->assertRedirect(route('example-items.index', ['language' => 'en']));

    expect(ExampleItem::first())
        ->title
            ->not->toBeNull()
            ->toEqual($data['title'])
        ->body
            ->not->toBeNull()
            ->toEqual($data['body'])
        ->is_active
            ->not->toBeNull()
            ->toBeTrue()
        ->type->toEqual(ExampleItemType::TYPE1);
});

test('example item edit view can be rendered', function () {
    $exampleItem = ExampleItem::factory()
        ->create();

    $this->get(route('example-items.edit', ['language' => 'en', 'example_item' => $exampleItem]))
        ->assertStatus(200);
});

test('example item can be updated', function () {
    $exampleItem = ExampleItem::factory()
        ->create([
            'is_active' => true
        ]);

    $data = [
        'title' => 'Title - Test',
        'type' => ExampleItemType::TYPE2->value,
    ];

    $this->put(route('example-items.update', ['language' => 'en', 'example_item' => $exampleItem]), $data)
        ->assertRedirect(route('example-items.index', ['language' => 'en']));

    expect($exampleItem->refresh())
        ->title
            ->not->toBeNull()
            ->toBeString()
            ->toEqual($data['title'])
        ->body
            ->not->toBeNull()
            ->toBeString()
        ->is_active
            ->not->toBeNull()
            ->toBeFalse()
        ->type->toEqual(ExampleItemType::TYPE2);
});

test('example item can be removed', function () {
    $exampleItem = ExampleItem::factory()
        ->create();

    $this->delete(route('example-items.destroy', ['language' => 'en', 'example_item' => $exampleItem]))
        ->assertRedirect(route('example-items.index', ['language' => 'en']));

    expect(ExampleItem::count())
        ->toBe(0);
});

Testy pro akce

Test "CreateExampleItemActionTest" (tests/Feature/App/Actions/Examples/ExampleItems):

<?php

use App\Actions\Examples\ExampleItems\CreateExampleItemAction;
use App\Enums\Examples\ExampleItemType;

it('creates example item', function () {
    $validatedData = [
        'title' => 'Example Title',
        'body' => 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Phasellus rhoncus. Aenean vel massa quis mauris vehicula lacinia. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?',
        'is_active' => true,
        'type' => ExampleItemType::TYPE1,
    ];

    $exampleItem = (new CreateExampleItemAction())->execute($validatedData);

    expect($exampleItem->refresh())
        ->title
            ->not->toBeNull()
            ->toEqual($validatedData['title'])
        ->body
            ->not->toBeNull()
            ->toEqual($validatedData['body'])
        ->is_active
            ->not->toBeNull()
            ->toBeTrue()
        ->type->toEqual($validatedData['type']);
});

Test "UpdateExampleItemActionTest" (tests/Feature/App/Actions/Examples/ExampleItems):

<?php

use App\Actions\Examples\ExampleItems\UpdateExampleItemAction;
use App\Enums\Examples\ExampleItemType;
use App\Models\Examples\ExampleItem;

it('updates example item', function () {
    $exampleItem = ExampleItem::factory()
        ->create([
            'is_active' => true,
            'type' => ExampleItemType::TYPE1
        ]);

    $validatedData = [
        'title' => 'Title - Test',
        'is_active' => false,
        'type' => ExampleItemType::TYPE2,
    ];

    (new UpdateExampleItemAction())->execute($exampleItem, $validatedData);

    expect($exampleItem)
        ->title
            ->not->toBeNull()
            ->toBeString()
            ->toEqual($validatedData['title'])
        ->body
            ->not->toBeNull()
            ->toBeString()
        ->is_active
            ->not->toBeNull()
            ->toBeFalse()
        ->type->toEqual($validatedData['type']);
});

Test "RemoveExampleItemActionTest" (tests/Feature/App/Actions/Examples/ExampleItems):

<?php

use App\Actions\Examples\ExampleItems\RemoveExampleItemAction;
use App\Models\Examples\ExampleItem;

it('removes example item', function () {
    $exampleItem = ExampleItem::factory()
        ->create();

    expect(ExampleItem::count())->toEqual(1);

    (new RemoveExampleItemAction())->execute($exampleItem);

    expect(ExampleItem::count())->toEqual(0);
});

Závěr

V tomto návodu jsme se podrobně seznámili s procesem vytváření CRUD operací v Laravelu. Prošli jsme si všechny klíčové kroky, od vytváření migrací a modelů, přes definování akcí a validačních requestů, až po vytváření šablon, továren a testů. Doufám, že Vám tento návod pomohl pochopit, jak pracovat s Laravelem a že Vám poskytl pevný základ pro další vývoj Vašich vlastních aplikací.

Sdílet:
5 / 5
Celkem hlasů: 2
Zatím jste nehodnotili.
Pavel Zaněk

Full-stack programátor & SEO konzultant

Pavel Zaněk je zkušený full-stack vývojář s odborností v SEO a programování v Laravelu. Jeho dovednosti zahrnují optimalizaci webových stránek, implementaci efektivních strategií pro zvýšení návštěvnosti a zlepšení pozic ve vyhledávačích. Pavel je expert na Laravel a jeho související technologie, včetně Livewire, Vue.js, MariaDB, Redis, TailwindCSS/Bootstrap a mnoho dalšího. Kromě svých programovacích dovedností má také silné zázemí v řízení VPS, což mu umožňuje zvládnout složité výzvy na straně serveru. Pavel je vysoce motivovaný a oddaný profesionál, který je zavázán k dodávání výjimečných výsledků. Jeho cílem je pomáhat klientům dosáhnout úspěchu v online prostoru a dosáhnout svých cílů s pomocí nejlepších webových technologií a strategií pro optimalizaci pro vyhledávače.

Doporučené články

Jak vytvořit RSS feed ve frameworku Laravel

Publikováno 10.08.2023 od Pavel Zaněk

Odhadovaná doba čtení: 16 minut

laravel

Průvodce vytvářením RSS feedu ve frameworku Laravel bez externích balíčků. Od základních principů až po pokročilé techniky. Ideální pro vývojáře hledající efektivní a bezpečné řešení.

Pokračovat ve čtení

Tato stránka používá cookies na vylepšení vašeho uživatelského zážitku. - Zásady Cookies