<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های مصطفی لوائی</title><link>https://virgool.ir/feed/@mostafa.lavaei</link><description>یه برنامه نویس که دوست داره داشته هاشو در اختیار دیگران بذاره</description><language>fa</language><pubDate>2026-11-11 01:40:52</pubDate><image><url>https://files.virgool.ir/upload/users/77321/avatar/5yQuD9.jpeg</url><title>مصطفی لوائی</title><link>https://virgool.ir/@mostafa.lavaei</link></image><item><title>اضافه کردن دیسک به LVM</title><link>https://virgool.ir/@mostafa.lavaei/اضافه-کردن-دیسک-به-lvm-cjpo7p9lfkxk</link><description>یکی از شرکت‌هایی که در گذشته توش کار می‌کردم دچار مشکل کمبود دیسک توی یکی از ماشین‌ها شده بود و ازم کمک خواستن:چطوری می‌تونیم دیسک رو به سرور اضافه کنیم؟ از طریق VMware Vsphere دیسک اضافه کردیم ولی توی سرور تغییری ایجاد نشده و دیسک قابل استفاده نیست.این موضوع باعث شد که یه متنی راجع به این قضیه اینجا بنویسم.قبل از اینکه سراغ حل مشکل بریم، می‌خوام با یه مقدمه شروع کنم که LVM چیه؟در حالت عادی (بدون استفاده از LVM) وقتی شما یه دیسک به سرور اضافه می‌کنید، اونو باید توی پارتیشن جدا اضافه کنید و نمی تونید پاریتشن فعلی رو با استفاده از اون دیسک گسترش بدید. مثلا روی دیسک شماره یک دایرکتوری‌های مربوط به خود سیستم عامل (از جمله var و etc و bin و ..) هست و دیسک شماره دو توی دایرکتوری‌ای به اسم storage اضافه می‌شه. حالا فرض کنید که یه اپلیکیشن روی این سرور بالا اوردید. این پروژه توی مسیر /var/www قرار داره و حالا بعد از گذشت یک سال از راه اندازی ماشین، کل حجم دیسک پر شده و شما می‌خواید فضایی که اپلیکیشن می‌تونه استفاده کنه رو افزایش بدید. بدیهیه که اضافه کردن دیسک به مسیری غیر از /var/www برای شما آورده‌ای نداره و این مسیر با دیسک اول اشغال شده. پس باید چکار کنیم؟اینجاست که LVM به کمکتون میاد. هدف LVM اینه که پیچیدگی‌ها و محدودیت‌های سخت‌افزاری رو از دید لایه اپلیکیشن حذف کنه. در واقع از دید لایه اپلیکیشن شما می تونید فقط پارتیشن داشته باشید و این LVM باشه که وظیفه ذخیره‌سازی روی لایه سخت‌افزار رو به عهده بگیره.حالا برگردیم به مشکلی که داشتیم.من زمانی که توی اون شرکت کار می‌کردم ماشین رو تنظیم کرده بودم که از LVM استفاده کنه، پس الان کافیه که دیسک جدید رو به LVM بشناسونیم. (LVM باید زمان نصب لینوکس تنظیم شده باشه وگرنه روی سرور فعال نمی‌تونید ازش استفاده کنید)اول از همه باید اسم دیسک جدید رو پیدا کنیم:fdisk -lکامند بالا لیست همه دیسک‌ها (و پارتیشن‌ها) رو بهتون می‌ده. برای اینکه ببینید کدوم دیسک‌ها در حال حاضر توی LVM استفاده شدن باید کامند زیر رو بزنید:pvscanتوی ماشینی که من داشتم روش کار می‌کردم، کامند ‌fdisk دیسک‌های زیر رو نشون می‌داد (خروجی کامند خیلی شلوغه ولی با یکم دقت به خروجی می‌تونید اسم دیسک‌ها رو از توش پیدا کنید):/dev/sda&#xA;/dev/sdb&#xA;/dev/sdc&#xA;/dev/sddو کامند pvscan هم این دیسک‌ها رو نشون می‌داد:/dev/sda&#xA;/dev/sdb&#xA;/dev/sdcپس دیسک sdd جدید اضافه شده و باید به LVM شناسونده بشه.حالا باید دیسک sdd رو فرمت و پارتیشن بندی کنیم تا بتونیم ادامه مراحل رو بریم:کامند زیر دیسک رو فرمت می‌کنه، پس دقت کنید که حتما از روشی که گفتیم دیسک درست رو شناسایی کرده باشید!fdisk /dev/sddبا زدن کامند بالا یه سری سوال در مورد نحوه فرمت کردن دیسک از شما پرسیده می‌شه. اگر خیلی از این کامند سر در نمیارید نترسید، سوال اول رو با n و سوال دوم رو با p جواب بدید. ما بقی سوال‌ها رو هم بدون وارد کردن چیزی فقط Enter بزنید تا رد شن. وقتی سوال‌ها تموم شد در آخر w رو وارد کنید و Enter بزنید که عملیات فرمت کردن شروع بشه.وقتی دیسک sdd رو به روش بالا فرمت کنیم، یه پارتیشن به اسم sdd1 ساخته می‌شه که در ادامه ازش استفاده می‌کنیم.حالا نیازه که ما یه Physical Volume بسازیم (توضیح مفاهیم مربوط به LVM از جمله Physical Volume، Volume Group و Logical Volume خارج از این پست هست و پست رو طولانی می‌کنه). برای این کار از کامند pvcreate استفاده می‌کنیم:pvcreate /dev/sdd1همونطور که می‌بینید باید اسم پارتیشن رو به pvcreate بدیم، نه دیسک.حالا که ما یه Physical Volume داریم، باید اونو به Volume Group اضافه کنیم. ولی قبل از اون باید اسم Volume Group فعال رو پیدا کنیم (به طور پیش‌فرض فقط یه Volume Group فعال وجود داره مگر اینکه خودتون تعداد بیشتری ساخته باشید):vgscanکامند بالا براتون اسم Volume Group رو چاپ می‌کنه. اسم Volume Group من ubuntu-vg بود پس در ادامه از این اسم توی کامندهام استفاده می‌کنم.برای اضافه کردن پارتیشن sdd1 به Volume Group این کامند رو اجرا می‌کنیم:vgextend ubuntu-vg /dev/sdd1و حالا نیاز هست Logical Volume رو به اندازه دیسکی که اضافه کردیم گسترش بدیم. برای این کار باید اسم Logical Volume فعال رو بدست بیاریم:lvscanدر اینجا من آدرس زیر رو به عنوان خروجی گرفتم:/dev/ubuntu-vg/ubuntu-lvفرض کنید که حجم دیسکی که اضافه کردیم 70G هست، با این فرض کامند گسترش Logical Volume رو به این صورت باید بنویسیم:lvextend -L+70G /dev/ubuntu-vg/ubuntu-lvدر آخر برای اینکه نیاز به Restart کردن سرور نباشه کامند زیر  رو می‌زنیم:resize2fs /dev/ubuntu-vg/ubuntu-lvتموم شد. حالا فضای خالی سرورتون باید به اندازه 70G زیاد شده باشه.</description><category>مصطفی لوائی</category><author>مصطفی لوائی</author><pubDate>Tue, 13 Sep 2022 18:44:06 +0430</pubDate></item><item><title>ایجاد درایور لاگ در لاراول (ذخیره لاگ در دیتابیس)</title><link>https://virgool.ir/@mostafa.lavaei/ایجاد-درایور-لاگ-در-لاراول-ذخیره-لاگ-در-دیتابیس-hdsavy60salq</link><description>منبع عکس: sergeyzhuk.meلاراول یه فریم‌ورک خیلی منعطفه، شاید برای همینه که توی دنیای PHP داره یکه تازی می کنه. یکی از چیزهایی که خیلی به لاراول قدرت و انعطاف داده، استفاده از یک Design Pattern معروف به اسم Strategy هست. این Design Pattern این امکان رو فراهم می کنه که در حین اجرای برنامه،‌ کارکرد یک Class تغییر کنه. به عنوان مثال، شما حتما توی لاراول با دیتابیس کار کردید و می دونید که می تونید خیلی راحت از طریق .env مشخص کنید که می خواید به چه دیتابیسی متصل بشید و لاراول به طرز جادویی‌ای کوئری های منحصر به اون دیتابیس رو برای شما تولید می کنه. خیلی خوبه نه؟لاراول از این Design Pattern خیلی جاهای دیگه هم استفاده کرده، مثلا برای Log، Cache، Storage و ... .لاراول علاوه بر اینکه برای مواردی که گفتیم پیاده‌سازی های مختلفی رو همراه خود فریم‌ورک به شما تحویل می ده (مثلا می تونید از MySQL، PostgreSQL و ... به عنوان Database استفاده کنید)، به شما اجازه می ده Driver دلخواه خودتون رو هم بنویسید (مثلا درایور MongoDB که این پکیج پیاده‌‌سازیش کرده). توی این پست می خوایم یه درایور (یا به قول خود لاراول Channel) برای Log بنویسیم. هدف اصلی این پست، خوندن کدهای لاراول و سر در آوردن از کارشه، ولی داریم این کار رو با یه مثال عملی انجام می دیم. برای همین ممکنه یکم طولانی باشه، ولی اگه تجربه این کار رو ندارید، به نظرم ارزشش رو داره.برای شروع، یه پروژه لاراول تر تمیز می سازیم و توی routes/web.php یه چندتا لاگ می ذاریم:Route::get(&#39;/&#39;, function() {    Log::debug(&#34;The first log.&#34;);    Log::debug(&#34;The second log.&#34;);    Log::debug(&#34;The third log.&#34;);    return view(&#39;welcome&#39;);});خوب با توجه به اینکه توی .env به طور پیش‌فرض LOG_CHANNEL=stack قرار داده شده، انتظار داریم که توی مسیر storage/logs/laravel.log یه فایل ببینیم که این خطوط توش نوشته شده باشه:[2021-12-16 15:01:55] local.DEBUG: The first log.  [2021-12-16 15:01:55] local.DEBUG: The second log.  [2021-12-16 15:01:55] local.DEBUG: The third log. تا اینجا هیچ کار خاصی نکردیم. اما می خوایم دو تا فیچر به لاگ لاراول اضافه کنیم: نمایش اطلاعات اضافه‌ در مورد لاگ (مثل کاربری که لاگین کرده و ..)ذخیره لاگ توی دیتابیسبرای شروع یه دایرکتوری به اسم Classes توی app می سازیم (اسم دایرکتوری رو هرچی بخواید می تونید بذارید، اما من عادت دارم کلاس های متفرقه رو توی یه دایرکتوری به اسم Classes ایجاد می کنم). بعد از اون برای دسته بندی بهتر یه دایرکتوری دیگه داخل Classes به اسم Logs می سازیم. فرض کنید که می خوایم اسم Log Channel جدیدی که می خوایم بسازیم رو Database بذاریم. برای همین یه دایرکتوری دیگه توی Logs به اسم Database می سازیم (همه این کارها رو فقط برای مرتب بودن ساختار پروژه کردیم، وگرنه هیچ اجباری نیست).ساختار دایرکتوری هااولین فایلی که نیازه ساخته بشه، فایل Logger هست. توی این فایل دو تا چیز رو مشخص می کنیم: «چه چیزی» باید لاگ شه«چطوری» باید لاگ شهپس یه فایل به اسم Logger توی دایرکتوری Database می‌سازیم:&lt;?php&#xA;namespace App\Classes\Logs\Database;&#xA;&#xA;use Monolog\Logger as MonologLogger;&#xA;use Psr\Log\LoggerInterface;&#xA;&#xA;class Logger&#xA;{&#xA;    /**&#xA;     * @param array $config&#xA;     * @return LoggerInterface&#xA;     */&#xA;    public function __invoke(array $config): LoggerInterface&#xA;    {&#xA;        $logger = new MonologLogger(&#39;database&#39;);&#xA;        $logger-&gt;pushProcessor(new Processor());&#xA;        $logger-&gt;pushHandler(new Handler());&#xA;&#xA;        return $logger;&#xA;    }&#xA;}همونطور که می بینید این فایل فقط یه تابع به اسم __invoke داره که یه آرگومان از جنس آرایه می گیره و یه instance که اینترفیس LoggerInterface رو پیاده‌سازی کرده باشه بر می گردونه. آرگومان تابع __invoke تنظیمات درایورمونه که توی فایل کانفیگ می تونیم مشخص می کنیم. مثلا می تونیم اسم کانکشن دیتابیس رو از طریق کانفیگ بگیریم که به Developer این قابلیت رو بدیم که انتخاب کنه توی چه دیتابیسی لاگ‌ها ریخته بشن. اما فعلا نمی خوایم پیچیده بشه برای همین از روش می پریم.بدنه تابع مجموعا 4 خط بیشتر نیست: ایجاد یه نمونه از یک کلاس که اینترفیس LoggerInterface رو پیاده سازی کرده (برای سادگی کار ما از Monolog استفاده کردیم، ولی می تونید حتی این قسمت رو هم خودتون بنویسید)مشخص کردن اینکه «چه چیزی» باید لاگ شه (pushProcessor)مشخص کردن اینکه «چطوری» باید لاگ بشه (pushHandler)کلاس های Processor و Handler وجود ندارن و باید خودمون بسازیمشون. برای همین کنار فایل Logger.php کلاس های Processor.php و Handler.php رو می سازیم.به عنوان قدم بعد می ریم فایل  Processor.php رو توسعه می دیم. داخل این کلاس هم یه تابع با نام __invoke باید باشه که یه آرگومان از جنس آرایه می گیره:&lt;?php&#xA;namespace App\Classes\Logs\Database;&#xA;&#xA;class Processor&#xA;{&#xA;    public function __invoke(array $record): array&#xA;    {&#xA;        &#xA;    }&#xA;}آرگومان تابع __invoke دیتایی هستی که به صورت عادی لاراول برای لاگ استفاده می کنه. وقتی $record رو dd کنیم همچین خروجی‌ای رو خواهیم دید:فرمت پیش فرض لاگ‌های Monologما توی این دیتا هر تغییری رو که بخوایم می تونیم بدیم. چیزی کم یا اضافه کنیم. بیاید یه سری اطلاعات اضافه در مورد کاربر به این دیتا اضافه کنیم:&lt;?php&#xA;&#xA;namespace App\Classes\Logs\Database;&#xA;&#xA;use Illuminate\Support\Facades\Auth;&#xA;&#xA;class Processor&#xA;{&#xA;    public function __invoke(array $record): array&#xA;    {&#xA;        $record[&#39;userID&#39;]        = Auth::id();&#xA;        $record[&#39;userAgent&#39;] = request()-&gt;userAgent();&#xA;        $record[&#39;ip&#39;]                = request()-&gt;ip();&#xA;        $record[&#39;params&#39;]     = request()-&gt;all();&#xA;&#xA;        return $record;&#xA;    }&#xA;}الان اگه باز هم قبل از return مقدار $record رو dd کنیم یه همچین چیزی خواهیم دید:فرمت جدید لاگکارمون با Processor همینجا تموم می شه. البته شما می تونید خیلی کارهای باحال دیگه بکنید و جزئیات بیشتری به لاگ اضافه کنید.فایل Handler.php باید حاوی یک کلاس باشه که از کلاس AbstractProcessingHandler ارث‌بری کرده باشه. وقتی می خواید از کلاس AbstractProcessingHandler ارث‌بری کنید، مجبور هستید تابع write رو پیاده‌سازی کنید، چون این تابع به صورت abstract تعریف شده. اما این دلیل نمی شه که ما بقیه توابع کلاس AbstractProcessingHandler رو نگاه نکنیم. پس قبل از پیاده سازی کلاس Handler بریم ببینیم که AbstractProcessingHandler چطوری کار می کنه.کلاس AbstractProcessingHandler یه همچین شکل و شمایلی داره:abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface&#xA;{&#xA;    use ProcessableHandlerTrait;&#xA;    use FormattableHandlerTrait;&#xA;&#xA;    /**&#xA;     * {@inheritDoc}&#xA;     */&#xA;    public function handle(array $record): bool&#xA;    {&#xA;        if (!$this-&gt;isHandling($record)) {&#xA;            return false;&#xA;        }&#xA;&#xA;        if ($this-&gt;processors) {&#xA;            /** @var Record $record */&#xA;            $record = $this-&gt;processRecord($record);&#xA;        }&#xA;&#xA;        $record[&#39;formatted&#39;] = $this-&gt;getFormatter()-&gt;format($record);&#xA;&#xA;        $this-&gt;write($record);&#xA;&#xA;        return false === $this-&gt;bubble;&#xA;    }&#xA;&#xA;    /**&#xA;     * Writes the record down to the log of the implementing handler&#xA;     *&#xA;     * @phpstan-param FormattedRecord $record&#xA;     */&#xA;    abstract protected function write(array $record): void;&#xA;&#xA;    /**&#xA;     * @return void&#xA;     */&#xA;    public function reset()&#xA;    {&#xA;        parent::reset();&#xA;&#xA;        $this-&gt;resetProcessors();&#xA;    }&#xA;}همونطور که می بینید خود این کلاس، از یه کلاس دیگه ارث‌بری کرده، چند اینترفیس رو پیاده‌سازی کرده و از چند Trait استفاده کرده. اما نترسید، اونقدرا هم پیچیده نیست. تابعی که خوندن کد رو باید ازش شروع کنید تابع handle هست (معمولا تابع handle توی لاراول تابعی هست که باید خوندن رو ازش شروع کنیم، مثل توی Job ها). این تابع 3 تا کار مهم می کنه: اگه Processorی تعریف شده باشه، با استفاده از اون(ها) یه Record لاگ رو پردازش می کنه. ما اینجا می دونیم که قراره تابع __invoke توی Processorی که پیشتر نوشتیم فراخونی بشه.با استفاده از تابع format یه کلید با نام formatted به Record لاگ اضافه می شه. این کلید، دیتای فرم داده شده رو نگهداری می کنه. مثلا وقتی می خوایم توی فایل لاگ رو ذخیره کنیم، دقیقا محتوایی که باید توی فایل ذخیره بشه رو نگهداری می کنه. حالا که می خوایم توی دیتابیس ذخیره کنیم، باید محتوایی که قراره توی دیتابیس ذخیره بشه رو بسازیم. مثلا اگه داریم از دیتابیسی مثل MySQL استفاده می کنیم، باید قبل از ذخیره‌سازی، مقادیر کلیدهای context، extra و params رو به JSON تبدیل کنیم، چون امکان ذخیره‌سازی آرایه توی MySQL وجود نداره.در نهایت با فراخونی تابع write عملیات ذخیره‌سازی رو انجام می ده. تابع write به صورت abstract تعریف شده و پیاده سازیش به عهده ماست.مرحله 1 که با نوشتن Processor و فراخونی pushProcessor توی فایل Logger.php به درستی داره کار می کنه و نیاز نیست براش کار دیگه‌ای انجام بدیم. می مونه مورد 2 و 3. آره می تونیم عملیات تبدیل آرایه به JSON رو توی تابع write انجام بدیم، ولی بذارید همه چیز اصولی و تر‌ و تمیز جلو بره. پس باید عملکرد تابع format رو تغییر بدیم. تابع format روی خروجی getFormatter فراخونی شده، پس باید ببینیم که getFormatter چیه و چطوری کار می کنه.تابع getFormatter توی FormattableHandlerTrait تعریف شده و FormattableHandlerTrait به عنوان Trait در AbstractProcessingHandler استفاده شده. اگه یه نگاه بهش بندازیم همچین چیزی می بینیم:/**&#xA; * {@inheritDoc}&#xA; */&#xA;public function getFormatter(): FormatterInterface&#xA;{&#xA;    if (!$this-&gt;formatter) {&#xA;        $this-&gt;formatter = $this-&gt;getDefaultFormatter();&#xA;    }&#xA;&#xA;    return $this-&gt;formatter;&#xA;}این تیکه کد داره می گه اگه پیش‌تر، از طریق setFormatter، مقداری برای formatter تعیین شده باشه، از اون استفاده بشه، در غیر این صورت از تابع getDefaultFormatter (که توی همین Trait تعریف شده) برای مقداردهی به formatter استفاده بشه.پس ما دو تا راه داریم، یا یه Formatter بنویسیم و از طریق setFormatter به Handler معرفیش کنیم، یا اینکه عملکرد getDefaultFormatter رو با Override کردن تابع تغییر بدیم. من گزینه دوم رو می خوام جلو برم. پس باید نگاه کنم که getDefaultFormatter چطوری پیاده‌سازی شده:protected function getDefaultFormatter(): FormatterInterface&#xA;{&#xA;    return new LineFormatter();&#xA;}می بینیم که به صورت پیش فرض از LineFormatter که یه کلاس توی پکیج Monolog هست داره استفاده می شه. اما ما می تونیم هر Formatter دیگه ای رو جایگزینش کنیم. با یه نگاه به LineFormatter می فهمیم که یه Formatter کلاسیه که اینترفیس FormatterInterface رو پیاده‌سازی کرده باشه. پس یه کلاس می نویسیم که FormatterInterface رو پیاده سازی کرده باشه و بعد getDefaultFormatter رو طوری تغییر می دیم که یک instance از کلاس Formatter ما رو برگردونه://Handler.php&#xA;&#xA;protected function getDefaultFormatter(): FormatterInterface&#xA;{&#xA;    return new Formatter();&#xA;}طبیعتا باید کلاس Formatter رو هم بسازیم:&lt;?php&#xA;&#xA;namespace App\Classes\Logs\Database;&#xA;&#xA;use Monolog\Formatter\FormatterInterface;&#xA;use Monolog\Utils;&#xA;&#xA;class Formatter implements FormatterInterface&#xA;{&#xA;&#xA;    public function format(array $record)&#xA;    {&#xA;        $record[&#39;context&#39;] = $this-&gt;toJson($record[&#39;context&#39;]);&#xA;        $record[&#39;extra&#39;]   = $this-&gt;toJson($record[&#39;extra&#39;]);&#xA;        $record[&#39;params&#39;]  = $this-&gt;toJson($record[&#39;params&#39;]);&#xA;&#xA;        return $record;&#xA;    }&#xA;&#xA;    public function formatBatch(array $records)&#xA;    {&#xA;        foreach($records as $key =&gt; $record)&#xA;        {&#xA;            $records[$key] = $this-&gt;format($record);&#xA;        }&#xA;&#xA;        return $records;&#xA;    }&#xA;&#xA;    /**&#xA;     * Return the JSON representation of a value&#xA;     *&#xA;     * @param mixed $data&#xA;     *&#xA;     * @return string if encoding fails &#39;null&#39; is returned&#xA;     */&#xA;    protected function toJson($data): string&#xA;    {&#xA;        return Utils::jsonEncode($data, Utils::DEFAULT_JSON_FLAGS, false);&#xA;    }&#xA;}تابع format عملیات format رو برای یک Record لاگ انجام می ده، formatBatch یه تعداد Record رو به یکباره format می کنه. تابع toJson هم خودمون برای تبدیل آرایه به json نوشتیم. دلیل اینکه از json_encode استفاده نکردیم این بود که ممکنه در آینده بخوایم فلگ‌هایی به json_encode اضافه یا کم بکنیم و برای اینکه هی نخوایم کدها رو تکرار کنیم، یه تابع ساختیم که بدون دغدغه فقط صداش کنیم. داخلش هم به جای تابع  json_encode از تابع jsonEncode کلاس Utils پکیج Monolog استفاده کردیم که کارمون راحت تر باشه.خوب برگردیم عقب، ما توی کلاس Handler بودیم و تا الان کلاسمون اینطوری شده:&lt;?php&#xA;&#xA;namespace App\Classes\Logs\Database;&#xA;&#xA;use Monolog\Formatter\FormatterInterface;&#xA;use Monolog\Handler\AbstractProcessingHandler;&#xA;&#xA;class Handler extends AbstractProcessingHandler&#xA;{&#xA;&#xA;    protected function write(array $record): void&#xA;    {&#xA;        &#xA;    }&#xA;&#xA;    protected function getDefaultFormatter(): FormatterInterface&#xA;    {&#xA;        return new Formatter();&#xA;    }&#xA;}اگه توی تابع write یه dd بذاریم، خروجی همچین چیزی خواهد شد:چیزی که برای تابع write ارسال می شهمی بینیم که طبق انتظارمون کلید formatted ایجاد شده اما اونطوری که کلاس Formatter  که الان نوشتیم تعیین کرده (به مقدار context، extra و params دقت کنید که تبدیل به JSON شدن).حالا همه چیز آمادست برای اینکه لاگ رو توی دیتابیس ذخیره کنیم. برای ارتباط با دیتابیس بهتره که Model و Migration مربوطه رو ایجاد کنیم. من فقط محتوای این دو فایل رو براتون می ذارم، چون می‌دونم که نحوه ساخت و معنی هر خطش رو خیلی خوب بلدید:&lt;?php&#xA;&#xA;use Illuminate\Database\Migrations\Migration;&#xA;use Illuminate\Database\Schema\Blueprint;&#xA;use Illuminate\Support\Facades\Schema;&#xA;&#xA;class CreateLogsTable extends Migration&#xA;{&#xA;    /**&#xA;     * Run the migrations.&#xA;     *&#xA;     * @return void&#xA;     */&#xA;    public function up()&#xA;    {&#xA;        Schema::create(&#39;logs&#39;, function(Blueprint $table) {&#xA;            $table-&gt;id();&#xA;            $table-&gt;string(&#39;message&#39;);&#xA;            $table-&gt;json(&#39;context&#39;);&#xA;            $table-&gt;integer(&#39;level&#39;)-&gt;unsigned();&#xA;            $table-&gt;string(&#39;level_name&#39;);&#xA;            $table-&gt;integer(&#39;userID&#39;)-&gt;unsigned()-&gt;nullable();&#xA;            $table-&gt;string(&#39;userAgent&#39;)-&gt;nullable();&#xA;            $table-&gt;string(&#39;ip&#39;)-&gt;nullable();&#xA;            $table-&gt;json(&#39;params&#39;);&#xA;            $table-&gt;timestamp(&#39;datetime&#39;);&#xA;            $table-&gt;timestamps();&#xA;        });&#xA;    }&#xA;&#xA;    /**&#xA;     * Reverse the migrations.&#xA;     *&#xA;     * @return void&#xA;     */&#xA;    public function down()&#xA;    {&#xA;        Schema::dropIfExists(&#39;logs&#39;);&#xA;    }&#xA;}&lt;?php&#xA;&#xA;namespace App\Models;&#xA;&#xA;use Illuminate\Database\Eloquent\Factories\HasFactory;&#xA;use Illuminate\Database\Eloquent\Model;&#xA;&#xA;class Log extends Model&#xA;{&#xA;    use HasFactory;&#xA;&#xA;    protected $fillable = [&#xA;        &#34;message&#34;,&#xA;        &#34;context&#34;,&#xA;        &#34;level&#34;,&#xA;        &#34;level_name&#34;,&#xA;        &#34;datetime&#34;,&#xA;        &#34;userID&#34;,&#xA;        &#34;userAgent&#34;,&#xA;        &#34;ip&#34;,&#xA;        &#34;params&#34;,&#xA;    ];&#xA;}حالا می تونیم تابع write توی Handler رو خیلی آسون پیاده‌سازی کنیم:&lt;?php&#xA;&#xA;namespace App\Classes\Logs\Database;&#xA;&#xA;use App\Models\Log;&#xA;use Monolog\Formatter\FormatterInterface;&#xA;use Monolog\Handler\AbstractProcessingHandler;&#xA;&#xA;class Handler extends AbstractProcessingHandler&#xA;{&#xA;&#xA;    protected function write(array $record): void&#xA;    {&#xA;        $log = new Log($record[&#39;formatted&#39;]);&#xA;        $log-&gt;save();&#xA;    }&#xA;&#xA;    protected function getDefaultFormatter(): FormatterInterface&#xA;    {&#xA;        return new Formatter();&#xA;    }&#xA;}طبیعتا باید migrate کنید تا جدول logs ساخته بشه.و حالا قدم آخر! اول پست گفتیم که لاراول به طور پیش‌فرض از کانال stack برای لاگ استفاده می کنه. برای همین همه لاگ ها داره توی storage/logs/laravel.log نوشته می شه. ما باید به لاراول بگیم که آقای لاراول لطفا از درایور دیتابیسی که نوشتم استفاده کن. برای این کار باید اول از همه فایل config/logging.php رو باز کنیم و توی قسمت channels این تیکه کد رو اضافه کنیم تا یه کانال به اسم database درست کنیم و از لاراول بخوایم که وقتی که بهش می گیم از کانال database استفاده کنه، برای لاگ کردن  از کلاس Logger که پیشتر نوشتیم استفاده کنه:...&#xA;&#xA;    &#39;channels&#39; =&gt; [&#xA;       ...&#xA;&#xA;        &#39;database&#39; =&gt; [&#xA;            &#39;driver&#39; =&gt; &#39;custom&#39;,&#xA;            &#39;via&#39; =&gt; Logger::class&#xA;        ],&#xA;    ],&#xA;&#xA;];کانال database ساخته شده. کافیه توی .env بگیم که از کانال database استفاده کنه:LOG_CHANNEL=databaseحالا وقتی توی مرورگر پروژه رو باز کنیم، باید بلافاصله ببینیم این خطوط توی دیتابیس اضافه شده:دیگه چه کارهایی می‌شه انجام داد؟خیلی کارها! مثلا می تونید برای اینکه سرعت اپلیکیشنتون حتی با وجود لاگ‌های زیاد افت محسوسی نکنه، به جای اینکه توی تابع write مستقیم لاگ رو توی دیتابیس ذخیره کنید، از طریق یه Job این کار رو بکنید. یا مثلا می تونید به جای ذخیره توی دیتابیس، از طریق یه درخواست HTTP لاگ ها رو توی سرور دیگه ای ذخیره کنید (اگه این کار رو می کنید یادتون نره که درخواست HTTP رو حتما به صورت async بفرستید وگرنه تاثیر بدی روی Performance اپلیکیشنتون می ذاره). یا مثلا اپلیکیشنتون رو به سیستم های لاگ‌گیری حرفه ای و یا حتی سیستم مدیریت پروژه یا پیام‌رسان ها متصل کنید! ایده ها ته ندارن :)اما چیزی که به نظرم اهمیت بیشتری داره، اینه که ذهنمون باز بشه برای توسعه درایورهای مختلف برای لاراول، نه فقط برای لاگ، بلکه برای هرجای دیگه‌ای که اجازه می ده درایور بنویسیم. </description><category>مصطفی لوائی</category><author>مصطفی لوائی</author><pubDate>Fri, 17 Dec 2021 20:12:35 +0330</pubDate></item><item><title>چطوری توی لاراول سرعت آپلود رو زیاد کردیم؟</title><link>https://virgool.ir/@mostafa.lavaei/چطوری-توی-لاراول-سرعت-آپلود-رو-زیاد-کردیم-l4d5w4kiakty</link><description>بیزینس چاپ‌آقا مبتنی بر فایله و گاها فایل‌های مشتری خیلی حجیم هستن (چند گیگ). معمولا برای آپلود فایل‌های حجیم، از روش‌هایی استفاده می شه که فایل به صورت قطعه قطعه شده از سمت کلاینت برای بک‌اند ارسال بشه. این روش علی رغم اینکه یکم سرعت آپلود رو کاهش می ده (به خاطر Overhead درخواست‌های HTTP) اما مزایای زیادی داره:مدیریت بهتر Memory سرور عدم نیاز به افزایش memory_limit توی php.iniعدم نیاز به افزایش max_post_size و max_upload_size توی php.iniامکان Retry کردن درخواست اگه یه لحظه اینترنت Unstable بشهامکان نمایش Progress به کاربر...البته اینکه اندازه هر قطعه (Chunk) رو چقدر بذارید که کمترین تاثیر رو روی Performance بذاره هم خیلی مهمه. هرچی اندازه Chunk ها کمتر باشه، سرعت آپلود کمتر می شه اما منابع سرور بهینه‌تر استفاده می شه و البته Progress دقیق‌تری هم به کاربر نمایش داده می‌شه.ما هم توی چاپ‌آقا از همین روش استفاده کرده بودیم و همه چیز ظاهرا داشت کار می کرد تا اینکه متوجه شدیم که فایل‌های حجیم خیلی کند آپلود می‌شن و از اون بدتر، از یه حجمی بالاتر به خطای Memory یا Timeout می خوریم! چرا؟ ? ما که داریم فایل رو با Chunk های 2 مگابایتی می فرستیم و انتظار داریم هر ریکوئست مقدار خیلی کمی Memory مصرف کنه و توی چند میلی‌ثانیه پردازش بشه!پیداکردن و حل مشکلبرای شروع یه فایل حجیم آپلود کردیم و توی Console درخواست‌های HTTP رو دنبال کردیم. درخواست‌های اول خیلی شیک و مجلسی ارسال می شدن و همه چی خوب بود، اما به مرور، زمان پردازش هر درخواست بیشتر و بیشتر می شد تا اینکه به جایی می رسید که هر درخواست نزدیک 30 ثانیه زمان می برد در حالی که حامل Chunk دو مگابایتی بود! 30 ثانیه زمان برای ذخیره 2 مگابایت؟ ?کدهای بک‌اند خیلی ساده و سرراست بود. Chunk رو می گرفت و به انتهای فایل می‌چسبوند:Storage::append($filePath, $chunk, &#39;&#39;);کجای این کد ساده مشکل داره؟ برای پیدا کردن مشکل باید می‌دیدیم لاراول تابع append رو چطوری پیاده سازی کرده:public function append($path, $data, $separator = PHP_EOL)&#xA;{&#xA;   if ($this-&gt;exists($path)) {&#xA;      return $this-&gt;put($path, $this-&gt;get($path).$separator.$data);&#xA;    }&#xA;    return $this-&gt;put($path, $data);&#xA;}چی شد؟ ? به جای اینکه هر Chunk رو Append کنیم داریم هر دفعه کل فایل رو می خونیم و توی Memory میایم Chunk رو بهش می چسبونیم و دوباره کل فایل رو ذخیره می کنیم؟ یعنی اگه یه فایل 1 گیگی رو بخوایم با Chunk های 2 مگابایتی آپلود کنیم، فقط توی درخواست آخر حدود 2 گیگ Read/Write روی دیسک و 1 گیگ Memory مصرف می کنیم! و این عمل 500 بار داره تکرار می شه.. چه فاجعه‌ای.ساده‌ترین راه (و نه البته بهترین راه) برای حل این مشکل، استفاده از توابع ساده PHP به جای این خط کد وحشتناک بود:$absoluteFilePath = Storage::path($filePath);&#xA;$fp = fopen($absoluteFilePath, &#39;a+&#39;);&#xA;fwrite($fp, $chunk);&#xA;fclose($fp);و تمام! مشکل حل شد و همه چیز همونطور که انتظار داشتیم کار می کنه!نتیجه اخلاقیاز نظر من فریم‌ورک ها فوق العاده هستن و شدیدا بهشون معتقدم، اما نقدی که وجود داره اینه که عموم افراد «نحوه کار با فریم‌ورک» رو یاد می گیرن، ولی «نحوه کار کردن فریم‌ورک» رو نه. دسته‌ای که فقط نحوه کار با فریم‌ورک رو یاد می گیرن، خیلی ساده از تابع append استفاده می کنن، بدون اینکه دقت کنن که ممکنه روی فایل های بزرگ و درخواست هایی که مکررا تکرار می شن به مشکل می خورن. وقتی هم که به مشکل می خورن معمولا متوجه نمی شن که چی شده و چرا به مشکل خوردن و در نهایت صورت مساله رو پاک می کنن. </description><category>مصطفی لوائی</category><author>مصطفی لوائی</author><pubDate>Sat, 11 Dec 2021 00:02:47 +0330</pubDate></item><item><title>چطور از Tailwindcss توی انگولار استفاده کنم؟</title><link>https://virgool.ir/@mostafa.lavaei/چطور-از-tailwindcss-توی-انگولار-استفاده-کنم-jkq5sw1zadgj</link><description>چند وقت پیش یه پست کوتاه توی کانال تلگرامیم گذاشته بودم که چرا باید از Tailwindcss استفاده کنیم و چرا الان یکی از ترند‌های دنیای فرانت اند شده.توی این پست می خوام نحوه استفاده از Tailwindcss توی انگولار رو آموزش بدم، چون یه مقدار چالش داره. ولی قبلش نیازه یه چیزایی رو توضیح بدم.فریم‌ورک Tailwindcss فقط مجموعه ای از استایل ها نیست، بلکه به شما این اجازه رو می ده به یه روش خیلی تر و تمیز استایل های Tailwindcss رو دست‌کاری کنید و یا استایل های جدید بنویسید. حسن نوشتن استایل در قالب پلاگین اینه که می تونید از قابلیت های بی نظیر Tailwindcss استفاده کنید و استایل هایی کاملا Scalable داشته باشید. شما می تونید Tailwindcss رو در قالب یک فایل CSS دانلود کنید و به پروژه ملحق کنید، اما در این صورت فقط می تونید از استایل‌های اون استفاده کنید و از قابلیت هایی مثل حذف کردن اتوماتیک استایل های بلااستفاده و یا پلاگین نویسی بی بهره می مانید. این روش اصلا مناسب نیست، چون بیشتر مزایای Tailwindcss رو زیر سوال می بره.قبل از این که بگیم که چطوری باید از Tailwindcss توی انگولار استفاده کنید، باید بگیم خود Tailwindcss چطوری کار می کنه. Tailwindcss یه پلاگین برای Postcss است که بار اصلی پردازش استایل ها به عهده اونه. مثلا Postcss به Tailwindcss برای حذف استایل هایی که استفاده نشدن کمک می کنه. طبیعیه که Postcss برای این که کار بکنه، باید پروژه رو بررسی کنه و عملیاتی رو روی استایل ها انجام بده (از این به بعد بهش می گیم Build). شاید براتون جالب باشه که کامپایلر انگولار درون خودش از Postcss استفاده می کنه. چالشی که الان داریم اینه که چطوری پلاگین Tailwindcss رو به کامپایلر انگولار معرفی کنیم؟یکی از راه های استفاده از Postcss، استفاده به عنوان پلاگین Webpack هست. ما قصد داریم از این روش استفاده کنیم. چالشی که وجود داره اینه که انگولار در حالت عادی به ما این اجازه رو نمی ده که تنظیمات Webpack رو دستکاری کنیم (امیدوارم در آینده این قابلیت به انگولار اضافه بشه)، اما به ما اجازه می ده که از کامپایلر های غیر Official انگولار استفاده کنیم. این کامپایلرها روی کامپایلر Official انگولار بنا شدن و همه قابلیت های اون رو دارن. توی این آموزش می خوایم از یکی از این کامپایلر‌ها استفاده کنیم که به ما قابلیت این رو می ده که از فایل کانفیگ دلخواه خودمون برای Webpack استفاده کنیم.در قدم اول باید پیش نیازها رو نصب کنیم:npm i -D @angular-builders/custom-webpack autoprefixer@^9.8.6 postcss-import@^12.0.1 postcss-loader@^3.0.0 postcss-scss@^2.1.1 postcss-url@^8.0.0 webpack-mergeپکیج های بالا به عنوان Dev Dependency نصب شدنورژن های ذکر شده، متناسب با ورژن Postcss انگولار 10 نوشته شده شده. برای انگولار های دیگه احتمالا باید از ورژن های دیگه ای استفاده کنید.فرض بر این گذاشته شده که از فرمت scss برای استایل ها استفاده می کنید، برای همین پکیج postcss-scss رو نصب کردیم.طبیعتا باید خود Tailwindcss رو هم نصب کنیم:npm i tailwindcssبعد از نصب این پکیج ها، باید به انگولار بگیم که از کامپایلر جدید برای کامپایل کردن استفاده کنه. این کار رو با تغییر angular.json می تونیم انجام بدیم. شما می تونید این فایل رو دستی تغییر بدید، ولی من اینجا برای خوانایی بیشتر، از کامند ng config استفاده می کنم (جای PROJECT_NAME اسم پروژتون رو باید بذارید):ng config projects.PROJECT_NAME.architect.build.builder @angular-builders/custom-webpack:browser&#xA;&#xA;ng config projects.PROJECT_NAME.architect.build.options.customWebpackConfig.path webpack.config.js&#xA;&#xA;ng config projects.PROJECT_NAME.architect.serve.builder @angular-builders/custom-webpack:dev-server&#xA;&#xA;ng config projects.PROJECT_NAME.architect.serve.options.customWebpackConfig.path webpack.config.jsالان کامپایلرمون عوض شد. اگه دقیت کنید آدرس فایل کانفیگ Webpack رو هم مشخص کردیم که توی یه فایل کنار angular.json و با نام webpack.config.js باشه. پس این فایل رو با محتویات زیر می سازیم:const {merge} = require(&#39;webpack-merge&#39;);&#xA;&#xA;module.exports = (config) =&gt; {&#xA;  const isProd         = config.mode === &#34;production&#34;&#xA;  const tailwindConfig = require(&#34;./tailwind.config.js&#34;)(isProd);&#xA;&#xA;  return merge(config, {&#xA;    module: {&#xA;      rules: [&#xA;        {&#xA;          test: /\.scss$/,&#xA;          use: [&#xA;            {&#xA;              loader: &#39;postcss-loader&#39;,&#xA;              options: {&#xA;                ident: &#39;postcss&#39;,&#xA;                syntax: &#39;postcss-scss&#39;,&#xA;                plugins: [&#xA;                  require(&#39;postcss-import&#39;),&#xA;                  require(&#34;postcss-url&#34;),&#xA;                  require(&#39;tailwindcss&#39;)(tailwindConfig),&#xA;                  require(&#39;autoprefixer&#39;),&#xA;                ]&#xA;              }&#xA;            }&#xA;          ]&#xA;        }&#xA;      ]&#xA;    }&#xA;  });&#xA;};به این صورت پلاگین Postcss به Webpack اضافه شد و چندتا پلاگین هم برای خود Postcss لود کردیم. دقت کنید که همه پلاگین های بالا باید لود بشن تا بتونید بدون مشکل از Postcss و Tailwindcss استفاده کنید.توی فایل webpack.config.js به فایل tailwind.config.js اشاره شده که مربوط به تنظیمات Tailwindcss هست. پس این فایل رو با محتوای زیر می سازیم (این محتوا حداقلیه و شما می تونید طبق داکیومنت Tailwindcss اونو گسترش بدید):module.exports = (isProd) =&gt; ({&#xA;  future: {&#xA;    removeDeprecatedGapUtilities: true,&#xA;    purgeLayersByDefault: true&#xA;  },&#xA;  purge: {&#xA;    enabled: isProd,&#xA;    content: [&#xA;      &#39;**/*.html&#39;,&#xA;      &#39;**/*.scss&#39;,&#xA;      &#39;**/*.ts&#39;,&#xA;    ]&#xA;  },&#xA;  theme: {}&#xA;});به عنوان قدم آخر، فایل styles.scss رو باز کنید و سه خط زیر رو در ابتدای اون paste کنید:@import &#39;../node_modules/tailwindcss/base&#39;;&#xA;@import &#39;../node_modules/tailwindcss/components&#39;;&#xA;@import &#39;../node_modules/tailwindcss/utilities&#39;;حالا می تونید از Tailwindcss لذت ببرید :)</description><category>مصطفی لوائی</category><author>مصطفی لوائی</author><pubDate>Thu, 08 Oct 2020 16:47:44 +0330</pubDate></item><item><title>توابع Generator در ES6</title><link>https://virgool.ir/@mostafa.lavaei/توابع-generator-در-es6-ybopijjurzfa</link><description>قبل از این که توابع Generator رو توضیح بدم، می خوام یکم مقدمه بگم که ممکنه برای بعضی ها خسته کننده باشه، اما از اونجا که بعضی از برنامه نویس ها این ها رو نمی دونن، می خوام توضیح بدم. در صورتی که می خواید مستقیم وارد مبحث بشید می تونید از مقدمه صرف نظر کنید.توی جاواسکریپت، در حالت عادی توابع خط به خط اجرا می شن تا این که یکی از حالت های زیر اتفاق بیافته:1- رسیدن به کلمه کلیدی return: توی این حالت، مقداری که return شده به کسی که تابع رو صدا زده برگشت داده می شه. مثلا کد زیر رو ببینید:function x()&#xA;{&#xA;    return 1;&#xA;}&#xA;&#xA;function y()&#xA;{&#xA;    return x();&#xA;}&#xA;&#xA;y();&#xA;بذارید مراحل اجرا رو یه دور با هم مرور کنیم. می دونیم که روی تعریف تابع، تا وقتی که فراخونی نشه هیچ اجرایی صورت نمی گیره، برای همین اولی خطی که اجرا می شه، فراخونی تابع y خواهد بود:y();وقتی مرورگر به این خط می رسه، می دونه که برای اجرای این خط باید بره جایی که تابع y تعریف شده رو پیدا کنه و تابع y رو از خط اول اجرا کنه. ولی قبل از اون باید محلی که الان توش قرار داره رو یه جا برای خودش نگه داره تا بعد از اجرای تابع y، دوباره به همینجا برگرده. بهترین روش برای نگهداری محل آخرین اجرا، stack هست. پس اگه نخوایم وارد پیچیدگی های دنیای واقعی بشیم، می تونیم بگیم که مرورگر عدد 12 (شماره خطی که الان داره اجرا می کنه)‌ رو توی stack ذخیره می کنه. پس الان stack  به این صورته:stack: [12]و کدهای زیر اجرا خواهد شد:function y()&#xA;{&#xA;    return x();&#xA;}توی بدنه تابع y تنها یه خط وجود داره، فراخونی تابع x. پس باید دوباره محل تعریف تابع x پیدا بشه و بدنه اون اجرا بشه. اما قبل از این که مرورگر تابع x رو اجرا کنه، باز هم محلی که الان توش قرار داره رو برای خودش توی stack ذخیره می کنه. با این حساب، stack به صورت زیر در میاد:stack: [12, 8]و کدهای زیر اجرا می شه:function x()&#xA;{&#xA;    return 1;&#xA;}توی تابع x فقط یک کار برای انجام هست، برگشت دادن عدد 1. پس مرورگر عدد 1 رو به جایی که تابع x رو صدا زده بر می گردونه. برای این کار به stack مراجعه می کنه و آخرین مقدار رو ازش برداشت می کنه. آخرین مقدار توی stack عدد 8 هست، پس stack به این صورت در میاد:stack: [12]و مرورگر به خط 8 می ره. خط 8 مربوط به فراخونی تابع x و return کردن مقدار برگشت داده شده از تابع x بود. الان مرورگر مقدار برگشت داده شده از تابع x رو می دونه. برای همین هم در واقع کد زیر اجرا می شه:return 1; // return x()چون باز هم به کلمه کلیدی return رسیدیم، مرورگر به stack مراجعه می کنه و عدد 12 رو ازش بر می داره. حالا دیگه stack خالی شده:stack: []وقتی stack خالیه یعنی دیگه به بالاترین سطح رسیدیم. چون عدد 12 از stack برداشته شده، مرورگر به سراغ اجرای خط 12 می ره. خط 12 چیزی جز فراخونی تابع y نبود. از اونجا که تابع y مقدار 1 رو برگردونده، خط به این صورت اجرا می شه:1; // y()2- به کلمه کلیدی throw برسه: توی این حالت، مقداری که throw شده، به کسی که تابع رو صدا زده برگشت داده می شه، وقتی صدا زننده تابع متوجه بروز خطا می شه، در صورتی که فراخونی تابع داخل try...catch باشه، از ادامه اجرای try منصرف می شه و کدهای داخل catch رو اجرا می کنه. اما اگه توی try...catch نباشه، یه مرحله به عقبتر بر می گرده و می ره سراغ کسی که اون تابع رو صدا زده. اینقدر این کار انجام می شه که یا به try...catch برسه، یا این که به بالاترین سطح برسه (stack خالی بشه). اگه به بالاترین سطح برسه و try..catch دیده نشه، یه خطا توی console نمایش داده می شه و برنامه از کار می افته.3- به آخر تابع برسه: آخرین راه پایان یک تابع، رسیدن به انتهای تابعه. در این صورت باز هم کنترل برنامه به کسی که تابع رو صدا زده برگشت داده می شه، با این تفاوتی که هیچ مقداری برگشت داده نشده (یا مقدار برگشت داده شده undefined هست).تابع زیر رو ببینید:function search(needle, haystack)&#xA;{&#xA;    let results = [];&#xA;    &#xA;    for(let i=0; i&lt;haystack.length; i++)&#xA;    {&#xA;        if(needle === haystack[i])&#xA;        {&#xA;                results.push(i);&#xA;        }&#xA;    }&#xA;    &#xA;    return results;&#xA;}&#xA;ورودی اول تابع search مقداریه که دنبالش می گردیم و ورودی دوم یک آرایست. تابع search دونه دونه مقادیر داخل haystack رو با needle مقایسه می کنه و در صورتی که برابر باشن، به آرایه result اضافه می کنه. در آخر تابع result برگشت داده می شه. خیلی هم عالی!اما تابع search توی مقیاس بزرگ دو تا اشکال داره:1- باید تابع به طور کامل اجرا بشه تا به نتیجه جستجو دسترسی داشته باشیم.2- مجبور شدیم آرایه ای به نام results تعریف کنیم و نتیجه جستجو رو توی اون بریزیم. اگه تعداد جوابهای جستجو زیاد باشه، آرایه results خیلی حجیم می شه و مموری زیادی مصرف می شه.خدا رو شکر توی ES6 توابع Generator معرفی شدن. این توابع به ما کمک می کنن که اگر تابعی مثل تابع search تعریف شده در بالا رو داریم، دچار مشکلات گفته شده نشیم. تابع search رو اگه بخوایم با استفاده از Generator بنویسیم به این شکل می شه:function* search(needle, haystack)&#xA;{&#xA;     for(let i=0; i&lt;haystack.length; i++)&#xA;    {&#xA;        if(needle === haystack[i])&#xA;        {&#xA;            yield i;&#xA;        }&#xA;    }&#xA;}&#xA;تغییراتی که توی کد داده شده ایناست:1- بعد از کلمه کلیدی function کاراکتر * اضافه شده. این کار برای اینه که مرورگر بدونه این یه تابع معمولی نیست!2- آرایه results به کلی حذف شده و در عوض هر جا که نتیجه جدیدی حاصل می شه، از کلمه کلیدی yield استفاده می کنیم. کلمه کلیدی yield خیلی شبیه return عمل می کنه، یعنی باعث بازگشت مقدار به کسی که تابع رو فراخونی کرده می شه اما بر خلاف return باعث خاتمه کار تابع نمی شه.اما نکته ای که وجود داره توی نحوه استفاده از توابع Generator هست. اگه تابع search فوق رو به صورت زیر اجرا کنیم نتیجه دلخواه رو نمی گیریم:console.log(search(&#34;x&#34;, [&#34;x&#34;, &#34;y&#34;, &#34;z&#34;, &#34;x&#34;])); // Generator{ }در واقع اصلا تابع اجرا نمی شه که بخواید نتیجه ای بگیرید. تنها اتفاقی که با فراخونی تابع search می افته اینه که آبجکتی از نوع Generator برگشت داده می شه. برای اجرای تابع search باید تابع next آبجکت Generator رو فراخونی کنید:console.log(search(&#34;x&#34;, [&#34;x&#34;, &#34;y&#34;, &#34;z&#34;, &#34;x&#34;]).next()); // Object { value: 0, done: false }شاید خروجی بالا هم خیلی مطلوبتون نباشه، ولی از خروجی قبلی خیلی بهتره! بذارید اول معنی مقدار برگشت داده شده رو بگیم. آبجکتی که برگشت داده شد، دو تا کلید داره:مقدار value مقداری که از تابع برگشت داده شده رو نشون می دهمقدار done مشخص می کنه که اجرای تابع به پایان رسیده یا نه کد بالا باعث می شه تابع search تا رسیدن به یکی از چهار حالت زیر ادامه پیدا کنه: رسیدن به return: در این حالت مقدار return شده به عنوان value قرار می گیره و done برابر true می شه.رسیدن به yield: در این حالت مقدار yield شده به عنوان value قرار می گیره و done برابر false می شه.رسیدن به انتهای تابع: در این حالت value برابر undefined و done برابر true خواهد بود.رسیدن به throw: این حالت با توابع عادی فرقی نداره.به عبارت دیگه وقتی تابع به کلمه کلیدی yield می رسه، به طور موقت متوقف می شه و دفعه بعد که شما دوباره تابع next رو صدا بزنید، تابع از همونجا که متوقف شده به کارش ادامه می ده. حالا با توضیحاتی که داده شد می شه کد بالا رو تکمیل کرد:const SEARCH_GEN = search(&#34;x&#34;, [&#34;x&#34;, &#34;y&#34;, &#34;z&#34;, &#34;x&#34;]);&#xA;&#xA;console.log(SEARCH_GEN.next()); // Object { value: 0, done: false }&#xA;console.log(SEARCH_GEN.next()); // Object { value: 3, done: false }&#xA;console.log(SEARCH_GEN.next()); // Object { value: undefined, done: true }&#xA;ظاهرا به چیزی که می خوایم رسیدیم. اما هنوز یکم تمیزکاری مونده. آبجکت Generator قابل استفاده توی حلقه for..of هست. یعنی می شه کد بالا رو به این صورت اصلاح کرد:const SEARCH_GEN = search(&#34;x&#34;, [&#34;x&#34;, &#34;y&#34;, &#34;z&#34;, &#34;x&#34;]);&#xA;&#xA;for(let result of SEARCH_GEN)&#xA;{&#xA;    console.log(result);&#xA;}&#xA;// 0 &#xA;// 3&#xA;// undefinedبرای راحت شدن از دست undefined آخر (که جزء جواب نیست) دو تا راه وجود داره:اگه بتونید برای آخرین جواب به جای yield از return استفاده کنید، اصلا به این مشکل بر نمی خورید. (توی تابع search این کار امکان پذیر نیست چون نمی دونید کی به آخرین جواب رسیدید)فیلتر کردن مقدار undefined:for(let result of SEARCH_GEN)&#xA;{&#xA;    if(type of result === &#34;undefined&#34;)&#xA;    {&#xA;        break;&#xA;    }&#xA;    &#xA;    console.log(result);&#xA;}&#xA;// 0&#xA;// 3متاسفانه توی توابع Generator به راحتی نمی تونید از توابع آسنکرون استفاده کنید. در واقع مشکل از توابع Generator نیست، مشکل از Callback هاییه که موقع استفاده از توایع آسنکرون تعریف می کنیم. اما برای حل این مشکل هم راه حل هایی وجود داره. یکی از راه حل ها، استفاده از async...await است. مثلا کد زیر درست کار نمی کنه:function generator()&#xA;{&#xA;    yield(1);&#xA;    &#xA;    setTimeout(() =&gt; {&#xA;        yield(2);&#xA;    }, 2000);&#xA;}&#xA;&#xA;for(let result of generator())&#xA;{&#xA;    console.log(result);&#xA;}&#xA;&#xA;// 1همونطور که می بینید فقط مقدار 1 لاگ می شه. اما کد بالا می تونه به این صورت اصلاح بشه که کار کنه:function sleep(time)&#xA;{&#xA;    return new Promise(&#xA;        (resolve, reject) =&gt; {&#xA;            setTimeout(() =&gt; resolve(), time);&#xA;        }&#xA;    );&#xA;}&#xA;&#xA;async function* generator()&#xA;{&#xA;    yield 1;&#xA;    await sleep(2000);&#xA;    yield 2;&#xA;}&#xA;&#xA;async function consumer()&#xA;{&#xA;    for await(let result of generator())&#xA;    {&#xA;        console.log(result);&#xA;    }&#xA;}&#xA;&#xA;consumer();&#xA;// 1&#xA;// 2اگه کد بالا رو اجرا کنید می بینید که اول 1 توی کنسول نمایش داده می شه و 2 ثانیه بعد، 2 نمایش داده می شه.توی کد بالا اول از همه اومدیم تابع setTimeout رو به یه Promise تبدیل کردیم که بتونیم از await استفاده کنیم. بعد توی حلقه for...of از قابلیت جدید ES استفاده کردیم و await رو بعد از کلمه کلیدی for قرار دادیم. با این کار می تونیم تابع generator که async هست رو توی حلقه استفاده کنیم. در نهایت از اونجا که نمی شه از await خارج از تابع async استفاده کرد، کل حلقه رو داخل تابع consumer که async هست قرار دادیم و در عوض تابع consumer رو فراخونی کردیم.</description><category>مصطفی لوائی</category><author>مصطفی لوائی</author><pubDate>Fri, 11 Oct 2019 19:01:54 +0330</pubDate></item><item><title>Primitive vs Non-Primitive</title><link>https://virgool.ir/@mostafa.lavaei/primitive-vs-non-primitive-hlw430ttcpwb</link><description>توی جاوااسکریپت متغیرها بر اساس تایپشون به دو دسته تقسیم می شن: Primitive و Non-Primitive. شاید بگید مگه جاوااسکریپت تایپ داره؟ بله متغیرها توی همه زبون ها تایپ دارن، اما توی بعضی از زبون ها مثل Java شما باید موقع تعریف متغیر تایپش رو مشخص کنید ولی توی زبونهایی مثل Javascript نوع متغیر، زمان اجرا و با توجه به مقدار درون متغیر مشخص می شه.به بحثمون برگردیم. تایپ های Number، String، Boolean، Symbol، Undefined و Null از نوع Primitive هستن. هر چیزی غیر از ۶ تایپی که گفته شد Non-Primitive یا Object Reference هستن.نحوه برخورد جاوااسکریپت با متغیرهای Primitive و Non-Primitive متفاوته، برای همین برای هر برنامه نویس جاوااسکریپت ضروریه که خصوصیاتشون رو بدونه. توی ادامه مطلب به تفاوت تایپ های Primitive و Non-Primitive می پردازم:تغییر ناپذیری: متغیرهایی که از نوع Primitive هستن، بر خلاف Non-Primitive ها، غیر قابل تغییرن. مثلا کد زیر رو ببینید:let primitive = &#39;Hello World&#39;;&#xA;primitive[0] = &#39;M&#39;;&#xA;console.log(primitive); //Hello Worldشاید توی کد بالا انتظار داشتید که عبارت Mello World نمایش داده بشه، ولی از اونجایی که تایپ String یه تایپ Primitive ه، غیر قابل تغییره. کد اصلاح شده اینطوریه:let primitive = &#39;Hello World&#39;;&#xA;primitive = &#39;Mello World&#39;;&#xA;console.log(primitive); //Mello Worldخاصیت تغییر ناپذیری توی تایپ های Non-Primitive وجود نداره، مثلا کد زیر رو ببینید:let nonPrimitive = {  foo: &#34;baz&#34; }&#xA;nonPrimitive.foo = &#34;baz&#34;&#xA;console.log(nonPrimitive); //{foo: &#34;baz&#34;}کد بالا همونطور که انتظار داریم کار می کنه.مقایسه: نحوه مقایسه دو متغیر از نوع Primitive با دو متغیر از نوع Non-Primitive کاملا متفاوته. متغیرها Primitive  با مقدارشون مقایسه می شن، اما متغیرهای Non-Primitive  بر اساس Reference مقایسه می شن. برای این که موضوع واضح تر بشه کد زیر رو ببینید:let num1= 110,&#xA;     num2= 110;&#xA;&#xA;console.log(num1 === num2) //trueاما دو متغیر Non-Primitive حتی اگه مقادیر یکسانی داشته باشن با هم برابر نیستن. کد زیر رو ببینید:let arr1= [110],&#xA;     arr2= [110];&#xA;     &#xA;console.log(arr1 === arr2) //falseدو متغیر Non-Primitive فقط وقتی با هم برابرن که به یه جا اشاره کنن (بله متغیرهای Non-Primitive در واقع اشاره گر هستن)let arr1= [110],&#xA;     arr2= [110],&#xA;     arr3= arr1;&#xA;&#xA; console.log(arr1 === arr2) //false&#xA; console.log(arr1 === arr3) //trueارسال به صورت پارامتر: از اونجا که یه متغیر Non-Primitive در واقع یه اشاره گره (برای همین به نام Object Reference هم شناخته می شن)، وقتی به صورت پارامتر برای تابعی ارسال بشه و اون تابع توی پارامتر ارسالی تغییری ایجاد کنه، متغیر اصلی هم تغییر می کنه:let primitive = 1,&#xA;     nonPrimitive  = {  foo: &#34;bar&#34;  };&#xA;     &#xA;test(primitive, nonPrimitive);&#xA;&#xA;function test(primitiveArg, nonPrimitiveArg){&#xA;    primitiveArg = 3;&#xA;    nonPrimitiveArg.foo = &#34;baz&#34;&#xA;}&#xA;&#xA;console.log(primitive) //1&#xA;console.log(nonPrimitive) //{foo: &#34;baz&#34;}پس باید حواستون باشه وقتی یه متغیر Non-Primitive رو برای تابعی می فرستید، اگر نمی خواید توی مقدار متغیر اصلی تغییری ایجاد کنید، حتما پارامتر ارسالی یه کپی بگیرید و بعد ازش استفاده کنید:let primitive = 1,&#xA;     nonPrimitive  = {  foo: &#34;bar&#34;  };&#xA;     &#xA;test(primitive, nonPrimitive);&#xA;&#xA;function test(primitiveArg, nonPrimitiveArg){&#xA;   let  cloneNonPrimitive = {...nonPrimitiveArg};&#xA;&#xA;    primitiveArg = 3;&#xA;    cloneNonPrimitive.foo = &#34;baz&#34;&#xA;}&#xA;&#xA;console.log(primitive) //1&#xA;console.log(nonPrimitive) //{foo: &#34;bar&#34;}&#xA;</description><category>مصطفی لوائی</category><author>مصطفی لوائی</author><pubDate>Mon, 07 Oct 2019 23:07:38 +0330</pubDate></item><item><title>افتتاحیه!</title><link>https://virgool.ir/@mostafa.lavaei/افتتاحیه-uomwb2qkv0u2</link><description>روز اولی که کتابم در مورد فریم ورک انگولار رو می نوشتم، اصلا انتظار نداشتم فروش چندانی داشته باشه. تنها هدفم این بود که توی اون زمان (که تازه انگولار ۲ اومده بود) که حتی رفرنس انگلیسی هم گیر نمی اومد، یه رفرنس خوب فارسی برای شروع کار با انگولار بنویسم. اما نتیجه فرا تر از انتظارم بود. کتابها (چه نسخه الکترونیکی چه کاغذی) خیلی بهتر از چیزی که فکر می کردم فروش رفت. این اتفاق و البته حمایت ها و تشویق های همسرم منو به فکر این انداخت که وارد وادی آموزش بشم. از آموزش فیزیکی (به همکاران و دوستان) تا فعالیت توی شبکه های اجتماعی (پاسخ به سوالات). اینجا بود که تازه فهمیدم چقدر به آموزش علاقه دارم.گذشت تا به یکی دو هفته پیش رسید. نمی دونم چی شد که تصمیم گرفتم توی کانال تلگرامم پست های آموزشی بذارم. نتیجه باز هم برام غافل گیر کننده بود. پیام های مخاطبام خیلی دلگرم کننده بود. برای همین به پیشنهاد یکی از همون مخاطبام، تصمیم گرفتم بلاگ نویس شم. موضوع این بلاگ قراره مطالب آموزشی در سطح متوسط و حرفه ای باشه (چون آموزش های ابتدایی مثل نخود و لوبیا تو سطح اینترنت ریخته). سعی می کنم بیشتر تمرکزم روی انگولار باشه اما قرار نیست فقط محدود به همین باشم.امیدوارم خدا توی راهی که انتخاب کردم کمکم کنه و بتونم به اندازه خودم سهمی توی گسترش علم بین هم میهنانم داشته باشم.</description><category>مصطفی لوائی</category><author>مصطفی لوائی</author><pubDate>Sat, 05 Oct 2019 21:40:14 +0330</pubDate></item></channel></rss>