Использование установщика с многомерными массивами

#php #mongodb #object #getter-setter

#php #mongodb #объект #getter-установщик

Вопрос:

Я пытаюсь создать __set для объекта в PHP, который работает с многомерными массивами. Возможно ли это вообще?

Я хотел бы иметь возможность делать что-то вроде следующего: $post->comments[0]['uid']=3; . Однако комментарии на самом деле будут ключом в частной переменной кэша $_cache['comments']=array() . Было бы неплохо, если бы функция __set могла каким-то образом получить как базовый ключ (комментарии), так и индекс (0), а также ключ / значение, которое она устанавливает (uid / 3). Однако это невозможно.

Я думал о создании $_cache['comments'] и массива ArrayObjects, но это не позволило бы мне определить пользовательскую перегрузку _get /_set. Вместо этого, я думаю, что в конечном итоге мне придется создать новый объект Comments, а затем заполнить ими массив. Однако мне действительно не хотелось бы этого делать, и было бы здорово, если бы PHP каким-то образом мог обрабатывать вложенные массивы при перегрузках __set.

Я использую Mongo и хотел бы, чтобы у меня мог быть только один объект для каждого документа. Однако объекты arrays в Mongo создают для меня небольшую проблему. Я хотел бы просто обрабатывать их как массив в PHP, но это кажется невозможным. Установщику необходимо взять $post->comments[0]['uid']=3 и обновить как кэш, так и настройки $this->data['comments'][0]['uid']=3 .

Я знаю, что если бы комментарии были массивом объектов, я мог бы сделать это:

 $post->comments[0]->uid=3; 
///Sets $_cache['comments'][0]->uid=3;
  

И это сработало бы, потому что средство получения комментариев вернуло бы массив объектов и позволило бы ему получить доступ к свойству uid. Тогда у меня мог бы быть получатель / установщик внутри объекта comments, который каким-то образом редактировал бы $post->data с помощью псевдо-функции «friend» / hack. Однако я не вижу простого способа добиться этого с помощью массивов….

Есть какие-нибудь советы?

Ответ №1:

Это сложнее, чем вы на самом деле представляете. Вы можете достичь желаемого с помощью кучи обходных путей, но это редко стоит затраченных усилий.

Если ->comments сам по себе разрешен методом получения, то присвоение чего-либо [0] подмассиву фактически не окажется в частной собственности. И ->comments[0]= даже не вызовет ваш метод установки. Вместо этого это доступ на чтение.

Чтобы это вообще работало, вам нужно было бы заставить ваш метод __get возвращать ссылку на amp; $this->_cache['comments'] .

Если вы хотите перехватить обращения к наборам в этом comments массиве, вам действительно понадобится ArrayObject . Разница в том, что для этого требуется переопределить offsetGet and offsetSet вместо __get и __set . Но опять же, поскольку вы обращаетесь к следующему подмассиву, фактически будет использоваться метод __get, и вам нужно вернуть другую ссылку или еще раз уровень обходного пути ArrayObject goo.

Комментарии:

1. Я мог бы поклясться, что что-то вроде foreach($post->comments as $x){ $x->uid=4} работает, где post::__get возвращает массив объектов comment. Вы уверены, что это $post->comments[0]->uid=4 не сработало бы?

2. Если он содержит подобъект [0]->uid вместо массива [0]['uid'] , то он будет работать, потому что ссылается на тот же объект. Но доступ к массиву выполняется путем копирования по умолчанию.

3. Тем не менее, я собираюсь повозиться со ссылками и посмотреть, смогу ли я заставить его работать с тем, что мне нужно.

4. @Mario Подождите, так что на самом деле нет никакого способа заставить это работать «автоматически» без наличия объектов для каждого элемента в массиве. Я пытаюсь отобразить серию довольно сложных документов Mongo, которые довольно часто меняются. В настоящее время я настраиваю схему в виде массива в каждом объекте, и хотя я использую «подобъекты» для более сложных моделей, я хотел бы просто иметь возможность использовать простой вложенный массив и заставить все работать без необходимости каждый раз создавать новый объект. Есть ли какой-либо способ, который вы можете придумать, чтобы заставить это работать?

5. Я пытался придумать что-то подобное. Вы, конечно, можете создать MagicArrayObject класс, который вы присоединяете к методам __get / offsetGet. Ему придется автоматически преобразовывать все доступные подмассивы и, по возможности, сохранять ссылку на исходный массив. — Но только с простыми массивами это не кажется мне выполнимым. Вам лучше отказаться от возможности наблюдать за доступом к наборам там. PHP делает это слишком сложным.

Ответ №2:

Я преодолел некоторые из этих препятствий при создании своего собственного класса-оболочки PHP.

https://github.com/gatesvp/MongoModel

Он все еще находится в разработке, но он обрабатывает некоторые базовые «сопоставить этот объект с DB».

Ответ №3:

В чатах PHP или документации по php не написано практически ничего стоящего, что могло бы быть тебе полезно, Адам. Большинство предложений имеют тенденцию к реализации interface ArrayAccess или расширению class ArrayObject , как в SPL. На самом деле, существует удивительно простое решение вашей проблемы: $post->comments[0]['uid']=3 использование перегруженного установщика __set() .

