PageRenderTime 24ms CodeModel.GetById 1ms app.highlight 18ms RepoModel.GetById 2ms app.codeStats 0ms

/lib/m2.php

https://bitbucket.org/thejeshgn/php-bootstrap-boilerplate
PHP | 542 lines | 291 code | 32 blank | 219 comment | 39 complexity | 1aa72aad4017728e14e9717b08cd0b34 MD5 | raw file
  1<?php
  2
  3/**
  4	MongoDB Mapper for the PHP Fat-Free Framework
  5
  6	The contents of this file are subject to the terms of the GNU General
  7	Public License Version 3.0. You may not use this file except in
  8	compliance with the license. Any of the license terms and conditions
  9	can be waived if you get permission from the copyright holder.
 10
 11	Copyright (c) 2009-2012 F3::Factory
 12	Bong Cosca <bong.cosca@yahoo.com>
 13
 14		@package M2
 15		@version 2.0.13
 16**/
 17
 18//! MongoDB Mapper
 19class M2 extends Base {
 20
 21	//@{ Locale-specific error/exception messages
 22	const
 23		TEXT_M2Connect='Undefined database',
 24		TEXT_M2Empty='M2 is empty',
 25		TEXT_M2Collection='Collection %s does not exist';
 26	//@}
 27
 28	//@{
 29	//! M2 properties
 30	private
 31		$db,$collection,$object,$cond,$seq,$ofs;
 32	//@}
 33
 34	/**
 35		M2 factory
 36			@return object
 37			@param $doc array
 38			@public
 39	**/
 40	function factory($doc) {
 41		$self=get_class($this);
 42		$m2=new $self($this->collection,$this->db);
 43		foreach ($doc as $key=>$val)
 44			$m2->object[$key]=is_array($val)?
 45				json_decode(json_encode($val)):$val;
 46		return $m2;
 47	}
 48
 49	/**
 50		Retrieve from cache; or save query results to cache if not
 51		previously executed
 52			@param $query array
 53			@param $ttl int
 54			@private
 55	**/
 56	private function cache(array $query,$ttl) {
 57		$cmd=json_encode($query,TRUE);
 58		$hash='mdb.'.self::hash($cmd);
 59		$cached=Cache::cached($hash);
 60		$db=(string)$this->db;
 61		$stats=&self::$vars['STATS'];
 62		if ($ttl && $cached && $_SERVER['REQUEST_TIME']-$cached<$ttl) {
 63			// Gather cached queries for profiler
 64			if (!isset($stats[$db]['cache'][$cmd]))
 65				$stats[$db]['cache'][$cmd]=0;
 66			$stats[$db]['cache'][$cmd]++;
 67			// Retrieve from cache
 68			return Cache::get($hash);
 69		}
 70		else {
 71			$result=$this->exec($query);
 72			if ($ttl)
 73				Cache::set($hash,$result,$ttl);
 74			// Gather real queries for profiler
 75			if (!isset($stats[$db]['queries'][$cmd]))
 76				$stats[$db]['queries'][$cmd]=0;
 77			$stats[$db]['queries'][$cmd]++;
 78			return $result;
 79		}
 80	}
 81
 82	/**
 83		Execute MongoDB query
 84			@return mixed
 85			@param $query array
 86			@private
 87	**/
 88	private function exec(array $query) {
 89		$cmd=json_encode($query,TRUE);
 90		$hash='mdb.'.self::hash($cmd);
 91		// Except for save method, collection must exist
 92		$list=$this->db->listCollections();
 93		foreach ($list as &$coll)
 94			$coll=$coll->getName();
 95		if ($query['method']!='save' && !in_array($this->collection,$list)) {
 96			trigger_error(sprintf(self::TEXT_M2Collection,$this->collection));
 97			return;
 98		}
 99		if (isset($query['map'])) {
100			// Create temporary collection
101			$ref=$this->db->selectCollection($hash);
102			$ref->batchInsert(iterator_to_array($out,FALSE));
103			$map=$query['map'];
104			$func='function() {}';
105			// Map-reduce
106			$tmp=$this->db->command(
107				array(
108					'mapreduce'=>$ref->getName(),
109					'map'=>isset($map['map'])?
110						$map['map']:$func,
111					'reduce'=>isset($map['reduce'])?
112						$map['reduce']:$func,
113					'finalize'=>isset($map['finalize'])?
114						$map['finalize']:$func
115				)
116			);
117			if (!$tmp['ok']) {
118				trigger_error($tmp['errmsg']);
119				return FALSE;
120			}
121			$ref->remove();
122			// Aggregate the result
123			foreach (iterator_to_array($this->db->
124				selectCollection($tmp['result'])->find(),FALSE) as $agg)
125				$ref->insert($agg['_id']);
126			$out=$ref->find();
127			$ref->drop();
128		}
129		elseif (preg_match('/find/',$query['method'])) {
130			// find and findOne methods allow selection of fields
131			$out=call_user_func(
132				array(
133					$this->db->selectCollection($this->collection),
134					$query['method']
135				),
136				isset($query['cond'])?$query['cond']:array(),
137				isset($query['fields'])?$query['fields']:array()
138			);
139			if ($query['method']=='find') {
140				if (isset($query['seq']))
141					// Sort results
142					$out=$out->sort($query['seq']);
143				if (isset($query['ofs']))
144					// Skip to record ofs
145					$out=$out->skip($query['ofs']);
146				if (isset($query['limit']) && $query['limit'])
147					// Limit number of results
148					$out=$out->limit($query['limit']);
149				// Convert cursor to PHP array
150				$out=iterator_to_array($out,FALSE);
151				if ($query['m2'])
152					foreach ($out as &$obj)
153						$obj=$this->factory($obj);
154			}
155		}
156		else
157			$out=preg_match('/count|remove/',$query['method'])?
158				// count() and remove() methods can specify cond
159				call_user_func(
160					array(
161						$this->db->selectCollection($this->collection),
162						$query['method']
163					),
164					isset($query['cond'])?$query['cond']:array()
165				):
166				// All other queries
167				call_user_func(
168					array(
169						$this->db->selectCollection($this->collection),
170						$query['method']
171					),
172					$this->object
173				);
174		return $out;
175	}
176
177	/**
178		Return current object contents as an array
179			@return array
180			@public
181	**/
182	function cast() {
183		return $this->object;
184	}
185
186	/**
187		Similar to M2->find method but provides more fine-grained control
188		over specific fields and map-reduced results
189			@return array
190			@param $fields array
191			@param $cond mixed
192			@param $map mixed
193			@param $seq mixed
194			@param $limit mixed
195			@param $ofs mixed
196			@param $ttl int
197			@param $m2 bool
198			@public
199	**/
200	function lookup(
201		array $fields,
202		$cond=NULL,
203		$map=NULL,
204		$seq=NULL,
205		$limit=0,
206		$ofs=0,
207		$ttl=0,
208		$m2=TRUE) {
209		$query=array(
210			'method'=>'find',
211			'fields'=>$fields,
212			'cond'=>$cond,
213			'map'=>$map,
214			'seq'=>$seq,
215			'limit'=>$limit,
216			'ofs'=>$ofs,
217			'm2'=>$m2
218		);
219		return $ttl?$this->cache($query,$ttl):$this->exec($query);
220	}
221
222	/**
223		Alias of the lookup method
224			@public
225	**/
226	function select() {
227		// PHP doesn't allow direct use as function argument
228		$args=func_get_args();
229		return call_user_func_array(array($this,'lookup'),$args);
230	}
231
232	/**
233		Return an array of collection objects matching cond
234			@return array
235			@param $cond mixed
236			@param $seq mixed
237			@param $limit mixed
238			@param $ofs mixed
239			@param $ttl int
240			@param $m2 bool
241			@public
242	**/
243	function find($cond=NULL,$seq=NULL,$limit=NULL,$ofs=0,$ttl=0,$m2=TRUE) {
244		$query=array(
245			'method'=>'find',
246			'cond'=>$cond,
247			'seq'=>$seq,
248			'limit'=>$limit,
249			'ofs'=>$ofs,
250			'm2'=>$m2
251		);
252		return $ttl?$this->cache($query,$ttl):$this->exec($query);
253	}
254
255	/**
256		Return an array of associative arrays matching cond
257			@return array
258			@param $cond mixed
259			@param $seq mixed
260			@param $limit mixed
261			@param $ofs int
262			@param $ttl int
263			@public
264	**/
265	function afind($cond=NULL,$seq=NULL,$limit=NULL,$ofs=0,$ttl=0) {
266		return $this->find($cond,$seq,$limit,$ofs,$ttl,FALSE);
267	}
268
269	/**
270		Return the first object that matches the specified cond
271			@return array
272			@param $cond mixed
273			@param $seq mixed
274			@param $ofs int
275			@param $ttl int
276			@public
277	**/
278	function findone($cond=NULL,$seq=NULL,$ofs=0,$ttl=0) {
279		list($result)=$this->find($cond,$seq,1,$ofs,$ttl)?:array(NULL);
280		return $result;
281	}
282
283	/**
284		Return the array equivalent of the object matching criteria
285			@return array
286			@param $cond mixed
287			@param $seq mixed
288			@param $ofs int
289			@public
290	**/
291	function afindone($cond=NULL,$seq=NULL,$ofs=0) {
292		list($result)=$this->afind($cond,$seq,1,$ofs)?:array(NULL);
293		return $result;
294	}
295
296	/**
297		Count objects that match condition
298			@return int
299			@param $cond mixed
300			@public
301	**/
302	function found($cond=NULL) {
303		$result=$this->exec(
304			array(
305				'method'=>'count',
306				'cond'=>$cond
307			)
308		);
309		return $result;
310	}
311
312	/**
313		Hydrate M2 with elements from framework array variable, keys of
314		which will be identical to field names in collection object
315			@param $name string
316			@public
317	**/
318	function copyFrom($name) {
319		if (is_array($ref=self::ref($name)))
320			foreach ($ref as $key=>$val)
321				$this->object[$key]=$val;
322	}
323
324	/**
325		Populate framework array variable with M2 properties, keys of
326		which will have names identical to fields in collection object
327			@param $name string
328			@param $fields string
329			@public
330	**/
331	function copyTo($name,$fields=NULL) {
332		if ($this->dry()) {
333			trigger_error(self::TEXT_M2Empty);
334			return;
335		}
336		if (is_string($fields))
337			$list=preg_split('/[\|;,]/',$fields,0,PREG_SPLIT_NO_EMPTY);
338		foreach (array_keys($this->object) as $field)
339			if (!isset($list) || in_array($field,$list)) {
340				$var=&self::ref($name);
341				$var[$field]=$this->object[$field];
342			}
343	}
344
345	/**
346		Dehydrate M2
347			@public
348	**/
349	function reset() {
350		// Dehydrate
351		$this->object=NULL;
352		$this->cond=NULL;
353		$this->seq=NULL;
354		$this->ofs=NULL;
355	}
356
357	/**
358		Retrieve first collection object that satisfies cond
359			@return mixed
360			@param $cond mixed
361			@param $seq mixed
362			@param $ofs int
363			@public
364	**/
365	function load($cond=NULL,$seq=NULL,$ofs=0) {
366		if ($ofs>-1) {
367			$this->ofs=0;
368			if ($m2=$this->findOne($cond,$seq,$ofs)) {
369				if (method_exists($this,'beforeLoad') &&
370					$this->beforeLoad()===FALSE)
371					return FALSE;
372				// Hydrate M2
373				foreach ($m2->object as $key=>$val)
374					$this->object[$key]=$val;
375				list($this->cond,$this->seq,$this->ofs)=
376					array($cond,$seq,$ofs);
377				if (method_exists($this,'afterLoad'))
378					$this->afterLoad();
379				return $this;
380			}
381		}
382		$this->reset();
383		return FALSE;
384	}
385
386	/**
387		Retrieve N-th object relative to current using the same cond
388		that hydrated M2
389			@return mixed
390			@param $count int
391			@public
392	**/
393	function skip($count=1) {
394		if ($this->dry()) {
395			trigger_error(self::TEXT_M2Empty);
396			return FALSE;
397		}
398		return $this->load($this->cond,$this->seq,$this->ofs+$count);
399	}
400
401	/**
402		Return next record
403			@return array
404			@public
405	**/
406	function next() {
407		return $this->skip();
408	}
409
410	/**
411		Return previous record
412			@return array
413			@public
414	**/
415	function prev() {
416		return $this->skip(-1);
417	}
418
419	/**
420		Insert/update collection object
421			@public
422	**/
423	function save() {
424		if ($this->dry() ||
425			method_exists($this,'beforeSave') &&
426			$this->beforeSave()===FALSE)
427			return;
428		// Let the MongoDB driver decide how to persist the
429		// collection object in the database
430		$obj=$this->object;
431		$this->exec(array('method'=>'save'));
432		if (!isset($obj['_id']))
433			// Reload to retrieve MongoID of inserted object
434			$this->object=
435				$this->exec(array('method'=>'findOne','cond'=>$obj));
436		if (method_exists($this,'afterSave'))
437			$this->afterSave();
438	}
439
440	/**
441		Delete collection object and reset M2
442			@public
443	**/
444	function erase() {
445		if (method_exists($this,'beforeErase') &&
446			$this->beforeErase()===FALSE)
447			return;
448		$this->exec(array('method'=>'remove','cond'=>$this->cond));
449		$this->reset();
450		if (method_exists($this,'afterErase'))
451			$this->afterErase();
452	}
453
454	/**
455		Return TRUE if M2 is NULL
456			@return bool
457			@public
458	**/
459	function dry() {
460		return is_null($this->object);
461	}
462
463	/**
464		Synchronize M2 and MongoDB collection
465			@param $coll string
466			@param $db object
467			@public
468	**/
469	function sync($coll,$db=NULL) {
470		if (!extension_loaded('mongo')) {
471			// MongoDB extension not activated
472			trigger_error(sprintf(self::TEXT_PHPExt,'mongo'));
473			return;
474		}
475		if (!$db) {
476			if (isset(self::$vars['DB']) && is_a(self::$vars['DB'],'MongoDB'))
477				$db=self::$vars['DB'];
478			else {
479				trigger_error(self::TEXT_M2Connect);
480				return;
481			}
482		}
483		if (method_exists($this,'beforeSync') &&
484			$this->beforeSync()===FALSE)
485			return;
486		// Initialize M2
487		list($this->db,$this->collection)=array($db,$coll);
488		if (method_exists($this,'afterSync'))
489			$this->afterSync();
490	}
491
492	/**
493		Return value of M2-mapped field
494			@return bool
495			@param $name string
496			@public
497	**/
498	function __get($name) {
499		return $this->object[$name];
500	}
501
502	/**
503		Assign value to M2-mapped field
504			@return bool
505			@param $name string
506			@param $val mixed
507			@public
508	**/
509	function __set($name,$val) {
510		$this->object[$name]=$val;
511	}
512
513	/**
514		Clear value of M2-mapped field
515			@return bool
516			@param $name string
517			@public
518	**/
519	function __unset($name) {
520		unset($this->object[$name]);
521	}
522
523	/**
524		Return TRUE if M2-mapped field exists
525			@return bool
526			@param $name string
527			@public
528	**/
529	function __isset($name) {
530		return array_key_exists($name,$this->object);
531	}
532
533	/**
534		Class constructor
535			@public
536	**/
537	function __construct() {
538		// Execute mandatory sync method
539		call_user_func_array(array($this,'sync'),func_get_args());
540	}
541
542}