1: <?php
2:
3: namespace RedBeanPHP;
4:
5: use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
6: use RedBeanPHP\QueryWriter as QueryWriter;
7: use RedBeanPHP\BeanHelper as BeanHelper;
8: use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
9: use RedBeanPHP\Repository as Repository;
10: use RedBeanPHP\Repository\Fluid as FluidRepo;
11: use RedBeanPHP\Repository\Frozen as FrozenRepo;
12:
13: /**
14: * RedBean Object Oriented DataBase.
15: *
16: * The RedBean OODB Class is the main class of RedBeanPHP.
17: * It takes OODBBean objects and stores them to and loads them from the
18: * database as well as providing other CRUD functions. This class acts as a
19: * object database.
20: *
21: * @file RedBeanPHP/OODB.php
22: * @author Gabor de Mooij and the RedBeanPHP community
23: * @license BSD/GPLv2
24: *
25: * @copyright
26: * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
27: * This source file is subject to the BSD/GPLv2 License that is bundled
28: * with this source code in the file license.txt.
29: */
30: class OODB extends Observable
31: {
32: /**
33: * @var array
34: */
35: private static $sqlFilters = array();
36:
37: /**
38: * @var array
39: */
40: protected $chillList = array();
41:
42: /**
43: * @var array
44: */
45: protected $stash = NULL;
46:
47: /*
48: * @var integer
49: */
50: protected $nesting = 0;
51:
52: /**
53: * @var DBAdapter
54: */
55: protected $writer;
56:
57: /**
58: * @var boolean
59: */
60: protected $isFrozen = FALSE;
61:
62: /**
63: * @var FacadeBeanHelper
64: */
65: protected $beanhelper = NULL;
66:
67: /**
68: * @var AssociationManager
69: */
70: protected $assocManager = NULL;
71:
72: /**
73: * @var Repository
74: */
75: protected $repository = NULL;
76:
77: /**
78: * @var FrozenRepo
79: */
80: protected $frozenRepository = NULL;
81:
82: /**
83: * @var FluidRepo
84: */
85: protected $fluidRepository = NULL;
86:
87: /**
88: * @var boolean
89: */
90: protected static $autoClearHistoryAfterStore = FALSE;
91:
92: /**
93: * If set to TRUE, this method will call clearHistory every time
94: * the bean gets stored.
95: *
96: * @param boolean $autoClear auto clear option
97: *
98: * @return void
99: */
100: public static function autoClearHistoryAfterStore( $autoClear = TRUE )
101: {
102: self::$autoClearHistoryAfterStore = (boolean) $autoClear;
103: }
104:
105: /**
106: * Unboxes a bean from a FUSE model if needed and checks whether the bean is
107: * an instance of OODBBean.
108: *
109: * @param OODBBean $bean bean you wish to unbox
110: *
111: * @return OODBBean
112: */
113: protected function unboxIfNeeded( $bean )
114: {
115: if ( $bean instanceof SimpleModel ) {
116: $bean = $bean->unbox();
117: }
118: if ( !( $bean instanceof OODBBean ) ) {
119: throw new RedException( 'OODB Store requires a bean, got: ' . gettype( $bean ) );
120: }
121:
122: return $bean;
123: }
124:
125: /**
126: * Constructor, requires a query writer.
127: *
128: * @param QueryWriter $writer writer
129: * @param array|boolean $frozen mode of operation: TRUE (frozen), FALSE (default, fluid) or ARRAY (chilled)
130: */
131: public function __construct( QueryWriter $writer, $frozen = FALSE )
132: {
133: if ( $writer instanceof QueryWriter ) {
134: $this->writer = $writer;
135: }
136:
137: $this->freeze( $frozen );
138: }
139:
140: /**
141: * Toggles fluid or frozen mode. In fluid mode the database
142: * structure is adjusted to accomodate your objects. In frozen mode
143: * this is not the case.
144: *
145: * You can also pass an array containing a selection of frozen types.
146: * Let's call this chilly mode, it's just like fluid mode except that
147: * certain types (i.e. tables) aren't touched.
148: *
149: * @param boolean|array $toggle TRUE if you want to use OODB instance in frozen mode
150: *
151: * @return void
152: */
153: public function freeze( $toggle )
154: {
155: if ( is_array( $toggle ) ) {
156: $this->chillList = $toggle;
157: $this->isFrozen = FALSE;
158: } else {
159: $this->isFrozen = (boolean) $toggle;
160: }
161:
162: if ( $this->isFrozen ) {
163: if ( !$this->frozenRepository ) {
164: $this->frozenRepository = new FrozenRepo( $this, $this->writer );
165: }
166:
167: $this->repository = $this->frozenRepository;
168:
169: } else {
170: if ( !$this->fluidRepository ) {
171: $this->fluidRepository = new FluidRepo( $this, $this->writer );
172: }
173:
174: $this->repository = $this->fluidRepository;
175: }
176:
177: if ( count( self::$sqlFilters ) ) {
178: AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
179: }
180:
181: }
182:
183: /**
184: * Returns the current mode of operation of RedBean.
185: * In fluid mode the database
186: * structure is adjusted to accomodate your objects.
187: * In frozen mode
188: * this is not the case.
189: *
190: * @return boolean
191: */
192: public function isFrozen()
193: {
194: return (bool) $this->isFrozen;
195: }
196:
197: /**
198: * Determines whether a type is in the chill list.
199: * If a type is 'chilled' it's frozen, so its schema cannot be
200: * changed anymore. However other bean types may still be modified.
201: * This method is a convenience method for other objects to check if
202: * the schema of a certain type is locked for modification.
203: *
204: * @param string $type the type you wish to check
205: *
206: * @return boolean
207: */
208: public function isChilled( $type )
209: {
210: return (boolean) ( in_array( $type, $this->chillList ) );
211: }
212:
213: /**
214: * Dispenses a new bean (a OODBBean Bean Object)
215: * of the specified type. Always
216: * use this function to get an empty bean object. Never
217: * instantiate a OODBBean yourself because it needs
218: * to be configured before you can use it with RedBean. This
219: * function applies the appropriate initialization /
220: * configuration for you.
221: *
222: * @param string $type type of bean you want to dispense
223: * @param string $number number of beans you would like to get
224: * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
225: *
226: * @return OODBBean
227: */
228: public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
229: {
230: if ( $number < 1 ) {
231: if ( $alwaysReturnArray ) return array();
232: return NULL;
233: }
234:
235: return $this->repository->dispense( $type, $number, $alwaysReturnArray );
236: }
237:
238: /**
239: * Sets bean helper to be given to beans.
240: * Bean helpers assist beans in getting a reference to a toolbox.
241: *
242: * @param BeanHelper $beanhelper helper
243: *
244: * @return void
245: */
246: public function setBeanHelper( BeanHelper $beanhelper )
247: {
248: $this->beanhelper = $beanhelper;
249: }
250:
251: /**
252: * Returns the current bean helper.
253: * Bean helpers assist beans in getting a reference to a toolbox.
254: *
255: * @return BeanHelper
256: */
257: public function getBeanHelper()
258: {
259: return $this->beanhelper;
260: }
261:
262: /**
263: * Checks whether a OODBBean bean is valid.
264: * If the type is not valid or the ID is not valid it will
265: * throw an exception: Security.
266: *
267: * @param OODBBean $bean the bean that needs to be checked
268: *
269: * @return void
270: */
271: public function check( OODBBean $bean )
272: {
273: $this->repository->check( $bean );
274: }
275:
276: /**
277: * Searches the database for a bean that matches conditions $conditions and sql $addSQL
278: * and returns an array containing all the beans that have been found.
279: *
280: * Conditions need to take form:
281: *
282: * <code>
283: * array(
284: * 'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
285: * 'PROPERTY' => array( POSSIBLE VALUES... )
286: * );
287: * </code>
288: *
289: * All conditions are glued together using the AND-operator, while all value lists
290: * are glued using IN-operators thus acting as OR-conditions.
291: *
292: * Note that you can use property names; the columns will be extracted using the
293: * appropriate bean formatter.
294: *
295: * @param string $type type of beans you are looking for
296: * @param array $conditions list of conditions
297: * @param string $addSQL SQL to be used in query
298: * @param array $bindings a list of values to bind to query parameters
299: *
300: * @return array
301: */
302: public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
303: {
304: return $this->repository->find( $type, $conditions, $sql, $bindings );
305: }
306:
307: /**
308: * Same as find() but returns a BeanCollection.
309: *
310: * @param string $type type of beans you are looking for
311: * @param string $addSQL SQL to be used in query
312: * @param array $bindings a list of values to bind to query parameters
313: *
314: * @return array
315: */
316: public function findCollection( $type, $sql = NULL, $bindings = array() )
317: {
318: return $this->repository->findCollection( $type, $sql, $bindings );
319: }
320:
321: /**
322: * Checks whether the specified table already exists in the database.
323: * Not part of the Object Database interface!
324: *
325: * @deprecated Use AQueryWriter::typeExists() instead.
326: *
327: * @param string $table table name
328: *
329: * @return boolean
330: */
331: public function tableExists( $table )
332: {
333: return $this->repository->tableExists( $table );
334: }
335:
336: /**
337: * Stores a bean in the database. This method takes a
338: * OODBBean Bean Object $bean and stores it
339: * in the database. If the database schema is not compatible
340: * with this bean and RedBean runs in fluid mode the schema
341: * will be altered to store the bean correctly.
342: * If the database schema is not compatible with this bean and
343: * RedBean runs in frozen mode it will throw an exception.
344: * This function returns the primary key ID of the inserted
345: * bean.
346: *
347: * The return value is an integer if possible. If it is not possible to
348: * represent the value as an integer a string will be returned. We use
349: * explicit casts instead of functions to preserve performance
350: * (0.13 vs 0.28 for 10000 iterations on Core i3).
351: *
352: * @param OODBBean|SimpleModel $bean bean to store
353: *
354: * @return integer|string
355: */
356: public function store( $bean )
357: {
358: $bean = $this->unboxIfNeeded( $bean );
359: $id = $this->repository->store( $bean );
360: if ( self::$autoClearHistoryAfterStore ) {
361: $bean->clearHistory();
362: }
363: return $id;
364: }
365:
366: /**
367: * Loads a bean from the object database.
368: * It searches for a OODBBean Bean Object in the
369: * database. It does not matter how this bean has been stored.
370: * RedBean uses the primary key ID $id and the string $type
371: * to find the bean. The $type specifies what kind of bean you
372: * are looking for; this is the same type as used with the
373: * dispense() function. If RedBean finds the bean it will return
374: * the OODB Bean object; if it cannot find the bean
375: * RedBean will return a new bean of type $type and with
376: * primary key ID 0. In the latter case it acts basically the
377: * same as dispense().
378: *
379: * Important note:
380: * If the bean cannot be found in the database a new bean of
381: * the specified type will be generated and returned.
382: *
383: * @param string $type type of bean you want to load
384: * @param integer $id ID of the bean you want to load
385: *
386: * @return OODBBean
387: */
388: public function load( $type, $id )
389: {
390: return $this->repository->load( $type, $id );
391: }
392:
393: /**
394: * Removes a bean from the database.
395: * This function will remove the specified OODBBean
396: * Bean Object from the database.
397: *
398: * @param OODBBean|SimpleModel $bean bean you want to remove from database
399: *
400: * @return void
401: */
402: public function trash( $bean )
403: {
404: $bean = $this->unboxIfNeeded( $bean );
405: return $this->repository->trash( $bean );
406: }
407:
408: /**
409: * Returns an array of beans. Pass a type and a series of ids and
410: * this method will bring you the corresponding beans.
411: *
412: * important note: Because this method loads beans using the load()
413: * function (but faster) it will return empty beans with ID 0 for
414: * every bean that could not be located. The resulting beans will have the
415: * passed IDs as their keys.
416: *
417: * @param string $type type of beans
418: * @param array $ids ids to load
419: *
420: * @return array
421: */
422: public function batch( $type, $ids )
423: {
424: return $this->repository->batch( $type, $ids );
425: }
426:
427: /**
428: * This is a convenience method; it converts database rows
429: * (arrays) into beans. Given a type and a set of rows this method
430: * will return an array of beans of the specified type loaded with
431: * the data fields provided by the result set from the database.
432: *
433: * @param string $type type of beans you would like to have
434: * @param array $rows rows from the database result
435: * @param string $mask mask to apply for meta data
436: *
437: * @return array
438: */
439: public function convertToBeans( $type, $rows, $mask = NULL )
440: {
441: return $this->repository->convertToBeans( $type, $rows, $mask );
442: }
443:
444: /**
445: * Counts the number of beans of type $type.
446: * This method accepts a second argument to modify the count-query.
447: * A third argument can be used to provide bindings for the SQL snippet.
448: *
449: * @param string $type type of bean we are looking for
450: * @param string $addSQL additional SQL snippet
451: * @param array $bindings parameters to bind to SQL
452: *
453: * @return integer
454: */
455: public function count( $type, $addSQL = '', $bindings = array() )
456: {
457: return $this->repository->count( $type, $addSQL, $bindings );
458: }
459:
460: /**
461: * Trash all beans of a given type. Wipes an entire type of bean.
462: *
463: * @param string $type type of bean you wish to delete all instances of
464: *
465: * @return boolean
466: */
467: public function wipe( $type )
468: {
469: return $this->repository->wipe( $type );
470: }
471:
472: /**
473: * Returns an Association Manager for use with OODB.
474: * A simple getter function to obtain a reference to the association manager used for
475: * storage and more.
476: *
477: * @return AssociationManager
478: */
479: public function getAssociationManager()
480: {
481: if ( !isset( $this->assocManager ) ) {
482: throw new RedException( 'No association manager available.' );
483: }
484:
485: return $this->assocManager;
486: }
487:
488: /**
489: * Sets the association manager instance to be used by this OODB.
490: * A simple setter function to set the association manager to be used for storage and
491: * more.
492: *
493: * @param AssociationManager $assoc sets the association manager to be used
494: *
495: * @return void
496: */
497: public function setAssociationManager( AssociationManager $assocManager )
498: {
499: $this->assocManager = $assocManager;
500: }
501:
502: /**
503: * Returns the currently used repository instance.
504: * For testing purposes only.
505: *
506: * @return Repository
507: */
508: public function getCurrentRepository()
509: {
510: return $this->repository;
511: }
512:
513: /**
514: * Binds an SQL function to a column.
515: * This method can be used to setup a decode/encode scheme or
516: * perform UUID insertion. This method is especially useful for handling
517: * MySQL spatial columns, because they need to be processed first using
518: * the asText/GeomFromText functions.
519: *
520: * @param string $mode mode to set function for, i.e. read or write
521: * @param string $field field (table.column) to bind SQL function to
522: * @param string $function SQL function to bind to field
523: *
524: * @return void
525: */
526: public function bindFunc( $mode, $field, $function )
527: {
528: list( $type, $property ) = explode( '.', $field );
529: $mode = ($mode === 'write') ? QueryWriter::C_SQLFILTER_WRITE : QueryWriter::C_SQLFILTER_READ;
530:
531: if ( !isset( self::$sqlFilters[$mode] ) ) self::$sqlFilters[$mode] = array();
532: if ( !isset( self::$sqlFilters[$mode][$type] ) ) self::$sqlFilters[$mode][$type] = array();
533:
534: if ( is_null( $function ) ) {
535: unset( self::$sqlFilters[$mode][$type][$property] );
536: } else {
537: if ($mode === QueryWriter::C_SQLFILTER_WRITE) {
538: self::$sqlFilters[$mode][$type][$property] = $function.'(?)';
539: } else {
540: self::$sqlFilters[$mode][$type][$property] = $function."($field)";
541: }
542: }
543:
544: AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
545: }
546: }
547: