魔术函数
__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
但是没这里的变量,模板中访问变量是万万不行的
所以模板中的变量和这里的变量有着“说不清”的关系。
可能对于大佬来说上述操作属于盲挖,或许另有高见,欢迎指出,互相学习。