تذکر: این یک پست آموزشی برای عموم نیست! بلکه تنها جایی برای یادداشتهای من حین یادگیریه تا بهتر به خاطر بسپارم و در صورت لزوم به اونها مراجعه کنم.
قسمت ۹۴ تا ۱۰۶
برای مطالعهٔ مستندات این بخش باید بریم سراغ Frontend و بخش Localization.
با استفاده از این قابلیت می تونیم سایت خودمون رو چند زبانه کنیم.
خب ما داخل view های خودمون ممکنه که یک سری متن static داریم؛ حالا اگر سایت ما چند زبانه باشه، با تغییر زبان باید این متنها تغییر کنن، پس چاره چیه؟ اگر خاطرمون باشه توی پوشهٔ resources/lang/en یه سری فایل داشتیم که توش آرایههای associative برگشت داده میشد و ما قبلا توی بحث validation اون رو به فارسی ترجمه کرده بودیم؛ حالا برای هندل کردن این قضیه به راحتی می تونیم یک فایل حالا به هر اسمی که دوست داشتیم؛ مثلا static_texts.php ایجاد کنیم و مثل سایر فایلهای داخل en بیاییم و یک آرایهٔ associative رو return کنیم:
// static_texts.php file return [ 'welcome' => 'This is a welcome text!' ]
حالا برای دسترسی به این متن در داخل view کافیه از یک helper function به اسم __ استفاده کنیم:
// in blade {{ __('static_texts.welcome') }}
حالا برای متنهای فارسی کافیه یک پشه به اسم مثلا fa ایجاد کنیم و دقیقا همون فایل static_texts.php رو کپی میکنیم ولی value ها رو فارسیسازی میکنیم. اما نیاز داریم که یک config داشته باشیم تا از پوشهٔ fa این فایل رو بخونه، برای این کار وارد پوشهٔ config و فایل app.php میشیم و مقدار key مربوط به locale رو به fa تغییر می دیم. حالا اگر توی fa یک کلیدی نباشه، میره از پشتیبان که fallback_locale باشه میخونه و اگر اونجا هم نباشه، دقیقا آرگومان خود __ رو به ورودی میفرسته.
روش دیگه ای برای localization:
توی این روش دیگه به این شکل filename.key متن رو نمیخونیم، و فقط از یک رشته استفاده میکنیم، یعنی فقط key، حالا این key از کجا خونده میشه؟ داخل پوشهٔ resources/lang باید به نام زبانهایی که داریم فایل json ایجاد کنیم؛ مثلا en.json یا fa.json و توی این روش، برنامه میاد و از config مقدار locale رو چک میکنه و طبق اون متن رو بر میگردونه:
// how to load text using __ in view {{ __('your key in json') }}
محتویات فایل json:
// en.json file { "key": "value" } // fa.json file { "key": "مقدار" }
معرفی یک متد کاربردی برای تغییر locale:
App::setLocale('en');
یک متدی داریم برای چک کردن نوع locale:
App::isLocale('en');
// send argument in view to lang files {{ __('static_texts.welcome', ['name', => 'Aref']) }}
نحوه دریافت آرگومان در فایلهای زبان:
// if you write :NAME, the result will be uppercase return [ 'welcome' => 'your argument will placed here :name' ];
گاهی پیش میاد که ما قراره یه سری ریکورد از دیتابیس بخونیم و مثلا یه پیام نشون بدیم که ۱۰ تا کاربر پیدا شد، یا مثلا کاربری پیدا نشد یا ۱ کاربر پیدا شد. مشکل بیشتر مربوط میشه به زبان انگلیسی:
1 user has been found. no user has been found. 10 users have been found.
همون طور که می بینیم ساختار جملات با توجه به تعداد عوض شده، در اینجا اگر قرار بود از core php استفاده کنیم؛ مجبور بودیم یه ساختار شرطی برای هندل کردن این قضیه ایجاد کنیم. اما لاراول تابعی داره به اسم trans_choice که به ما کمک میکنه با توجه به تعداد، متن خاصی رو از فایلهای lang برگشت بدیم:
// how to use trans_choice in view {{ trans_choice('static_texts.users', 2) }} // lang file configuration return [ 'users' => '{1}...|[2,9]...|[10,*]...' ]
مبحث Pagination:
صفحه بندی :)
خب، برای درک بهتر این قضیه، توی جدول posts در دیتابیس حدود ۵۰ تا ریکورد اضافه کردیم و میخوایم اون ها رو توی view به نمایش بذاریم؛ توی کنترلر میاییم کل ریکوردها رو میخوانیم و با compact و view helper function اون ها رو توی خرورجی نمایش میدیم:
محتویات فایل view:
توی فایل view یک foreach directive قرار دادیم که میاد تک تک پستهای ما رو داخل یک card قرار میده.
توی تصویر زیر هم نمایی از جدول posts رو داریم:
توی تصویر زیر هم نمونهای از خروجی کار رو میبینیم:
اما مشکل اینه که وقتی ریکوردها زیاده، کلی باید اسکرول کنیم و لود شدن اونها هم خودش معضلی هست؛ برای این کار میاییم و از pagination استفاده میکنیم.در قدم اول توی کنترلر به جای فراخوانی متد all روی فساد Post میاییم و از متد paginate استفاده میکنیم.
توی تصویر زیر، حالا اومده به روشهای مختلف دیتا رو از توی دیتابیس خونده و در نهایت روی اونها متد paginate رو صدا زده:
نمونهای از خروجی کار که البته dd شده اما در ادامه ui رو هم ردیف میکنیم:
نمایش صفحهبندی در view:
برای ایجاد دکمه های pagination کافیه که توی فایل blade بنویسیم:
// how to show pagination buttons {{ $posts->links() }} // how many page number around the current page {{ $posts->onEachSide(3)->links('vendor') }}
خروجی کار:
علاوه بر paginate یک متد دیگهای داریم به اسم simplePaginate که دیگه اعداد رو نشون نمیده و فقط توی view یک next و previous خواهیم داشت.
خب ما توی پروژه داریم از بوت استرپ استفاده میکنیم و فایلهای مربوط به paginate بوت استرپ هم داخل مسیر زیر قرار داره:
vendor/laravel/framework/src/Illuminate/Pagination/resources/views
اما ما حق دست بردن در فایلهای داخل پوشهٔ vendor رو نداریم؛ پس چاره چیه؟
باید از artisan کمک بگیریم تا بیاد و برای ما یک نسخه از فایل paginate بوت استرپ داخل vendor رو داخل پوشهٔ ریسورسها puslish کنه:
php artisan vendor:publish --tag=laravel-pagination
خروجی دستور بالا:
حالا اگر وارد resources/views/vendor/pagination بشیم؛ میتونیم فایلهای blade مربوط به pagination رو ببینیم و تغییر بدیم:
خب حالا چطور فایل pagination خودمون رو تغییر بدیم؟ مثلا فایل default.blade.php رو ران کنیم؟ کافیه که وقت فراخونی لینکها از دستور زیر استفاده کنیم:
// load another pagination file {{ $posts->onEachSide(3)->links('vendor.pagination.default') }}
خروجی کد بالا، view زیر هست که هیچ استایل و کلاسی نداره:
البته ما میتونیم به راحتی یک فایل دلخواه برای pagination ایجاد کنیم و مثلا بذاریمش توی پوشهٔ views و بهش دسترسی داشته باشیم؛ اما برای اینکه کار خودمون رو راحت کنیم و نیازی نباشه که هر سری بگیم ما میخوایم از فلان pagination استفاده کنیم؛ کافیه از AppServiceProvider کمک بگیریم و به شکل زیر عمل کنیم:
خود Paginator هم باید داخل AppServiceProvider استفاده - use - بشه:
use Illuminate\Pagination\Paginator
البته Paginator متدهایی دیگهای هم داره مثل:
Paginator::defaultSimpleView('simplePagination');
وقتی که ما روی مدل Post متد paginate رو ران میکنیم؛ در واقع یک شی از نوع paginator ایجاد کردیم که یه سری متد داره مثل hasPages یا onFirstPage که اگر داخل فایل blade رو نگاه کنیم؛ میبینیم که بر مبنای همین شی و directiveهای blade اومده pagination رو هندل کرده.
قسمت مهم بعدی، بحث appending هست؛ یعنی چی؟ فرض کنیم که url ما علاوه بر page=num یه سری پارامتر get دیگه هم داره، حالا اگر ما بین صفحات جابهجا بشیم، اون پارامترهای دیگه نادیده گرفته میشن، تکلیف چیه؟ اینجاست که بحث appending میاد وسط:
localhost:8000/home?search=keyword&page=10 // after navigate to another page localhost:8000/home?page=11
برای حل این مشکل:
// solve problem {{ $posts->appends(['search' => request('search')])->links() }}
اما اگر query strings ما زیاد بشه چه باید کرد؟ اینجا از متدی به اسم withQueryString استفاده میشه:
// with query string {{ $posts->withQueryString()->links() }}
دیگه نیازی نیست که ما دستی بهش query strings رو بدیم و خودش اتومات میاد و هندلش میکنه.
مبحث fragment:
فرض کنیم که یک مقالهٔ بلند بالایی رو توی یک سایتی خوندیم و رسیدیم به بخش نظرات، حالا میخوایم بریم صفحه دوم نظرت، در حالت عادی با رفتن به صفحه بعدی، دوباره صفحه از اول میاد و باید اسکرول کنیم تا بخش نظرات و ادامه ماجرا.
توی html یاد گرفتیم که میشه برای لینک دادن به یک بخش خاصی از صفحه از #id استفاده کرد؛ توی لاراول هم fragment همین کار رو میکنه، کافیه که id اون بخشی که قرار بریم توش رو بزنیم:
{{ $posts->fragment('comments')->links() }}
ذخیرهسازی لاگ در لاراول، خود لاراول از کتابخونهای به اسم monolog برای لاگ کردن استفاده میکنه.
مثلا در HomeController میخوایم یک لاگ ایجاد کنیم:
Log::info('message');
خب فساد Log متدهای زیادی داره مثلا info که یک پیام رو ذخیره میکنه و به نوعی یک سیستم طبقهبندی هست؛ اگر یک Error داشته باشیم؛ یعنی چیزی که مشکلزا باشه از error استفاده میکنیم.
تنظیمات مربوط به لاگ در پوشهٔ config و فایل logging.php ذخیره شده که دیفالت ما اینه:
'default' => env('LOG_CHANNEL', 'stack'),
حالا اگر وارد فایل env بشیم؛ میبینیم که مقدار LOG_CHANNEL برابر هست با stack، اما مفهوم این stack چیه؟
خب در واقع stack یک نوع log channel هست که این موارد توی کلید channels ذخیره شدن:
اگر نگاهی به تابع storage_path بندازیم؛ میبینیم که مسیر ذخیرهسازی فایلهای لاگ ما اینه:
logs/laravel.log // but the final path will be storage/logs/laravel.log
ما میتونیم channels مربوط به stack رو به جای single به daily تغییر بدیم؛ با این کار ذخیرهسازی در همان مسیر انجام میشه با این تفاوت که لاگ هر روز متفاوته، اما اگر از نوع سینگل باشه، کل لاگها درون یک فایل ذخیره خواهد شد:
نکته قابل توجه در مورد stack اینه که وقتی ما داریم channels رو ست میکنیم؛ چون کانالها رو به صورت آرایه دریافت میکنه، میتونیم بهش هم daily بدیم و هم single و یا حتی سرویسهایی مثل slack.
توی داک خود لاراول اومده و انواع درایورها رو توضیح داده:
ارسال پارامتر به لاگ:
Log::error('An error occurred during creating new user', ['id' => 3]);
مبحث Ignition Page (صفحهٔ ارور لاراول):
فرض کنید داخل یکی از کنترلرهای خودمون اومدیم و یک متغیر رو dd کردیم بدون اینکه از قبل تعریف و مقداردهی شده باشه، خب طبیعتا با مشکل مواجه میشیم و وارد صفحهای میشیم که به ما ارور نشون میده، این صفحه در واقع Ignition Page لاراول هست؛ یا صفحهٔ «آتشسوزی!»، این Ignition page در واقع یک ابزار خارجی هست برای مدیریت Exception ها در لاراول که میتونیم از سایتش دریافت و در پروژههای PHP ازش استفاده کنیم:
flareapp.io/ignition
این Ignition page از ورژن ۶ به بعد اضافه شده و در ورژنهای قبلی متفاوت بوده. البته نسخه Pro هم داره که یه سری ویژگیهاش پولی هست و باید خریداری بشه.
خود لاراول هم اگر مشکلی باشه، مثل Error و Warning و اینا، میاد داخل log فایلها قرار میده و میتونیم چک کنیم. البته باید توجه داشته باشیم که ترتیب قرارگیری لاگها برعکسه چیزی هست که مثلا توی پایتون میبینیم؛ یعنی اولین خط از سری ارورهای جدید مربوط میشه به آخرین کد اجرا شده.
اما وقتی پروژهٔ ما قراره روی سرور پروداکشن deploy بشه، باید جلوی نمایش صفحهٔ Ignition رو بگیریم. چرا؟ چون کلی اطلاعات از پروژه به بازدیدکنندگان و طبعا هکرها میده که میتونن از اون سو استفاه کنند؛ چار چیه؟ باید غیر فعالش کنیم؟
برای این کار باید وارد فایل env بشیم و مقدار APP_DEBUG رو برابر با false قرار بدیم. حالا دیگه لاراول یه سری ارور نشون میده که خیلی کلی هست؛ مثلا ارور ۵۰۰ که برابر با server error هست و ما باید داخل log ها دنبال ارور اتفاق افتاده باشیم.
لاراول داخل مسیر app/Exceptions/Handler.php میاد و exception ها رو هندل میکنه؛ این فایل دو تا متد داره به اسم report و render که report میاد و خطا رو گزارش میکنه و در حالت دیفالت لاگ میندازه و رندر میاد و کارهایی که باید بعد از exception اتفاق افتاده رو هندل میکنه، مثلا صفحهٔ ۵۰۰ رو نشون میده یا ۴۰۴ و... .
توی فایل Handler.php یه سری آرایه از نوع protected داریم مثل dontFlash و dontReport که توی dontReport ما exceptionهایی رو قرار میدیم که نمیخوایم متد report برای ما گزارش کنه، مثلا لاگ بندازه و توی dontFlash مربوط به old میشه که اگر خطایی اتفاق افتاد، دوباره فرم رو با همون مقادیر قدیمی پر کنه، حالا به صورت دیفالت password و password_confirmation از این ماجرا استثنا شدن، چرا که داخل این آرایهٔ dontFlash قرار گرفتن، حالا ما میتونیم چیزهایی دیگه ای که دلمون میخواد رو اینجا بذاریم مثلا یک input با name برابر با family، اینطوری دیگه بعد از اتفاق افتادن خطا و ریفرش صفحه دیگه اون فیلد پر نمیشه.
php artisan make:exception MyException
این فایل در پوشهٔ Exception قرار میگیره:
روش اجرای exception سفارشی ما:
که البته باید کلاس MyException هم use بشه:
use App\Exceptions\MyException;
و خروجی Exception که اتفاق افتاده در Ignition:
خب ما میتونیم با تغییر توابع report و render نحوه نمایش ارورها رو تغییر بدیم. چطوری؟ خب وقتی یک exception اتفاق می افته، اول وارد تابع report میشه پس ما میتونیم مثلا این حرکت رو بریم:
خب اینجا ما اومدیم و گفتیم که اگر خطای رخ داده از نوع MyException بود؛ جملهٔ Error ... رو توی لاگ بنویس و دیگه ادامه پیدا نکن، وقتی ما خودمون دستی چیزی رو توی لاگ قرار میدیم و دیگه خطا رو به report واگذار نمی کنیم؛ دیگه trace رو به ما نشون نمیده.
ما حتی میتونیم جلوی نمایش ارورهای Exception خودمون رو بگیریم:
البته برای چک شدن dontReport حتما باید parent::report اجرا بشه.
دقیقا همین کارهایی رو که در بالا انجام دادیم میتونیم توی متد render هم انجام بدیم:
اما اگر تعداد exceptionهایی که قراره چک کنیم زیاد بشه، کار مشکل میشه، به خاطر همین میایم و از کلاس Exception خودمون استفاده میکنیم و از اونجا متدهای report و render رو صدا میزنیم.
در واقع با این کار ما اومدیم و از داخل HomeController این Exception رو فراخوانی کردیم؛ اگر داخل کلاسی که تعریف کردیم؛ متد report یا render رو تعریف نکنیم؛ این Exception میاد و کلاسها رو از parent خودش میخونه، اما در بالا ما اومدیم و متد report رو override کردیم. با این کار دیگه کنترل Exception اومده به دست چیزی که ما سفارشی کردیم.
try { //code...; } catch (Throwable $th) { //throw $th; }
توی کد بالا، Throwable ما در واقع MyException هست و توی قسمت catch ما اومدیم و اون error رو هندل کردیم؛ شی ex رو اگر dd بگیریم یه سری property داره، با استفاده از متد getMessage میتونیم به پیام error دسترسی داشته باشیم.
از اونجایی که ما داریم از بلاک try...catch استفاده میکنیم؛ دیگه متد report مربوط به Exception را نمیشه و اگر نیاز باشه که report هم اجرا بشه، ما باید از report helper function استفاده کنیم.
این ها در واقع همون HTTP Status Codes هستن:
تصویری از مهمترین status codeهای مربوط به HTTP:
توی لاراول یک helper function داریم به اسم abort (یعنی صرف نظر کردن، متوقف کردن) که میاد و طبق عدد int ای که بهش می دیم برای ما یک view مربوط به status codes نشون میده:
نتیجه:
ما برای اینکه view خودمون برای status codeها داشته باشیم؛ میتونیم یک پوشه به اسم errors داخل views بسازیم و یک فایل blade هم نام با status code هایی که میخوایم تغییر بدیم قرار میدیم:
404.blade.php