وحدات التحكّم في Laravel

من موسوعة حسوب

مقدمة

بدلًا من تعريف كامل منطق معالجة طلباتك (request handling logic) على أنّها نطاقات مغلقة (‎(Closures في ملفّات المسارات، قد ترغب في تنظيم هذا السلوك (behavior) باستخدام أصناف ووحدات التحكّم. تستطيع وحدات التحكّم جمع منطق معالجة الطلبات ذي الصلة في صنف واحد. تخزّن وحدات التحكّم في المجلّد app/Http/Controllers.

وحدات التحكّم الأساسية

تعريف وحدات التحكّم

تجد أدناه مثالًا عن وحدة تحكّم بسيطة. لاحظ أن وحدة التحكّم تُوسّع صنف وحدة التحكّم الأساسية المُحتواة في Laravel. يوفّر الصنف الأساسي عددًا من التوابع المفيدة مثل التابع middleware الذي يُستخدم لإرفاق برمجيّة وسيطة بأفعال وحدة التحكّم:

<?php

namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * المستخدم المعطى profile عرض
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

يمكنك تعريف مسار لهذه الوحدة كما يلي:

Route::get('user/{id}', 'UserController@show');

والآن عندما يطابق طلب ما رابط URI المسار المُحدّد، سيُنفّذ التابع show على الصنف UserController. ستُمرّر طبعا معاملات المسار لهذا التابع.

ملاحظة: توسيع وحدات التحكّم لصنف أساسي ليس اجباريًّا. لكن في الحالة العكس، لن تتوفر لك القدرة على استخدام خاصيّات مفيدة مثل التوابع middleware، و validate، و dispatch.

وحدات التحكّم ومجالات الأسماء (Controllers & Namespaces)

من المهم جدًّا ملاحظة أننا لم نحتاج لتحديد مجال اسم وحدة التحكّم (controller namespace) الكامل عندما عرّفنا مسار وحدة التحكّم. بما أن RouteServiceProvider يُحمّل ملفّات مساراتك داخل مجموعة مسارات تحتوي على مجال الاسم، لم نحدّد إلا جزء اسم الصنف الذي يأتي بعد جزء App\Http\Controllers من مجال الاسم.

إن اخترت أن تضع وحدة تحكّمك في موضع أعمق من المُجلّد App\Http\Controllers،استخدم اسم الصنف المخصّص لمجال الاسم الجذر App\Http\Controllers. أي إن كان صنف وحدة تحكّمك كاملا هو App\Http\Controllers\Photos\AdminController، عليك أن تُسجّل المسارات المؤدّية لوحدة التحكّم كما يلي:

Route::get('foo', 'Photos\AdminController@method');

وحدات التحكّم ذات الفعل الواحد (Single Action Controllers)

في حال رغبت في تعريف وحدة تحكّم تعالج فعلًا واحدًا، تستطيع وضع تابع invoke__ وحيد عليها:

<?php

namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class ShowProfile extends Controller
{
    /**
     * المستخدم المعطى profile عرض     *
     * @param  int  $id
     * @return Response
     */
    public function __invoke($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

لا تحتاج لتحديد تابع عند تسجيل المسارات لوحدات التحكّم ذات الفعل الواحد:

Route::get('user/{id}', 'ShowProfile');

تستطيع توليد وحدة تحكّم قابلة للاستدعاء (invokable) باستخدام الخيار invokable-- من أمر Artisan  make:controller:

php artisan make:controller ShowProfile --invokable

برمجيّة وحدة تحكّم الوسيطة

يمكن تعيين برمجيّة وسيطة على مسارات وحدة التحكّم في ملف مساراتك:

Route::get('profile', 'UserController@show')->middleware('auth');

بالرغم من ذلك، يظل من المناسب أكثر تحديد البرمجيّات الوسيطة داخل التابع الباني (constructor). تستطيع تعيين البرمجيّات الوسيطة على أفعال وحدة التحكّم بسهولة باستخدام التابع middleware من تابع وحدة التحكّم الباني (constructor). يمكنك حتى حصر البرمجيّة الوسيطة لتوابع معيّنة من صنف وحدة التحكّم:

class UserController extends Controller
{
    /**
     * إنشاء نسخة وحدة تحكّم جديدة
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');

        $this->middleware('log')->only('index');

        $this->middleware('subscribed')->except('store');
    }
}

تسمح لك وحدات التحكّم أيضًا بتسجيل البرمجيّات الوسيطة باستخدام نطاق مغلق (Closure) طريقة ملائمة لتعريف برمجيّة وسيطة لوحدة تحكّم واحدة دون تعريف صنف برمجيّة وسيطة كامل:

$this->middleware(function ($request, $next) {
   // ...

    return $next($request);
});

ملاحظة: يمكنك تعيين برمجيّة وسيطة على أجزاء من أفعال لوحدة التحكّم لكن قد ينجرّ عن ذلك تضخّم لها. خذ تقسيم وحدة تحكّمك لعدّة أجزاء أصغر بالحسبان.

وحدات تحكّم الموارد

يعيّن توجيه (routing) الموارد في Laravel المسارات "CRUD" المعتادة على وحدة التحكّم بسطر برمجي واحد. على سبيل المثال، قد ترغب بإنشاء وحدة تحكّم تعالج كل الطلبات HTTP للصور "photos" المخزونة بتطبيقك. نستطيع إنشاء وحدة تحكّم من هذا النوع بسرعة باستخدام أمر  make:controller Artisan:

php artisan make:controller PhotoController --resource

يُولّد هذا الأمر وحدة تحكّم في app/Http/Controllers/PhotoController.php. ستحتوي وحدة التحكّم على تابع لكل عمليّة (operation) موارد متوفّرة. قد تريد في الخطوة التالية تسجيل مسار كثير الموارد (resourceful) لوحدة التحكّم:

Route::resource('photos', 'PhotoController');

يُنشئ تصريح المسار الوحيد هذا عدّة مسارات لمعالجة أفعال متنوّعة على المورد. ستحتوي وحدة التحكّم المُولّدة على بذار (stubs) توابع لكل فعل من هذه الأفعال مع ملاحظات تعلمك بطرق HTTP (بمعنى HTTP verbs) والروابط ‎(URIs) التي يعالجونها. قد تُسجّل عدّة وحدات تحكّم موارد مرّة واحدة بتمرير مصفوفة للتابع resources:

Route::resources([
    'photos' => 'PhotoController',
    'posts' => 'PostController'
]);

الطرق التي تعالجها وحدة تحكّم الموارد

اسم المسار الفعل العنوان URI الطريقة
photos.index index /photos GET
photos.create create /photos/create GET
photos.store store /photos POST
photos.show show {‎/photos/{photo GET
photos.edit edit /photos/{photo}/edit GET
photos.update update {‎/photos/{photo PUT/PATCH
photos.destroy destroy {‎/photos/{photo DELETE

تحديد نموذج المورد

إذا كنت تستخدم ارتباط نموذج المسارات وترغب في جعل توابع وحدة التحكّم تُلمّح على نسخة من النموذج، تستطيع استخدام الخيار model-- عند توليد وحدة التحكّم:

php artisan make:controller PhotoController --resource --model=Photo

انتحال توابع الاستمارات (Spoofing Form Methods)

نظرًا لأن استمارات HTML لا تقدر على تقديم طلبات PUT أو PATCH أو DELETE، ستحتاج إلى إضافة حقل method_ مخفي لانتحال طرق HTTP. يمكن للتوجيه method Blade@ إنشاء هذا الحقل لك:

<form action="/foo/bar" method="POST">
    @method('PUT')
</form>

مسارات الموارد الجزئية

يمكنك تحديد مجموعة فرعية من الأفعال (actions) التي يجب أن تُعالجها وحدة التحكّم بدلاً من معالجة المجموعة الكاملة من الأفعال الافتراضية عند التصريح بمسار مورد:

Route::resource('photos', 'PhotoController')->only([
    'index', 'show'
]);

Route::resource('photos', 'PhotoController')->except([
    'create', 'store', 'update', 'destroy'
]);

مسارات موارد الواجهة البرمجيّة

نحتاج عادة عند التصريح بمسارات الموارد التي ستستهلكها الواجهات البرمجيّة إلى  المسارات التي تقدم قوالب HTML مثل create و edit. لتسهيل العمليّة ، يمكنك استخدام التابع apiResource لاستبعاد هذين المسارين تلقائيًا:

Route::apiResource('photos', 'PhotoController');

يمكنك تسجيل العديد من وحدات تحكّم موارد الواجهة البرمجيّة  مرّة واحدة من خلال تمرير مصفوفة للتابع apiResources:

Route::apiResources([
    'photos' => 'PhotoController',
    'posts' => 'PostController'
]);

لإنشاء وحدة تحكّم مورد واجهة برمجيّة بسرعة ولا تتضمن التابعين create أو edit، استخدم المفتاح api-- (بمعنى switch) عند تنفيذ الأمر make:controller:

php artisan make:controller API/PhotoController --api

تسمية مسارات الموارد

لكل أفعال وحدة تحكّم الموارد اسم مسار افتراضيًّا. ولكن يمكنك إعادة تعريف (override) هذه الأسماء بتمرير مصفوفة names  تحتوي اختياراتك:

Route::resource('photos', 'PhotoController')->names([
    'create' => 'photos.build'
]);

تسمية معاملات مسارات الموارد

سينشئ Route::resource معاملات (parameters) المسار لمسارات مواردك استنادًا إلى الإصدار (version) "المُفرَّد" (singularized) من اسم المورد. يمكنك إعادة تعريف هذا بسهولة لكل مورد على انفراد باستخدام التابع parameters. يجب أن تكون المصفوفة المُمرّرة للتابع parameters مصفوفةً ترابطيّةً (associative array) لأسماء المصادر وأسماء المعاملات:

Route::resource('users', 'AdminUserController')->parameters([
    'users' => 'admin_user'
])

ينشئ المثال أعلاه عناوين روابط URI التالية لمسار المورد show:

/users/{admin_user}

توطين روابط URIs الموارد

سيُنشئ Route::resource افتراضيًّا روابط URIs للموارد باستخدام أفعال (verbs) انجليزية. إذا احتجت لترجمة أفعال الإجراء  create و edit، يمكنك استخدام التابع Route::resourceVerbs. يمكن القيام بذلك في التابع boot من AppServiceProvider:

use Illuminate\Support\Facades\Route;

/**
 * مهّد أي خدمة من التطبيق (Bootstrap)
 *
 * @return void
 */
public function boot()
{
    Route::resourceVerbs([
        'create' => 'crear',
        'edit' => 'editar',
    ]);
}

بمجرد أن تُخصّص الأفعال، سيُنتج تسجيل مسار مورد مثل Route::resource('fotos', 'PhotoController')‎ العناوين URI التالية:

/fotos/crear

/fotos/{foto}/editar

تكملة وحدات تحكّم الموارد

إذا كنت بحاجة لإضافة مسارات لوحدة تحكّم مورد تتعدى مجموعة مسارات الموارد الافتراضية، عليك تعريف هذه المسارات قبل مناداتك Route::resource؛ وإلا قد تملك المسارات المُعرّفة من طرف تابع الموارد الأولويّة على المسارات التكميليّة من غير قصد:

Route::get('photos/popular', 'PhotoController@method');

Route::resource('photos', 'PhotoController');

ملاحظة: تذكّر أن تُبقي وحدات تحكّمك مُركزّة. إن وجدت نفسك في حاجة لتوابع من خارج مجموعة أفعال الموارد الاعتيادية بشكل روتيني، ضع تقسيم وحدة تحكّمك لوحدتين أصغر بالحسبان.

إضافة الاعتماديّات ووحدات التحكّم (Dependency Injection & Controllers)

إضافة التابع الباني Constructor

يُستخدم حاوي خدمات Laravel لاستبيان كافة وحدات تحكّم Laravel. كنتيجة، يمكنك كتابة أية اعتماديّات قد تحتاجها وحدة تحكّمك في تابعها الباني. ستُستبين الاعتماديّات المُصرّح عنها تلقائيًّا ثم تضاف لنسخة وحدة التحكّم:

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * نسخة مستودع المُستخدم
     */
    protected $users;

    /**
     * إنشاء نسخة وحدة تحكّم جديدة
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }
}

يمكنك طبعا التلميح إلى نوع (type-hint) أي عقد Laravel إذا أمكن للحاوي استبيانه (resolve) .قد تُوفّر إضافة اعتماديّاتك لوحدة تحكّمك قدرة اختبار أفضل.

إضافة التوابع

إضافة للتابع الباني (constructor)، تستطيع التلميح أايضا على نوع الإعتماديّات في توابع وحدة تحكّمك. إضافة النسخة Illuminate\Http\Request لتوابع وحدة تحكّمك هي حالة استخدام شائعة لإضافة التوابع:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * خزّن مستخدمًا جديدًا
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        $name = $request->name;

       //
    }
}

إن اشترط تابع وحدة تحكّمك أيضًا إدخالًا من معاملة مسار، أورد متغيّرات مسارك الوسيطة بعد اعتماديّاتك الأخرى. على سبيل المثال، إن عُرّف مسارك بهذا الشكل:

Route::put('user/{id}', 'UserController@update');

تظل قادرًا على التلميح إلى نوع Illuminate\Http\Request وعلى الوصول لمعاملتك id عبر تعريف وحدة تحكّمك كما يلي:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * تحديث المُستخدم المُحدّد
     *
     * @param  Request  $request
     * @param  string  $id
     * @return Response
     */
    public function update(Request $request, $id)
    {
       //
    }
}

ملاحظة: لا يمكن تخزين المسارات المُستندة على نطاق مغلق (Closure) مؤقتًا. لاستخدام تخزين المسار المؤقّت، عليك تحويل أي مسارات نطاق مغلق لأصناف وحدات تحكّم. إن استخدم تطبيقك المسارات المعتمدة على وحدات التحكّم حصرًا، عليك الاستفادة من ذاكرة تخزين المسار المؤقتة في Laravel. يُقلّص استخدام ذاكرة تخزين المسار المؤقتة الوقت اللازم لتسجيل جميع مسارات التطبيق بشكل جذري. قد ترتفع سرعة تسجيل مساراتك أكثر من 100 مرة  في بعض الحالات. لتوليد ذاكرة تخزين مؤقتة للمسار، ما عليك إلا تنفيذ أمر Artisan route:cache:

php artisan route:cache

سيُحمّل ملف مساراتك المُخزّنة بعد تنفيذ هذا الأمر مع كل طلب. تذكّر إن أضفت أي مسارات جديدة ستحتاج إلى إنشاء ذاكرة تخزين مسارات مؤقتة جديدة. لهذا السبب، يجب فقط تنفيذ الأمر route:cache عند نشر (deployment) مشروعك. يُمكنك استخدام الأمر route:clear لتفريغ ذاكرة تخزين المسار المؤقّتة:

php artisan route:clear

مصادر