تذکر: این یک پست آموزشی برای عموم نیست! بلکه تنها جایی برای یادداشتهای من حین یادگیریه تا بهتر به خاطر بسپارم و در صورت لزوم به اونها مراجعه کنم.
قسمت ۷۰ تا ۸۰
یک wrapper برای آرایههای که امکانات زیادی رو به ما میده. با استفاده از این مفهوم میتونیم یک مفهوم شیگرایی به آرایهها بدیم.
نحوهٔ ایجاد یک Collection:
//Array $array = [1,2,3] // Collection $collection = collect([1,2,3]) //Example of OOP $collection->get(index); $collection->all(); $collection->first(); $collection->count(); $collection->toJson(); $collection->toArray();
ما با استفاده از helper function ای به نام collect میتونیم یک آرایه رو تبدیل به Collection کنیم و از مزایای اون بهره ببریم. متدهایی که روی object ما ران شده، مشابه متدهایی هستن که ما از دیتابیس میگرفتیم! علت؟ چون لاراول اون دیتا رو به شکل Collection برای ما برمیگردونه.
چک کردن وجود مقداری خاص در کالکشن:
$collection->has(value);
چک کردن پر و خالی بودن آرایه:
$collection->isEmpty();
دسترسی به مقادیر آرایههای Associative:
$collection->get('key');
حذف کلیدی خاص از کالکشن:
$result = $collection->forget('key');
آرایهای از آرایهها و اعمال متدهای جالب روی آنها:

