مقدمه

در بخش اول مقاله یک سری مفاهیم و مسائل که در لاراول به کار رفته است را بیان کردیم، حال قصد داریم به صورت کامل و کاربردی تر مسائل مهمی را در بخش دوم مقاله برایتان بیان کنم که میتواند کمک بسیاری خوبی به شما بکند، لطفا تا انتهای مقاله حتما پیش بروید که مطالب مفیدی دستگیرتان خواهد شد.

۱۵- ماکرو (macro) چیست و چه کاربردی دارد ؟

ما توسط ماکرو ها میتواینم از طریق یک کلاس داخلی در لاراول مثل Request و Response یک متد دلخواه را در آن اضافه کنیم و توسعه دهیم. این عمل اضافه کردن در هنگام اجرای اپ به صورت Run-Time رخ می دهد. با ماکرو، کلاس‌هایی رو میشه توسعه داد که از یک Trait به اسم Macroable استفاده می‌کنن برای مثال به کد زیر دقت کنید :

نمونه اول :

Request::macro('sayHello', function($argument) {
    return 'Hello';
});

نمونه دوم :

use Illuminate\Support\Collection;
use Illuminate\Support\Str;

Collection::macro('toUpper', function () {
    return $this->map(function ($value) {
        return Str::upper($value);
    });
});

با تعریف قطع کد بالا به همین راحتی می توانید یک ماکرو برای خود تعریف کنید ولی فقط نکته اینجاس که باید درون متد boot در داخل service provider آن را تعریف کنید. حالا به شکل زیر میتوانید آن را فراخوانی کنید :

$collection = collect(['first', 'second']);

$upper = $collection->toUpper();

----------------------------------------------------------------------

echo Request::sayHello(); // Hello

۱۶- Eager Loading چیست ؟

من در دو بخش مشکل و راه حل مفهوم Eager Loading را توضیح میدهم که خوب متوجه شوید چرا از این روش استفاده می شود.

خب مشکل چیست ؟ این تکنیک برای حل کردن مشکل N + 1 ساخته شده، شما فرض کنید که ما در داخل دیتابیس دوتا موجودیت به نام های books و authors داریم خب بین آنها یک رابطه به وجود آمده است که هر نویسنده کتابی دارد و هر کتاب متعلق به یک نویسنده می باشد، خب مشکل اینجاس که وقتی ما میخواهیم به عنوان مثال کوئری بنویسم که کتاب های که متعلق به نویسنده های مختلف است را برایمان بیاورد، به کد زیر دقت کنید :

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    /**
     * Get the author that wrote the book.
     */
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}

خب حالا با قطع کد زیر تمام کتاب ها را بر میگردانیم :‌

use App\Models\Book;

$books = Book::all();

foreach ($books as $book) {
    echo $book->author->name;
}

همانطور که در بالا گفتیم کوئری ما N کتاب را برگرداند، ولی باعث به وجود امدن مشکل N+1 شده، به زبان ساده تر برای لود کردن کتاب های هر نویسنده باید یک کوئری جدا بزند خروجی به شکل زیر می شود :

select * from books where author_id = 1
select * from books where author_id = 2
select * from books where author_id = 3
select * from books where author_id = 4

خب راه حل چیست ؟ با Eager Loading می‌تونیم به جای N + 1 کوئری، فقط ۲ تا کوئری داشته باشیم. یک کوئری برای خواندن کاربرها و یک کوئری برای خواندن کتابشون، این تکنیک توسط متدهای with و load توی لاراول قابل اجرا هست به کد زیر دقت کنید :‌

$authors = Author::all()->load('book');

foreach ($authors as $author) {
    echo $author->book->name;
}

خروجی کد SQL به شکل زیر می شود:

select * from authors
...
select * from books where author_id in (1, 2, 3, 4)

نکته آخر اینکه فرق بین متد with و load را هم برایتان بگویم،هر دو متد برای پیاده‌سازی تکنیک Eager Loading معرفی شدن هنگامی که میخواین به صورت پیوسته (کوئری دوم بلافاصله بعد از کوئری اول زده میشه) کوئری شما اجرا شود از متد with استفاده کنید به مثال زیر دقت کنید :

$users = Author::with('book')->get();

ولی هنگامی که میخواهید یک عملیاتی را انجام دهید و بعد از اجرای کوئری اول،کوئری دوم را در مکان و زمان دیگر اجرا کنید از متد load می توانید استفاده کنید که به اون Lazy Eager Loading هم گفته میشه، به کد زیر دقت کنید :

