تذکر: این یک پست آموزشی برای عموم نیست! بلکه تنها جایی برای یادداشتهای من حین یادگیریه تا بهتر به خاطر بسپارم و در صورت لزوم به اونها مراجعه کنم.
قسمت ۱۰۷ تا ۱۲۳
لاراول از کتابخونهٔ Swift Mailer برای ارسال ایمیل استفاده میکنه.
تنظیمات mail در لاراول:
باید وارد env و بخشی بشیم که با MAIL شروع میشه:
همونطور که توی تصویر بالا داریم میبینیم MAIL_MAILER ما از نوع پروتکل smtp یا simple mail transfer protocol ست شده، حالا اگر وارد پوشهٔ config و فایل mail.php بشیم؛ میبینیم که default همین smtp ست شده، اما میشه از سرویسهای بیرونی دیگه هم استفاده کرد، مثل ses و... توی این فایل چیزهایی مثل from و... هم از env خونده میشه.
php artisan make:mail TestMail
این کلاس در پوشهای به نام Mail در app ایجاد میشود. کلاس میل ما از کلاس مادری Mailable ارثبری داره. این کلاس یک متد داره به اسم build که عملیات ارسال ایمیل رو انجام میده. داخل این متد یک view برگشت داده میشه.
خب حالا ما چطوری این کلاس رو صدا بزنیم تا کار ارسال ایمیل رو برای ما انجام بده؟
خیلی ساده میتونیم داخل یکی از کنترلرهای خودمون مثلا HomeController به شکل زیر صداش کنیم:
Mail::send(new TestMail());
و در متد build مقدار زیر رو return میکنیم:
return $this->view('your.view')->subject('Your subject')->to('aref@gmail.com'); // instead of view method, we can use text, the text method does not render // the HTML codes, just return whatever in the blade file
متنی که ایمیل میشه، در واقع چیزی هست که داخل viewی ما اومده.
توی بحث View Data ما میخوایم اطلاعات رو از کنترلر که اون رو مثلا از یه مدلی دریافت کردیم؛ به کلاس ایمیل خودمون ارسال کنیم و از اونجا مثلا پاس بدیم به view مربوط به ایمیل تا برای ما ایمیل رو ارسال کنه، اینجا توی کنترلر یک user رو از مدل User خوندیم و به عنوان پارامتر به کلاس TestMail که خودمون ایجاد کردیم؛ ارسال کردیم:
حالا این پارامتر رو در constructor دریافت میکنیم و اون رو به یک متغیر کلاسی assign میکنیم:
حالا خیلی ساده، میتونیم از این property در هر قسمتی از این کلاس که میخوایم استفاده کنیم؛ یا حتی پاسش بدیم به view خودمون، اما جالب اینه که پاس دادن این پارامتر به view به صورت دیفالت انجام میشه و کافیه که ما در view اینطوری بهش دسترسی داشته باشیم:
اینم تصویری از build:
برای این کار ما از متد attach روی شی TestMail استفاده میکنیم. خب ما اینجا فایل خودمون رو در storage/app/public قرار میدیم و اینطوری از دید کاربران مخفی میمونه. حالا یک فایل در public ایجاد میکنیم با اسم myfile.txt و اینطوری داخل build عملیات attaching رو انجام می دیم:
** نکته: توی سرورهای ویندوز، بزرگی و کوچیکی حروف در path مسالهٔ مهمی نیست؛ اما در لینوکس حروف بزرگ و کوچیک با هم متفاوت هستن و اسلش و بک اسلش هم که مشخصه، اما لاراول slash و back-slash رو خودش هندل میکنه و بهتره که از slash استفاده کنیم.
توی تصویر بالا، همونطور که مشخصه اومدیم روی کلاس TestMail که با this بهش اشاره کردیم متدهایی رو ران کردیم که یکیش attach هست؛ به این متد هم تابع storage_path رو به همراه مسیر فایلی که درون مسیر storage هست پاس دادیم و از این طریق به راحتی فایل رو بهش ضمیمه کردیم.
اگر بخوایم چند تا فایل رو ضمیمه کنیم؛ باید دوباره یک متد attach روش بزنیم. این متد به عنوان آرگومان دوم یک آرایه می گیره که میتونیم توش یه سری تنظیمات داشته باشیم، مثلا با کلید as میتونیم اسم فایلی که ارسال میشه رو rename کنیم:
یعنی قبل از ارسال، یک پیشنمایشی از ایمیل داشته باشیم. برای این کار یک Route تعریف کردیم برای preview گرفتن از ایمیلهای خودمون:
اپ آنلاین برای استایلدهی به مارک داون
وقتی میخوایم یک کلاس میل بسازیم با استفاده از آپشن -m میتونیم قالب رو از نوع Mark Down تعریف کنیم.
php artisan make:mail MarkDownMail --markdown emails.test-markdown
اینم از متد build کلاس MarkDownMail:
تصویری از فایل test-markdown که توش view ما قرار گرفته:
اینجا ما یه سری component داریم؛ قبلا در مورد نحوه call یا فراخوانی componentهای خونده بودیم؛ اینطوری می اومدیم و از پوشهٔ resources/views/components اطلاعات رو میخوندیم؛ اما توی فرمت بالا از :: استفاده شده که معنی و مفهوم دیگهای داره:
@component('components.alert')
خب معنی این :: اینه که میاد و دیتا رو از vendor برای ما واکشی میکنه، اما برای تغییر این کامپوننتها ما مجاز به تغییر vendor نیستیم و باید توی resources/views اون رو publish کنیم:
php artisan vendor:publish --tag=laravel-mail
خب، حالا از دایرکتوری اصلی vendor فایلهای mail کپی شدن به پروژهٔ ما:
ما قبلا برای pagination این کار رو کرده بودیم؛ حالا توی vendor پروژهٔ خودمون دو تا پوشهٔ mail و pagination رو داریم که توی mail هم html و text داریم.
وقتی که میگیم mail::message میره و دنبال فایل message.blade.php داخل mail میگرده:
نمایش اعلانهای مختلف، روی کانالهای مختلف. مثلا از طریق ایمیل، sms یا ذخیرهسازی در دیتابیس و نمایش در یک view و حتی ارسال با تلگرام (با کتابخونه و API).
برای ایجاد notification باید از artisan کمک بگیریم:
php artisan make:notification PostPublished
بعد از اجرای این دستور یک پوشه به اسم Notifications داخل پوشهٔ app ایجاد میشه که داخل اون یک فایلی هست به نام PostPublished.php که در واقع همون اسمی هست که برای کلاس notification خودمون انتخاب کرده بودیم.
این کلاس، مثل همهٔ کلاسها یک سازنده داره که میتونیم بهش متغیر پاس بدیم و همچنین یک متد داره به اسم via که یک آرایه برمیگردونه:
وقتی که داخل آرایه mail داریم؛ notification ما از طریق ایمیل ارسال میشه.
حالا هر کانالی که دوست داشتیم رو کافیه به این آرایه اضافه و کانفیگ کنیم تا برای ما ارسال رو انجام بده:
return ['mail', 'database', 'sms'];
متدی داریم به اسم toMail، وقتی که ما داخل via میایم و mail رو به عنوان یکی از کانالهای ارتباطی انتخاب میکنیم؛ باید متد toMail رو هم به این کلاس اضافه کنیم (به صورت دیفالت هست). یا مثلا متد toArray هم داریم که برای ذخیرهسازی در دیتابیس هست.
اگر بخوایم یک پیامی رو از طریق ایمیل ارسال کنیم؛ باید از متد toMail استفاده کنیم؛ این متد یک کلاس از نوع Mail به اسم MailMessage رو return میکنه که البته این کلاس در Core لاراول قرار داره و نیازی نیست که ما ایجادش کنیم.
توی تصویر زیر، یک instance از کلاس MailMessage ایجاد کردیم و روش متدهای line و action رو فراخوانی کردیم؛ متد line یک خط به ایمیل ما اضافه میکنه و متد action یک button.
این line و action قابل customize شدن هستند.
روشهای مختلفی برای ارسال notification وجود داره، مثلا یکی از روشها استفاده از Notifiable هست؛ ما معمولا میخوایم برای userهای خودمون پیام ارسال کنیم؛ بنابراین، باید توی مدل User بیاییم و Notifiable رو use کنیم:
معمولا به صورت دیفالت این Notifiable داخل مدل User مورد استفاده قرار میگیره.
حالا میخوایم داخل کنترلر، به User شماره فلان یا User که لاگین کرده یه ایمیل ارسال کنیم. برای این کار از کدهای زیر در کنترلر استفاده میکنیم:
$user = auth()->user(); // don't forget to use PostPublished $user->notify(new PostPublished());
متد notify میاد و متد via کلاس PostPublished رو چک میکنه، و مثلا وقتی میبینه که داخل آرایه نوشته شده mail، حالا متد toMail رو ران میکنه و یک ایمیل به کاربری فعلی ارسال میکنه.
روش دومی که میتونیم برای ارسال notification استفاده کنیم؛ استفاده از Notification facade هست:
$user = auth()->user(); Notification::send($user, new PostPublished()); // simply send to all users $users = User::all(); Notification::send($users, new PostPublished()); // send parameters to PostPublished Notification $users = User::all(); Notification::send($users, new PostPublished('something'));
۱- ایجاد کلاس Notification با artisan
۲- تعریف mail در via
۳- دریافت پارامتر در constructor
۴- استفاده از Notifiable در مدل مد نظر
۵- ارسال پیام با متد notify مربوط به Notifiable یا فساد Notification
بریم برای درک بهتر متد toMail:
متد toMail یک آرگومان ورودی داره به اسم notifiable که اگر ازش dd بگیریم، متوجه میشیم که این متغیر در واقع همون userای هست که قراره براش ایمیل رو ارسال کنیم.
برای اینکه بتونیم notification خودمون رو customize کنیم یعنی view اون رو تغییر بدیم؛ باید فایل vendor رو دستکاری کنیم؛ اما چون نباید خود پوشه vendor رو دست بزنیم؛ بنابراین، باید مثل Mail و Pagination عملیات publish رو انجام بدیم؛ به این صورت:
php artisan vendor:publish --tag=laravel-notifications
خب حالا اگر وارد resources/views/vendor/notifications بشیم؛ میبینیم که فقط یک فایل email.blade.php هست و میتونیم با تغییر اون به فرم دلخواه خودمون برسیم. این فایل در واقع داره از componentهای mail استفاده میکنه و با تغییر در اونها میتونیم view رو دستکاری کنیم.
ما در متد toMailبه راحتی میتونیم از view هم به جای template دیفالت استفاده کنیم:
php artisan make:notification MarkdownNotification --markdown emails.markdown-notification
با این دستور یک کلاس جدید به پوشهٔ Notifications داخل app اضافه شده به اسم MarkdownNotification.php که مثل همون PostPublished هست؛ اما تنها تفاوتش در متد toMail هست که به جای line و action یا view از متد markdown استفاده کرده:
اینم از template مربوط به markdown-notification:
داخل HomeController خودمون میتونیم با دستور زیر یک Notification ارسال کنیم:
$user = auth()->user(); Notification::send($user, new MarkdownNotification());
خب، اول از همه باید ببینیم که فایلهای مربوط به اینها کجا هستن؟ اگر مسیر زیر رو دنبال کنیم:
vendor/laravel/framework/src/Illuminate/Auth/Notifications/
میتونیم توش دو تا فایل زیر رو ببینیم:
ResetPassword.php VerifyEmail.php
توی متد toMail ما این رو داریم:
حالا کافیه برای هر یک از این جملات داخل فایل fa.json خودمون یک معادل قرار بدیم.
برای تغییر استایل هم میتونیم فایل زیر رو تغییر بدیم:
resources/views/vendor/mail/html/themes/default.css
برای این کار باید یک جدول در دیتابیس ایجاد کنیم؛ در واقع دستور زیر میاد و مایگریشن این جدول رو برای ما ایجاد میکنه:
php artisan notifications:table
تصویری از خود Schema جدول:
نکته: uuid الگوریتمی برای ایجاد یک id یکتا هست.
نکته: برای اینکه timestamp ما دقیق باشه، باید توی config/app.php مقدار timezone رو به Asia/Tehran تغییر بدیم.
خب حالا به راحتی migrate میزنیم که توی دیتابیس بشینه:
php artisan migrate
توی این جدول type رو داریم که نوع notification رو مشخص میکنه و همینطور داخلش یک رابطهٔ پلیمورفیک داریم که برای ما ستونهای notifiable_id و notifiable_type رو ایجاد میکنه، ستون data داریم برای ذخیرهسازی دیتا و پیام هایی که برای کاربر ارسال میکنیم و ستون read_at که از نوع timestamp هست و زمان خوانده شدن پیام توسط کاربر توش ذخیره میشه و در نهایت timestamp که updated_at و created_at رو اضافه میکنه.
تصویر زیر نمایهای جدول ایجاد شده در دیتابیس رو نشون میده:
برای ذخیره notification توی دیتابیس، باید تغییراتی در متد via کلاس notification خودمون بدیم، برای ایک کار علاوه بر email باید database رو هم به لیستی که return میشه اضافه کنیم و متد toArray رو اینطوری بنویسیم:
توی متد toArray اون چیزی که return میشه در واقع پیامی هست که میره و توی ستون data میشینه. ما داریم یک آرایه رو return میکنیم که توی دیتابیس به شکل json ذخیره میشه.
خب حالا توی HomeController درخواست ارسال notif رو میدیم:
Notification::send($user, new PostNotification($post));
حالا اگر وارد دیتابیس بشیم؛ میبینیم که پیام ما اینطوری ذخیره شده:
پروپرتی که میتونیم روی مدل User ران کنیم:
$user = User::find(2); $user->notifications;
که برای ما اینو برمیگردونه:
یه متد داریم برای اینکه فیلد read_at رو ست کنیم؛ به این معنی که کاربر پیام رو خونده:
$notification = $user->notifications->first(); $notification->markAsRead();
یک پروپرتی داریم که notifهای خوانده نشده (اون هایی که فیلد reat_at فاقد مقداره) رو برمیگردونه:
$user->unreadNotifications;
یک متد داریم که باهاش میتونیم یک notif رو پاک کنیم:
$notification->delete();
یا مثلا آپدیت یک notif:
$notification->update([]);
دریافت data که به صورت json توی db ذخیره شده در قالب Array:
$notification->data;
لاراول در اصل از یک کتابخونه به اسم Nexmo برای ارسال sms استفاده میکنه، اما توی ایران ظاهرا تحریم/فیلتر هستن و به درد ما نمیخوره، بنابراین باید از API سرویس های داخلی استفاده کنیم.
برای این کار ما میاییم و یک پوشه به اسم Channels داخل پوشهٔ app ایجاد میکنیم و داخل این پوشهٔ Channels یک فایل به اسم SmsChannel.php با محتویات زیر:
متد send داره ۲ تا ورودی میگیره، یکی $notifiable که در واقع همون شی ساخته شده از مدل ما میشه (مثلا User) و $notification که یک شی از کلاس Notification هست.
برای دریافت متن sms به وسیله متد send، یک متد داخل کلاس Notification ایجاد میکنیم که یک متنی رو return میکنه، حالا اینجا ما یه چیز ثابت گذاشتیم؛ اما میتونیم داینامیکش کنیم و از داخل HomeController یک مقداری رو پاس بدیم و داخل سازنده این کلاس دریافت و به جای Test Sms استفاده کنیم.
و به آرایه برگشتی via مقدار SmsChannel::class رو اضافه میکنیم.
حالا اگر یک پیامی رو notify کنیم؛ کانالهای داخل آرایه via فراخوانی میشن، که اینجا متد send کانال SmsChannel فراخوانی میشه و بهش مدل و notification پاس داده میشه و ما میتونیم روی شی notification مثلا تابع toSms رو ران کنیم و متن دلخواه رو دریافت کنیم.
(تا جایی که فهمیدم تقریبا اینطوری بود؛ اما دقیقا از مکانیسمش مطمئن نیستم باید با dd و... از ترتیب فراخوانیها سر در بیارم):
خب حالا از طریق مدل user میتونیم به شماره تلفن دسترسی داشته باشیم و آرگومان دوم هم متن پیام.
رویداد یا event، به حالتی گفته میشه که ما میخوایم منتظر باشیم؛ وقتی یک اتفاقی افتاد، یک کار خاصی رخ بده.
مثلا اگر فلان نویسنده یک پست منتشر کرد؛ برای مدیر سایت پیام ارسال بشه.
اصطلاحات مهم در این مبحث:
Event Raise/Dispatch/Fire Listener
در واقع listener گوش میده که اگر فلان اتفاق (event) افتاد؛ فلان عملکرد Dispatch بشه. مثلا اگر کاربران سفارش ثبت کردند؛ براشون sms یا ایمیل ارسال بشه.
هسته لاراول از Eventها زیاد استفاده میکنه، مثلا وقتی ما داریم با Eloquent یک دیتایی رو ایجاد میکنیم؛ لاراول داره گوش میده و با توجه به رخدادهایی که داریم؛ یک کار خاصی انجام میده.
ممکنه یک Event چندین listener داشته باشه.
سناریو: با ایجاد شدن یک پست یک ایمیل ارسال کن.
php artisan make:event CreatePost php artisan make:listener CreatePostEmail // you can add more listeners for a signle event
توی پوشهٔ app دو تا پوشهٔ جدید Events و Listeners اضافه شده، هر کدوم از این پوشهها شامل فایلهای php خودشون هستن که به ترتیب شامل CreatePost.php و CreatePostEmail.php هست.
نکته: Broadcast قابلیتی هست در لاراول که وقتی یک اتفاقی در سمت سرور رخ میده، اون رو به سمت client هدایت میکنه و برای این کار از یک کتابخونه به اسم pusher استفاده میکنه که یک کتابخونه برای ایجاد real time هست. مثلا توی سیستم چت، پیام میاد سمت سرور و سرور سریع باید اون پیام رو به سمت کلاینت بفرسته این بحث broadcast و سوکت میشه.
نکته: SerializesModel نوعی استاندارد فرمتبندی داده مثل json هست.
مهمترین متد در کلاس CreatePostEmail؛ متد handle هست؛ listener میاد و گوش میده که هر وقت event مربوط به CreatePost اجرا شد، متد handle رو برای ما صدا میزنه.
برای این کار باید وارد پوشهٔ Providers بشیم و EventServiceProvider.php رو باز کنیم؛ و event و listener مربوط رو داخل آرایهٔ $listen اضافه کنیم:
نکته: use کردن کلاسها فراموش نشه!
حالا اگر چند تا listener داشتیم؛ خیلی ساده میتونیم اون ها رو در کنار CreatePostEmail::class اضافه کنیم.
بریم برای dispatch یا fire کردن event:
برای این کار ما یک helper function داریم به اسم event() که به راحتی عمل dispatch رو اجرا میکنه:
event(new CreatePost());
وقتی این event اتفاق افتاد؛ Listener که فال گوش وایستاده بود؛ متوجه میشه و متد handle خودش رو صدا میزنه.
خیلی ساده میتونیم از HomeController دیتا رو پاس بدیم به constructor کلاس CreatePost یا همون Event خودمون و برای اینکه بتونیم به پارامترهای ارسالی داخل متد handle مربوط به کلاس CreatePostEmail دسترسی داشته باشیم کافیه از $event که به handle پاس داده میشه استفاده کنیم.
نکتهٔ دیگه اینکه اگر مقادیر دریافتی در سازنده CreatePost رو در متغیرهای private ذخیره کنیم؛ برای دستیابی به اونها داخل متد handle کلاس CreatePostEnail باید از getter استفاده کنیم.