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
方法,并没有赋值。
网友评论