Dynamic Method dengan Laravel Macro!

Bayangkan jika kamu bisa membuat sebuah method/function di sebuah class tanpa harus menulisnya di class yang bersangkutan? bahkan bisa mengakses object  $this & semua object di class tersebut penasaran gimana? Let’s check it out!

Kalo kamu lihat di docs Laravel pada bagian Response kamu akan mendapati section tentang macro yang di extend kedalam class response https://laravel.com/docs/5.6/responses#response-macros, kira-kira seperti ini.

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Response;

class ResponseMacroServiceProvider extends ServiceProvider
{
    /**
     * Register the application's response macros.
     *
     * @return void
     */
    public function boot()
    {
        Response::macro('caps', function ($value) {
            return Response::make(strtoupper($value));
        });
    }
}

Di dalam ResponseMacroServiceProvider kita menggunakan Facade Response, lalu di method boot kita menuliskan macro yang akan kita extends ke Facade Response, parameter pertama menerima nama method yang akan digunakan, parameter kedua menerima Callable/Closure instance apa yang method tersebut lakukan.

Nah, dibelakang layar Trait Macroable akan meregister macro tersebut ke dalam property $macros menjadi sebuah asociative array.

/**
 * Register a custom macro.
 *
 * @param  string $name
 * @param  object|callable  $macro
 *
 * @return void
 */
public static function macro($name, $macro)
{
    static::$macros[$name] = $macro;
}

https://github.com/laravel/framework/blob/5.6/src/Illuminate/Support/Traits/Macroable.php#L19-L30,

Tapi apa yang terjadi saat kita menggunakan macro tersebut?

response()->caps('macro is good!'); // MACRO IS GOOD!

Jadi, saat kita memanggil method yang tidak ada pada class, PHP akan mengeksekusi/men-trigger method __call()Nah, jadi Trait Macroable meng-override method __call() untuk dapat mengakses object macro.

/**
 * Dynamically handle calls to the class.
 *
 * @param  string  $method
 * @param  array   $parameters
 * @return mixed
 *
 * @throws \BadMethodCallException
 */
public function __call($method, $parameters)
{
    if (! static::hasMacro($method)) {
        throw new BadMethodCallException(sprintf(
            'Method %s::%s does not exist.', static::class, $method
        ));
    }
    $macro = static::$macros[$method];
    if ($macro instanceof Closure) {
        return call_user_func_array($macro->bindTo($this, static::class), $parameters);
    }
    return call_user_func_array($macro, $parameters);
}

https://github.com/laravel/framework/blob/5.6/src/Illuminate/Support/Traits/Macroable.php#L86-L110

Nah, pada method __call() yang ada di Trait Macroable pertama macroable akan mengecek apakah ada method $method di dalam property $macros jika tidak ada, maka Macroable akan melemparkan sebuah exception BadMethodCallException.

Jika method tersebut ada di property $macros maka Macroable akan mengambilnya dan macro tersebut bisa di akses dengan static::$macros[$method].

Jika macro tersebut adalah instance dari Closure maka Macroable akan melakukan binding kepada parent class menggunakan method bindTo() dari instance Closure yang akan menambahkan $this object dari Closure Macro kedalam static::class keyword yang mana itu adalah Class yang menggunakan method tersebut yang akan menghasilkan instance Class tersebut namun dengan object yang telah di tambahkan sebelumnya. http://php.net/manual/en/closure.bindto.php.

Macro dapat diakses secara static Class:customMacro() & juga non static $obj = new Class(); $b->customMacro().

Maka dari itu Macroable juga meng-override magic method __callStatic()  jika kita mengakses static method yang tidak ada dalam class, Konsepnya sama saja dengan __call().

/**
 * Dynamically handle calls to the class.
 *
 * @param  string  $method
 * @param  array   $parameters
 * @return mixed
 *
 * @throws \BadMethodCallException
 */
