اذهب إلى المحتوى

لارافيل للمبتدئين-الجزء الثالث: استخدام عمليات CRUD لإنشاء مدونة بسيطة


Ola Abbas

سنستخدم في هذا المقال كل ما تعلّمناه في المقالين السابقين من سلسلة مقالات لارافيل للمبتدئين لإنشاء مشروع حقيقي، حيث سننشئ مدونة صغيرة تحتوي على منشورات فقط بدون فئات Categories أو وسوم Tags، وكل منشور له عنوان ومحتوى، إذ لن ننشئ تطبيق مدونة كامل المواصفات، وسنوضّح كيفية استرداد البيانات من قاعدة البيانات، وكيفية إنشاء أو تحديث معلومات جديدة، وكيفية حفظها في قاعدة البيانات، وكيفية حذفها، حيث يمثّل الاختصار CRUD عمليات الإنشاء ‎Create والقراءة ‎Read والتحديث ‎Update والحذف ‎Delete.

تصميم بنية قاعدة البيانات

سننشئ أولًا ملف تهجير Migration للجدول post، ويجب أن يحتوي هذا الجدول على العنوان والمحتوى، ويمكنك توليد ملف تهجير باستخدام الأمر التالي:

php artisan make:migration create_posts_table

وسيتضمن الملف ما يلي:

<?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->text('content');
       });
   }

   /**
    * عكس عمليات التهجير
    */
   public function down(): void
   {
       Schema::dropIfExists('posts');
   }
};

أنشأنا في هذا المثال خمسة أعمدة في الجدول post باستدعاء 5 توابع وهي:

  • ينشئ التابع id()‎ عمود المعرّف id الذي يُستخدَم عادة للفهرسة.
  • ينشئ التابع timestamps()‎ عمودين هما: created_at و uptated_at، وسيُحدَّث هذان العمودان تلقائيًا عند إنشاء السجل وتحديثه.
  • ينشئ التابع string('title')‎ العمود title الذي نوعه VARCHAR ويبلغ طوله الافتراضي 255 بايتًا.
  • ينشئ التابع string('content')‎ عمود المحتوى content.

يمكنك تطبيق التغييرات من خلال تشغيل الأمر التالي:

php artisan migrate

ويجب أن ينشأ جدول posts جديد كما يلي:

01 database

يمكننا الآن إنشاء النموذج Model المقابل لهذا الجدول باستخدام الأمر التالي:

php artisan make:model Post

وسيتضمّن هذا النموذج app/Models/Post.php ما يلي:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
   use HasFactory;
}

لننشئ الآن متحكم الموارد Resource Controller المقابل باستخدام الأمر التالي:

php artisan make:controller PostController --resource

وأخيرًا، سجّل هذا المتحكم في الموجِّه Router كما يلي:

use App\Http\Controllers\PostController;

Route::resource('posts', PostController::class);

يمكنك التحقق من الوِجهات Routes المُسجَّلة من خلال تشغيل الأمر التالي:

php artisan route:list

وسيظهر الخرج التالي:

GET|HEAD        posts ................................................................. posts.index › PostController@index
POST            posts ................................................................. posts.store › PostController@store
GET|HEAD        posts/create ........................................................ posts.create › PostController@create
GET|HEAD        posts/{post} ............................................................ posts.show › PostController@show
PUT|PATCH       posts/{post} ........................................................ posts.update › PostController@update
DELETE          posts/{post} ...................................................... posts.destroy › PostController@destroy
GET|HEAD        posts/{post}/edit ....................................................... posts.edit › PostController@edit

يحتوي الخرج السابق على معلومات مثل توابع الطلبات Request Methods وتوابع المتحكّمات Controller Methods بالإضافة إلى أسماء الوِجهات، وتُعَد هذه المعلومات مهمة جدًا وسنحتاج إليها لاحقًا في هذا المقال.

عمليات CRUD