توی تصویر بالا با اینکه یه آرایهٔ Associative داریم؛ اما به با استفاده از متد avg که میاد و میانگین می گیره، خودش کل آرایههای داخلی رو iterate کرد و مقادیر رو میانگین گرفت.
تقسیمبندی یا chunk کردن آرایهها:
$collection->chunk(2);
یعنی اگر روی کالکشن تصویر بالا متد chunk رو اجرا کنیم؛ یک کالکشن با دو تا آرایه تحویل می گیریم که توی هر کدام از این آرایهها دو تا آرایهٔ دیگه هست.
و حتی میتونیم همون chunk شده رو به آرایه تبدیل کنیم یا متدهای دیگه ای رو روش ران کنیم:
$collection->chunk(2)->toArray();
چک کردن مقدار value یک کلید خاص:
$collection->contains('key', 'value');
حتی میتونیم متد بالا رو توی آرایهای از آرایهها اجرا کنیم.
کالشکن متدهایی مثل filter و map که توی پایتون هم داریم رو در اختیار ما قرار میده.
فیلتر اینطوری عمل میکنه که هر یک از عناصر آرایه رو دریافت میکنه، یه شرط خاصی روی تک تک عناصر اعمال میشه و اگر پاس شد؛ فقط اون عنصری که شرط رو پاس کرده، برگشت داده میشه و بقیه فیلتر میشن، مثال:
<?php namespace App\Http\Controllers; use App\Models\Hacker; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; class HomeController extends Controller { public function index() { $collection = collect([ ['name' => 'Aref', 'age' => 20], ['name' => 'Jamshid', 'age' => 21], ]); dd($collection->filter(function($value, $key){ return $value['name'] == 'Aref'; })); } }
خروجی:
Illuminate\Support\Collection {#304 ▼ #items: array:1 [▼ 0 => array:2 [▼ "name" => "Aref" "age" => 20 ] ] #escapeWhenCastingToString: false }
اما این $value در واقع متغیری هست که هر یک از عناصر آرایه توش قرار میگیرن و $key مربوط میشه به شماره index اون عنصر.
حالا بریم سراغ map کردن، map اینطوری عمل میکنه که هر مقداری که بهش داده میشه، روش یه پروسس انجام میده و برگشت میده:
<?php $collection = collect([1, 2, 3, 4, 5]); $multiplied = $collection->map(function ($item, $key) { return $item * 2; }); $multiplied->all(); // [2, 4, 6, 8, 10] ?>
متد کاربردی بعدی، متد pluck (کندن، چیدن) هست که مقادیر مربوط به یک کلید خاص رو برای ما بر میگردونه:
<?php namespace App\Http\Controllers; use App\Models\Hacker; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; class HomeController extends Controller { public function index() { $collection = collect([ ['name' => 'Aref', 'age' => 20], ['name' => 'Jamshid', 'age' => 21], ]); dd($collection->pluck('name')); } }
خروجی:
Illuminate\Support\Collection {#297 ▼ #items: array:2 [▼ 0 => "Aref" 1 => "Jamshid" ] #escapeWhenCastingToString: false }
تابع implode رو هم داریم:
<?php $collection = collect([ ['account_id' => 1, 'product' => 'Desk'], ['account_id' => 2, 'product' => 'Chair'], ]); $collection->implode('product', ', '); // Desk, Chair ?>
یه متد کاربردی و جالب دیگه، keyBy هست؛ این متد اینطوری عمل میکنه که ما بهش یک key به عنوان آرگومان میدیم؛ این میاد مقدار متناظرش رو میگیره و به عنوان کلید همون عنصر قرار میده و دیگه به شکل index عددی کلیدگذاری نمیشن:
<?php $collection = collect([ ['product_id' => 'prod-100', 'name' => 'Desk'], ['product_id' => 'prod-200', 'name' => 'Chair'], ]); $keyed = $collection->keyBy('product_id'); $keyed->all(); /* [ 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], ] */ ?>
درخواستهای http ناپایدار یا stateless هستند یعنی وقتی یک سایتی توسط چندین نفر فراخوانی میشه، وب سرور نمیتونه در هر بار درخواست/پاسخ تشخیص بده که کدوم کاربر داره باهاش صحبت میکنه، برای حل این مشکل میتونیم از مفاهیمی مثل session و کوکی استفاده کنیم که اینجا مورد بحث قرار میگیرن:
در واقع session سیستمی هست برای نگهداری اطلاعات request/response:
تنظیمات مربوط به session داخل فایل session.php داخل پوشهٔ config قرار گرفته، یکی از آپشنهای مهم در تنظیمات session مربوط به ست کردن driver اون هست که میتونیم روی file database redis cookie و... قرار بدیم که توی لاراول ۹ به صورت دیفالت روی فایل هست و این فایلها داخل پوشهٔ storage/framework/sessions قرار میگیرن.
اگر بخوایم ازجدول دیتابیس برای ذخیرهسازی دیتای مربوط به session استفاده کنیم؛ میتونیم به راحتی با استفاده از artisan مایگریشن اون رو ایجاد کنیم:
php artisan session:table
خب، در حالت پیشفرض ما از file استفاده میکنیم چون نسبتا خوب جواب میده، اما حالا که توی فایل config این مورد رو لحاظ کردیم؛ باید بریم سراغ فایل .env و وانجا هم مقدار SESSION_DRIVER رو به file تغییر بدیم.
برای ایجاد و ذخیرهسازی sessions کافیه که از یک سری helper function استفاده کنیم:
session()->put('key', 'value'); session()->get('key'); //another way to put session(['key','value']);
با استفاده از متد all میتونیم به کل مقادیر ذخیره شده در session دسترسی داشته باشیم.
نمونه ای از مقدار برگشتی متد all:

بحث flash sessions رو هم داریم که جلوتر در موردشون بحث میکنیم...
متد has که میاد و یک کلید خاصی رو جستجو میکنه و اگر value اون غیر null بود مقدار true رو برمیگردونه، اما یک متد مشابه داریم به اسم exists که میاد و دقیقا فقط و فقط کلید رو سرچ میکنه و اگر بود (value اون مهم نیست)، مقدار true رو برمیگردونه.
متد forget میاد و یک کلید خاصی رو حذف میکنه.
متد flush میاد و کل session رو پاک میکنه و تبدیلش میکنه به یک آرایه خالی.
اگر بخوایم دادهها رو فقط برای درخواست بعدی توی session ذخیره کنیم؛ از flash sessions استفاده میکنیم. دادههای ذخیره شده به این روش فقط و فقط در درخواست http بعدی در دسترس هستند و بعد از اون بلافاصله حذف میشن و برای status messages ایده آل هستن.
مثلا یک کاربری یک دیتایی رو توی دیتابیس ذخیره میکنه و اگر موفق/ناموفق بود، میفرستیم براش و فقط همون یک بار نمایش داده میشه:
session()->flash('key', 'value');
حالا چطور به این flash data دسترسی داشته باشیم؟ خیلی ساده:
$session_data = session()->all(); dd($session);
نمونه خروجی آموزش:

اون مقدار name که در old ذخیره شده، در واقع همون کلید ما هست که value داره.
اما یک نکتهٔ مهم اینه که ما مثلا وقتی توی یک پیجی میایم و flash data ست میکنیم و قراره توی یک پیج دیگه (Route) ازش استفاده کنیم؛ حتما باید return بشه تا اون حالت flash رو داشته باشه و محتواش پاک بشه:

اینجا old و همینطور کلید name پاک شدن.
اگر بخوایم که مقدار flash data رو نگه داریم و در درخواست بعدی هم استفاده کنیم باید از متد reflash استفاده کنیم:
session()->reflash();
نمونه خروجی reflash شده که کلید ما داخل new قرار میگیره:

ما میتونیم از متد keep هم استفاده کنیم؛ مثلا اگر چند تا دیتای فلش داشته باشیم؛ با keep میتونیم فقط یک سری دیتای خاص رو حفظ کنیم:
session()->flash('key1', 'value1'); session()->flash('key2', 'value2'); ------------ session()->keep('key1');
در این حالت فقط key1 در new قرار خواهد داشت.
کش کردن باعث میشه تا سرعت اجرای سایت و یا سرویسهای ما بره بالاتر، چطوری؟ فرض کنیم که یک کوئری داریم که اجرای شدنش n ثانیه زمان میبره، حالا اگر قرار باشه که کاربر هر سری این کوئری رو بزنه و n ثانیه منتظر باشه، کلی وقتگیر و اعصاب خردکن میشه. چاره چیه؟ استفاده از همین سیستم کش، که میاد و دیتای واکشی شده از کش رو برای ما یه گوشه نگه می داره و ما دوباره می تونیم ازش استفاده کنیم.
تنظیمات کش در لاراول:
پوشهٔ config، فایل cache.php که از file redis و... ساپورت میکنه، توی کلید default باید درایور رو ست کنیم که به صورت دیفالت روی file هست و میشه از طریق فایل .env هم تغییرش داد. به جای file میتونیم از table هم استفاده کنیم و مایگریشن اون رو با artisan ایجاد کنیم:
php artisan cache:table
توی env باید CACHE_DRIVER رو ست کنیم.
اگر از فایل درایور استفاده کنیم؛ مقادیر کش شده توی framework/cache/data قرار میگیرن.
حالا چطور یک فایل رو کش کنیم؟
توی کنترلر، از Cache facade و متد put استفاده میکنیم:
Cache::put('key', 'value', 'expire_time_secs');
یک مثال:
Cache::put('name', 'Aref', 15);
حالا میتونیم توی یک route دیگه به این شکل بهش دسترسی داشته باشیم:
$value = Cache::get('name', 'return_sth_else_if_name_does_not_exist')'; dd($value);
این مقدار کش شده در مدت زمان مشخص شده در هر جایی از پروژه در دسترس خواهد بود.
متد remember میاد و چک میکنه که آیا یک کلیدی توی کش وجود داره یا خیر، اگر وجود داشت که value اون رو برمیگردونه و اگر وجود نداشت با استفاده از دو تا پارامتر بعدی میاد و اون رو ایجاد میکنه:

دو تا دیگه از متدهای کاربردی Cache متدهای مربوط به حذف میشن که یکی forget هست و دیگری pull، اما یک تفاوتی دارن و اونم اینه که وقتی از forget استفاده میشه، بعد از حذف موفقیت آمیز دیتا، مقدار true رو برمیگردونه و pull مقداری value اون key که حذف کرده.
اما artisan هم دستوراتی برای کار با کش داره:
cache cache:clear Flush the application cache cache:forget Remove an item from the cache cache:table Create a migration for the cache database table config:cache Create a cache file for faster configuration loading config:clear Remove the configuration cache file event:cache Discover and cache the application's events and listeners event:clear Clear all cached events and listeners optimize:clear Remove the cached bootstrap files package:discover Rebuild the cached package manifest route:cache Create a route cache file for faster route registration route:clear Remove the route cache file schedule:clear-cache Delete the cached mutex files created by scheduler view:cache Compile all of the application's Blade templates
پلی بین درخواست و پاسخ و کارش فیلتر کردنه، مثلا کاربری یک درخواستی رو برای app ما ارسال میکنه، و نیازه که ما این درخواست کاربر رو بررسی کنیم و برعکس، وقتی قراره که سرور پاسخی رو به سمت کاربر بفرسته نیازه که چک کنیم که آیا این پاسخ اوکی هست یا خیر، در اینجا نقش middleware اهمیت پیدا میکنه.
نمونهٔ خیلی سادهٔ این middleware در واقع سیستم authentication هست؛ مثلا فقط یک یا چند کاربر خاص حق ارسال پست رو توی سایت دارن، ما با middleware میام و چک میکنیم که آیا این کاربر لاگین کرده یا خیر...
خود لاراول یک سری middleware آماده توی app/Http/Middleware داره و middleware های ایجاد شده توسط ما هم اینجا قرار میگیره، توی این پوشه مثلا فایل Authenticate.php رو میبینیم که در واقع میدلور authentication ما هست که بالا در موردش نوشتم.
یا middleware مربوط به VerifyCsrfToken که فرمهای پست رو چک میکنه که آیا CSRF token ست شده یا خیر.
چطور Middleware ایجاد کنیم؟
php artisan make:middleware CheckNameParameter
محتویات فایل middleware ما به شکل زیره، متد handle در واقع جایی هست که عملیات فیلتر کردن صورت میگیره و به نوعی هسته میان افزار ما محسوب میشه:

برای اینکه middleware ما توی سیستم شناسایی بشه، باید اون رو توی فایل Kernel.php در مسیر app/Http/Kernel.php تعریف کنیم:
توی این فایل یه سری آرایه داریم به نامهای $middleware و middlewareGroup و... که وابسته به کاربردی که دارن، میتونیم از اونها استفاده کنیم.
اگر یک middleware توی آرایهٔ middleware تعریف بشه، روی تمامی routeهای ما اعمال میشه، اما اگر قرار باشه فقط روی یک route خاص مثل web یا api ران بشه، باید اون رو توی آرایهٔ middlewareGroups تعریف بشه.
یک routeMiddleware داریم که در واقع یک آرایهٔ associative هست و میتونیم برای یک middleware یک اسم انتخاب کنیم (در واقع کلیدها هستند) و هر جایی که نیاز بود روی route های خودمون تنها با یک نام اعمالشون کنیم.
توی آرایهٔ middlewarePriority هم اولویت middlewareها رو تعریف میکنیم.
برای مثال ما middleware خودمون رو که بالاتر تعریف کرده بودیم؛ توی آرایهٔ routeMiddleware توی سطر آخر اضافه کردیم:

به این کار میگیم، ثبت یا رجیستر کردن middleware.
برای استفاده از middleware در route:

خب، وقتی که یک middleware تازه تعریف شده رو روی یک route اعمال میکنیم؛ متد handle مقدار زیر رو return میکنه، بدون اینکه فیلتر خاصی اعمال کرده باشه و این ما هستیم که باید با توجه به نیاز خودمون تابع یا متد handle رو کامل کنیم:
return $next($request)
توی تصویر زیر، اومدیم چک کردیم که آیا request کاربر شامل name هست یا خیر و اگر نبود با استفاده از abort helper function اومدیم و صفحهٔ 404 یا not found رو نشون دادیم:

یک نکته دیگه اینه که ما میتونیم یک مقدار هم به middleware پاس بدی:
Route::get('/', 'HomeController@index')->middleware('name:par1,par2');
برای دسترسی به این مقدار در middleware باید یک متغیر به متد handle اضافه کنیم:
public function handle($request, Closure $next, $par1, $par2){ .... return $next($request); ... }
۱- نوع before:
همون نوعی که توی مطالب پیشین ازش استفاده کردیم؛ یعنی چک کردیم که اگر یک درخواست یا request اوکی بود؛ بریم برای ادامهٔ کار.
۲- نوع after:
برای زمانی هست که درخواست با request کاربر پردازش شده و قراره یک پاسخی برای کاربر خودمون ارسال کنیم؛ در این حالت باید از after استفاده کنیم. این middleware روی response اعمال میشه. مثلا قبل از فرستادن پاسخ، بیاییم و یه چیزهایی رو به header اضافه کنیم.
۳- نوع terminate:
مدل پایانی بلافاصله بعد از ارسال پاسخ به مرورگر فراخوانی و اجرا میشه.



توی داکهای خود لاراول باید وارد بخش security و authentication بشیم تا به محتوایی که تیم لاراول برای کار با این امکان فراهم کرده دسترسی پیدا کنیم.
با دو تا دستور ساده میتونیم Authentication رو به سیستم خودمون اضافه کنیم؛ با این کار scaffolding سیستم authentication به app ما اضافه میشه:
composer require laravel/ui // for compiling bootstrap files (like css, js...) npm run dev php artisan ui bootstrap --auth
بعد از اجرای دستور دوم، scaffolding بوت استرپ به همراه سیستم authentication به package.json اضافه میشه و ما تنها باید با دستور زیر، اون نیازمندیها رو به پروژه اضافه کنیم:
npm install && npm run dev
با اجرای دستورات بالا، تغییراتی در پروژه اعمال میشه که شامل اضافه شدن ۲ تا route به web.php و همینطور اضافه شدن کنترلرهای سیستم auth در مسیر app/Http/Controllers/Auth هست.

همین طور پوشهٔ auth به پوشهٔ views در resources اضافه شده.
حالا اگر وارد root route بشیم میبینیم که به view ما login و register هم اضافه شده.
حالا میخوایم ببینیم که کد Auth::routes() چه چیزهایی رو به سیستم روتینگ ما اضافه کرده، برای این کار کافیه که از artisan کمک بگیریم:
php artisan route:list
حالا چیزی شبیه به این خواهیم داشت:

اگر وارد مسیر vendor/laravel/ui/src بشیم؛ فایلی داریم به نام AuthRouteMethodes.php که routeهای مربوط به Auth::routes() درش جنریت شده:

توی تصویر بالا داریم میبینیم که اول مثلا چک میکنه که register ست شده هست یا خیر که به صورت دیفالت ست شده و اگر ست شده بود روتهای اون رو میسازه، ما با دادن یک associative array به تابع routes میتونیم جنریت شدن اون رو غیر فعال کنیم:
Auth::routes(['register' => false]):
اگرم قصد نداریم که از routeهای ساخته شده به وسیلهٔ Auth::routes() استفاده کنیم؛ کافیه که به صورت دستی داخل web.php روتهای خودمون رو تعریف کنیم.
ما حق دستکاری مستقیم روتها داخل vendor رو نداریم؛ چرا که ما با composer.json میام و این فولدر رو ایجاد میکنیم و اصلا این پوشه نباید به کس دیگهای داده بشه و خود به خود از composer.json ایجاد میشه.
اما نکته ای هم که در مورد روتهای ایجاد شده به وسیلهٔ Auth وجود داره اینه که وقتی وارد اون کنترلر میشیم؛ ممکنه که متدی که وابسته به اون روت هست رو به شکل مستقیم نبینیم؛ اما اون متد با use شدن از یک ترد خاص در کنترلر مورد استفاده قرار میگیره، مثلا:
use AuthenticatesUsers;
حالا بریم سراغ، کنترلرهای سیستم authentication، اولین کنترلری که برسی میکنیم؛ کنترلر register خواهد بود. سیستم auth لاراول دو تا روت register داره که یکی از اونها به صورت get هست و دیگر post، در واقع get برای نمایش صفحهٔ register هست و post برای ثبت یک کاربر جدید در دیتابیس.
در واقع متدهای سیستم auth لاراول که توی route ها میبینم از طریق فایلهایی که در مسیر vendor/laravel/ui/auth-backend قرار گرفتن، توی کنترلر use میشن و از این طریق از اونها استفاده میکنیم:

خب، از اونجایی که ما حق نداریم به شکل مستقیم توی vendor دست ببریم؛ باید توی همون کنترلر مد نظر که فایل auth-backend مورد استفاده (use) قرار گرفته، بیاییم و اون متد خاص رو override کنیم.
با اینکه امکان تغییر تمامی این موارد وجود داره، اما بهتره که از سیستم دیفالت لاراول استفاده کنیم.
توی کنترلرهای auth که توی app/Http/Controllers/Auth قرار دارن اگر متد validator ای وجود داشته باشه، کاملا قابل config شدن هست و میتونیم validation های خاص خودمون رو اعمال کنیم. طبیعتا توی RegisterController.pph این متد هست (حال ندارم متدهای دیگه رو دستی چک کنم!).
آها، توی جلسات قبلی، توی مبحث migration و... با جدول users که خود لاراول به شکل دیفالت مایگریشن و... اون رو می سازه آشنا شدیم؛ حالا کافیه که migration رو با artisan انجام بدیم تا این جدول توی دیتابیس ساخته بشه و حتی می تونیم با seeder یا factory اون رو از دیتای مهمل پر کنیم!
حالا اگر وارد روت register بشیم؛ خیلی راحت میتونیم ثبت نام و لاگین کنیم.
توی سازنده HomeController از میدلور auth استفاده کردیم تا چک کنه که لاگین صورت گرفته یا خیر و اگر صورت نگرفت دوباره ریدایرکت میکنه به صفحهٔ لاگین.