laravel之PHP魔术函数__call()的应用

1 篇文章 0 订阅
1 篇文章 0 订阅

魔术函数
__call():在对象中调用一个不可访问方法时,__call() 会被调用。
场景:在用laravel框架实现一个博客后台管理系统时,需要用到一个标签对内容进行分类管理
然后在对应的控制器定义变量输出blade模板。
正常输出的样子
在这里插入图片描述

一、发现问题
在学laravel时碰到一个魔术方法withTags这个方法找遍了也没找到,实际上也不存在

class TagController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     * withTags 魔术方法 调用不存在的方法可以通过 __call魔术函数兜底
     */
    public function index()
    {
        $tags = Tag::all();
        return view('admin.tag.index')->withTags($tags);
    }
}

对应的blade模板变量所在位置

@foreach ($tags as $tag)
    <tr>
        <td>{{ $tag->tag }}</td>
        <td>{{ $tag->title }}</td>
        <td class="hidden-sm">{{ $tag->subtitle }}</td>
        <td class="hidden-md">{{ $tag->page_image }}</td>
        <td class="hidden-md">{{ $tag->meta_description }}</td>
        <td class="hidden-md">{{ $tag->layout }}</td>
        <td class="hidden-sm">
            @if ($tag->reverse_direction)
                逆序
            @else
                升序
            @endif
        </td>
        <td>
            <a href="/admin/tag/{{ $tag->id }}/edit" class="btn btn-xs btn-info">
                <i class="fa fa-edit"></i> 编辑
            </a>
        </td>
    </tr>
@endforeach

二、提出疑问
这里反正这个函数不存在,为什么这里不能直接用__call()方法,或者把withTags换成testTags()、myTags().这些都会报错,唯独withTags()方法不会报错?
排除:经过查看__call方法的定义,排除这里直接用__call的猜想。
为了找到__call方法所在的类,于是就干脆先把方法名改为__call然后ctrl+鼠标左键定位找到__call方法定义的位置

public function index()
{
    $tags = Tag::all();
    return view('admin.tag.index')->__call($tags);
}

找到__call方法定义的位置如下

public function __call($method, $parameters)
{
    if (static::hasMacro($method)) {
        return $this->macroCall($method, $parameters);
    }

    if (! Str::startsWith($method, 'test')) {
        throw new BadMethodCallException(sprintf(
            'Method %s::%s does not exist.', static::class, $method
        ));
    }

    return $this->with(Str::camel(substr($method, 4)), $parameters[0]);
}

然后修改startsWith方法的第二个参数,把方法名改为test。即表示魔术函数要以test开头才有效

/**
 * Display a listing of the resource.
 *
 * @return \Illuminate\Http\Response
 * withTags 魔术方法 调用不存在的方法可以通过 __call魔术函数兜底
 */
public function index()
{
    $tags = Tag::all();
    return view('admin.tag.index')->testTags($tags);
}

可以访问了

三、继续深挖
继续看__call定义中的startWith方法,

public function __call($method, $parameters)
{
    if (static::hasMacro($method)) {
        return $this->macroCall($method, $parameters);
    }

    if (! Str::startsWith($method, 'with')) {
        throw new BadMethodCallException(sprintf(
            'Method %s::%s does not exist.', static::class, $method
        ));
    }

    return $this->with(Str::camel(substr($method, 4)), $parameters[0]);
}

在后再定位startWith方法定义的位置,在Str类中

/**
 * Determine if a given string starts with a given substring.
 *
 * @param  string  $haystack
 * @param  string|string[]  $needles
 * @return bool
 */
public static function startsWith($haystack, $needles)
{
    foreach ((array) $needles as $needle) {
        if ((string) $needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0) {
            return true;
        }
    }

    return false;
}

可以看到这里必须两个参数字符相等才返回true
接着尝试:
是不是两个字符串相等就可以正常输出变量到blade模板 例如这里withTags
(注:可以看到上面__call方法中用了camel函数,目的是把对应的参数转化为驼峰命名规则,所以这里不在去深究非驼峰命名的变量,那没意义。)
即len(with)=len(Tags),所以能成功输出变量到模板
为了验证这一观点是否合理,于是我修改了index方法中的变量名为txy,魔术函数为withTxyz。

public function index()
{
    $txy = Tag::all();
    return view('admin.tag.index')->withTxyz($txy);
}

然后对应的模板变量也改为$txy

@foreach ($txy as $tag)
    <tr>
        <td>{{ $tag->tag }}</td>
        <td>{{ $tag->title }}</td>
        <td class="hidden-sm">{{ $tag->subtitle }}</td>
        <td class="hidden-md">{{ $tag->page_image }}</td>
        <td class="hidden-md">{{ $tag->meta_description }}</td>
        <td class="hidden-md">{{ $tag->layout }}</td>
        <td class="hidden-sm">
            @if ($tag->reverse_direction)
                逆序
            @else
                升序
            @endif
        </td>
        <td>
            <a href="/admin/tag/{{ $tag->id }}/edit" class="btn btn-xs btn-info">
                <i class="fa fa-edit"></i> 编辑
            </a>
        </td>
    </tr>
@endforeach

欣喜若狂,打开浏览器。访问:
在这里插入图片描述

咿,咋回事呢?方法名应该没错啊
难道是变量名错了,于是修改变量名也为txyz

public function index()
{
    $txyz = Tag::all();
    return view('admin.tag.index')->withTxyz($txyz);
}

对应的模板也改了
刷新浏览器;

成功!
四、在挖一波
在想是不是变量的位数不够 修改变量名为$txyz1

public function index()
{
    $txyz1 = Tag::all();
    return view('admin.tag.index')->withTxyz($txyz1);
}

然后模板中的也设置
刷新浏览器:
在这里插入图片描述

GG 依然失败。
这次我点击了上图的神奇按钮
在这里插入图片描述

然后说成功了,还别说laravel真强大
在这里插入图片描述

然后我就刷新
在这里插入图片描述

呀!熟悉的界面又回来了 OK!
检查代码发现点击后Fix Typo 后把模板中的变量改成了

@foreach ($txyz as $tag)
    <tr>
        <td>{{ $tag->tag }}</td>
        <td>{{ $tag->title }}</td>
        <td class="hidden-sm">{{ $tag->subtitle }}</td>
        <td class="hidden-md">{{ $tag->page_image }}</td>
        <td class="hidden-md">{{ $tag->meta_description }}</td>
        <td class="hidden-md">{{ $tag->layout }}</td>
        <td class="hidden-sm">
            @if ($tag->reverse_direction)
                逆序
            @else
                升序
            @endif
        </td>
        <td>
            <a href="/admin/tag/{{ $tag->id }}/edit" class="btn btn-xs btn-info">
                <i class="fa fa-edit"></i> 编辑
            </a>
        </td>
    </tr>
@endforeach

而此时控制器中的方法名没变

public function index()
{
    $txyz1 = Tag::all();
    return view('admin.tag.index')->withTxyz($txyz1);
}

模板中的变量只是与with后面的字符串同名即txyz
但是没这里的变量,模板中访问变量是万万不行的
所以模板中的变量和这里的变量有着“说不清”的关系。
可能对于大佬来说上述操作属于盲挖,或许另有高见,欢迎指出,互相学习。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值