升级 PHP7 后 isset 不太对了
52

升级 php7 后 isset 不太对了

公司升级 php7 后出现了一个问题
类似这样 isset($post->user->name) 始终为 false
之前的php 5.6 就很正常
laravel 版本是 5.1.35(很久没升级了)

先看看isset

isset 用来检测变量是否设置

首先我们来看官方的一个例子

大致上是下面这个意思

<?php

class Post
{
    protected $attributes = ['content' => 'foobar'];

    public function __get($key)
    {
        if (isset($this->attributes[$key])) {
            return $this->attributes[$key];
        }
    }
}

$post = new Post();
echo isset($post->content);  // false

上面这个例子将永远返回 false,因为 foo 并不是 Post 的属性,而是 __get 取出来的

魔术方法 __isset

那么怎么解决上面那个问题呢?使用魔术方法

<?PHP
class Post
{
    protected $attributes = ['content' => 'foobar'];

    public function __get($key)
    {
        if (isset($this->attributes[$key])) {
            return $this->attributes[$key];
        }
    }

    public function __isset($key)
    {
        if (isset($this->attributes[$key])) {
            return true;
        }

        return false;
    }
}

$post = new Post();
echo isset($post->content);   //true

类似 Eloquent 的例子

看着 laravel 5.1.35 的代码,我们自己写一个简单的例子

先有一个 Model,简单的实现。__get__set__isset

class Model
{
    // 存放属性
    protected $attributes = [];

    // 存放关系
    protected $relations = [];

    public function __get($key)
    {
        if( isset($this->attributes[$key]) ) {
            return $this->attributes[$key];
        }

          // 找到关联的对象,放在关系里面
        if (method_exists($this, $key)) {

              $relation = $this->$method();   

              return $this->relations[$method] = $relation;
        }
    }

    public function __set($k, $v)
    {
        $this->attributes[$k] = $v;
    }

    public function __isset($key)
    {
        if (isset($this->attributes[$key]) || isset($this->relations[$key])) {
            return true;
        }

        return false;
    }
}

然后我们定义一个 Post Moel 和一个 User Moel

class Post extends Model
{

    protected function user()
    {
        $user = new User();
        $user->name = 'user name';
        return $user;
    }

}

class User extends Model
{
}

好了来验证一下isset

$post = new Post();

echo 'isset 发帖用户:';
echo isset($post->user) ? 'true' : 'false';  // false
echo PHP_EOL;

echo 'isset 发帖用户的名字:';
echo isset($post->user->name) ? 'true' : 'false';  // false
echo PHP_EOL;

echo '发帖用户的名字:';
echo $post->user->name;    // user name
echo PHP_EOL;

echo '再次判断 isset 发帖用户的名字:';
echo isset($post->user->name) ? 'true' : 'false';   // true
echo PHP_EOL;

答案

分析上面的结果,感觉像是 php 7 isset 方法对对象的判断有了变化,如果先执行一次,$post->user->name,也就是将 user 放在 post 的 relations 中,这样 isset($post->user) 为 true,随后 isset($post->user->name) 才为 true。

最后在 Eloquent model 的 git log 中 找到了答案,

PHP 7 has fixed a bug with __isset which affects both the 
native isset and empty methods. This causes specific issues 
with checking isset or empty on relations in Eloquent. In 
PHP 7 checking if a property exists on an unloaded relation, 
for example isset($this->relation->id) is always 
returning false because unlike PHP 5.6, PHP 7 is now 
checking the offset of each attribute before chaining to 
the next one. In PHP 5.6 it would eager load the relation 
without checking the offset. This change brings back the 
intended behavior of the core Eloquent model __isset method 
for PHP 7 so it works like it did in PHP 5.6.

For reference, please check the following link, 
specifically Nikita Popov's comment (core PHP dev) - 
https://bugs.php.net/bug.php?id=69659

大致上是 php7 isset 判断的时候,会依次判断。php5.6 则会预加载关系。其实 laravel 也早在5月份就做了相关的处理,所以升级 laravel 后,自然也就没有这个问题了。

本帖已被设为精华帖!
本帖由 Summer 于 2年前 加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 8
Summer

wow 很棒的发现

2年前
overtrue

@liyu001989 这个还真没仔细留意过,感谢分享!

2年前

这个知识点非常细,受教了

2年前

总感觉更规范了~

2年前
skyLee

赞哦

2年前

很理论,我听着也很享受,一点都不会有被虐的感觉

2年前

居然还有这个坑

2年前

刚遇到这个问题,感谢分享。

1年前

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!