public static function __callStatic($method, $parameters)
{
    if (! static::hasMacro($method)) {
        throw new BadMethodCallException(sprintf(
            'Method %s::%s does not exist.', static::class, $method
        ));
    }
    if (static::$macros[$method] instanceof Closure) {
        return call_user_func_array(Closure::bind(static::$macros[$method], null, static::class), $parameters);
    }
    return call_user_func_array(static::$macros[$method], $parameters);
}

https://github.com/laravel/framework/blob/5.6/src/Illuminate/Support/Traits/Macroable.php#L62-L84

 

Setelah tau bagaimana Macro bekerja, sekarang, bagaimana cara membuat Macro?. Untuk dapat menggunakan Macro, Class harus menggunakan trait Macroable, yang berada di namespace Illuminate\Support\Traits\Macroable, Seperti Class Response pada Laravel.

https://laravel.com/api/5.6/Illuminate/Http/Response.html

Nah untuk membuatnya, kita akan memanggil static method macro dari Macroable dan mendefinisikan method apa yang akan kita buat, tempat terbaik membuatnya adalah di ServiceProvider.

Pertama Kita akan membuat Class terebih dahulu yang akan kita gabungkan dengan Macro.

<?php 

namespace App\Services;

use Illuminate\Support\Traits\Macroable;

class Nama {
    
    use Macroable;

}

Seperti Class diatas, ia menggunakan Trait Macroable, dan class tersebut tidak memiliki method sama sekali, karena akan kita tambahkan melalui macro.

Setelah itu Kita membuat Service Provider untuk menampung macro yang akan kita buat.

php artisan make:provider MacroServiceProvider

… dan me-registerkanya kedalam config/app.php.

'providers' => [
     .........
     App\Providers\MacroServiceProvider::class,
]

Selesai! Kita sekarang dapat membuat macro untuk class App\Services\Nama!

<?php

namespace App\Providers;

use App\Services\Nama;
use Illuminate\Support\ServiceProvider;

class MacroServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        Nama::macro('depan', function($nama) {
            return "Halo! nama depan saya {$nama}!";
        });

        Nama::macro('belakang', function ($nama) {
            return "dan nama belakang saya {$nama}!";
        });

    }

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

Kita membuat method macro di dalam method boot() pada service provider, Setelah method dibuat saatnya kita test!

Yap! Macro berhasil kita buat! Bisa di panggil secara static dan non static juga!.

Beberapa component Laravel juga menggunakan Macro sehingga kita bisa extends Fungsionalitas components tersebut dan membuat fitur kita sendiri, sebagai Contoh untuk memberikan response JSON tanpa harus menginisialisasi class Response kita bisa menggunakan macro, seperti yang ditulis om Rahmat Awaludin di https://medium.com/laravel-indonesia/menggunakan-response-macro-di-laravel-c9b2f2c8a466 atau bahkan membuat databse relationship kita sendiri.

Dan ini adalah komponen laravel yang menggunakan Macro.

Facades

  • Cache
  • File
  • Lang
  • Request
  • Response
  • Route
  • URL

Illuminate Classes

  • Illuminate\Cache\Repository
  • Illuminate\Console\Scheduling\Event
  • Illuminate\Database\Eloquent\Builder
  • Illuminate\Database\Eloquent\Relation
  • Illuminate\Database\Query\Builder
  • Illuminate\Filesystem\Filesystem
  • Illuminate\Foundation\Testing\TestResponse
  • Illuminate\Http\RedirectResponse
  • Illuminate\Http\Request
  • Illuminate\Http\UploadedFile
  • Illuminate\Routing\ResponseFactory
  • Illuminate\Routing\Router
  • Illuminate\Routing\UrlGenerator
  • Illuminate\Support\Arr
  • Illuminate\Support\Collection
  • Illuminate\Support\Str
  • Illuminate\Translation\Translator
  • Illuminate\Validation\Rule

 

Banyak sekali kegunaan Macro apalagi jika kita menerapkan SOLID & DRY, Macro akan sangat berguna!,  Ini adalah beberapa referensi yang saya gunakan untuk membuat artikel ini, yang mungkin akan menambah penegetahuan juga.

 

Referensi :

Terimakasih.

Digital Ocean baner