$authors = Author::all();

// Somewhere in the app
if ($condition) {
    $authors->load('books');
}

۱۷- کاربرد Route Model Binding چیست ؟

در لاراول حتما بارها با این مفهوم برخورد کرده اید که در متدی یا constructor های درون کلاس یک مدلی پاس داده شده است.به عنوان مثال در روت های که تعریف می کنید حتما شده است که $id یک کاربر را پاس داده اید ولی در قسمت ورودی تابع به شما مدلی از User بر می گردد :

Route::get('user/{id}', '[email protected]');

متدی هم که در این کنترلر وجود دارد به نام show به شکل زیر است :

public function show($id)
{
    $user = User::findOrFail($id);

    dd($user);
}

این عمل خواندن یک شناسه به عنوان پارامتر و اجرا کردن از دیتابیس به صورت خودکار توسط لاراول انجام میشه به این تکنیک میگن Route Model Binding .

شکل Bind شده مدل مورد نظر به این صورت تغییر می کند :‌

public function show(User $user)
{    
    dd($user);
}

۱۸- Throttle چیست و چطوری اون رو فعال کنیم؟

Throttle یا به اون Rate Limit هم گفته می شود که با آن میتوانیم تعیین کنیم در یک بازه زمانی مشخص چه تعداد درخوست مجاز است به سمت برنامه بیاید.

مثلاً یک فرم داریم و کاربر باید بتونه توی 1 دقیقه فقط ۱۰ بار این فرم رو ثبت کنه و اگه بیشتر از ۱۰ بار شد، باید با خطای HTTP 429 (Too Many Requests) مواجه بشه. یعنی تعداد بیشتر از حد درخواست توی یک بازه زمانی.

برای همین منظور لاراول یک میدل ور برای ما اماده کرده است به نام Throttle که می توانیم روی روت های که میخواهیم تنظیم کنیم به این صورت :

Route::post('login', 'َ[email protected]')->middleware('throttle:20,1');

۱۹- روت Resource و Named چیست و چه کاربردی دارد ؟

هنگامی روت ها درون برنامه ما ایجاد می شوند ممکن است تعدادشان خیلی زیاد شود و اگر بخواهیم از آنها برای مسیردهی های داخل برنامه ی خودت استفاده کنیم یادآوری و به کار بردن آنها نسبتا سخت میشه و همچنین ممکن بعدا عوض بشوند، برای همین مفهوم Named به ما کمک می کند تا برای روت های خود یک اسم کوتاه و مختصر بگذاریم به کد زیر دقت کنید :

Route::get('/posts/laravel-project-10221/nz7eX', '...')->name('post');

حالا بجای نوشتن همچین ادرس سختی کافی است فقط نام آن را صدا بزنیم در صفحه های html خود به شکل زیر :

<form action="{{'{{'}} route('post') }}"></form>

خب مفهومی بعدی که میخواهیم در مورد آن صحبت کنیم Resource می باشد خیلی موقع ها شما یک عملیات تکرای مثل CRUD را در نوشتن روت های خود دارید یک روش این است که هر دفع بیاید برای یک موجودیت یا مدل هر چهار عمل را تکرار کنید به شکل زیر :

Route::get('books', '[email protected]');
Route::get('books/{book}', '[email protected]');
Route::post('books', 'BookCo[email protected]');
Route::patch('books/{book}', '[email protected]');
Route::delete('books/{book}', '[email protected]');

هدف از این مفهوم خلاصه و کم حجم کردن روت‌های هست که تعریف کردیم و به راحتی متوانید با قطع کد زیر روت های بالا را مدیریت و خلاصه کنید :

php artisan make:controller BookController --resource

در بخش روت ها به جای تمام آنها کد زیر را داریم :

Route::resource('books', 'BookController');

۲۰- ACL چیست و روش استفاده از آن چگونه است ؟

یکی از بخش های مهم و پرکاربرد می باشد پس خوب توجه کنید، فرض کنید که ما یک فروشگاه اینترنتی داریم که از بخش های مثل مشتریان، محصولات، بلاگ، نظرات تشکیل شده است، خب ما قصد داریم برای کسانی که قرار است با اپ و یا سایت ما کار کنند محدودیت و سیاست گذاری های خاصی بگذاریم، به عنوان مثال میخواهیم بعضی از کاربرها فقط دسترسی بخش نظرات و خواندن آن ها داشته باشند و بعضی ها بتوانن گزارش گیری های مالی را انجام دهند و مثال های از این قبیل که بتوانیم سطوح دسترسی مختلفی داشته باشیم، خب برای اینکار مفهومی به نام ACL مخفف Access-control list وجود دارد که میتوانیم بدون دستکاری در جداول بی‌نهایت مجوز و نقش تعریف کنیم.

