مهدی قوسیان
مهدی قوسیان
خواندن ۱ دقیقه·۴ سال پیش

سه نکته ساده برای بهبود کد های laravel شما

۱. کالکشن‌های خود را ریفکتور کنید

تصور کنید که در حال ایجاد وبسایتی هستید که دانش‌آموزان یا دانشجویان در پروژه‌های آن شرکت می‌کنند و هر هفته به آن‌ها نمره داده می‌شود، و وظیفه شما این است که میانگین نمرات یک پروژه‌ را به مربی(استاد،معلم) نشان دهید، و میانگین نمره دانش‌جویان را به ترتیب درجه‌بندی کنید ( به منظور مشاهده پیشرفت آن‌ها).

ممکن است شما با یک Project class مثل این رو‌به رو شوید :

}<?phpclass Project extends Model{/** ... code omitted for brevity **/public function studentsAverageScore() {$participants = $this->participants;$sum = 0;$totalStudents = 0;foreach($participants as $participant) {if ($participant->isStudent()) {$totalStudents++;$sum += $participant->student->lastRating()->averageScore();}}return $sum / $totalStudents;}

به نظر می‌رسد که متد studentsAverageScore() ما بسیار خوب کار می‌کند. با استفاده از یک foreach می‌بینیم که شرکت کنندگان جز دانشجویان هستند یا نه ( برخی از شرکت‌کنندگان می‌توانند از بین استادان باشند) و سپس آخرین میانگین نمره را جمع می‌زنیم(میانگین هر تستی در یک ریت مشخص شده است.)

مسأله اینجاست که اگر یک نفر بعدا به این متد برای باگ فیکس یا یک تغییر ضروری برگردد، هم تیمی یا حتی خود شما باید قبل از انجام هرکار دیگری در ذهن خود این foreach را کامپایل کند. حلقه‌ها generic هستند، و در این مورد، ما در هر pass چندین کار انجام می‌دهیم: بررسی می‌کنیم که دانشجو هستند یا نه و سپس به sum اضافه می‌کنیم و فقط دوباره در return statement مقدار می‌دهیم.

البته این فقط یک نمونه ساده است، اما تصور کنید که بخواهیم کار بیشتری انجام دهیم؟ اگر بخواهیم این را فقط برای بعضی از دانشجویان فیلتر کنیم یا چیزی به آن‌ها اضافه کنیم چه؟ شاید همه‌ی امتیازهای آن‌ها را بخواهیم نه فقط آخرین آن‌ را ؟ این کارها به سرعت از دست من و شما خارج می‌شود.

بنابراین چگونه می‌توانیم این بررسی‌ها و محاسبات را بهتر کنیم؟ خوشبختانه ما می‌توانیم با متدهایی که Eloquent در اختیار ما می‌گذارد، کمی از برنامه‌نویسی فانکشال استفاده کنیم.

به جای اینکه به صورت دستی چک کنید که شرکت‌کنندگان دانشجو هستند یا نه، می‌توانید با استفاده از متد filter فقط دانشجویان را return کنید :

}<?phppublic function studentsAverageScore() {$participants = $this->participants;$participants->filter(function ($participant) {return $participant->isStudent();});

با استفاده از فانکشن filter، ما می‌توانیم فقط یک فانکشن را به عنوان یک آرگومان pass کنیم تا فقط شرکت‌کنندگانی که دارای تمام شروط ما هستند return شوند. در این حالت، این call یک زیرمجموعه از $participants را برخواهد گرداند: فقط دانش‌جویان را.

به طور طبیعی، ما نیاز داریم که با حساب کردن میانگین نمره آن‌ها این کار را به پایان برسانیم. خب حالا به foreach نیاز داریم؟ این که هنوز هم بهینه نیست. یک راه حل داخلی یا built-in در فانکش دیگری وجود دارد، که average (میانگین)نام دارد و در کالکشن Eloquent ما وجود دارد؛ در جایی که ما فقط می‌خواهیم مقداری را از کل کالکشن میانگین بگیریم و return کنیم  کمی شبیه به filter را رفتار می‌کند و به عبارت دیگر قوانینی که filter دنبال می‌کند را دنبال خواهد کرد. کد نهایی به صورت زیر است :

}<?phppublic function studentsAverageScore() {$participants = $this->participants;return $participants->filter(function ($participant) {return $participant->isStudent();})->average(function ($participant) {return $participant->student->lastRating()->averageScore();});

از آنجایی که  average یک عدد را برمی‌گرداند، پس دقیقاً همان چیزی است که ما می‌خواهیم. توجه کنید که چگونه call های خود را زنجیروار کرده‌ایم و چقدر کد بهتری داریم. تقریباً می‌توانید آن را مثل یک زبان طبیعی بخوانید : شرکت ‌کنندگانی را که دانشجو هستند را بگیر، و سپس مقدار میانگین آخرین نمره‌ آن‌ها را return کن. هدف ما از این کار این است که کدی تمیزتر و مفهومی‌تر داشته باشیم.

این کار نه تنها برای php یا Eloquent صدق می‌کند، بلکه می‌توانید کارهای مشابه این را با جاوااسکریپت نیز انجام دهید. گرچه که این از محدوده‌ی مقاله‌ی ما خارج است اما اگر تا بحال در مورد filter ،map و reduce در جاوااسکریپت نشنیده‌اید، آن‌ها را بررسی کنید.

۲.بدانید و آگاه باشید که مشکلی مثل N+۱ کوئری وجود دارد!

خب بیایید بعضی از نکاتی را که در مورد کد خود در بالا گفتیم را انجام دهیم. توجه داشته باشید که چگونه Student model را برای یک شرکت‌کننده مشخص در فانکشن average فچ کردیم. این مسأله بسیار مشکل‌ساز است چراکه ما همزمان چندین student models را لود می‌کنیم و یک sql کوئری اضافه (در پشت صحنه) انجام می‌گیرد.

راه‌حل بهتر برای این کار این است که برای اولین کوئری خود eager load را انجام دهیم. هنگامی که این‌ کار را انجام می‌دهیم، می‌توانیم به جای داشتن کوئری‌های N+۱ ، این تعداد را به مقدار قابل توجهی کاهش دهیم ( پس از نام این مشکل بترسید).

کار ساده‌ای است که این را با eloquent و با متد with انجام دهیم. بیایید کد بالا را دوباره ریفکتور کنیم :

}<?phppublic function studentsAverageScore() {$participants = $this->participants()->with('student')->get();return $participants->filter(function ($participant) {return $participant->isStudent();})->average(function ($participant) {return $participant->student->lastRating()->averageScore();});

خب، هر زمان که ما $participant→student را فراخوانی میکنیم، student model مربوط به شرکت‌‌کننده هم قبلاً در اولین فراخوانی ما کش شده( $this→participants() ).

(در ضمن، هنوز یک فراخوانی بهینه نشده مربوط به همین نکته در کد بالا وجود دارد، آیا می‌تونید تشخیص بدید؟ در بخش نظرات برای ما بنویسید).

۳. فایل‌های Blade خود را بهبود دهید

من عاشق blade هستم، balde یک templating engine فوق‌العاده قدرتمند است که با laravel عرضه شده و سینتکس شگفت‌انگیزی دارد. اما آیا شما از تمام این قدرت و پتانسیل استفاده می‌کنید؟

همه ما برای نمایش برخی از کالکشن‌ها به کاربران باید از @foreach استفاده کنیم، اما اگر کالکشن empty باشد چه باید کرد؟ پاسخ ساده این است که باید قبل از @foreach از @if statement استفاده کنید. راه بهتری نیز برای نوشتن این وجود دارد :

@endforelse<?php@forelse ($participants as $participant)<li>{{ $participant->email }}</li>@empty<p>No participants in this project :(</p>

@forelse شباهت زیادی به @foreach دارد، اما با اضافه شدن بخش @empty ، بسیار کار تمیزتر از استفاده @if شده، اینطور نیست؟

صحبت از @if شد، blade دستور جذاب دیگری نیز دارد که من عاشق آن هستم: @unless و @endunless. این دقیقاً برعکس @if است و بسیار بهتر از یک @if با شرط منفی است و خوانایی کد را بالا می‌برد. اگر تا به حال از روبی استفاده کرده باشید، می‌دانید که چگونه کار می‌کند.

همچنین استفاده از میانبرهای احراز هویت (@auth/@endauth و @guest/@endguest) بسیار بهتر از @if(Auth::check()) است.خوانایی کد را بالا می‌برد.

مثالی از داکیومنت laravel ۵.۵ :

@endguest<?php@auth// The user is authenticated...@endauth@guest// The user is not authenticated...

در اسناد رسمی laravel موارد بیشتری وجود دارد که می‌توانید از آن استفاده کنید. اکیدا توصیه می‌کنم به جا استفاده از دسته‌ای if بی‌معنی از این روش‌ها استفاده کنید تا فایل template خود را بهتر و بهتر کنید.

برنامه نویسیطراحی وبوبلاراولبرنامه نویسی وب
یه UI-UX کار و برنامه نویس Back-end عاشق کتاب
شاید از این پست‌ها خوشتان بیاید