حان الوقت الآن لنتعمّق في التطبيق نفسه، فمن غير المحتمل أن ننشئ جميع المتحكمات أولًا ثم نصمّم القوالب ثم ننتقل إلى الموجّهات عند إنشاء تطبيقات واقعية، إذ يجب أن نفكّر من وجهة نظر المستخدم وفي الإجراءات التي قد يرغب المستخدم في اتخاذها. يجب أن يكون لدى المستخدم القدرة على إجراء أربع عمليات لكل مورد، والذي هو Post في حالتنا، وهذه العمليات هي:

  • الإنشاء Create: يجب أن يكون المستخدم قادرًا على إنشاء موارد جديدة وحفظها في قاعدة البيانات.
  • القراءة Read: يجب أن يكون المستخدم قادرًا على قراءة الموارد سواءً من خلال استرداد قائمة الموارد أو التحقق من تفاصيل مورد محدّد.
  • التحديث Update: يجب أن يكون المستخدم قادرًا على تحديث الموارد الموجودة وتحديث سجل قاعدة البيانات المقابلة لها.
  • الحذف Delete: يجب أن يكون المستخدم قادرًا على إزالة مورد من قاعدة البيانات.

ويشار إلى هذه العمليات مع بعضها البعض باسم عمليات CRUD.

إجراء الإنشاء Create

لا تزال قاعدة بياناتنا فارغة حاليًا، لذا قد يرغب المستخدم في إنشاء منشورات جديدة، فلنبدأ بإجراء الإنشاء (C)، حيث يمكن إكمال هذا الإجراء من خلال استخدام التابعين التاليين:

  • تابع المتحكم create()‎ الذي يعرض استمارة، مما يسمح للمستخدم بملء العنوان والمحتوى.
  • تابع المتحكم store()‎ الذي يحفظ المنشور الذي أنشأناه حديثًا في قاعدة البيانات، ويعيد توجيه المستخدم إلى صفحة القائمة.

يطابق التابع create()‎ نمط URL الذي هو ‎/posts/create (التابع GET)، ويطابق التابع store()‎ نمط URL الذي هو ‎/post (التابع POST).

إليك مراجعةً مختصرة لتوابع HTTP في حال احتياجك إلى تجديد بعض المعلومات:

  • تابع GET هو تابع طلبات HTTP الأكثر استخدامًا، ويُستخدَم لطلب البيانات والموارد من الخادم.
  • يُستخدَم تابع POST لإرسال البيانات إلى الخادم، ويُستخدَم لإنشاء أو تحديث المورد.
  • يعمل تابع HEAD مثل تابع GET تمامًا، باستثناء أن استجابة HTTP ستحتوي على الترويسة فقط بدون الجسم، ويستخدم المطورون هذا التابع لأغراض تنقيح الأخطاء Debugging.
  • يُعَد تابع PUT مشابهًا لتابع POST، مع اختلاف واحد بسيط، حيث إذا أرسلتَ باستخدام التابع POST موردًا موجودًا مسبقًا على الخادم، فلن يسبّب هذا الإجراء أيّ فرق، ولكن سيكرّر تابع PUT هذا المورد في كل مرة نقدّم فيها الطلب.
  • يزيل تابع DELETE موردًا من الخادم.

لنبدأ بالتابع create()‎، حيث سيتضمّن ملف المتحكم app/Http/Controllers/PostController.php ما يلي:

<?php

namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Contracts\View\View;
. . .

class PostController extends Controller
{
   . . .

   /**
    *  عرض الاستمارة الخاصة بإنشاء مورد جديد
    */
   public function create(): View
   {
       return view('posts.create');
   }

   . . .
}

سيُنفّذ هذا التابع عندما ترسل طلب GET إلى ‎/posts/create، وسيؤشّر إلى العرض views/posts/create.blade.php. لاحظ تغيير الاستجابة Response إلى عرض View في السطر 16، لأن هذه التوابع يجب أن تعيد عرضًا.

يجب بعد ذلك أن ننشئ العرض المقابل، حيث سنبدأ بعرض تخطيط الصفحة layout، إذ سيتضمّن الملف views/layout.blade.php ما يلي:

