سننشئ في هذا المقال تطبيق مدونة باستخدام لارافيل ونجعل تطبيقنا كامل الميزات ويحتوي على منشورات وفئات Categories ووسوم Tags، حيث ناقشنا في المقال السابق عمليات CRUD للمنشورات، وسنكرر اليوم هذه العمليات نفسها على كل من الفئات والوسوم، كما سنناقش كيفية التعامل مع العلاقات فيما بينها.
تهيئة مشروع لارافيل جديد
سنبدأ بمشروع لارافيل جديد، حيث سننشئ مجلد العمل وننتقل إليه، ولكن تأكّد من تشغيل دوكر Docker، ثم نفّذ الأمر التالي:
curl -s https://laravel.build/<app_name> | bash
انتقل إلى مجلد التطبيق وابدأ تشغيل الخادم كما يلي:
cd <app_name>
./vendor/bin/sail up
لننشئ اسمًا بديلًا للأداة sail
لتسهيل الأمور، لذا شغّل الأمر التالي:
alias sail='[ -f sail ] && sh sail || sh vendor/bin/sail'
يمكنك من الآن فصاعدًا تشغيل الأداة Sail مباشرةً دون تحديد المسار بأكمله كما يلي:
sail up
استيثاق المستخدم User authentication
يأتي إطار عمل لارافيل مع نظام بيئي كبير فهو يحتوي على العديد من الأدوات والموارد الإضافية التي تسهل على المطورين برمجة التطبيقات، وتُعَد حزمة Breeze من لارافيل جزءًا من هذا النظام، فهي توفر طريقة سريعة لإعداد استيثاق المستخدم وتسجيله في تطبيق لارافيل. تتضمّن حزمة Breeze عروضًا Views ومتحكّمات Controllers مبنية مسبقًا خاصة بالاستيثاق، بالإضافة إلى مجموعة من واجهات برمجة التطبيقات الخلفية للتعامل مع استيثاق المستخدم وتسجيله، وصُمِّمت هذه الحزمة لتكون سهلة التثبيت والضبط مع وجود الحد الأدنى من الإعداد المطلوب.
استخدم الأوامر التالية لتثبيت حزمة Breeze من لارافيل:
sail composer require laravel/breeze --dev sail artisan breeze:install sail artisan migrate sail npm install sail npm run dev
ستولّد هذه العملية تلقائيًا كل من المتحكمات والبرمجيات الوسيطة والعروض المطلوبة اللازمة لإنشاء نظام استيثاق مستخدم أساسي في تطبيقك. يمكنك الوصول إلى صفحة التسجيل من العنوان http://127.0.0.1/register
، ثم تسجّل حسابًا جديدًا وسيُعاد توجيهك إلى لوحة التحكم.
لن نناقش في هذا المقال كيفية عمل نظام استيثاق المستخدم، لأنه يرتبط ببعض المفاهيم المتقدمة إلى حدٍ ما، ولكن يوصَى بشدة بإلقاء نظرة على الملفات التي تولّدت هنا، فهي توفر نظرة أعمق حول كيفية العمل في لارافيل.
إعداد قاعدة البيانات
يجب بعد ذلك أن يكون لدينا تصور حول كيفية ظهور تطبيق المدونة، حيث نحتاج أولًا إلى قاعدة بيانات يمكنها تخزين المنشورات والفئات والوسوم، وسيكون لكل جدول قاعدة بيانات البنيةُ التالية:
جدول المنشورات Posts:
المفتاح | نوعه |
---|---|
id | عدد صحيح كبير BigInteger |
created_at | timestamps يملأ تلقائيًا عند إنشاء سجل جديد |
updated_at | timestamps يملأ تلقائيًا عند تحديث سجل جديد |
title | سلسلة نصية String |
cover | سلسلة نصية String |
content | نص Text |
is_published | قيمة منطقية Boolean |
جدول الفئات Categories:
المفتاح | نوعه |
---|---|
id | عدد صحيح كبير |
created_at | timestamps يملأ تلقائيًا عند إنشاء سجل جديد |
updated_at | timestamps يملأ تلقائيًا عند تحديث سجل جديد |
name | سلسلة نصية String |
جدول الوسوم Tags:
المفتاح | نوعه |
---|---|
id | عدد صحيح كبير |
created_at | timestamps يملأ تلقائيًا عند إنشاء سجل جديد |
updated_at | timestamps يملأ تلقائيًا عند إنشاء سجل جديد |
name | سلسلة نصية |
يجب أن يكون هناك أيضًا جدول للمستخدمين users
، ولكن لاحاجة لأن ننشئه فقد ولّدته حزمة Breeze من لارافيل مسبقًا، لذا سنتخطى هذه الخطوة حاليًا.
يكون لهذه الجداول علاقات مع بعضها البعض كما هو موضح فيما يلي:
- كل مستخدم لديه منشورات متعددة.
- كل فئة لديها العديد من المنشورات.
- كل وسم لديه العديد من المنشورات.
- يعودة كل منشور إلى مستخدم واحد.
- يعود كل منشور إلى فئة واحدة.
- كل منشور له العديد من الوسوم.
يمكننا إنشاء هذه العلاقات، ولكن يجب تعديل جدول المنشورات كما يلي:
جدول المنشورات مع العلاقات:
المفتاح | نوعه |
---|---|
id | عدد صحيح كبير |
created_at | timestamps يملأ تلقائيًا عند إنشاء سجل جديد |
updated_at | timestamps يملأ تلقائيًا عند تحديث سجل جديد |
title | سلسلة نصية String |
cover | سلسلة نصية String |
content | نص Text |
is_published | قيمة منطقية |
user_id | عدد صحيح كبير |
category_id | عدد صحيح كبير |
ونحتاج أيضًا إلى جدول منفصل لعلاقة المنشور مع الوسم كما يلي:
جدول يمثل علاقة المنشور مع الوسم Post/Tag:
المفتاح | نوعه |
---|---|
post_id | عدد صحيح كبير |
tag_id | عدد صحيح كبير |
تطبيق بنية قاعدة البيانات
يمكن تطبيق التصميم السابق من خلال توليد النماذج Models وملفات التهجير Migration باستخدام الأوامر التالية:
sail artisan make:model Post --migration
sail artisan make:model Category --migration
sail artisan make:model Tag --migration
بالإضافة إلى توليد ملف تهجير منفصل للجدول post_tag
باستخدام الأمر التالي:
sail artisan make:migration create_post_tag_table
وسينشأ ملف التهجير database/migrations/create_posts_table.php
التالي:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * تشغيل عمليات التهجير */ public function up(): void { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->string('title'); $table->string('cover'); $table->text('content'); $table->boolean('is_published'); $table->bigInteger('user_id'); $table->bigInteger('category_id'); }); } /** * عكس عمليات التهجير */ public function down(): void { Schema::dropIfExists('posts'); } };
وسينشأ ملف التهجير database/migrations/create_categories_table.php
التالي:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * تشغيل عمليات التهجير */ public function up(): void { Schema::create('categories', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->string('name'); }); } /** * عكس عمليات التهجير */ public function down(): void { Schema::dropIfExists('categories'); } };
وسينشأ ملف التهجير database/migrations/create_tags_table.php
التالي:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * تشغيل عمليات التهجير */ public function up(): void { Schema::create('tags', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->string('name'); }); } /** * عكس عمليات التهجير */ public function down(): void { Schema::dropIfExists('tags'); } };
وسينشأ أيضُا ملف التهجير database/migrations/create_post_tag_table.php
التالي:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * تشغيل عمليات التهجير */ public function up(): void { Schema::create('post_tag', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->bigInteger('post_id'); $table->bigInteger('tag_id'); }); } /** * عكس عمليات التهجير */ public function down(): void { Schema::dropIfExists('post_tag'); } };
طبّق هذه التغييرات باستخدام الأمر التالي:
sail artisan migrate
يجب بعد ذلك تفعيل الإسناد الجماعي Mass Assignment لحقول محدَّدة بالنسبة للنماذج المقابلة حتى نتمكّن من استخدام تابعَي create
أو update
معها كما ناقشنا في المقال السابق، ويجب أيضًا تعريف العلاقات بين جداول قاعدة البيانات.
سيكون ملف النموذج app/Models/Post.php
كما يلي:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Post extends Model { use HasFactory; protected $fillable = [ "title", 'content', 'cover', 'is_published' ]; public function user(): BelongsTo { return $this->belongsTo(User::class); } public function category(): BelongsTo { return $this->belongsTo(Category::class); } public function tags(): BelongsToMany { return $this->belongsToMany(Tag::class); } }
وسيكون ملف النموذج app/Models/Category.php
كما يلي:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Category extends Model { use HasFactory; protected $fillable = [ 'name', ]; public function posts(): HasMany { return $this->hasMany(Post::class); } }
ويكون ملف النموذج app/Models/Tag.php
كما يلي:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Tag extends Model { use HasFactory; protected $fillable = [ 'name', ]; public function posts(): BelongsToMany { return $this->belongsToMany(Post::class); } }
ويكون ملف النموذج app/Models/User.php
كما يلي:
<?php namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; /** * السمات Attributes التي نسندها إسنادًا جماعيًا * * @var array<int, string> */ protected $fillable = [ 'name', 'email', 'password', ]; /** * السمات التي يجب أن تكون مخفية لعملية السَلسلة Serialization * * @var array<int, string> */ protected $hidden = [ 'password', 'remember_token', ]; /** * السمات التي يجب تغيير نوعها Cast * * @var array<string, string> */ protected $casts = [ 'email_verified_at' => 'datetime', ]; public function posts(): HasMany { return $this->hasMany(Post::class); } }
المتحكمات Controllers والوِجهات Routes
نحتاج إلى إنشاء متحكم موارد واحد لكل مورد (منشور وفئة ووسم) باستخدام الأوامر التالية:
php artisan make:controller PostController --resource
php artisan make:controller CategoryController --resource
php artisan make:controller TagController --resource
ننشئ بعد ذلك وِجهات لكل من هذه المتحكمات في الملف routes/web.php
كما يلي:
<?php use App\Http\Controllers\CategoryController; use App\Http\Controllers\PostController; use App\Http\Controllers\ProfileController; use App\Http\Controllers\TagController; use Illuminate\Support\Facades\Route; // وِجهات لوحة التحكم Route::prefix('dashboard')->group(function () { // الصفحة الرئيسية للوحة التحكم Route::get('/', function () { return view('dashboard'); })->name('dashboard'); // مورد الفئة للوحة التحكم Route::resource('categories', CategoryController::class); // مورد الوسم للوحة التحكم Route::resource('tags', TagController::class); // مورد المنشور للوحة التحكم Route::resource('posts', PostController::class); })->middleware(['auth', 'verified']); Route::middleware('auth')->group(function () { Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); }); require __DIR__ . '/auth.php';
لاحظ تجميع جميع الوِجهات باستخدام البادئة /dashboard
، ويكون للمجموعة البرمجيةُ الوسيطة auth
، مما يعني أنه يجب على المستخدم تسجيل الدخول للوصول إلى لوحة التحكم.
متحكمات الفئة والوسم Category/Tag
يُعََد المتحكمان CategoryController
و TagController
واضحَين إلى حدٍ ما، ويمكنك إعدادهما بالطريقة نفسها التي أنشأنا بها المتحكم PostController
في المقال السابق.
إذًا لننشئ أولًا متحكم الفئة app/Http/Controllers/CategoryController.php
كما يلي:
<?php namespace App\Http\Controllers; use App\Models\Category; use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; class CategoryController extends Controller { /** * عرض قائمة الموارد */ public function index(): View { $categories = Category::all(); return view('categories.index', [ 'categories' => $categories ]); } /** * عرض الاستمارة الخاصة بإنشاء مورد جديد */ public function create(): View { return view('categories.create'); } /** * تخزين المورد الذي أنشأناه حديثًا في وحدة التخزين */ public function store(Request $request): RedirectResponse { // الحصول على البيانات من الطلب $name = $request->input('name'); // إنشاء نسخة جديدة من المنشور Post ووضع البيانات المطلوبة في العمود المقابل $category = new Category(); $category->name = $name; // حفظ البيانات $category->save(); return redirect()->route('categories.index'); } /** * عرض المورد المُحدَّد */ public function show(string $id): View { $category = Category::all()->find($id); $posts = $category->posts(); return view('categories.show', [ 'category' => $category, 'posts' => $posts ]); } /** * عرض الاستمارة الخاصة بتعديل المورد المُحدَّد */ public function edit(string $id): View { $category = Category::all()->find($id); return view('categories.edit', [ 'category' => $category ]); } /** * تحديث المورد المُحدَّد في وحدة التخزين */ public function update(Request $request, string $id): RedirectResponse { // الحصول على البيانات من الطلب $name = $request->input('name'); // البحث عن الفئة المطلوبة ووضع البيانات المطلوبة في العمود المقابل $category = Category::all()->find($id); $category->name = $name; // حفظ البيانات $category->save(); return redirect()->route('categories.index'); } /** * إزالة المورد المٌحدَّد من وحدة التخزين */ public function destroy(string $id): RedirectResponse { $category = Category::all()->find($id); $category->delete(); return redirect()->route('categories.index'); } }
ولننشئ الآن متحكم الوسم app/Http/Controllers/TagController.php
كما يلي:
<?php namespace App\Http\Controllers; use App\Models\Tag; use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; class TagController extends Controller { /** * عرض قائمة الموارد */ public function index(): View { $tags = Tag::all(); return view('tags.index', [ 'tags' => $tags ]); } /** * عرض الاستمارة الخاصة بإنشاء مورد جديد */ public function create(): View { return view('tags.create'); } /** * تخزين المورد الذي أنشأناه حديثًا في وحدة التخزين */ public function store(Request $request): RedirectResponse { // الحصول على البيانات من الطلب $name = $request->input('name'); // إنشاء نسخة جديدة من المنشور Post ووضع البيانات المطلوبة في العمود المقابل $tag = new Tag(); $tag->name = $name; // حفظ البيانات $tag->save(); return redirect()->route('tags.index'); } /** * عرض المورد المُحدَّد */ public function show(string $id): View { $tag = Tag::all()->find($id); $posts = $tag->posts(); return view('tags.show', [ 'tag' => $tag, 'posts' => $posts ]); } /** * عرض الاستمارة الخاصة بتعديل المورد المُحدَّد */ public function edit(string $id): View { $tag = Tag::all()->find($id); return view('tags.edit', [ 'tag' => $tag ]); } /** * تحديث المورد المُحدَّد في وحدة التخزين */ public function update(Request $request, string $id): RedirectResponse { // الحصول على البيانات من الطلب $name = $request->input('name'); // البحث عن الوسم المطلوب ووضع البيانات المطلوبة في العمود المقابل $tag = Tag::all()->find($id); $tag->name = $name; // حفظ البيانات $tag->save(); return redirect()->route('tags.index'); } /** * إزالة المورد المُحدَّد من وحدة التخزين */ public function destroy(string $id): RedirectResponse { $tag = Tag::all()->find($id); $tag->delete(); return redirect()->route('tags.index'); } }
تذكّر أنه يمكنك التحقق من اسم الوِجهات باستخدام الأمر التالي:
sail artisan route:list
متحكم المنشور Post
يُعَد متحكم المنشور PostController
أكثر تعقيدًا بعض الشيء، إذ يجب عليك التعامل مع عمليات رفع الصور والعلاقات بين الجداول في قاعدة البيانات في التابع store()
. إذًا لننشئ هذا المتحكم app/Http/Controllers/PostController.php
كما يلي:
<?php namespace App\Http\Controllers; use App\Models\Category; use App\Models\Post; use App\Models\Tag; use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; use Illuminate\Database\Eloquent\Builder; class PostController extends Controller { . . . /** * تخزين المورد الذي أنشأناه حديثًا في وحدة التخزين */ public function store(Request $request): RedirectResponse { // الحصول على البيانات من الطلب $title = $request->input('title'); $content = $request->input('content'); if ($request->input('is_published') == 'on') { $is_published = true; } else { $is_published = false; } // إنشاء نسخة جديدة من المنشور Post ووضع البيانات المطلوبة في العمود المقابل $post = new Post(); $post->title = $title; $post->content = $content; $post->is_published = $is_published; // حفظ صورة الغلاف $path = $request->file('cover')->store('cover', 'public'); $post->cover = $path; // ضبط المستخدم $user = Auth::user(); $post->user()->associate($user); // ضبط الفئة $category = Category::find($request->input('category')); $post->category()->associate($category); // حفظ المنشور $post->save(); // ضبط الوسوم $tags = $request->input('tags'); foreach ($tags as $tag) { $post->tags()->attach($tag); } return redirect()->route('posts.index'); } . . . }
هناك بعض الأشياء التي يجب ملاحظتها في التابع store()
، حيث سنستخدم في الأسطر من 28 إلى 32 مربع اختيار HTML لتمثيل الحقل is_published
، وتكون قيمته إما 'on'
أو null
، ولكن تُحفَظ قيمته في قاعدة البيانات بوصفها true
أو false
، لذلك يجب استخدام التعليمة if
لحل هذه المشكلة.
يمكننا استرداد الملفات في السطور من 41 إلى 42 من خلال استخدام التابع file()
بدلًا من التابع input()
، ويُحفظ الملف في القرص public
ضمن المجلد cover
.
نحصل في السطور من 45 إلى 46 على المستخدم الحالي باستخدام التابع Auth::user()
، ونربط المنشور بالمستخدم باستخدام التابع associate()
، وتفعَل السطور من 49 إلى 50 الشيء نفسه بالنسبة للفئة، وتذكّر أنه يمكنك تطبيق ذلك فقط مع المتغير $post
وليس $user
أو $category
، لأن العمودان user_id
و category_id
موجودان في الجدول posts
.
وأخيرًا، يجب حفظ المنشور الحالي في قاعدة البيانات، ثم استرداد قائمة الوسوم وإرفاق كل منها بالمنشور واحدًا تلوَ الآخر باستخدام التابع attach()
بالنسبة للوسوم كما هو موضح في السطور من 56 إلى 60.
تجري الأمور بطريقة مشابهة بالنسبة إلى التابع update()
، باستثناء أنه يجب إزالة جميع الوسوم الموجودة قبل أن تتمكّن من إرفاق الوسوم الجديدة كما يلي:
$post->tags()->detach();
العروض Views
تذكر دائمًا أن تكون منظّمًا عند إنشاء نظام عرض، حيث سنتّبع البنية التالية في مثالنا:
resources/views ├── auth ├── categories │ ├── create.blade.php │ ├── edit.blade.php │ ├── index.blade.php │ └── show.blade.php ├── components ├── layouts ├── posts │ ├── create.blade.php │ ├── edit.blade.php │ ├── index.blade.php │ └── show.blade.php ├── profile ├── tags │ ├── create.blade.php │ ├── edit.blade.php │ ├── index.blade.php │ └── show.blade.php ├── dashboard.blade.php └── welcome.blade.php
أنشأنا ثلاثة مجلدات هي: posts
و categories
و tags
، ولكل منها أربعة قوالب هي: create
و edit
و index
و show
باستثناء المجلد posts
، لأن وجود صفحة show
للمنشورات في لوحة التحكم أمر غير ضروري.
يؤدي تضمين جميع هذه العروض في مقال واحد إلى جعل هذا المقال طويلًا بلا داعٍ، لذا سنوضّح فقط صفحات إنشاء وتعديل وفهرس المنشورات، ولكن يمكنك الاطلاع على الشيفرة المصدرية الكاملة على Github.
عرض إنشاء منشور
لننشئ أولًا عرض إنشاء المنشور resources/views/posts/create.blade.php
كما يلي:
<x-app-layout> <x-slot name="header"> <div class="flex justify-between"> <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight" > {{ __('Posts') }} </h2> <a href="{{ route('posts.create') }}"> <x-primary-button>{{ __('New') }}</x-primary-button> </a> </div> <script src="https://cdn.tiny.cloud/. . ./tinymce.min.js" referrerpolicy="origin" ></script> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg"> <div class=""> <form action="{{ route('posts.store') }}" method="POST" class="mt-6 space-y-3" enctype="multipart/form-data" > {{ csrf_field() }} <input type="checkbox" name="is_published" id="is_published" /> <x-input-label for="is_published" >Make this post public</x-input-label > <br /> <x-input-label for="title">{{ __('Title') }}</x-input-label> <x-text-input id="title" name="title" type="text" class="mt-1 block w-full" required autofocus autocomplete="name" /> <br /> <x-input-label for="content">{{ __('Content') }}</x-input-label> <textarea name="content" id="content" cols="30" rows="30" ></textarea> <br /> <x-input-label for="cover">{{ __('Cover Image') }}</x-input-label> <x-text-input id="cover" name="cover" type="file" class="mt-1 block w-full" required autofocus autocomplete="cover" /> <br /> <x-input-label for="category">{{ __('Category') }}</x-input-label> <select id="category" name="category"> @foreach($categories as $category) <option value="{{ $category->id }}">{{ $category->name }}</option> @endforeach </select> <br /> <x-input-label for="tags">{{ __('Tags') }}</x-input-label> <select id="tags" name="tags[]" multiple> @foreach($tags as $tag) <option value="{{ $tag->id }}">{{ $tag->name }}</option> @endforeach </select> <br /> <x-primary-button>{{ __('Save') }}</x-primary-button> </form> <script> tinymce.init({. . .}); </script> </div> </div> </div> </div> </x-app-layout>
استخدمنا في مثالنا محرّر النصوص TinyMCE، ولكن يمكنك استخدام محرّر نصوص آخر عوضًا عنه، أو استخدم
العنصر <textarea></textarea>
إذا أدرتَ ذلك.
يجب أن تحتوي الاستمارة الموجودة في السطر 24 على السمة التي هي enctype="multipart/form-data"
لأننا لا ننقل النصوص فحسب، بل توجد ملفات أيضًا. تذكر في السطر 59 استخدام السمة type="file"
لأننا نرفع صورة، وستُنقَل قيمة الخيار في السطور من 67 إلى 71 إلى الواجهة الخلفية.
هناك شيئان يجب الانتباه إليهما في الأسطر من 74 إلى 78، فلاحظ أولًا السمة name="tags[]"
، حيث تخبر هذه الأقواس []
لارافيل بنقل مصفوفة قابلة للتكرار بدلًا من النصوص. ثانيًا، تنشئ السمة multiple
استمارة متعددة التحديد بدلًا من تحديد فردي مثل استمارة الفئات.
عرض تعديل المنشور
لننشئ الآن العرض resources/views/posts/edit.blade.php
كما يلي:
<x-app-layout> <x-slot name="header"> <div class="flex justify-between"> <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight" > {{ __('Posts') }} </h2> <a href="{{ route('posts.create') }}"> <x-primary-button>{{ __('New') }}</x-primary-button> </a> </div> <script src="https://cdn.tiny.cloud/. . ./tinymce.min.js" referrerpolicy="origin" ></script> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg"> <div class=""> <form action="{{ route('posts.update', ['post' => $post->id]) }}" method="POST" class="mt-6 space-y-3" enctype="multipart/form-data" > {{ csrf_field() }} {{ method_field('PUT') }} <input type="checkbox" name="is_published" id="is_published" @checked($post- />is_published)/> <x-input-label for="is_published" >Make this post public</x-input-label > <br /> <x-input-label for="title">{{ __('Title') }}</x-input-label> <x-text-input id="title" name="title" type="text" class="mt-1 block w-full" required autofocus autocomplete="name" value="{{ $post->title }}" /> <br /> <x-input-label for="content">{{ __('Content') }}</x-input-label> <textarea name="content" id="content" cols="30" rows="30"> {{ $post->content }}</textarea > <br /> <x-input-label for="cover" >{{ __('Update Cover Image') }}</x-input-label > <img src="{{ Illuminate\Support\Facades\Storage::url($post->cover) }}" alt="cover image" width="200" /> <x-text-input id="cover" name="cover" type="file" class="mt-1 block w-full" autofocus autocomplete="cover" /> <br /> <x-input-label for="category">{{ __('Category') }}</x-input-label> <select id="category" name="category"> @foreach($categories as $category) <option value="{{ $category->id }}" @selected($post-> category->id == $category->id)>{{ $category->name }} </option> @endforeach </select> <br /> <x-input-label for="tags">{{ __('Tags') }}</x-input-label> <select id="tags" name="tags[]" multiple> @foreach($tags as $tag) <option value="{{ $tag->id }}" @selected($post-> tags->contains($tag))>{{ $tag->name }} </option> @endforeach </select> <br /> <x-primary-button>{{ __('Save') }}</x-primary-button> </form> <script> tinymce.init({. . .}); </script> </div> </div> </div> </div> </x-app-layout>
لاحظ في السطور من 24 إلى 26 أن لغة HTML لا تدعم التابع PUT
افتراضيًا، لذا نستخدم السمة method="POST"
ثم نخبر لارافيل باستخدام التابع PUT
من خلال التعليمة {{ method_field('PUT') }}
.
عرض فهرس المنشورات
لننشئ الآن العرض resources/views/posts/index.blade.php
كما يلي:
<x-app-layout> <x-slot name="header"> <div class="flex justify-between"> <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight" > {{ __('Posts') }} </h2> <a href="{{ route('posts.create') }}"> <x-primary-button>{{ __('New') }}</x-primary-button> </a> </div> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> @foreach($posts as $post) <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg mb-4 px-4 h-20 flex justify-between items-center" > <div class="text-gray-900 dark:text-gray-100"> <p>{{ $post->title }}</p> </div> <div class="space-x-2"> <a href="{{ route('posts.edit', ['post' => $post->id]) }}"> <x-primary-button>{{ __('Edit') }}</x-primary-button></a > <form method="post" action="{{ route('posts.destroy', ['post' => $post->id]) }}" class="inline" > {{ csrf_field() }} {{ method_field('DELETE') }} <x-danger-button> {{ __('Delete') }} </x-danger-button> </form> </div> </div> @endforeach </div> </div> </x-app-layout>
لاحظ أن زر الحذف ليس زرًا عاديًا، إذ يجب أن يكون استمارة مع تابع DELETE
، لأن الرابط العادي يمتلك التابع GET
فقط.
يُفترَض الآن أن تكون قادرًا على إنشاء بقية نظام العرض بسهولة.
لقطات الشاشة
وأخيرًا، إليك بعض لقطات الشاشة للوحة التحكم التي أنشأناها:
صفحة إدارة التصنيفات:
ننتقل لصفحة الإدارة كما يلي:
ثم نختار التصنيفات Categories من الصورة أعلاه لننتقل للصفحة التالية لإدارة التصنيفات:
صفحة إنشاء تصنيف جديد:
صفحة تحديث المنشور:
تابع معنا المقال التالي والأخير من هذه السلسلة حيث سننشئ فيه كل ما يخص الواجهة الأمامية من التطبيق كي يتمكن المستخدمون من رؤية منشورات المدونة والتفاعل معها.
ترجمة -وبتصرُّف- للمقال Laravel for Beginners #4 - Create a Dashboard لصاحبه Eric Hu.
اقرأ أيضًا
- المقال السابق: لارافيل للمبتدئين-الجزء الثالث: استخدام عمليات CRUD لإنشاء مدونة بسيطة
- تجريد إعداد قواعد البيانات في لارافيل باستعمال عملية التهجير Migration والبذر Seeder
- كيف تستخدم منشئ الاستعلامات Query builder للتخاطب مع قاعدة البيانات في Laravel
- إنشاء استمارة اتصال في Laravel باستخدام ميزة Form Request
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.