【面试题】PHP迭代器+魔术方法实现键名转义

131 阅读2分钟

题目

class User { public $name; public $age; }
$User = new User(); 
$User->name = "Kingmax"; 
$User->age = 30;
foreach($User as $key=>$value) {
    echo $key.":".$value."\n"; 
}

输出

name:Kingmax 
age:30

如果我要以上例程输出的结果变成:

姓名:Kingmax 
年龄:30

解法过程

习惯于在各种框架上进行model操作的我们乍一看这不是根据获取器和拦截器简单安排嘛。例如来laravel上面我们对UserModel的拦截器getNameAttribute()做出修改就能够得到年龄这个值。

emmm不太对,获取器修改得到的是值,没办法改动到键名

仔细看一下其实是遍历然后修改键名,所以我们有必要对键名做一个中文映射于是定义了$data私有属性。显而易见的可以想得到通过魔术方法做到这件事情。我们通过__get()__set()两个魔术方法可以轻易的做到映射维护。题目要求我们仅能修改class user{}作出修改从而兼容客户端,便有了以下代码。

class User implements Iterator  
{  
    private array $data = [  
        'name' => [  
            'cn' => '姓名',  
            'value' => null,  
    ],  
        'age' => [  
            'cn' => '年龄',  
            'value' => null,  
        ],  
    ];  
  
    public function current()  
    {  
        return $this->data[key($this->data)]['value'];  
    }  
  
    public function next()  
    {  
        return next($this->data);  
    }  
  
    public function key()  
    {  
        return $this->data[key($this->data)]['cn'];  
    }  
  
    public function valid(): bool  
    {  
        return key($this->data) !== null;  
    }  
  
    public function rewind(): void  
    {  
        reset($this->data);  
    }  
  
    public function __set(string $name, $value): void  
    {  
        if (!isset($this->data[$name])) {  
            throw new InvalidArgumentException(__CLASS__ . " 设置 $name 属性失败"); 
        }  
        $this->data[$name]['value'] = $value;  
    }  
  
    public function __get(string $name)  
    {  
        if (!isset($this->data[$name])) {  
            throw new InvalidArgumentException(__CLASS__ . " 获取 $name 属性不存在");  
        }  
        return $this->data[$name]['value'];  
    }  
}

输出之后得到预期结果

姓名:Kingmax 
年龄:30

下面我们来复盘几个知识点

  1. foreach的基础知识点
  2. php的魔术方法
  3. 什么是迭代器
foreach的基础知识点
  • foreach 是 PHP 的一个内置函数,它用于遍历数组中的每个元素并对其进行操作。一般语法为:
$array = [];
foreach ($array as $key => $value) {  }
  • foreach 也支持对对象的非空公有属性进行遍历。对于对象,遍历的顺序是按照属性定义的顺序进行的。例如题目User{}中的$name属性就是一个公共属性。至于要强调非空是因为在PHP中null,'',0,false都可以定义为空。null是一种特殊数据结构,而下面的例子的属性则不会被遍历出来,因为没有未被赋值。
class User{
    public $name;
    public $age;
}
$user = new User();
$user->name = 'tty199';
foreach($User as $key=>$value) {
    echo $key.":".$value."\n"; 
}
#输出结果中并没有age
#输出
> name : tty199
  • foreach也可以遍历实现了Interator即迭代器接口的类。迭代器在不同的语言中有实现差异。
php的魔术方法
  • php提供了一组魔术方法能够在对象生命周期中自动调用以方便我们做一些特殊处理,例如构造函数__construct()可以在函数创建时被调用。其功能和java的类同名方法功能相似。我们前面使用了__set($name,$value)是PHP提供的功能是当设置的属性在类实例中不存在时会被调用到。其中$name为属性名$value为具体任何类型值。

在上述改造后的user{}中我定义了一个私有属性$data用于存储或获取我需要的两个属性并且做了中文映射。下面是PHP提供的全部魔术方法。

方法作用
__construct()构造函数,在对象创建时自动调用
__destruct()析构函数,在对象销毁时自动调用
__get()获取不存在或不可访问属性时自动调用
__set()设置不存在或不可访问属性时自动调用
__isset()检查不存在或不可访问属性是否已设置时自动调用
__unset()取消设置不存在或不可访问属性时自动调用
__call()调用不存在或不可访问方法时自动调用
__callStatic()调用不存在或不可访问静态方法时自动调用
__toString()将对象转换为字符串时自动调用
__invoke()将对象作为函数调用时自动调用
__set_state()当 var_export() 导出类时调用 (php8已放弃)
__clone()对象被克隆时自动调用
什么是迭代器
  • 它提供了一种统一的方式来遍历集合中的元素。foreach内置函数会调用实现了迭代器接口的实例的五个方法。前面的例子中我们使用了Iterator 接口来定义自定义user迭代器。该接口定义了 rewind()valid()key()current()next() 方法,分别用于重置迭代器指针、检查是否还有元素可遍历、返回当前元素的键和值,以及将迭代器指针指向下一个元素。

显然,我们通过编写key()这个函数配合$data实现了对键名的映射。

...
private array $data = [  
        'name' => [  
            'cn' => '姓名',  
            'value' => null,  
    ],  
        'age' => [  
            'cn' => '年龄',  
            'value' => null,  
        ],  
    ]; 
...
public function key()  
    {  
        return $this->data[key($this->data)]['cn'];  
    }