<!doctype html>
<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <script src="https://cdn.tailwindcss.com"></script>
   @yield('title')
 </head>

 <body class="container mx-auto font-serif">
   <div class="bg-white text-black font-serif">
     <div id="nav">
       <nav class="flex flex-row justify-between h-16 items-center shadow-md">
         <div class="px-5 text-2xl">
           <a href="{{ route('posts.index') }}"> My Blog </a>
         </div>
         <div class="hidden lg:flex content-between space-x-10 px-10 text-lg">
           <a
             href="{{ route('posts.create') }}"
             class="hover:underline hover:underline-offset-1"
             >New Post</a
           >
           <a
             href="https://github.com/ericnanhu"
             class="hover:underline hover:underline-offset-1"
             >GitHub</a
           >
         </div>
       </nav>
     </div>

     @yield('content')

     <footer class="bg-gray-700 text-white">
       <div
         class="flex justify-center items-center sm:justify-between flex-wrap lg:max-w-screen-2xl mx-auto px-4 sm:px-8 py-10"
       >
         <p class="font-serif text-center mb-3 sm:mb-0">
           Copyright ©
           <a href="https://www.ericsdevblog.com/" class="hover:underline"
             >Eric Hu</a
           >
         </p>

         <div class="flex justify-center space-x-4">. . .</div>
       </div>
     </footer>
   </div>
 </body>
</html>

لاحظ في السطرين 16 و 20 وجود الأقواس المزدوجة المتعرجة {{ }} التي تسمح بتنفيذ شيفرة PHP البرمجية ضمن القالب، وبالتالي سيعيد التابع route('posts.index')‎ الوِجهة التي اسمها posts.index، حيث يمكنك التحقق من أسماء الوِجهات من خلال الرجوع إلى خرج الأمر php artisan route:list.

لننتقل بعد ذلك إلى العرض create، حيث يجب أن تكون منظمًا في هذا العرض. يُعَد العرض create مُخصّصًا للإجراءات المتعلقة بالمنشور، لذا أنشأنا المجلد post لتخزين هذا العرض. سيتضمن العرض views/posts/create.blade.php ما يلي:

@extends('layout') @section('title')
<title>Create</title>
@endsection @section('content')
<div class="w-96 mx-auto my-8">
 <h2 class="text-2xl font-semibold underline mb-4">Create new post</h2>
 <form action="{{ route('posts.store') }}" method="POST">
   {{ csrf_field() }}
   <label for="title">Title:</label><br />
   <input
     type="text"
     id="title"
     name="title"
     class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"
   /><br />
   <br />
   <label for="content">Content:</label><br />
   <textarea
     type="text"
     id="content"
     name="content"
     rows="15"
     class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"
   ></textarea
   ><br />
   <br />
   <button
     type="submit"
     class="font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center"
   >
     Submit
   </button>
 </form>
</div>
@endsection

توجد بعض الأشياء التي يجب ملاحظتها عند استخدام الاستمارات لنقل البيانات، وهي:

  • السطر 6: تحدِّد سمة Attribute الإجراء action ما يحدث عند إرسال هذه الاستمارة، وتوجّه المتصفح في هذه الحالة لزيارة الوِجهة posts.store باستخدام تابع HTTP الذي هو POST.
  • السطر 7 ‎{{ csrf_field() }}‎: يُعَد CSRF هجومًا ضارًا يستهدف تطبيقات الويب، وتوفر الدالة csrf_field()‎ الحماية من هذا النوع من الهجمات. اطلع على مقال تعرف على أمان مواقع الويب لمعرفة المزيد عن هجمات تزوير الطلبات عبر المواقع Cross-Site Request Forgery -أو CSRF اختصارًا.
  • السمة name في السطر 12 و 20: يُربَط ما يدخله المستخدم بمتغير عند إرسال الاستمارة، وتحدّد السمة name اسم هذا المتغير، فمثلًا إذا كان name="title"‎، فسيُربَط دخل المستخدم بالمتغير title، ويمكن الوصول إلى قيمته باستخدام التعليمة ‎$request->input('title')‎، حيث سنرى كيفية عملها لاحقًا.
  • السطر 27: يجب ضبط السمة type على القيمة submit حتى تعمل هذه الاستمارة.

شغّل الآن خادم التطوير وانتقل إلى العنوان http://127.0.0.1:8000/posts/create حيث ستظهر الصفحة التالية:

