#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
базовый класс?