MyTetra Share
Делитесь знаниями!
Что не так с функцией array_key_exists() и имплементацией ArrayAccess в PHP5?
Время создания: 14.04.2017 17:49
Автор: Xintrea
Текстовые метки: PHP, PHP5, ArrayAccess, ArrayObject, array_key_exists, in_array
Раздел: Компьютер - Программирование - Язык PHP
Запись: xintrea/mytetra_syncro/master/base/1492181386xcu0bf53ck/text.html на raw.github.com

Интерфейс ArrayAccess - это хороший и быстрый способ создать объект, способный представить свое содержание как массив. В официальной документации все кратко и точно расписано.


Но есть одно но. Если с таким объектом начинает работать функция array_key_exists(), то она не будет видеть элемента с существующим ключем. Почему так? Вот пример кода, поясняющего что происходит на самом деле:


<?php
class A implements ArrayAccess {
    public $data;


    public function offsetExists($offset) {
        return isset($this->data[$offset]);
    }
    public function __get($property) {
        return $this->data[$property];
    }
}

$a = new A();
$a->data['somekey'] = 1;

var_dump(array_key_exists('somekey', $a)); // returns false
var_dump(array_key_exists('data', $a));  // returns true
?>


Здесь написана реализация переопределяемого метода offsetExists(). И, казалось бы, что функция array_key_exists() должна работать правильно. Но видно, что элемента с существующим ключем 'somekey' эта функция не видит. Зато видит элемент с несуществующим ключем 'data'. Почему так?


Потому что эта функция является "осколком" интерфейсов, которые позволяют работать со свойствами и методами объекта как с итерируемыми сущностями. Функция array_key_exists() вообще не обращает внимание на реализацию offsetExists(), она работает совсем по-другому: проверяет наличие элемента класса (элементы класса - это свойства и методы класса). В нашем примере она находит публичное свойство $data, и поэтому считает, что у объекта есть элемент 'data'.


Как же узнать, если ли в объекте-массиве настоящий элемент? Можно воспользоваться функцией isset:


var_dump(isset($a['somekey'])); // returns true


Однако в таком решении есть проблема: если значение элемента массива null, то несмотря на то что элемент существует, isset() вернет false.


Все вышесказанные проблемы перечеркивают все достоинства наследования от ArrayAccess.


UPD1:


Говорят, что есть более правильное решение: воспрользоваться неаследованием от ArrayObject. У такого объекта будет работать не только доступ по скобкам, но и итерирование через foreach (если переопределить метод getIterator(). Но практика показала, что и для объекта, унаследованного от ArrayObject, функция array_key_exists() работает точно так же как и для ArrayAccess.


UPD2:


Единственное более-менее вменяемое решение данной проблемы следующее.


Вместо array_key_exists() и in_array() можно написать специальные методы класса:


// Equivalent of array_key_exists()

public function arrayKeyExists($key)

{

return $this->offsetExists($key);

}

// Equivalent of in_array()

public function inArray($value)

{

return in_array($value, $this->array);

}


После чего можно использовать следующий код:


var_dump( $a->arrayKeyExists('somekey') ); return true

var_dump( $a->inArray(1) ); return true




Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования