laravel 对象以数组方式访问

2019-07-22  本文已影响0人  小东班吉

laravel 对象以数组方式访问

laravle ORM查询出来的数据是一个对象,但支持我们以数组的方式访问,这里其实是继承了php的一个与定义接口ArrayAccess

之前遇到过一个问题,举例如下:

$test = [
    'postion' => 1,
];
$oder_info = Order::whereBuyerId(133499)->first();
$oder_info['test'] = $test;
$oder_info['test']['postion'] = 2;
var_dump($order_info);

这个时候会抛一个异常

Indirect modification of overloaded element of App\Models\Order has no effect

但如果不修改test数组的值,就没问题,那么问题来了,这是为什么呢?

Model基础的类继承了ArrayAccess接口\Illuminate\Database\Eloquent\Model。

ArrayAccess {
/* 方法 */
abstract public offsetExists ( mixed $offset ) : boolean
abstract public offsetGet ( mixed $offset ) : mixed
abstract public offsetSet ( mixed $offset , mixed $value ) : void
abstract public offsetUnset ( mixed $offset ) : void
}

\Illuminate\Database\Eloquent\Model类对这几个方法进行了重写,而我们上面的例子,对$order_info对象进行赋值就等于调用了offsetSet这个方法。

public function offsetSet($offset, $value)
{
    $this->setAttribute($offset, $value);
}

public function setAttribute($key, $value)
{
    // First we will check for the presence of a mutator for the set operation
  // which simply lets the developers tweak the attribute as it is set on
  // the model, such as "json_encoding" an listing of data for storage.
  if ($this->hasSetMutator($key)) {
    return $this->setMutatedAttributeValue($key, $value);
  }

  // If an attribute is listed as a "date", we'll convert it from a DateTime
  // instance into a form proper for storage on the database tables using
  // the connection grammar's date format. We will auto set the values.
  elseif ($value && $this->isDateAttribute($key)) {
    $value = $this->fromDateTime($value);
  }

  if ($this->isJsonCastable($key) && ! is_null($value)) {
    $value = $this->castAttributeAsJson($key, $value);
  }

  // If this attribute contains a JSON ->, we'll set the proper value in the
  // attribute's underlying array. This takes care of properly nesting an
  // attribute in the array's value in the case of deeply nested items.
  if (Str::contains($key, '->')) {
    return $this->fillJsonAttribute($key, $value);
  }
  $this->attributes[$key] = $value;
  return $this;
}

由上面代码可知道,赋值的操作其实等于是给了\Illuminate\Database\Eloquent\Concerns\HasAttributes类的一个attributes属性,属性的名称就是我们定义的key,当访问的时候就从attributes属性里取出来,参考\Illuminate\Database\Eloquent\Concerns\HasAttributes::getAttribute方法。要修改数组里面的值除非对属性赋值为引用传递。而这其实是由php的重载机制实现的,PHP的重载和其他语言不一样,传统的重载是用于提供多个同名的类方法,但各方法的参数类型和个数不同,是由__set, __get等魔术方法实现的,魔术方法的参数是不能够引用传递。

class PropertyTest
{
    /**  被重载的数据保存在此  */
    private $data = array();


    /**  重载不能被用在已经定义的属性  */
    public $declared = 1;

    /**  只有从类外部访问这个属性时,重载才会发生 */
    private $hidden = 2;

    public function __set($name, $value)
    {
        echo "Setting '$name' to '$value'\n";
        $this->data[$name] = $value;
    }

    public function __get($name)
    {
        echo "Getting '$name'\n";
        if (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        }

        $trace = debug_backtrace();
        trigger_error(
            'Undefined property via __get(): ' . $name .
            ' in ' . $trace[0]['file'] .
            ' on line ' . $trace[0]['line'],
            E_USER_NOTICE);
        return null;
    }
}

$obj = new PropertyTest;

$obj->a = ['value' => 1];
var_dump($obj->a);
$obj->a['value'] = 2;
var_dump($obj->a['value']);

我们可以看到,其实 $obj->a['value'] = 2;其实是访问了__get方法,并没有赋值。

上一篇下一篇

猜你喜欢

热点阅读