خب این مفهوم شامل چندین بخش می شود که من تک به تک آنها را توضیح میدهم :

Gate چیست ؟

همانطور که گفتیم میخواهیم فعالیت کاربر را قبل انجام عملی یا دیدن صفحه ای با تعیین سطوح دسترسی محدود کنیم به این معنی که آیا یک کاربر مجوز انجام یک کار خاص رو داره یا نه، که از کلاس (فساد) Gate استفاده و آن را عبور می دهیم.

بعنوان مثال میخواهیم برسی کنیم که در صورتی که یک کاربر نقش نویسنده را دارد بتواند پست ایجاد کند :

Gate::define('create-post', function ($user) {
    return $user->isAuthor;
});

به صورت زیر استفاده می شود :

if (Gate::allows('create-post')) {
    // The current user can create posts
}

فقط اینجا یک نکته ای مهم است که ما برای تعریف انواع سطح دسترسی باید دوتا کار انجام دهیم اگر میخواهیم به صورت Gate::define‍ آن را استفاده کنیم باید آن را توی متد boot توی فایل AuthServiceProvider تعریف کنیم.

یا اینکه باید برای مدل های که در برنامه خود داریم از طریق دستور زیر Policy تعریف کنیم :

php artisan make:policy PostPolicy -m Post

هنگامی که قطع کد بالا را اجرا میکنیم از طریق Command Line درون پوشه app یک پوشه ساخته ای می شود به نام Policies که تمام Policy های ما آنجا وجود دارد فقط آخرین کاری که باید انجام شود این است که آن را در داخل AuthServiceProvider معرفی کنیم به این صورت :

protected $policies = [
        Post::class => PostPolicy::class
];

در اخر من فقط نمونه های استفاده از این مفهوم رو به عنوان sample code میزارم تا ازش الگو بگیرید و بدانید در لاراول برای انجام یک کار راه های متعددی وجود دارد.

// ACL (Access Control List)

// if($post->owner_id !== auth()->user()->id) {
//     abort(403);
// }

// abort_if( $post->owner_id !== auth()->user()->id , 403);

// abort_unless(auth()->user()->owns($post) ,403);

// $this->authorize('view' , $post);

// if(\Gate::denies('view' , $post)){
//     abort(403);
// }

// abort_unless(\Gate::allows('view' , $post) , 403);

// auth()->user()->can('view' , $post);

۲۱- گارد (Guard) چیست؟

برای اینکه بخوایم تعیین کنیم که احرازهویت از طریق چه دستورالعملی صورت بگیره از گارد استفاده می کنیم، بعنوان مثال می خواهیم مشخص کنیم که احرازهویت از طریق رشته ای از Token صورت بگیره یا از طریق Session یا Cookie .

در لاراول ما به صورت کلی دو نوع گارد به نام های web و api داریم که web به صورت پیش فرض در لاراول استفاده می شود که تعیین می کند احرازهویت از طریق Session یا Cookie صورت بگیرد و api برای رشته ای از Token می باشد .

در قطع کد زیر می توانید مشاهده کنید که به چه صورت گارد مورد استفاده قرار گرفته :

$user = Auth::user();
$user = Auth::guard('api')->user();
$user = Auth::guard('web')->user();

خب اول از گارد پیش فرض خود لاراول استفاده کردیم برای خواندن اطلاعات کاربر در خط دوم و سوم از گارد api و web به صورت صریح استفاده کردیم تا اطلاعات کاربر را بخوانیم.

توی فایل config/auth.php می‌تونیم گاردها رو ببینیم و همچنین گاردهای دلخواهمون رو تعریف کنیم.

۲۲- فِساد (Facade) چیست؟

یک از مفاهیم مهم دیگر Facade می باشد که متعدد به آن بر میخورید، اگر میخواهید الگوی کد نویسی لاراول را به خوبی درک کنید حتما به مطالعه Design Pattern بپردازید که تمام این مفاهیم از طریق این الگو پیاده سازی شده است و در خیلی از فرم ورک های دیگر هم بکار رفته است.