Определите private $comments = array(); в классе post . Для удобства используйте текстовый ключ для первого нижнего индекса $comments : здесь целое число 0 становится, скажем, «нулем». Затем вы вызываете установщик следующим образом:

 $post->zero = ['uid', 3];
  

Это вызывает установщик magic, потому что в $zero классе post нет публично объявленного свойства: «Методы перегрузки вызываются при взаимодействии со свойствами или методами, которые не были объявлены или не видны в текущей области видимости». (страница руководства PHP 5 о перегрузке.)

Установщиком также может быть setComments() , что удобно, поскольку вам не придется различать входящие свойства, чтобы определить те, которые предназначены для массива comments , но синтаксис вызова становится менее естественным.

Ваша перегруженная автоматически запускаемая функция __set получает два аргумента: свойство и значение:

 public function __set($property, $value) {
  

очень напоминает протокол JSON Крокфорда. Полезно думать об этом в этих терминах.

Поскольку свойство «ноль», которое вы отправили, не существует в классе post , его необходимо перехватить, и мой предпочтительный метод, поскольку первый нижний индекс в свойстве comments , вероятно, будет иметь несколько значений, заключается в определении частного массива поддерживаемых значений нижнего индекса в post :

 private $indices = [
     "zero"  => 0,
     "one"   => 1,
     "two"   => 2,
     "three" => 3 
];
  

Когда индекс для comments поступает в __set() as $property , проверяется, что он существует в $indices . Теперь вы просто выполняете итерацию по массиву, предоставленному в $value , извлекаете
uid и его соответствующее значение, затем присвоите $comments следующим образом:

 public function __set($property, $value) {
    if (array_key_exists($property, $this->indices) amp;amp; is_array($value))
        foreach ($value as $uid => $uid_value)  
            $this->comments[$this->indices[property]][$uid] = $uid_value;
    else
        ...
}
  

с $this->indices[property] используется для извлечения целочисленного значения 0, которое будет использоваться для
проиндексируйте первое измерение comments и $uid_value извлеките со значением int 3, которое будет присвоено.

Описанный здесь подход не является уловкой, обходным путем или хитроумным трюком. Это простой метод проектирования, предназначенный для работы с одним из средств SPL и, в принципе, может быть распространен на массивы произвольной размерности. У меня есть дизайн, реализованный в производственной системе, поэтому, если у вас все еще возникают трудности, напишите здесь, и я помогу вам отладить ваше приложение. Желаю удачи!

Ответ №4:

Я полагаю, что самое близкое, что вы можете сделать для перегрузки некоторых свойств, — это использовать магический метод __set(), определенный здесь:http://us.php.net /__set

Я не уверен, что вы сможете обработать [0] до того, как он будет принят компилятором PHP…

Итак, вашим другим решением было бы преобразовать комментарии в метод

 public function comments($id) {
   return $this->obj[$id]; // Obj
}
  

И возвращаемый вами объект имеет свойство __set

 class Obj {
   private $id;
   public function __set($key, $value) {
       if($key === 'uid') {
           $_cache = $GLOBALS['_cache'];
           $_cache['comments'][$this->id]->uid = $value;
       }
   }
}
  

Здесь не хватает большого количества кода, но вы можете выяснить, как это сделать с помощью этого метода __set()

Комментарии:

1. Я обновил свой первоначальный пост, чтобы, возможно, немного лучше объяснить, что я пытаюсь сделать. Я уже использую перегрузку __set. Он работает с одномерными массивами объектов ($this->comments[0]-> uid), но он не работает для многомерных массивов.

2. Ну, дело в том, что вы хотите иметь $ comments[0], чтобы ВОЗВРАЩАТЬ значение. Итак, в таком случае, если вы можете сделать это с помощью __get(), это идеально. Затем, как только вы вернете значение Obj (например, в моем классе exemple), на этот раз вы можете работать с магической ссылкой __set(), чтобы заставить uid = 3 работать.

Ответ №5:

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

 public function setCommentUid($commentId, $uid) {
    $this->_cache['comments'][$commentId]->uid = $uid;
}

//then...
$post->setCommentUid(0, 3);
  

Это значительно упрощает использование класса и намного проще видеть, что он делает.

Комментарии:

1. К сожалению, я ищу решение для базового класса, которое я могу расширять для каждой реализации. Я использую Mongo и хотел бы просто иметь возможность использовать в нем свою модель документа и позволить всему происходить «автоматически». Я уверен, что есть какое-то решение….

2. В зависимости от того, какая функциональность вам нужна для автоматического выполнения, может сработать использование __call . Вы могли бы использовать его, чтобы разрешить вызовы функций, которые не существуют, и динамически обрабатывать это, чтобы значения переходили в нужные вам массивы. У меня есть ощущение, что то, что вы делаете, может быть не лучшим подходом, но по имеющейся информации трудно судить, и, вероятно, это в любом случае выходит за рамки этого вопроса 🙂

3. Почему бы не set($cachevar, $id, $basevar, $baseval) и не вызвать $this->_cache[$cachevar][$id]->$basevar = $baseval базовый класс?