From Baidu Encyclopedia:
A macro in computer science refers to batch processing rules. Generally, it’s a pattern or syntactic substitution that defines how specific inputs (usually strings) are transformed into corresponding outputs (also strings) according to predefined rules. This substitution occurs during preprocessing, called macro expansion.
- My first encounter with macros was during a university computer basics course when the professor demonstrated Office macros. Though I didn’t fully grasp it then, I remembered macros as powerful tools that could simplify repetitive tasks.
- Today we’ll explore macro operations in Laravel
Complete Source Code First
<?php
namespace Illuminate\Support\Traits;
use Closure;
use ReflectionClass;
use ReflectionMethod;
use BadMethodCallException;
trait Macroable
{
/**
* The registered string macros.
*
* @var array
*/
protected static $macros = [];
/**
* Register a custom macro.
*
* @param string $name
* @param object|callable $macro
*
* @return void
*/
public static function macro($name, $macro)
{
static::$macros[$name] = $macro;
}
/**
* Mix another object into the class.
*
* @param object $mixin
* @return void
*/
public static function mixin($mixin)
{
$methods = (new ReflectionClass($mixin))->getMethods(
ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
);
foreach ($methods as $method) {
$method->setAccessible(true);
static::macro($method->name, $method->invoke($mixin));
}
}
/**
* Checks if macro is registered.
*
* @param string $name
* @return bool
*/
public static function hasMacro($name)
{
return isset(static::$macros[$name]);
}
/**
* 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("Method {$method} does not exist.");
}
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);
}
/**
* 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("Method {$method} does not exist.");
}
$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);
}
}
Key Components Explained
Macroable::macro
Method
public static function macro($name, $macro)
{
static::$macros[$name] = $macro;
}
This simple method stores macros in a static array. The $macro
parameter can accept either a closure or an object (thanks to PHP’s magic methods):
class Father
{
public function __invoke()
{
echo __CLASS__;
}
}
class Child
{
use \Illuminate\Support\Traits\Macroable;
}
// Register macro
Child::macro('show', new Father);
// Output: Father
(new Child)->show();
Macroable::mixin
Method
Injects methods from another object:
public static function mixin($mixin)
{
$methods = (new ReflectionClass($mixin))->getMethods(
ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
);
foreach ($methods as $method) {
$method->setAccessible(true);
static::macro($method->name, $method->invoke($mixin));
}
}
// Usage example
class Father
{
public function say() { return fn() => echo 'say'; }
protected function eat() { return fn() => echo 'eat'; }
}
Child::mixin(new Father);
// All methods become available
(new Child)->say(); // Output: say
(new Child)->eat(); // Output: eat
Macroable::hasMacro
Method
Simple existence check:
public static function hasMacro($name)
{
return isset(static::$macros[$name]);
}
- Magic Methods
__call
&__callStatic
Enable dynamic method handling:
public function __call($method, $parameters)
{
if (!static::hasMacro($method)) {
throw new BadMethodCallException("Method {$method} does not exist.");
}
$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);
}
// Example with context binding
Child::macro('show', function() {
echo $this->name; // Accesses Child's property
});
(new Child)->show(); // Output: father
Implementing Macros in Laravel Classes
Many Laravel classes use the Macroable
trait (e.g., Illuminate\Filesystem\Filesystem
). To add custom methods:
- Register macros in
App\Providers\AppServiceProvider
:
// app/Providers/AppServiceProvider.php
public function register()
{
Filesystem::macro('customMethod', function($param) {
return "Working with: $param";
});
}
- Create test route:
// routes/web.php
Route::get('/test-macro', function() {
return Storage::customMethod('demo');
});
- Visit
/test-macro
to see “Working with: demo” output.
This approach extends functionality without modifying core classes, demonstrating Laravel’s elegant extensibility through macros.