معجزه ماکرو در لاراول

دسته بندی: لاراول
زمان مطالعه: 15 دقیقه
۱۷ اردیبهشت ۱۳۹۹

در این مقاله در مورد معجزه‌ای به اسم ماکرو (Macro) در لاراول صحبت می‌کنیم. در هنگام انجام و تکرار کدها، این ویژگی لاراول بسیار به کمک ما خواهد آمد، ما ‌می‌توانیم در خیلی از موارد تکراری با ساخت یک ماکرو کدهای زیباتر و خواناتری ایجاد کنیم و کار خودمان را آسان کنیم.

فهرست محتوای این مقاله

ماکرو (Macro) چیست؟

ماکرو یک روش برای اضافه کردن توابع درونی لاراول است که به شدت از تکراری شدن کدها جلوگیری می‌کند. به این صورت که در ماکرو ما یک تابع می‌سازیم و یک سری فرآیند‌های تکراری از پروژه را حذف می‌کنیم و آن کدها را فقط یک بار می‌نویسیم. ماکرو را فقط در کلاس هایی می‌توانیم استفاده کنیم که به اصطلاح Macroable باشد و از Trait آن استفاده می‌کند. لاراول یک Trait برای Macroable ایجاد کرده است که شما می‌توانید کلاس‌های خود را نیز Macroable کنید و توابع ماکرو را در کلاس خود ایجاد کنید. برای مثال یک کلاس Response در لاراول Macroable می‌باشد که شما می‌توانید توابع ماکرو را پیاده سازی کنید که در ادامه توضیح خواهم داد.

کلاس‌هایی که می‌توانیم از ماکرو استفاده کنیم

  • Request: Illuminate\Http\Request
  • Response: Illuminate\Http\Response
  • Collection: Illuminate\Support\Collection
  • Str: Illuminate\Support\Str
  • Router: Illuminate\Routing\Router
  • UrlGenerator: Illuminate\Routing\UrlGenerator
  • Cache: Illuminate\Cache\Repository
  • Filesystem: Illuminate\Filesystem\Filesystem
  • Arr: Illuminate\Support\Arr
  • Rule: Illuminate\Validation\Rule

این کلاس‌ها که مربوط به خود لاراول هستند قابلیت استفاده از ماکرو را دارند یا به اصطلاح Macroable هستند. چرا که از صفت Trait Macroable استفاده می‌کنند.

ایجاد یک متد با ماکرو

چگونه با استفاده از Macro کلاس‌های هسته ی لاراول را گسترش دهیم؟

حال می‌خواهیم یک متد با ماکرو ایجاد کنیم و برای مثال در ورودی آن یک کلمه می‌گیرد و حروف آن را به حروف بزرگ تبدیل می‌کند.

برای ایجاد ماکرو‌ها در فایل app/Providers/AppServiceProvider.php و در متد boot می‌توانیم کدهای خود را به صورت زیر بنویسیم.

Str::macro('upCase', function ($word){
            return strtoupper($word);
});

کد بالا را اگر در متد boot فایل AppServiceProvider.php بنویسیم بعد در هر جایی از پروژه می‌توانیم از تابع upCase در کلاس Str استفاده کنیم. دقت کنید کلاس Illuminate\Support\Str را وارد فایل (Import) کنید.

حالا با کد زیر می‌توانید از ماکرو مورد نظر استفاده کنید.

Str::upCase('7learn');
7LEARN

ماکرو در Response ها

اگر در حال توسعه یک RESTful Api با لاراول هستید قطعا برای درخواست‌ها نیاز به یک روش و ساختار مشخص پاسخ (Response) دارید. کد زیر را در نظر بگیرید.
public function index()
    {
        return response([
           'status' => 201,
           'message' => 'Successfully Created!',
            'data' => $someData
        ]);
    }

در کد بالا تابع یک پاسخ را بر می‌گرداند که به شکل خاصی نوشته شده است و این شکل ممکن است در بقیه‌ی قسمت‌های پروژه نیز با وضعیت، پیام و داده‌ی مختلفی استفاده شود.

ما می‌توانیم یک ماکرو برای کلاس Response بنویسیم و از آن به این صورت استفاده کنیم. در فایل app/Providers/AppServiceProvider.php و در متد boot کد زیر را قرار دهید.

Response::macro('apiResponseFormat', function ($status, $message, $data) {
            return Response::json([
                'status' => $status,
                'message' => $message,
                'data' => $data
            ]);
        });

