اكتشف إطار PHP Laravel


الدرس: الأحداث والتراخيص


الصفحة السابقة
لإنهاء هذه الدورة ، سوف أستحضر في هذا الفصل الأخيرالبعض من النقاط التي لم أتطرق إليها بعد والتي يمكن أن تشكل بدائل مثيرة للاهتمام مقارنة بالتشفير الذي رأيناه.
سنراجع الأحداث (events) والتراخيص (authorization).
لاستحضار نقاط هذا الفصل أقترح عليك تثبيت  تطبيق المثال الذي أودعته على github  . يمنحك الفرصة لرؤية حالة واقعية مقارنة بالتطبيقات الجزئية التي استخدمناها في هذه الدورة التدريبية.
استكشف التطبيق قليلاً قبل متابعة قراءة هذا الفصل.

الأحداث


ينشئ نمط تصميم Observer علاقة نوع n : 1 بين الكائنات. إذا تغير كائن الجانب 1 ، فسيبلغ الكائنات الجانبية n حتى يتمكن من التصرف وفقًا لذلك. إنه يشتمل على الإشراف ، ويجب أن تكون الكائنات قادرة على التسجيل والاستماع إلى أحداث الموضوع.
هنا تصور لنمط التصميم هذا:
framework Laravel MVC
نمط تصميم المراقب
لدينا موضوع والمراقبون. يجب أن يسجل المراقبون (نقول أيضًا الاشتراك) مع الموضوع. عندما يتغير الموضوع ، فإنه يقع اعلام جميع المشتركين فيه. يمكن للمراقب أيضًا إلغاء الاشتراك ، ولن يتلقى إعلامات بعد ذلك.
تنفذ Laravel نمط التصميم هذا وتجعله سهل الاستخدام كما سنرى.
أحداث الإطار
يمكننا استخدام الأحداث الموجودة بالفعل في الإطار الذي يقدمها من حيث المصادقة:
  • محاولة اتصال (login) ،
  • تسجيل الدخول (login) بنجاح ،
  • تسجيل الخروج  (logout) بنجاح.
هناك أيضًا أحداث مع Eloquent :
  • Creating ,
  • Created ,
  • Updating ,
  • Updated ,
  • Saving ,
  • Saved ,
  • Deleting ,
  • Deleted ,
  • restoring, restored .
مُزود الخدمات
يوجد مزود خدمة مخصص للأحداث:
framework Laravel MVC
مزود الخدمة للأحداث
مع هذا الكود:

<?php
namespace App\Providers;

use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider {

    /**
     * The event handler mappings for the application.
     *
     * @var  array
     */
    protected $listen = [
        'Illuminate\Auth\Events\Login' => ['App\Listeners\LoginSuccess'],
        'Illuminate\Auth\Events\Logout' => ['App\Listeners\LogoutSuccess'],
        'App\Event\UserAccess' => ['App\Listeners\UserAccess']
    ];

    /**
     * Register any other events for your application.
     *
     * @param  \Illuminate\Contracts\Events\Dispatcher $events
     * @return  void
     */
    public function boot(DispatcherContract $events)
    {
        parent::boot($events);
    }

}
لدينا خاصية listen  للاشتراكات. إنها صفيف بسيط يأخذ الحدث (event) كمفتاح والمراقب (listener) كقيمة.
في تطبيق المثال ، نريد أن نعرف متى يقوم شخص ما بالاتصال أو قطع الاتصال. لذلك نحن نوفر مراقبين في المزود:

<?php
protected $listen = [
    'Illuminate\Auth\Events\Login' => ['App\Listeners\LoginSuccess'],
    'Illuminate\Auth\Events\Logout' => ['App\Listeners\LogoutSuccess'],
    ...
];
يمكنك العثور على هذه الأحداث في الوثائق .
Laravel يضع المستمعين في هذا المجلد كصفوف:
framework Laravel MVC
ملف المراقب
لتبسيط الإدارة ، يتم حفظ حالة المستخدم (دوره) في الجلسة. لدينا 3 أدوار:
  • المسؤول (admin): مع جميع الحقوق ،
  • كاتب (redac): مع الحق في إدارة المقالات ،
  • المستخدم (user): مع الحق فقط في ترك التعليقات.
لذلك بمجرد اتصال شخص ما ، ننظر إلى دوره ونحفظه في الجلسة ، إذا نظرنا إلى المستمع:

<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;

class LoginSuccess extends ListenerBase
{
    /**
     * Handle the event.
     *
     * @param    Login  $login
     * @return  void
     */
    public function handle(Login $login)
    {
        $this->statut->setLoginStatut($login);
    }
}
لدينا الخاصية statut التي تظهر. للعثور على الأصل يجب أن نرى أن فئتنا ترث من ListenerBase هذا:

<?php

namespace App\Listeners;

use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Services\Statut;

class ListenerBase
{
    /**
     * The Statut instance.
     *
     * @var  App\Services\Statut
     */
    protected $statut;

