Baidu Encyclopedia definition: computer science in the Macro, is a batch processing title. Generally speaking, a macro is a rule or pattern, or syntactic substitution, that explains how a particular input (usually a string) is converted to a corresponding output (usually a string) according to predefined rules. This substitution occurs at precompilation time and is called macro expansion.

  • I first got to know Macro when I was taking a basic computer course in college, and the teacher told me about Office. At the time, the teacher didn’t pay much attention to macros, only remembering that they were powerful and made everyday tasks much easier.
  • Today we’ll talk about macros in Laravel

First complete source code


      

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);
        }

        returncall_user_func_array($macro, $parameters); }}Copy the code
  • Macroable::macromethods
public static function macro($name, $macro)
{
	static::$macros[$name] = $macro;
}
Copy the code

$macro can pass a closure or an object, according to the comments on the parameter, thanks to the magic method in PHP

class Father
{
    // We can use the object as a closure by adding the magic method **__invoke**.
	public function __invoke(a)
    {
        echo __CLASS__; }}class Child
{
	use \Illuminate\Support\Traits\Macroable;
}

// After adding the macro directive, we can call methods that do not exist in the Child object
Child::macro('show'.new Father);
/ / output: Father
(new Child)->show();
Copy the code
  • Macroable::mixinMethod This method injects the return result of an object’s method into the original object
public static function mixin($mixin)
{
    // Get all public and protected methods in the object by reflection
	$methods = (new ReflectionClass($mixin))->getMethods(
		ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
	);

	foreach ($methods as $method) {
	    // Set methods are accessible because protected ones cannot be called externally
		$method->setAccessible(true);
		
		// Call macro to batch create macro directives
		static::macro($method->name, $method->invoke($mixin)); }}// Actual use
class Father
{
    public function say(a)
    {
        return function (a) {
            echo 'say';
        };
    }

    public function show(a)
    {
        return function (a) {
            echo 'show';
        };
    }

    protected function eat(a)
    {
        return function (a) {
            echo 'eat'; }; }}class Child
{
    use \Illuminate\Support\Traits\Macroable;
}

// Batch bind macroinstructions
Child::mixin(new Father);

$child = new Child;
/ / output: say
$child->say();
/ / output: the show
$child->show();
/ / output: eat
$child->eat();
Copy the code

As you can see from the above code, mixins can bind a class’s methods to a macro class. It is important to note that the method must return a closure type.

  • Macroable::hasMacromethods
public static function hasMacro($name)
{
	return isset(static::$macros[$name]);
}
Copy the code

So this is a very simple method and there’s nothing complicated about it, just to see if there’s a macro instruction. This is usually done before using macros.

  • Macroable::__callandMacroable::__callStaticMethods It is because of these two methods that we can perform macro operations, the code is similar except for the way they are executed. So let’s talk about that__call
public function __call($method, $parameters)
{
    // If the macro does not exist, throw an exception
	if (! static::hasMacro($method)) {
		throw new BadMethodCallException("Method {$method} does not exist.");
	}

    // Get the stored macros
	$macro = static::$macros[$method];

	// Closures do a little bit of special processing
	if ($macro instanceof Closure) {
		return call_user_func_array($macro->bindTo($this.static::class), $parameters);
	}

	// When not a closure, such as an object, run directly through this method, but make sure the object has an '__invoke' method
	return call_user_func_array($macro, $parameters);
}


class Child
{
    use \Illuminate\Support\Traits\Macroable;

    protected $name = 'father';
}

// For closure special handling, all you need to do is bind $this, as in
Child::macro('show'.function (a) {
    echo $this->name;
});

/ / output: father
(new Child)->show();
Copy the code

The Closure::bindTo method is used in the __call method because the Closure::bindTo method can be called in the Closure through $this when we bind the macro.

Closure::bindTo copies the current Closure object and binds the specified $this object to the class scope.

Add macros to classes in Laravel

Many classes in Laravel use the trait macros

Illuminate\Filesystem\Filesystem::class

  1. We just need to get toApp\Providers\AppServiceProvider::registerMethod to add macros (you can also create a new service provider to handle it)
  2. Then add a test route to test our new method
  3. Then open the browser and run it, and you’ll see that our code will run and output the results

The original address