خب همانطور که قبلا اشاره کردیم سرویس ها (کلاس) مثل Session و DB و Cookie درون Service Container ثبت شده اند، خب حالا برای دسترسی راحت به سرویس ها Service Container از مفهومی به نام Facade استفاده می کنیم. فساد، یک کلاس (رابط) برای دسترسی به این سرویس‌ها بصورت استاتیک هست به عنوان مثال برای دسترسی به کلاس DB به این صورت عمل می کنیم :

$users = DB::select('select * from users where active = ?', [1]);

۲۳- صف (Queue) چه کاربردی دارد ؟

فرض کنید عملیاتی داریم که زمان اجرای آن طولانی می باشد، به عنوان مثال ارسال ایمیل به صورت انبوه بین تمام کاربران سایت، خب اگر بخواهیم این عمل را به صورت همزمان انجام دهیم و همان لحظه تمام پردازش صورت بگیرید تمام کاربران برای مدتی باید منتظر پاسخ از سرور باشند و انباشته شدن این همه درخواست به سرور هم فشار زیادی می آورد که ممکن است باعث اختلاس در سرور شود.

خب اینجا است که صف وارد عمل می شود تا این مسئله را حل کند، صف زمانی به کار می آید که می خواهیم عملیات سنگین را به یک زمان دیگر موکول کنیم این لایه از چرخه Request و Response جدا می باشد تا اختلالی در آنها ایجاد نشود.

۲۴- seeder چیست ؟

حتما به این موضوع بر خورده اید که در هنگام توسعه یک برنامه نیاز دارید که دیتای Fake ایجاد کنید و روی آنها عملیات های مختلفی را انجام دهید خب در اینجا مفهومی به نام seeder کمک ما می آید که از طریق Factory می توانیم به راحتی برای هر چندتا دیتای Fake از آن استفاده کنیم.

من به عنوان مثال یک بار فرایند ایجاد کردن آن را می گویم :

php artisan make:factory UserTable

خروجی این دستور به صورت زیر می باشد و کلاسی با نامFaker به آن bind شده است که میتوانید با یک سرچ ساده استفاده از دیتای آن را متوجه شوید :

<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\User;
use Faker\Generator as Faker;

$factory->define(User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
    ];
});

بعد باید برای این Factory یک Seeder درست کنیم تا آن را با تعداد دفعات دلخواه ما فراخوانی کند :

php artisan make:seeder UsersTableSeeder

خروجی آن به این صورت می باشد :

<?php

use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\User::class, 10)->create();
    }
}

حالا فقط کافی است در فایلی به نام DatabaseSeeder که در مسیر database/seeds/ وجود دارد آن را فراخوانی کنیم به صورت زیر :

<?php

use App\Profile;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(UsersTableSeeder::class);
    }
}

در آخر فقط کافی است دستور زیر اجرا کنیم :

php artisan db:seed

۲۵- Accessor و Mutator چیست ؟‌

اگر بخواهیم attributes های یک مدل رو طبق یک فرمت خاص نمایش دهیم یا ذخیره کنیم از این مفهوم استفاده می کنیم .

: Accessor برای زمانی که بخواهیم یک attr را به فرمت خاص نمایش دهیم به عنوان مثال یک attr را حرف اولش را به صورت Uppercase نمایش دهیم .اگه کاربرا یک Attribute به اسم name داشته باشن، اسم Accessor میشه getNameAttribute که باید اون رو بصورت یک متد توی مدل User تعریف کنیم:

<?php

namespace App\Models;

class User extends Model
{
    public function getNameAttribute($name)
    {
        return ucfirst($name);
    }
}

: Mutator فرض میکنیم که میخواهیم یک attr مقدار لخواه تنظیم کنیم به عنوان مثال میخواهیم هنگامی که password مقدار میدهیم همیشه عمل Hash شدن به صورت خودکار اتفاق بیفتد.

<?php

namespace App\Models;

class User extends Model
{
    public function setPasswordAttribute($value)
    {
        $this->attributes['password'] = Hash::make($value);
    }
}

جمع بندی

در بخش آخر این مقاله سعی کردم بیشتر مفاهیم کاربردی که در لاراول مطرح است را پوشش بدهم همانطور که قبلا هم گفتم سعی بر درک مفاهیم بود تا مثال های اجرای، ولی بازم تا تونستم مثال های کاربردی زدم تا بهتر درک بشه، امیدوارم این مقاله برای شما مفید باشد.