حال می‌توانید با استفاده از کد زیر و فقط با یک خط پاسخ‌ها را به شکل بالا ارائه دهید.

return Response::apiResponseFormat(201, 'Successfully Created!', $someData);

ماکرو در Reqeust ها

یکی دیگر از کلاس‌های لاراول که از Macroable پیروی می‌کند کلاس Request است. فرض کنید می‌خواهید در زمان ارسال پارامترها در یک درخواست، پارامترهایی که null هستند را فیلتر کنید تا آن‌ها را حذف کند. در فایل ServiceProvider مربوط به ماکرو خودتان کد زیر را قرار دهید.

Request::macro('filter', function () {
        return collect($this->all())->filter();
    });

حال با ارسال درخواستی به شکل زیر و استفاده از تابع filter تمامی پارامتر‌های غیر null در خروجی به شما نمایش داده می‌شود.

// input: ['email' => 'vahid@7learn.com', 'name' => null]
$request->filter();
// result: ['email' => 'vahid@7learn.com']

ماکرو با Mixin 

در یک مرحله جلوتر ما در لاراول از Mixin‌ها استفاده می‌کنیم و ماکروهای خودمان را به کلاس مورد نظر اضافه می‌کنیم.

برای این کار یک فایل به اسم StrMixin.php در مسیر app/Mixins ایجاد کنید.

<?php
namespace App\Mixins;

use Closure;

class StrMixin
{
    /**
     * @return Closure
     */
    public function isLength(): callable
    {
        return static function ($str, $length) {
            return static::length($str) === $length;
        };
    }

    /**
     * @return Closure
     */
    public function appendTo(): callable
    {
        return static function ($str, $char) {
            return $char . $str;
        };
    }
}

یک کلاس با دو متد یکی برای مقایسه‌ی اندازه‌ی رشته با مقدار ورودی آن، و دیگری برای چسباندن یک کاراکتر به رشته‌ی ورودی دوم، ساختیم.

حالا با استفاده از mixin می‌خواهیم این دو تابع را به کلاس Str بدهیم. فایل AppServiceProviders.php را به صورت زیر تغییر دهید.

<?php

namespace App\Providers;

use App\Mixins\StrMixin;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     * @throws \ReflectionException
     */
    public function boot()
    {
        //
        Str::mixin(new StrMixin);
    }
}

حال به توابعی که در کلاس StrMixin ساختید می‌توانید به صورت زیر در هرجایی که کلاس Str وارد (Import) شده باشد، دسترسی داشته باشید.

Str::appendTo('7Learn','@');
Output Here ... @7Learn

ایجاد Provider خودمان

Providers یا فراهم کننده‌ها، مکان اصلی برنامه‌های راه انداز (bootstrapping) نرم افزار لاراول هستند. به عبارت آسان‌تر، Providers در هنگام راه اندازی (bootstrap) پروژه اجرا می‌شوند.
ما می‌توانیم برای ماکروهای برنامه‌ی خودمان یک Provider بسازیم و ماکروهای خودمان را در آن بنویسیم. برای ساخت یک Provider دستور زیر را در ترمینال اجرا کنید.
php artisan make:provider MacroServiceProvider
فایلی به نام MacroServiceProvider.php در مسیر app/Providers ساخته شده است که دارای دو متد boot و register است.
قبل از هر کاری برای اجرا شدن Provider ما در هنگام راه اندازی پروژه نیاز است که کلاس آن را در لیست Provider‌های برنامه قرار دهیم. به این منظور فایل config/app.php را باز کنید و در آرایه‌ای به اسم providers کد زیر را اضافه کنید.
App\Providers\MacroServiceProvider::class,

حال کدهای نوشته شده در تابع boot در هنگام راه اندازی پروژه اجرا خواهند شد که مکان خوبی برای نوشتن ماکروهای ما است.

ماکرو در روابط مدل‌ها

ما از ماکروها در روابط نیز می‌توانیم استفاده کنیم زیرا این کلاس‌ها Macroable هستند. فرض کنید یک مدل به اسم user و یک مدل دیگر به اسم post داریم. رابطه‌ی کاربر و پست به صورت یک به چند است، یا به عبارتی هر کاربر چندین پست دارد و به صورت زیر تعریف می‌شود. فایل User.php به شکل زیر است.

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    public function posts()
    {
        return $this->hasMany(Post::class, 'user_id');
    }
}