02 the create page

إذا نقرتَ على زر الإرسال "Submit"، فسيرسل المتصفح طلب POST إلى الخادم، وسيُنفَّذ التابع store()‎ هذه المرة، وسيحتوي طلب POST على دخل المستخدم، ويمكن الوصول إليه كما يلي في الملف PostController.php:

<?php
namespace App\Http\Controllers;
. . .
class PostController extends Controller
{
   . . .

   /**
    * تخزين مورد أنشأناه حديثًا في وحدة التخزين
    */
   public function store(Request $request): RedirectResponse
   {
       // الحصول على البيانات من الطلب
       $title = $request->input('title');
       $content = $request->input('content');

       // إنشاء نسخة جديدة من‫ المنشور Post ووضع البيانات المطلوبة في العمود المقابل
       $post = new Post;
       $post->title = $title;
       $post->content = $content;

       // حفظ البيانات
       $post->save();

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

   . . .
}

سيُعاد توجيهك إلى الوِجهة posts.index بعد تخزين البيانات. عُد إلى متصفحك واكتب عنوانًا ومحتوًى جديدًا، ثم انقر على زر الإرسال. لم ننشئ العرض index بعد، لذا ستُعاد رسالة خطأ، ولكن إذا تحققتَ من قاعدة البيانات، فيجب وجود سجل جديد مضاف كما يلي:

03_new-record.png

إجراء القائمة List

لننتقل الآن إلى عملية القراءة Read، حيث يوجد نوعان مختلفان من عمليات القراءة هما: الأول هو إجراء القائمة الذي يعيد قائمةً بجميع المنشورات إلى المستخدم، والإجراء الثاني سنوضّحه في الفقرة التالية. يقابل إجراء القائمة التابعَ index()‎ في الملف PostController.php كما يلي:

<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class PostController extends Controller
{
   /**
    * عرض قائمة الموارد
    */
   public function index(): View
   {
       $posts = Post::all();

       return view('posts.index', [
           'posts' => $posts,
       ]);
   }

   . . .
}

ويكون عرض index المقابل هو views/post/index.blade.php كما يلي:

@extends('layout') @section('title')
<title>Page Title</title>
@endsection @section('content')
<div class="max-w-screen-lg mx-auto my-8">
 @foreach($posts as $post)
 <h2 class="text-2xl font-semibold underline mb-2">
   <a href="{{ route('posts.show', ['post' => $post->id]) }}"
     >{{ $post->title }}</a
   >
 </h2>
 <p class="mb-4">{{ Illuminate\Support\Str::words($post->content, 100) }}</p>
 @endforeach
</div>
@endsection

تتكرر حلقة foreach في السطر 5 على جميع المنشورات ‎$posts المُسترَدة، ثم تسنِد كل قيمة إلى المتغير ‎$post. لاحظ في السطر7 كيفية تمرير معرّف id المنشور إلى الوِجهة posts.show، ثم ستمرِّر هذه الوِجهة هذا المتغير إلى تابع المتحكم show()‎ الذي سنراه لاحقًا. يُعَد التابع Str::words()‎ في السطر 11 دالةً مساعدة في لغة PHP، وسيأخذ أول 100 كلمة من المحتوى content فقط.

إجراء العرض Show

عملية القراءة الثانية هي إجراء العرض الذي يعرض تفاصيل مورد معين، ويتحقّق هذا الإجراء باستخدام التابع show()‎ في الملف PostController.php كما يلي:

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class PostController extends Controller
{
   . . .

   /**
    * عرض المورد المُحدَّد
    */
   public function show(string $id): View
   {
       $post = Post::all()->find($id);

       return view('posts.show', [
           'post' => $post,
       ]);
   }

   . . .
}

ويكون عرض show المقابل هو views/post/show.blade.php كما يلي:

@extends('layout') @section('title')
<title>{{ $post->title }}</title>
@endsection @section('content')
<div class="max-w-screen-lg mx-auto my-8">
 <h2 class="text-2xl font-semibold underline mb-2">{{ $post->title }}</h2>
 <p class="mb-4">{{ $post->content }}</p>

