Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

13. Macroable 解析 #13

Open
xiaohuilam opened this issue Sep 27, 2018 · 0 comments
Open

13. Macroable 解析 #13

xiaohuilam opened this issue Sep 27, 2018 · 0 comments
Labels
book The digital book for laravel learning

Comments

@xiaohuilam
Copy link
Owner

xiaohuilam commented Sep 27, 2018

还记得那个《高速修车》的陈年老梗吗:

公司的业务呢,就像跑在高速路上的车,车不能停,但是新需求和修bug也不能停。

这里要跟大家扒代码的 Macroable 呢,有点像上面这个悖论的出路;但其实也不是,因为他没有实现 且不停,只实现了 且不停。请原谅我这尴尬的幽默。


从陈年教程入手

网上很多教程都是提及了,Laravel 绝大部分类,都可以扩展方法。比如这个例子:

use Illuminate\Support\Collection;

Collection::macro('bcsum', function () {
    $sum = 0;
    foreach ($this as $item) {
        $sum = bcadd($sum, $item, 2);
    }
    return $sum;
});

dump((new Collection([1,2,3,4]))->bcsum()); // it says "10.00"

我们可以看到,在不改变代码的前提下,我们在使用的过程中给 Illuminate\Support\Collection 扩充了 bcsum 的功能。就如同在其中加入了如下代码:

<?php

class Collection ...
{
    public function bcsum()
    {
        $sum = 0;
        foreach ($this as $item) {
            $sum = bcadd($sum, $item, 2);
        }
        return $sum;
    }
}

代码解析

class Collection implements ArrayAccess, Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable
{
use Macroable;

我们看到,Illuminate\Support\Collection 使用了 Illuminate\Support\Traits\Macroable 这个 trait

macro 的代码

作用是将闭包存进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;
}

魔术方法

在调用我们的方法时,按名字匹配出来,参数传入触发。

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

这里之所以定义了两个方法,__call__callStatic,是因为 laravel 很多类,部分可以静态调用,部分可以动态调用。macroable 如果要能被这两种类可用,那么就需要定义两个魔术方法。


经验分享

失灵的情况

一. 魔术一个魔术方法,不可行

我们现在知道了 Macroable 的神奇,但是其实Macroable 也有失灵的时候。举个例子:

<?php
class JoeDoe
{
    use \Illuminate\Support\Traits\Macroable;
}

JoeDoe::macro('__toString', function () {
    return '123';
});

echo new JoeDoe();

而运行时,其还是报了这个错:

PHP Recoverable fatal error:  Object of class JoeDoe could not be converted to string

但是如果我们在 JoeDoe 这个类中手工硬编码 __toString 这个方法却是可以运行的的;
为什么?

其实道理很简单,因为 echo JoeDoe 对象时候,php 内核检查其中有没有 __toString 这个方法时,就已经报错了,根本还来不及走到 __call 这个魔术方法。

@xiaohuilam xiaohuilam changed the title Macroable 解析 [0%] 13. Macroable 解析 [0%] Sep 27, 2018
@xiaohuilam xiaohuilam changed the title 13. Macroable 解析 [0%] 13. Macroable 解析 Sep 27, 2018
@xiaohuilam xiaohuilam added the book The digital book for laravel learning label Sep 27, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
book The digital book for laravel learning
Projects
None yet
Development

No branches or pull requests

1 participant