در بالا یک رابطه‌ی hasMany برای کاربر تعریف شده است. حالا می‌خواهیم یک ماکرو برای این رابطه بنویسیم تا آخرین پست کاربر را به صورت رابطه‌ی ‌hasOne بگیریم.

فایل MacroServiceProvider.php را به صورت زیر می‌نویسیم.

<?php

namespace App\Providers;

use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\ServiceProvider;

class MacroServiceProvider extends ServiceProvider
{
    public $foreignKey = 'user_id';

    public $localKey = 'id';

    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        HasMany::macro('toHasOne', function () {
            return new HasOne(
                $this->getQuery(),
                $this->getParent(),
                $this->foreignKey,
                $this->localKey
            );
        });
    }
}

دو ویژگی به نام foreignKey و localKey به صورت عمومی در کلاس بالا تعریف کرده‌ایم.

حالا در فایل ‌User.php متد زیر را به کلاس User اضافه می‌کنیم.

public function lastPost()
    {
        return $this->posts()->latest()->toHasOne();
    }

حالا در هر جایی از پروژه می‌توانید به راحتی یک رابطه‌ی یک به یک بین کاربر و آخرین پست او از طریق تابع lastPost به‌دست بیاورید. برای مثال کد زیر نشان دهنده همین موضوع است.

$user = User::find(2);
$lastPost = $user->lastPost()->get();

ماکروها در View

در این بخش یک روش برای استفاده از ماکروها در فایل‌های View و فرانت پروژه به شما ارائه می‌کنیم. از یک پکیج به نام laravelcollective/html استفاده می‌کنیم.

این پکیج یک پکیج برای ساخت فرم‌های HTML در ظاهر سایت است.

برای نصب پکیج دستور زیر را در ترمینال اجرا کنید.

composer require laravelcollective/html

بعد از نصب پکیج بالا باید Provider‌ها و Alias‌های آن را به پروژه اضافه کنیم.

فایل config/app.php را باز کنید و کلاس‌های زیر را طبق کد زیر اضافه کنید.

'providers' => [
    // ...
    Collective\Html\HtmlServiceProvider::class,
    // ...
],
'aliases' => [
    // ...
      'Form' => Collective\Html\FormFacade::class,
      'Html' => Collective\Html\HtmlFacade::class,
    // ...
],

حالا یک Providers طبق دستوری که قبل‌تر توضیح داده بودم با نام HtmlMacroServiceProvider بسازید که به صورت زیر است.

<?php

namespace App\Providers;

use App\Services\Macros;
use Collective\Html\HtmlServiceProvider;

class HtmlMacroServiceProvider extends HtmlServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        parent::register();

        $this->app->singleton('form', function ($app) {
            $form = new Macros($app['html'], $app['url'], $app['view'], $app['session.store']->token());
            return $form->setSessionStore($app['session.store']);
        });
    }
}

حالا یک Provider برای استفاده از ماکروهای HTML خود ساختیم ولی قبل از هر چیزی نیاز است آن را به پروژه اضافه کنیم.

در فایل config/app.php کد زیر را به providers اضافه کنید.

\App\Providers\HtmlMacroServiceProvider::class,

ما در Provider سفارشی خودمان از یک کلاس به اسم Macros استفاده کرده‌ایم تا بتوانیم ماکروهای خودمان را در آن پیاده سازی کنیم. پس نیاز است تا کلاس Macros را در مسیر app/Services بسازیم و کدهای زیر را در آن قرار دهیم.

<?php
namespace App\Services;

use Collective\Html\FormBuilder;

class Macros extends FormBuilder
{
    /**
     * Generate Bank drop down list
     *
     * @param $name
     * @param null $selected
     * @param array $options
     * @return \Illuminate\Support\HtmlString
     */

    public function selectBank($name, $selected = null, $options = array())
    {
        $list = [
            "" => "Select Bank...",
            "melli" => "بانک ملی",
            "mellat" => "بانک ملت",
            "tejarat" => "بانک تجارت",
            "saderat" => "بانک صادرات"
        ];

        return $this->select($name, $list, $selected, $options);
    }
}

یک تابع ساختیم به اسم selectBank که در View‌ها می‌توانیم با استفاده از این تابع یک تگ select با گزینه‌هایی که در متغیر list وجود دارد، ایجاد کنیم.

با کد زیر در View یک select ایجاد می‌شود و شما می‌توانید مقادیر ورودی را بر اساس نیاز خودتان وارد کنید.

<div class="form-group">
<label for="bank_name">Bank Name</label>
{{ Form::selectBank("bank_name", $merchant['paymentInfo']->bank_name ?? null,["class"=>"form-control"]) }}
</div>