 <div class="grid grid-cols-2 gap-x-2">
   <a
     href="{{ route('posts.edit', ['post' => $post->id]) }}"
     class="font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center"
     >Update</a
   >
   <form
     action="{{ route('posts.destroy', ['post' => $post->id]) }}"
     method="POST"
   >
     {{ csrf_field() }} {{ method_field('DELETE') }}
     <button
       type="submit"
       class="font-sans text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center"
     >
       Delete
     </button>
   </form>
 </div>
</div>
@endsection

هناك بعض الأشياء التي يجب أن نلاحظها في المثال السابق. أولًا، لاحظ كيف أن زر التحديث "Update" هو رابط بسيط، ولكن زر الحذف "Delete" هو استمارة، لأن الرابط سيخبر المتصفح بإرسال طلب GET إلى الخادم، بينما نحتاج شيئًا آخر لإجراء الحذف، حيث إذا نظرتَ داخل الاستمارة، فستجد أنها تحتوي على السمة method="POST"‎، ولكننا نحتاج التابع DELETE لإجراء الحذف. تدعم لغة HTML التابعين GET و POST افتراضيًا، وإذا كنت بحاجة شيء آخر، فيجب أن تضبط السمة method="POST"‎، وأن تستخدم التابع method_field()‎ بدلًا من ذلك.

إجراء التحديث Update

تستخدم عملية التحديث التابع edit()‎ الذي يعرض استمارة HTML، والتابع update()‎ الذي يجري تغييرات على قاعدة البيانات، لذا ضع هذين التابعين في الملف PostController.php كما يلي:

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class PostController extends Controller
{
   . . .

   /**
    * عرض الاستمارة الخاصة بتعديل المورد المُحدَّد
    */
   public function edit(string $id): View
   {
       $post = Post::all()->find($id);

       return view('posts.edit', [
           'post' => $post,
       ]);
   }

   /**
    * تحديث المورد المحدَّد في وحدة التخزين
    */
   public function update(Request $request, string $id): RedirectResponse
   {
       // الحصول على البيانات من الطلب
       $title = $request->input('title');
       $content = $request->input('content');

       // البحث عن المنشور المطلوب ووضع البيانات المطلوبة في العمود المقابل
       $post = Post::all()->find($id);
       $post->title = $title;
       $post->content = $content;

       // حفظ البيانات
       $post->save();

       return redirect()->route('posts.show', ['post' => $id]);
   }

   . . .
}

ويكون عرض edit المقابل هو views/post/edit.blade.php كما يلي:

@extends('layout') @section('title')
<title>Edit</title>
@endsection @section('content')
<div class="w-96 mx-auto my-8">
 <h2 class="text-2xl font-semibold underline mb-4">Edit post</h2>
 <form
   action="{{ route('posts.update', ['post' => $post->id]) }}"
   method="POST"
 >
   {{ csrf_field() }} {{ method_field('PUT') }}
   <label for="title">Title:</label><br />
   <input
     type="text"
     id="title"
     name="title"
     value="{{ $post->title }}"
     class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"
   /><br />
   <br />
   <label for="content">Content:</label><br />
   <textarea
     type="text"
     id="content"
     name="content"
     rows="15"
     class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"
   >
{{ $post->content }}</textarea
   ><br />
   <br />
   <button
     type="submit"
     class="font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center"
   >
     Submit
   </button>
 </form>
</div>
@endsection

إجراء الحذف Delete

وأخيرًا، يوجد التابع destroy()‎ للحذف، إذًا لنضعه في الملف PostController.php كما يلي:

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class PostController extends Controller
{
   . . .

   /**
    * إزالة المورد المُحدَّد من وحدة التخزين
    */
   public function destroy(string $id): RedirectResponse
   {
       $post = Post::all()->find($id);

       $post->delete();

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

لا يتطلب هذا الإجراء عرضًا، بما أنه يعيد توجيهك إلى posts.index بعد اكتمال الإجراء.

ترجمة -وبتصرُّف- للمقال Laravel for Beginners #3 - The CRUD Operations لصاحبه Eric Hu.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...