You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							405 lines
						
					
					
						
							9.3 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							405 lines
						
					
					
						
							9.3 KiB
						
					
					
				| <?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.'.'. | |
| 					uniqid(NULL,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]+$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(); | |
| 	} | |
| 
 | |
| }
 | |
| 
 |