    /**
     * Create the event listener.
     *
     * @param  App\Services\Statut $statut  
     * @return  void
     */
    public function __construct(Statut $statut)
    {
        $this->statut = $statut;
    }
}
الغرض من هذه الفئة الأساسية هو تحديد تخصيص الخاصية بدقة. إذا نظرنا إلى الطريقة المستهدفة في نهاية المطاف ، نرى أننا نحفظ المعلومات في الجلسة:

<?php
public function setLoginStatut($login)
{
    session()->put('statut', $login->user->role->slug);
}
ومن الواضح في حالة انقطاع الاتصال أن هذه هي نفس العملية مع المستمع LogoutSuccess، في النهاية نجعل المستخدم زائرًا بسيطًا:

<?php
public function setVisitorStatut()
{
    session()->put('statut', 'visitor');
}
ولكن يجب أن نأخذ في الاعتبار الحالة الأخيرة: عندما يصل المستخدم إلى صفحة ، سوف نتحقق مما إذا كانت هناك حالة مخزنة في الجلسة وإذا لم يتم إنشاء حالة.
هذه المرة ليس حدثًا موجودًا باعتباره تسجيل الدخول أو الخروج ، لذلك يجب علينا إنشاؤه.
Laravel تضع الأحداث في هذا المجلد كصفوف:
framework Laravel MVC
مجلد الأحداث
ابحث في الملف عن app/Http/Middlewares/App.php  هذا الكود:

<?php
public function handle($request, Closure $next)
{
    ...
    event(new UserAccess);
    ...
}
يمكنني استخدام المساعد event لإطلاق حدث. بدون مساعدة ، سيكون من الضروري استخدام الواجهة:

<?php
Event::fire(new UserAccess);
على مستوى التسجيل لدي هذا:

<?php
protected $listen = [
    ...
    'App\Event\UserAccess' => ['App\Listeners\UserAccess']
];
هنا فئة الحدث:

<?php

namespace App\Events;

use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class UserAccess extends Event
{
    use SerializesModels;

    /**
     * Get the channels the event should be broadcast on.
     *
     * @return  array
     */
    public function broadcastOn()
    {
        return [];
    }
}
والمستمع:

<?php

namespace App\Listeners;

use App\Events\UserAccess;

class UserAccess extends ListenerBase
{
    /**
     * Handle the event.
     *
     * @param    UserAccess  $event
     * @return  void
     */
    public function handle(UserAccess $event)
    {
        $this->statut->setStatut();
    }
}
لذلك ندعو الطريقة في الخدمة:

<?php
public function setStatut()
{
    if(!session()->has('statut')) 
    {
        session()->put('statut', auth()->check() ?  auth()->user()->role->slug : 'visitor');
    }
}
إنشاء مستمع
في التطبيق كل شيء موجود بالفعل. كيف ننشأ مستمع؟ خذ حالة الاتصال ...
يجب عليك إنشاء المراقب مع Artisan:

php artisan make:listener LoginSuccess --event=Illuminate\Auth\Events\Login
نعطي اسم المراقب واسم الحدث الذي نريد مراقبته. 
ما يخلق هذا المراقب فارغ:

<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class LoginSuccess
{
    /**
     * Create the event listener.
     *
     * @return  void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param    Login  $event
     * @return  void
     */
    public function handle(Login $event)
    {
        //
    }
}
يكفي بعد ذلك إضافة منطق المراقب في طريقة handle المراقب (أو استدعاء طريقة فئة أخرى):

<?php
public function handle(Login $login)
{
    $this->statut->setLoginStatut($login);
}
نظرًا لأنني اخترت استخدام فئة الأصل لتجنب تكرار التعليمات البرمجية ، يتم إنشاء الخاصية فيه.
إنشاء حدث
خذ مثال UserAccess . مع Artisan يمكنني إنشاء الفصل:

php artisan make:event UserAccess
وانتهى بي الأمر مع هذا الكود الأساسي:

<?php

namespace App\Events;

use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class UserAccess extends Event
{
    use SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return  void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the channels the event should be broadcast on.
     *
     * @return  array
     */
    public function broadcastOn()
    {
        return [];
    }
}
ثم مجرد استكمال حسب الحاجة. في حالة التطبيق لا يوجد شيء خاص للقيام به. 
يمكنك العثور على الوثائق الكاملة هنا .

أذونات Autorisations


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

@if(session('statut') == 'admin')
    {!! link_to_route('admin', trans('back/admin.administration'), [], ['class' => 'navbar-brand']) !!}
@else
    {!! link_to_route('blog.index', trans('back/admin.redaction'), [], ['class' => 'navbar-brand']) !!}
@endif
يمكننا أيضا إعداد الوسيطة لتصفية الوصول. في تطبيق المثال لدينا الوسيطة isAdmin :

<?php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\RedirectResponse;

class IsAdmin {

    /**
     * Handle an incoming request.
     *
     * @param    \Illuminate\Http\Request  $request
     * @param    \Closure  $next
     * @return  mixed
     */
    public function handle($request, Closure $next)
    {
        if (session('statut') === 'admin')
        {
            return $next($request);
        }
        return new RedirectResponse(url('/'));
    }

}
ثم استخدمه فقط على الطرق:

<?php
Route::get('admin', [
    'uses' => 'AdminController@admin',
    'as' => 'admin',
    'middleware' => 'admin'
]);
مكان آخر حيث يمكننا تأمين التطبيق هو على مستوى طلبات النموذج. لقد رأينا أن هناك طريقة authorize . في تطبيق المثال ، توجد هذه الطريقة فقط في الفصل Request الذي يعد أساسًا لكل العناصر الأخرى:

<?php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

abstract class Request extends FormRequest {

    public function authorize()
    {
        // Honeypot 
        return  $this->input('address') == '';
    }

}
في الواقع ، في جميع الأشكال هناك سيطرة خفية. على سبيل المثال في نموذج الاتصال ، نجد هذا الكود:

{!! Form::text('address', '', ['class' => 'hpet']) !!}
الفئة hpet   تجعل المراقبة غير مرئية. الإنسان لا يرى هذه المراقبة ، وبالتالي لا يكملها ، ولكن الروبوت يخاطر باستخدامها. وهذا ما يسمى جرة العسل (لجذب النحل الافتراضي). لذلك نحن نتحقق مما إذا كان هذا الفحص قد اكتمل ، وإذا كان الأمر كذلك ، فإننا نمنع العملية.
التراخيص
بالإضافة إلى هذه الاحتمالات ، يقدم لنا Laravel نظام ترخيص كاملًا. أسهل طريقة لمعرفة كيفية عملها هي أخذ مثال. لدينا مجلد app/Policies مع ملف:
framework Laravel MVC
ملف الترخيص
لنرى هذا الملف: 

<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    /**
     * Grant all abilities to administrator.
     *
     * @param    \App\Models\User  $user
     * @param    string  $ability
     * @return  bool
     */
    public function before(User $user, $ability)
    {
        if (session('statut') === 'admin') {
            return true;
        }
    }

    /**
     * Determine if the given post can be changed by the user.
     *
     * @param    \App\Models\User  $user
     * @param    \App\Models\Post  $post
     * @return  bool
     */
    public function change(User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }

}
before  هي الطريقة الأولى التي يتم استدعاؤها. في هذا المستوى ، نتحقق مما إذا كان المستخدم مسؤولًا ، وبالتالي يتمتع بجميع الحقوق ، وإذا كان هذا هو الحال نُرجع true .
ثم لدينا الطريقة change  مع المستخدم والمقال كمُدخلات. إذا كان المستخدم هو منشئ المقال ، فنُرسل مرة أخرى true . 
الآن وقد أصبحت هذه التراخيص سارية ، يجب أن نعلنها. انظر إلى الملف app/Providers/AuthServiceProvider  :

<?php

namespace App\Providers;

use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

use App\Models\Post;
use App\Policies\PostPolicy;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var  array
     */
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

    /**
     * Register any application authentication / authorization services.
     *
     * @param    \Illuminate\Contracts\Auth\Access\Gate  $gate
     * @return  void
     */
    public function boot(GateContract $gate)
    {
        parent::registerPolicies($gate);
    }
}
في الملكية policies  نخطط لإضافة الفئة PostPolicy .
علينا فقط أن نرى كيف نستخدمها.
بشكل تلقائي ، تستخدم وحدة التحكم الأساسية هذا الخط   AuthorizesRequests، بحيث تكون جميع وحدات التحكم على دراية بوجود التراخيص المسجلة. 
انظر إلى هاتين الطريقتين لوحدة التحكم  BlogController  :

<?php
/**
 * Show the form for editing the specified resource.
 *
 * @param    App\Repositories\UserRepository $user_gestion
 * @param    int  $id
 * @return  Response
 */
public function edit(
    UserRepository $user_gestion, 
    $id)
{
    $post = $this->blog_gestion->getByIdWithTags($id);

    $this->authorize('change', $post);

    $url = config('medias.url');

    return view('back.blog.edit',  array_merge($this->blog_gestion->edit($post), compact('url')));
}

/**
 * Update the specified resource in storage.
 *
 * @param    App\Http\Requests\PostUpdateRequest $request
 * @param    int  $id
 * @return  Response
 */
public function update(
    PostRequest $request,
    $id)
{
    $post = $this->blog_gestion->getById($id);

    $this->authorize('change', $post);

    $this->blog_gestion->update($request->all(), $post);

    return redirect('blog')->with('ok', trans('back/blog.updated'));        
}
الغرض منها هو الأول لعرض نموذج تعديل المقالة والثاني لمعالجة تقديم التعديلات. في كلتا الحالتين يتم تنفيذ التفويض:

<?php
$this->authorize('change', $post);
لذلك إذا لم يكن المسؤول أو صاحب المقال ، فسوف تتعثر هنا.
يمكنك العثور على الوثائق الكاملة هنا .

في الخلاصة


  • لدى Laravel نظام إدارة أحداث كامل وبسيط.
  • لدى Laravel نظام إدارة ترخيص كامل وبسيط.