. */ namespace DB\Mongo; //! MongoDB mapper class Mapper extends \DB\Cursor { protected //! MongoDB wrapper $db, //! Legacy flag $legacy, //! Mongo collection $collection, //! Mongo document $document=[], //! Mongo cursor $cursor, //! Defined fields $fields; /** * Return database type * @return string **/ function dbtype() { return 'Mongo'; } /** * Return TRUE if field is defined * @return bool * @param $key string **/ function exists($key) { return array_key_exists($key,$this->document); } /** * Assign value to field * @return scalar|FALSE * @param $key string * @param $val scalar **/ function set($key,$val) { return $this->document[$key]=$val; } /** * Retrieve value of field * @return scalar|FALSE * @param $key string **/ function &get($key) { if ($this->exists($key)) return $this->document[$key]; user_error(sprintf(self::E_Field,$key),E_USER_ERROR); } /** * Delete field * @return NULL * @param $key string **/ function clear($key) { unset($this->document[$key]); } /** * Convert array to mapper object * @return static * @param $row array **/ function factory($row) { $mapper=clone($this); $mapper->reset(); foreach ($row as $key=>$val) $mapper->document[$key]=$val; $mapper->query=[clone($mapper)]; if (isset($mapper->trigger['load'])) \Base::instance()->call($mapper->trigger['load'],$mapper); return $mapper; } /** * Return fields of mapper object as an associative array * @return array * @param $obj object **/ function cast($obj=NULL) { if (!$obj) $obj=$this; return $obj->document; } /** * Build query and execute * @return static[] * @param $fields string * @param $filter array * @param $options array * @param $ttl int|array **/ function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) { if (!$options) $options=[]; $options+=[ 'group'=>NULL, 'order'=>NULL, 'limit'=>0, 'offset'=>0 ]; $tag=''; if (is_array($ttl)) list($ttl,$tag)=$ttl; $fw=\Base::instance(); $cache=\Cache::instance(); if (!($cached=$cache->exists($hash=$fw->hash($this->db->dsn(). $fw->stringify([$fields,$filter,$options])).($tag?'.'.$tag:'').'.mongo', $result)) || !$ttl || $cached[0]+$ttlcollection->group( $options['group']['keys'], $options['group']['initial'], $options['group']['reduce'], [ 'condition'=>$filter, 'finalize'=>$options['group']['finalize'] ] ); $tmp=$this->db->selectcollection( $fw->HOST.'.'.$fw->BASE.'.'. uniqid('',TRUE).'.tmp' ); $tmp->batchinsert($grp['retval'],['w'=>1]); $filter=[]; $collection=$tmp; } else { $filter=$filter?:[]; $collection=$this->collection; } if ($this->legacy) { $this->cursor=$collection->find($filter,$fields?:[]); if ($options['order']) $this->cursor=$this->cursor->sort($options['order']); if ($options['limit']) $this->cursor=$this->cursor->limit($options['limit']); if ($options['offset']) $this->cursor=$this->cursor->skip($options['offset']); $result=[]; while ($this->cursor->hasnext()) $result[]=$this->cursor->getnext(); } else { $this->cursor=$collection->find($filter,[ 'sort'=>$options['order'], 'limit'=>$options['limit'], 'skip'=>$options['offset'] ]); $result=$this->cursor->toarray(); } if ($options['group']) $tmp->drop(); if ($fw->CACHE && $ttl) // Save to cache backend $cache->set($hash,$result,$ttl); } $out=[]; foreach ($result as $doc) $out[]=$this->factory($doc); return $out; } /** * Return records that match criteria * @return static[] * @param $filter array * @param $options array * @param $ttl int|array **/ function find($filter=NULL,array $options=NULL,$ttl=0) { if (!$options) $options=[]; $options+=[ 'group'=>NULL, 'order'=>NULL, 'limit'=>0, 'offset'=>0 ]; return $this->select($this->fields,$filter,$options,$ttl); } /** * Count records that match criteria * @return int * @param $filter array * @param $options array * @param $ttl int|array **/ function count($filter=NULL,array $options=NULL,$ttl=0) { $fw=\Base::instance(); $cache=\Cache::instance(); $tag=''; if (is_array($ttl)) list($ttl,$tag)=$ttl; if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify( [$filter])).($tag?'.'.$tag:'').'.mongo',$result)) || !$ttl || $cached[0]+$ttlcollection->count($filter?:[]); if ($fw->CACHE && $ttl) // Save to cache backend $cache->set($hash,$result,$ttl); } return $result; } /** * Return record at specified offset using criteria of previous * load() call and make it active * @return array * @param $ofs int **/ function skip($ofs=1) { $this->document=($out=parent::skip($ofs))?$out->document:[]; if ($this->document && isset($this->trigger['load'])) \Base::instance()->call($this->trigger['load'],$this); return $out; } /** * Insert new record * @return array **/ function insert() { if (isset($this->document['_id'])) return $this->update(); if (isset($this->trigger['beforeinsert']) && \Base::instance()->call($this->trigger['beforeinsert'], [$this,['_id'=>$this->document['_id']]])===FALSE) return $this->document; if ($this->legacy) { $this->collection->insert($this->document); $pkey=['_id'=>$this->document['_id']]; } else { $result=$this->collection->insertone($this->document); $pkey=['_id'=>$result->getinsertedid()]; } if (isset($this->trigger['afterinsert'])) \Base::instance()->call($this->trigger['afterinsert'], [$this,$pkey]); $this->load($pkey); return $this->document; } /** * Update current record * @return array **/ function update() { $pkey=['_id'=>$this->document['_id']]; if (isset($this->trigger['beforeupdate']) && \Base::instance()->call($this->trigger['beforeupdate'], [$this,$pkey])===FALSE) return $this->document; $upsert=['upsert'=>TRUE]; if ($this->legacy) $this->collection->update($pkey,$this->document,$upsert); else $this->collection->replaceone($pkey,$this->document,$upsert); if (isset($this->trigger['afterupdate'])) \Base::instance()->call($this->trigger['afterupdate'], [$this,$pkey]); return $this->document; } /** * Delete current record * @return bool * @param $quick bool * @param $filter array **/ function erase($filter=NULL,$quick=TRUE) { if ($filter) { if (!$quick) { foreach ($this->find($filter) as $mapper) if (!$mapper->erase()) return FALSE; return TRUE; } return $this->legacy? $this->collection->remove($filter): $this->collection->deletemany($filter); } $pkey=['_id'=>$this->document['_id']]; if (isset($this->trigger['beforeerase']) && \Base::instance()->call($this->trigger['beforeerase'], [$this,$pkey])===FALSE) return FALSE; $result=$this->legacy? $this->collection->remove(['_id'=>$this->document['_id']]): $this->collection->deleteone(['_id'=>$this->document['_id']]); parent::erase(); if (isset($this->trigger['aftererase'])) \Base::instance()->call($this->trigger['aftererase'], [$this,$pkey]); return $result; } /** * Reset cursor * @return NULL **/ function reset() { $this->document=[]; parent::reset(); } /** * Hydrate mapper object using hive array variable * @return NULL * @param $var array|string * @param $func callback **/ function copyfrom($var,$func=NULL) { if (is_string($var)) $var=\Base::instance()->$var; if ($func) $var=call_user_func($func,$var); foreach ($var as $key=>$val) $this->set($key,$val); } /** * Populate hive array variable with mapper fields * @return NULL * @param $key string **/ function copyto($key) { $var=&\Base::instance()->ref($key); foreach ($this->document as $key=>$field) $var[$key]=$field; } /** * Return field names * @return array **/ function fields() { return array_keys($this->document); } /** * Return the cursor from last query * @return object|NULL **/ function cursor() { return $this->cursor; } /** * Retrieve external iterator for fields * @return object **/ function getiterator() { return new \ArrayIterator($this->cast()); } /** * Instantiate class * @return void * @param $db object * @param $collection string * @param $fields array **/ function __construct(\DB\Mongo $db,$collection,$fields=NULL) { $this->db=$db; $this->legacy=$db->legacy(); $this->collection=$db->selectcollection($collection); $this->fields=$fields; $this->reset(); } }