laravel自定义模型方法抛异常Non-static method XXX should not be called statically

目标

我在laravel项目中自定义的Model中添加了一个方法,我想像Model的其他方法如all方法一样支持在实例上调用和静态方式调用:

$flights  =  (new App\Flight)->all(); //在实例上调用
$flights  =  App\Flight::all();	//静态方式调用

我们的Model名称为AvatarPic,我编写的测试代码为:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AvatarPic extends Model
{
	 protected $table = 'avatar_pic';
	  /**
	 * @var string
	 */  
	 private $path;

	 public function test()
	 {
	 	echo 'test';
	 }
}

问题

当我在实例上调用时,代码可以正常执行:

(new App\Models\AvatarPic)->test(); // 将会打印出test

当我使用静态方式调用时,代码将会抛出异常:

App\Models\AvatarPic::test(); //将会抛出ErrorException (E_DEPRECATED)

异常描述为:

Non-static method App\Models\AvatarPic::test() should not be called statically

我的环境信息:

  • 操作系统:windows10
  • PHP版本:PHP 7.2.1
  • laravel版本:Laravel Framework 5.5.48

检测

在看过laravel中Illuminate\Database\Eloquent\Model代码后,我对laravel模型像all方法的调用方式有了初步了解。
根据Illuminate\Database\Eloquent\Model__callStatic的使用,我编写了测试代码来探测问题发生在哪里。
我编写一个测试文件test.php

<?php
class TestService
{
		public static function __callStatic($name, $arguments)
		{
			echo 't';
		}

		public function test()
		{
			echo 'test';
		}
}
TestService::test();

当我运行这个测试文件时,程序成功打印出了’test’:

laravel中的什么设置使PHP中的__callStatic抛出Non-static method xxx should not be called statically异常?
这个结果给我了实现目标的希望,几乎同样的代码,在laravel外可以正常执行。为了验证是否由于继承Illuminate\Database\Eloquent\Model造成的异常,我在laravel项目中编写了一样的代码:

<?php


namespace App\Services;


class TestService
{
    public static function __callStatic($name, $arguments)
    {
        echo '__callStatic';
    }

    public function test()
    {
        echo 'test';
    }
}

当我运行代码时,程序还是抛出了异常:

laravel中的什么设置使PHP中的__callStatic抛出Non-static method xxx should not be called statically异常?
为了排除环境因素,我在centos7、php 7.3.1、laravel 5上执行也是如此。

猜想

  1. 应该是因为laravel内部做了限制。
  2. 在laravel整个项目中搜索"Non-static method",并没有搜索结果,也就是说这个异常是php产生的,不是laravel定义的。
  3. 可能laravel内部对php的配置做了修改。

在打印的异常信息中有"ErrorException (E_DEPRECATED)",那么上面的写法在laravel中的异常级别是E_DEPRECATED,使用原生php执行没有抛异常,可能是默认的php异常级别与laravel中配置的异常级别不一致。

验证

查看laravel中的错误级别:
laravel error_reporting
查看PHP中错误级别:
PHP error_reporting
laravel中设置为报告所有异常错误,PHP设置的报告异常有
PHP error
我们修改我们在测试代码中添加error_reporting(-1)``,重新执行程序:

<?php

error_reporting(-1);
class TestService
{
    public static function __callStatic($name, $arguments)
    {
        echo 't';
    }

    public function test()
    {
        echo 'test';
    }
}

TestService::test();
(new TestService)->test();

这时,使用php执行也会抛出异常:
PHP error

结尾

虽然知道了laravel和原生php执行结果不同的原因,但使用两种方式调用的目标依然没有解决。
要想静态访问可以设置自定义方法的访问限定为protected;参考:PHP 调用静态方法不存在时首先检查方法是否是静态的而不是走__callStatic? | Laravel China 社区

  • public可以在实例上调用;
  • protected可以静态调用;

对于像下面find一样laravel提供的即可静态方式调用又可对象上调用的方法,又是怎么实现的呢?
find

  • 首先,App\User中肯定没定义find方法。
  • App\User继承了\Illuminate\Database\Eloquent\ModelModel没定义find方法,但定义了__callStatic方法:
// \Illuminate\Database\Eloquent\Model::__callStatic
public static function __callStatic($method, $parameters)
{
    return (new static)->$method(...$parameters);
}

__callStatic内部new了一个对象,在对象上调用find方法。此时,User::find(1)的调用被转化为类似于(new User)->find(1)的调用。

  • App\User类对象没有find方法,但从Model继承了_call方法:
// \Illuminate\Database\Eloquent\Model::__call
/**
 * Handle dynamic method calls into the model.
 *
 * @param  string  $method
 * @param  array  $parameters
 * @return mixed
 */
public function __call($method, $parameters)
{
    if (in_array($method, ['increment', 'decrement'])) {
        return $this->$method(...$parameters);
    }

    return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}

//\Illuminate\Support\Traits\ForwardsCalls::forwardCallTo
/**
  * Forward a method call to the given object.
  *
  * @param  mixed  $object
  * @param  string  $method
  * @param  array  $parameters
  * @return mixed
  *
  * @throws \BadMethodCallException
  */
 protected function forwardCallTo($object, $method, $parameters)
 {
     try {
         return $object->{$method}(...$parameters);
     } catch (Error | BadMethodCallException $e) {
         $pattern = '~^Call to undefined method (?P<class>[^:]+)::(?P<method>[^\(]+)\(\)$~';

         if (! preg_match($pattern, $e->getMessage(), $matches)) {
             throw $e;
         }

         if ($matches['class'] != get_class($object) ||
             $matches['method'] != $method) {
             throw $e;
         }

         static::throwBadMethodCallException($method);
     }
 }

_call中使用ForwardsCallsforwardCallTo方法,在this->newQuery()上调用find方法。

  • this->newQuery()返回\Illuminate\Database\Eloquent\Builder对象,Builder类中定义了find方法:
// \Illuminate\Database\Eloquent\Builder::find
public function find($id, $columns = ['*'])
{
    if (is_array($id) || $id instanceof Arrayable) {
        return $this->findMany($id, $columns);
    }

    return $this->whereKey($id)->first($columns);
}

所以,find方法不是App\User类继承的方法。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值