ورودی اول مقدار name تگ می‌باشد، ورودی دوم مقدار انتخاب شده (selected) و ورودی سوم آرایه‌ای از ویژگی‌هایی (Attributes) که می‌خواهید روی تگ موردنظر اعمال شود،‌ است.

ماکرو در کالکشن‌ها (Collections)

کالکشن‌ها جز برترین قابلیت‌های لاراول و پر استفاده‌ترین آن‌ها بوده است. در واقع کالکشن‌ها همان نوع داده‌ای آرایه ولی با امکانات بسیار بیشتر و بهتر است. نمونه‌ای از استفاده‌ی کالکشن‌ها در دریافت اطلاعات از دیتابیس توسط ORM لاراول است. برای مطالعه‌ی بیشتر می‌توانید مقاله‌ی آموزش کامل Collection در لاراول را بخوانید.

حال می‌خواهیم با استفاده از ماکرو بر روی کالکشن‌ها تغییرات و ویژگی‌هایی اضافه کنیم. فرض کنید از کد زیر برای uppercase کردن اعضای یک کالکشن استفاده کنیم.

$uppercaseWords = collect(['7learn', 'programming'])->map(function($word)  {
   return strtoupper($word);
});

حال اگر بخواهیم از این کد در جاهای مختلف استفاده کنیم، تکرار این کد بسیار خسته کننده خواهد بود. به همین منظور از ماکرو بر روی کلاس کالکشن استفاده می‌کنیم.

در مسیری که ServiceProvider مربوط به ماکرو وجود دارد کد زیر را قرار دهید.

use Illuminate\Support\Collection;

Collection::macro('uppercase', function() {

    return collect($this->items)->map(function($word) {
        return strtoupper($word);
    });

});

دقت کنید که در بالای فایل مربوطه قسمت use را انجام دهید و از خط سوم به بعد را در متد boot قرار دهید.

با استفاده از کد بالا یک متد به نام uppercase برای کلاس Collection ایجاد کردیم که می‌توانیم در هر قسمت از پروژه به صورت زیر از آن استفاده کنیم و تمامی محتویات کالکشن را به حروف بزرگ تبدیل کنیم.

$uppercaseWords = collect(['code', 'daily'])->uppercase();
$moreUppercaseWords = collect(['code', 'every', 'day'])->uppercase();
$evenMoreUppercaseWords = collect(['laravel', 'facade'])->uppercase();

شاید به این فکر کنید که می‌توانیم یک تابع برای این کار در نظر بگیریم ولی تفاوتی که بین ماکرو و تابع عادی وجود دارد این است که می‌توانید توابع خود را به اصطلاح Chain کنید و کد شما خوانایی بهتری خواهد داشت که به صورت زیر خواهد بود.

//استفاده از تابع
function4(function3(function2(function1(collect(['Ali','Iran'])))));

//استفاده از ماکرو
collect(['i', 'want', 'to', 'live', 'in','peace'])
  ->function1()
  ->function2()
  ->function3()
  ->function4();

به ترتیب توابع function1, function2, function3, function4 را می‌توانید به صورت ماکرو بنویسید و ورودی هر ماکرو آرایه ورودی در تابع collect خواهد بود.

جمع‌بندی:

ماکروها می‌توانند در تمامی قسمت‌های پروژه کار ما را بسیار آسان‌تر کرده و کد تمیزتری خواهیم داشت. در این مقاله موارد پایه‌ای ماکروها را توضیح دادیم، همچنین ساخت Provider سفارشی را توضیح دادیم، و در مبحثی تخصصی‌تر در مورد استفاده از ماکروها در View پروژه، را مورد بررسی قرار دادیم، و در نهایت ماکرو در کالکشن‌ها را آموزش دادیم، تجربه‌ی شما از استفاده‌ی ماکروها چیست؟ خوشحال می‌شویم که اگر تجربه یا سوالی داشتید در بخش نظرات با ما به اشتراک بگذارید.

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

چه امتیازی به این مقاله می دید؟
نویسنده وحید حسنی
از دوست داران دنیای تکنولوژی مخصوصا دنیای وب از توسعه دهندگان وب مخصوصا بک اند از عاشقان پی اچ پی مخصوصا لاراول از دیوانگان کار کردن مخصوصا در آی ویرا ;-)

نظرات کاربران

اولین دیدگاه این پست رو تو بنویس !

ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :