2021-05-03 19:26:47 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
|
|
|
|
|
|
|
|
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
|
|
|
|
|
|
|
|
This is free software: you can redistribute it and/or modify it under the
|
|
|
|
terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation, either version 3 of the License, or later.
|
|
|
|
|
|
|
|
Fat-Free Framework is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
|
|
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
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]+$ttl<microtime(TRUE)) {
|
|
|
|
if ($options['group']) {
|
|
|
|
$grp=$this->collection->group(
|
|
|
|
$options['group']['keys'],
|
|
|
|
$options['group']['initial'],
|
|
|
|
$options['group']['reduce'],
|
|
|
|
[
|
|
|
|
'condition'=>$filter,
|
|
|
|
'finalize'=>$options['group']['finalize']
|
|
|
|
]
|
|
|
|
);
|
|
|
|
$tmp=$this->db->selectcollection(
|
|
|
|
$fw->HOST.'.'.$fw->BASE.'.'.
|
2022-03-26 23:36:32 +01:00
|
|
|
uniqid('',TRUE).'.tmp'
|
2021-05-03 19:26:47 +02:00
|
|
|
);
|
|
|
|
$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]+$ttl<microtime(TRUE)) {
|
|
|
|
$result=$this->collection->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();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|