Использование объектов в качестве массивов (на примере реального мира) в PHP


Иногда удобно, когда вы можете получить доступ к объектам класса в виде массивов. Например, время, когда одно из свойств класса относится к типу array, и вы хотите манипулировать им так же, как вы делаете с массивом, не подвергая его воздействию во время создания объекта. Я включил реальный пример того, где это может быть полезно, в конце статьи. Так что читайте дальше!

В PHP есть предопределенный интерфейс, называемый ArrayAccess поставляется в PHP 5, 7, который используется для реализации такого варианта использования. Когда класс реализует ArrayAccessим нужно реализовать несколько методов, чтобы соответствовать интерфейсу, и это методы, которые заставляют объекты вести себя как массивы.

ArrayAccess интерфейс

ArrayAccess Интерфейс, как я уже говорил ранее, является предопределенным интерфейсом в PHP со следующей структурой.

interface ArrayAccess 
{
    abstract public offsetExists ( mixed $offset ) : bool
    abstract public offsetGet ( mixed $offset ) : mixed
    abstract public offsetSet ( mixed $offset , mixed $value ) : void
    abstract public offsetUnset ( mixed $offset ) : void
}

Как видите, интерфейс содержит четыре абстрактных метода, которые имеют разное назначение для них, когда-то реализованные классами. Давайте попробуем понять, что делает каждый из них.

offsetExists метод

Этот метод может использоваться, чтобы проверить, существует ли конкретное смещение массива в объекте или нет, и вернуть логическое значение при использовании Исеть () или пустой () на реализацию объектов ArrayAccess, Для этого вы можете написать собственную реализацию в методе.

Возьмите следующие, например,



class MyObj implements ArrayAccess 
{
    
    // excluded other methods for brevity

    public function offsetExists($var) 
    {
        var_dump(__METHOD__);
        if ($var == 'foobar') {
            return true;
        }
        return false;
    }
}

Теперь, когда смещение вызывается для объекта, как показано ниже,

$myObj = new MyObj();

var_dump(isset($myObj('foobar')));

offsetExists Метод будет вызван и простит базовую реализацию, которая выдаст следующий результат.

string(17) "obj::offsetExists"
bool(true)

Если смещение было чем-то отличным от «foobar», оно вернуло бы bool(false) вместо.

Тем не менее, если вы используете isset() на объекте без смещения, как показано ниже,

$myObj = new MyObj();

var_dump(isset($myObj));

Метод offsetExists всегда возвращается true независимо.

offsetGet метод

Этот метод возвращает значение для указанного смещения для объекта. Метод также вызывается при вызове empty() на смещение. Вот как это работает.



class MyObj implements ArrayAccess 
{
    private $container = ();

    public function __construct()
    {
        $this->container = (
            'apple'   => 1,
            'banana'   => 2
        );
    }   
    
    // excluded other methods for brevity

    public function offsetGet($offset) 
    {
        return isset($this->container($offset)) ? $this->container($offset) : null;
    }
}

Так что теперь, когда я пытаюсь получить доступ к указанному смещению на объекте, offsetGet метод будет вызван и вернет значение для $container для этого смещения и null если не существует указанного смещения.

$myObj = new MyObj();

var_dump($myObj('apple')); // 1
var_dump($myObj('kiwi')); // null

offsetSet метод

Метод присваивает значение указанному смещению объекта.

abstract public ArrayAccess::offsetSet ( mixed $offset , mixed $value ) : void

Метод принимает следующие два параметра:

  • offset – Смещение, которому нужно присвоить значение.
  • value – Значение, которое нужно установить на смещение.

Проверьте следующее, например.


class MyObj implements ArrayAccess 
{
    private $container = ();

    public function __construct()
    {
        $this->container = (
            'apple'   => 1,
            'banana'   => 2
        );
    }   
    
    // excluded other methods for brevity

    public function offsetGet($offset) 
    {
        return isset($this->container($offset)) ? $this->container($offset) : null;
    }
    
    public function offsetSet($offset, $value) 
    {
        if (is_null($offset)) {
            $this->container() = $value;
        } else {
            $this->container($offset) = $value;
        }
    }
}

$myObj = new MyObj;

$myObj('kiwi') = 10;

var_dump($myObj('kiwi')); // 10

$myObj('apple') = 5

var_dump($myObj('apple')); // 5

$myObj() = 50; // will add a "0" offset in the array and set the value
?>

offsetUnset метод

Этот метод вызывается при попытке снята с охраны смещение на объекте.

abstract public ArrayAccess::offsetUnset ( mixed $offset ) : void

Метод принимает один параметр offset который может быть использован для сброса смещения. Ниже приведен пример того же.


class MyObj implements ArrayAccess 
{
    private $container = ();

    public function __construct()
    {
        $this->container = (
            'apple'   => 1,
            'banana'   => 2
        );
    }   
    
    // excluded other methods for brevity
    
    public function offsetUnset($offset) 
    {
        unset($this->container($offset));
    }
}

$myObj = new MyObj;

unset($myObj('apple'))

var_dump($obj('apple')); // null
?>

С помощью __invoke получить доступ ко всему массиву

В случае, если вы хотите получить доступ ко всему объекту в виде массива, вы можете реализовать __invoke() магический метод в классе и вернуть весь массив из него, как это.


class MyObj implements ArrayAccess 
{
    private $container = array();

    public function __construct()
    {
        $this->container = (
            'apple'   => 1,
            'banana'   => 2
        );
    }   
    
    // excluded other methods for brevity
    
    public function __invoke()
    {
        return $this->container;
    }
}

$myObj = new MyObj;

var_dump($myObj);

/*
object(MyObj)#1 (1) {
  ("container":"MyObj":private)=>
  array(2) {
    ("apple")=>
    int(1)
    ("banana")=>
    int(2)
  }
}
*/
?>

Реальный случай использования

Недавно я наткнулся на этот пиар # 30817 в Laravel рамки Тейлор Отвелл. В этом он реализовал ArrayAccess на обоих JsonResponse и TestResponse чтобы иметь возможность прокси прямо в ответ JSON, не проходя через original свойство.

И благодаря этому код, который используется для доступа к полезной нагрузке JSON в ответе, выглядит следующим образом.

$response->original('foo');

Теперь сводится к следующему из-за реализации ArrayAccess,

$response('foo');

Довольно аккуратно, правда?