wCMF  3.6
 All Classes Namespaces Files Functions Variables Groups Pages
class.PersistenceFacadeImpl.php
Go to the documentation of this file.
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2014 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of any of the following licenses
7  * at your choice:
8  *
9  * - GNU Lesser General Public License (LGPL)
10  * http://www.gnu.org/licenses/lgpl.html
11  * - Eclipse Public License (EPL)
12  * http://www.eclipse.org/org/documents/epl-v10.php
13  *
14  * See the license.txt file distributed with this work for
15  * additional information.
16  *
17  * $Id: class.PersistenceFacade.php 1079 2009-09-08 15:21:33Z iherwig $
18  */
19 require_once(BASE."wcmf/lib/core/class.WCMFException.php");
20 require_once(BASE."wcmf/lib/util/class.Log.php");
21 require_once(BASE."wcmf/lib/util/class.InifileParser.php");
22 require_once(BASE."wcmf/lib/output/class.OutputStrategy.php");
23 require_once(BASE."wcmf/lib/persistence/class.PersistenceMapper.php");
24 require_once(BASE."wcmf/lib/persistence/class.ObjectQuery.php");
25 require_once(BASE."wcmf/lib/persistence/class.StringQuery.php");
26 require_once(BASE."wcmf/lib/persistence/class.PagingInfo.php");
27 require_once(BASE."wcmf/lib/persistence/class.ChangeListener.php");
28 
29 /**
30  * @class PersistenceFacadeImpl
31  * @ingroup Persistence
32  * @brief PersistenceFacadeImpl delegates persistence operations to the type-specific PersistenceMappers.
33  *
34  * @note The OID parameter must provide enough information to select the appropriate mapper.
35  * This may be achived by different strategies, e.g. coding the object type into the
36  * OID or having a global registry which maps OIDs to objects. wCMF uses the first method.
37  * The OID is a string in order to make it easier to serialize it in HTML forms. The following
38  * notation is used: type:id1:id2:... where type is the object type and id1, id2, .. are the
39  * values of the primary key columns (in case of simple keys only one). PersistenceFacade provides
40  * methods to handle OIDs (composeOID(), decomposeOID(), ...)
41  *
42  *
43  * @author ingo herwig <ingo@wemove.com>
44  */
46 {
47  var $_mapperObjects = array();
48  var $_createdOIDs = array();
49  var $_dbConnections = array();
50  var $_cache = array();
51  var $_logging = false;
52  var $_logStrategy = null;
53  var $_isReadOnly = false;
54  var $_isCaching = false;
55  var $_inTransaction = false;
56 
57  /**
58  * Prevent anything to be stored in the session
59  */
60  function __sleep() {}
61  /**
62  * Load an object from the database.
63  * @param oid OID of the object to construct
64  * @param buildDepth One of the BUILDDEPTH constants or a number describing the number of generations to build (except BUILDDEPTH_REQUIRED)
65  * @param buildAttribs An assoziative array listing the attributes to load (default: null loads all attributes)
66  * (keys: the types, values: an array of attributes of the type to load)
67  * Use this to load only a subset of attributes
68  * @param buildTypes An array listing the (sub-)types to include
69  * @return A reference to the object, null if oid does not exist or a given condition prevents loading.
70  */
71  function &load($oid, $buildDepth, $buildAttribs=null, $buildTypes=null)
72  {
73  if ($buildDepth < 0 && !in_array($buildDepth, array(BUILDDEPTH_INFINITE, BUILDDEPTH_SINGLE)))
74  WCMFException::throwEx("Build depth not supported: $buildDepth", __FILE__, __LINE__);
75 
76  $obj = null;
77 
78  // lookup the object in the cache
79  if ($this->_isCaching)
80  {
81  $cacheKey = $this->getCacheKey($oid, $buildDepth, $buildAttribs, $buildTypes);
82  if (isset($this->_cache[$cacheKey]))
83  {
84  $obj = &$this->_cache[$cacheKey];
85  }
86  }
87 
88  // if not cached, load
89  if ($obj == null)
90  {
91  $oidParts = PersistenceFacade::decomposeOID($oid);
92  $mapper = &$this->getMapper($oidParts['type']);
93  if ($mapper != null)
94  $obj = &$mapper->load($oid, $buildDepth, $buildAttribs, $buildTypes);
95 
96  if ($obj != null)
97  {
98  // prepare the object (readonly/locked)
99  if ($this->_isReadOnly)
100  $obj->setImmutable();
101 
102  // cache the object
103  if ($this->_isCaching)
104  {
105  $cacheKey = $this->getCacheKey($oid, $buildDepth, $buildAttribs, $buildTypes);
106  $this->_cache[$cacheKey] = &$obj;
107  }
108  }
109  }
110  return $obj;
111  }
112  /**
113  * Construct the template of an Object of a given type.
114  * @param type The type of object to build
115  * @param buildDepth One of the BUILDDEPTH constants or a number describing the number of generations to build
116  * @param buildAttribs An assoziative array listing the attributes to create (default: null creates all attributes)
117  * (keys: the types, values: an array of attributes of the type to create)
118  * Use this to create only a subset of attributes
119  * @return A reference to the object.
120  */
121  function &create($type, $buildDepth, $buildAttribs=null)
122  {
123  if ($buildDepth < 0 && !in_array($buildDepth, array(BUILDDEPTH_INFINITE, BUILDDEPTH_SINGLE, BUILDDEPTH_REQUIRED)))
124  WCMFException::throwEx("Build depth not supported: $buildDepth", __FILE__, __LINE__);
125 
126  $obj = null;
127  $mapper = &$this->getMapper($type);
128  if ($mapper != null)
129  {
130  $obj = &$mapper->create($type, $buildDepth, $buildAttribs);
131 
132  // register as change listener to track the created oid, after save
133  $obj->addChangeListener($this);
134  }
135 
136  return $obj;
137  }
138  /**
139  * Save an object to the database (inserted if it is new).
140  * @param object A reference to the object to save
141  * @return True/False depending on success of operation
142  */
143  function save(&$object)
144  {
145  if ($this->_isReadOnly)
146  return true;
147 
148  $result = false;
149  $mapper = &$object->getMapper();
150  if ($mapper != null)
151  $result = $mapper->save($object);
152  return $result;
153  }
154  /**
155  * Delete an object from the database (together with all of its children).
156  * @param oid The object id of the object to delete
157  * @param recursive True/False whether to physically delete it's children too [default: true]
158  * @return True/False depending on success of operation
159  */
160  function delete($oid, $recursive=true)
161  {
162  if ($this->_isReadOnly)
163  return true;
164 
165  $result = false;
166  $oidParts = PersistenceFacade::decomposeOID($oid);
167  $mapper = &$this->getMapper($oidParts['type']);
168  if ($mapper != null)
169  $result = $mapper->delete($oid, $recursive);
170  return $result;
171  }
172  /**
173  * Get the object ids of newly created objects of a given type.
174  * @param type The type of the object
175  * @return An array containing the objects ids
176  */
177  function getCreatedOIDs($type)
178  {
179  if (!isset($this->_createdOIDs[$type]))
180  return $this->_createdOIDs[$type];
181  return array();
182  }
183  /**
184  * Get the object id of the last created object of a given type.
185  * @param type The type of the object
186  * @return The object id or null
187  */
188  function getLastCreatedOID($type)
189  {
190  if (isset($this->_createdOIDs[$type]) && sizeof($this->_createdOIDs[$type]) > 0)
191  return $this->_createdOIDs[$type][sizeof($this->_createdOIDs[$type])-1];
192  return null;
193  }
194  /**
195  * Get the object ids of objects matching a given criteria. If a PagingInfo instance is passed it will be used and updated.
196  * @param type The type of the object
197  * @param criteria An assoziative array holding name value pairs of attributes for selecting objects or a single string
198  * representing a (mapper specific) query condition (maybe null). [default: null]
199  * @param orderby An array holding names of attributes to order by (maybe null). [default: null]
200  * @param pagingInfo A reference PagingInfo instance (optional, default null does not work in PHP4).
201  * @return An array containing the objects ids
202  */
203  function getOIDs($type, $criteria=null, $orderby=null, &$pagingInfo)
204  {
205  $result = array();
206  $mapper = &$this->getMapper($type);
207  if ($mapper != null)
208  $result = $mapper->getOIDs($type, $criteria, $orderby, $pagingInfo);
209  return $result;
210  }
211  /**
212  * @deprecated use getOIDs() instead
213  */
214  function getOIDsEx($type, $where=null, $orderby=null, &$pagingInfo)
215  {
216  Log::error("use of deprecated method getOIDsEx. use getOIDs() instead.\n".WCMFException::getStackTrace(), __CLASS__);
217  }
218  /**
219  * Get the first object id of objects matching a given condition. If a PagingInfo instance is passed it will be used and updated.
220  * @param type The type of the object
221  * @param criteria An assoziative array holding name value pairs of attributes for selecting objects or a single string
222  * representing a (mapper specific) query condition (maybe null). [default: null]
223  * @param orderby An array holding names of attributes to ORDER by (maybe null). [default: null]
224  * @param pagingInfo A reference PagingInfo instance (optional, default null does not work in PHP4).
225  * @return An object id or null
226  */
227  function getFirstOID($type, $criteria=null, $orderby=null, &$pagingInfo)
228  {
229  $oids = $this->getOIDs($type, $criteria, $orderby, $pagingInfo);
230  if (sizeof($oids) > 0)
231  return $oids[0];
232  else
233  return null;
234  }
235  /**
236  * @deprecated use getFirstOID() instead
237  */
238  function getFirstOIDEx($type, $where=null, $orderby=null, &$pagingInfo)
239  {
240  Log::error("use of deprecated method getFirstOIDEx. use getFirstOID() instead.\n".WCMFException::getStackTrace(), __CLASS__);
241  }
242  /**
243  * Load the objects matching a given condition. If a PagingInfo instance is passed it will be used and updated.
244  * @param type The type of the object
245  * @param buildDepth One of the BUILDDEPTH constants or a number describing the number of generations to build (except BUILDDEPTH_REQUIRED)
246  * @param criteria An assoziative array holding name value pairs of attributes for selecting objects or a single string
247  * representing a (mapper specific) query condition (maybe null). [default: null]
248  * @param orderby An array holding names of attributes to ORDER by (maybe null). [default: null]
249  * @param pagingInfo A reference PagingInfo instance (optional, default null does not work in PHP4).
250  * @param buildAttribs An assoziative array listing the attributes to load (default: null loads all attributes)
251  * (keys: the types, values: an array of attributes of the type to load)
252  * Use this to load only a subset of attributes
253  * @param buildTypes An array listing the (sub-)types to include
254  * @return An array containing the objects
255  */
256  function loadObjects($type, $buildDepth, $criteria=null, $orderby=null, &$pagingInfo, $buildAttribs=null, $buildTypes=null)
257  {
258  $result = array();
259  $mapper = &$this->getMapper($type);
260  if ($mapper != null)
261  $result = $mapper->loadObjects($type, $buildDepth, $criteria, $orderby, $pagingInfo, $buildAttribs, $buildTypes);
262  return $result;
263  }
264  /**
265  * Load the first object matching a given condition. If a PagingInfo instance is passed it will be used and updated.
266  * @param type The type of the object
267  * @param buildDepth One of the BUILDDEPTH constants or a number describing the number of generations to build (except BUILDDEPTH_REQUIRED)
268  * @param criteria An assoziative array holding name value pairs of attributes for selecting objects or a single string
269  * representing a (mapper specific) query condition (maybe null). [default: null]
270  * @param orderby An array holding names of attributes to ORDER by (maybe null). [default: null]
271  * @param pagingInfo A reference PagingInfo instance (optional, default null does not work in PHP4).
272  * @param buildAttribs An assoziative array listing the attributes to load (default: null loads all attributes)
273  * (keys: the types, values: an array of attributes of the type to load)
274  * Use this to load only a subset of attributes
275  * @param buildTypes An array listing the (sub-)types to include
276  * @return A reference to the object or null
277  */
278  function &loadFirstObject($type, $buildDepth, $criteria=null, $orderby=null, &$pagingInfo, $buildAttribs=null, $buildTypes=null)
279  {
280  $objects = $this->loadObjects($type, $buildDepth, $criteria, $orderby, $pagingInfo, $buildAttribs, $buildTypes);
281  if (sizeof($objects) > 0)
282  return $objects[0];
283  else
284  return null;
285  }
286  /**
287  * Start a transaction. Used for PersistenceMapper classes that need to explicitely start and commit transactions.
288  * If this method is called, the startTransaction() method of every used PersistenceMapper will be called - until
289  * commitTransaction() is called.
290  * @note There is only ONE transaction active at a time. Repeated calls of this method will leave the initial
291  * transaction active until commitTransaction() ore rollbackTransaction() is called.
292  */
293  function startTransaction()
294  {
295  if (!$this->_inTransaction)
296  {
297  // log action
298  if ($this->isLogging())
299  Log::debug("Start Transaction", __CLASS__);
300 
301  // end transaction for every mapper
302  $mapperEntries = array_keys($this->_mapperObjects);
303  for ($i=0; $i<sizeof($mapperEntries); $i++)
304  $this->_mapperObjects[$mapperEntries[$i]]->startTransaction();
305 
306  $this->_inTransaction = true;
307  }
308  }
309  /**
310  * Commit the started transaction. Used for PersistenceMapper classes that need to explicitely start and commit transactions.
311  * If this method is called, the commitTransaction() method of every used PersistenceMapper will be called.
312  * @note There is only ONE transaction active at a time. Repeated calls of this method will do nothing until
313  * a new transaction was started by calling startTransaction().
314  */
315  function commitTransaction()
316  {
317  if ($this->_inTransaction)
318  {
319  // log action
320  if ($this->isLogging())
321  Log::debug("Commit Transaction", __CLASS__);
322 
323  // commit transaction for every mapper
324  $mapperEntries = array_keys($this->_mapperObjects);
325  for ($i=0; $i<sizeof($mapperEntries); $i++)
326  $this->_mapperObjects[$mapperEntries[$i]]->commitTransaction();
327 
328  $this->_inTransaction = false;
329  }
330  }
331  /**
332  * Rollback the started transaction. Used for PersistenceMapper classes that need to explicitely start and commit transactions.
333  * If this method is called, the rollbackTransaction() method of every used PersistenceMapper will be called.
334  * @note There is only ONE transaction active at a time. Repeated calls of this method will do nothing until
335  * a new transaction was started by calling startTransaction(). Rollbacks have to be supported by the data storage.
336  */
338  {
339  if ($this->_inTransaction)
340  {
341  if ($this->isLogging())
342  Log::debug("Rollback Transaction", __CLASS__);
343 
344  // rollback transaction for every mapper
345  $mapperEntries = array_keys($this->_mapperObjects);
346  for ($i=0; $i<sizeof($mapperEntries); $i++)
347  $this->_mapperObjects[$mapperEntries[$i]]->rollbackTransaction();
348 
349  $this->_inTransaction = false;
350  }
351  }
352  /**
353  * Get the PersistenceMapper for a given type. If no mapper for this type is defined the mapper for type '*' will be returned
354  * @param type The type of the object to get the PersistenceMapper for
355  * @return A reference to the PersistenceMapper, null on error
356  */
357  function &getMapper($type)
358  {
359  $mapper = null;
360  // find type-specific mapper
361  if (!isset($this->_mapperObjects[$type]))
362  {
363  // first use
364  // find mapper in configfile
365  $parser = &InifileParser::getInstance();
366  if (($mapperClass = $parser->getValue($type, 'typemapping')) === false)
367  {
368  if (($mapperClass = $parser->getValue('*', 'typemapping')) === false)
369  {
370  WCMFException::throwEx("No PersistenceMapper found in configfile for type '".$type."' in section 'typemapping'", __FILE__, __LINE__);
371  return null;
372  }
373  }
374  // find mapper class file
375  $classFile = $this->getClassFile($parser, $mapperClass);
376  // find mapper params
377  $initParams = null;
378  if (($initSection = $parser->getValue($mapperClass, 'initparams')) !== false)
379  {
380  if (($initParams = $parser->getSection($initSection)) === false)
381  {
382  WCMFException::throwEx("No '".$initSection."' section given in configfile.", __FILE__, __LINE__);
383  return null;
384  }
385  }
386 
387  // if connection is already opened reuse it
388  $connectionKey = join(':', array_values($initParams));
389  if (isset($this->_dbConnections[$connectionKey]))
390  $initParams = array('dbConnection' => &$this->_dbConnections[$connectionKey]);
391 
392  // see if class is already instantiated and reuse it if possible
393  $isAlreadyInUse = false;
394  $mapperObjects = array_values($this->_mapperObjects);
395  for ($i=0; $i<sizeof($mapperObjects); $i++)
396  {
397  if (strtolower(get_class($mapperObjects[$i])) == strtolower($mapperClass))
398  {
399  $this->_mapperObjects[$type] = &$mapperObjects[$i];
400  $isAlreadyInUse = true;
401  break;
402  }
403  }
404 
405  // instantiate class if needed
406  if (!$isAlreadyInUse)
407  {
408  if (file_exists(BASE.$classFile))
409  {
410  require_once(BASE.$classFile);
411  if ($initParams)
412  $mapperObj = new $mapperClass($initParams);
413  else
414  $mapperObj = new $mapperClass;
415  $this->_mapperObjects[$type] = &$mapperObj;
416  }
417  else
418  {
419  WCMFException::throwEx("Definition of PersistanceMapper ".$mapperClass." in '".$classFile."' not found.", __FILE__, __LINE__);
420  return null;
421  }
422 
423  // lookup converter
424  if (($converterClass = $parser->getValue($type, 'converter')) !== false ||
425  ($converterClass = $parser->getValue('*', 'converter')) !== false)
426  {
427  $classFile = $this->getClassFile($parser, $converterClass);
428  if ($classFile != null)
429  {
430  // instatiate class
431  if (file_exists(BASE.$classFile))
432  {
433  require_once(BASE.$classFile);
434  $converterObj = new $converterClass;
435  $mapperObj->setDataConverter($converterObj);
436  }
437  else
438  {
439  WCMFException::throwEx("Definition of DataConverter ".$converterClass." in '".$classFile."' not found.", __FILE__, __LINE__);
440  return null;
441  }
442  }
443  }
444  }
445  }
446 
447  if (isset($this->_mapperObjects[$type]))
448  $mapper = &$this->_mapperObjects[$type];
449  else
450  $mapper = &$this->_mapperObjects['*'];
451 
452  return $mapper;
453  }
454  /**
455  * Get the class file name from the classmapping section of ini file
456  * @param parser The ini file parser
457  * @param className The class name
458  * @return The class file name
459  */
460  function getClassFile($parser, $className)
461  {
462  if (($classFile = $parser->getValue($className, 'classmapping')) === false)
463  {
464  WCMFException::throwEx($parser->getErrorMsg(), __FILE__, __LINE__);
465  return null;
466  }
467  return $classFile;
468  }
469  /**
470  * Explicitly set a PersistentMapper for a type
471  * @param type The type to set the mapper for
472  * @param mapper A reference to the mapper
473  */
474  function setMapper($type, &$mapper)
475  {
476  $this->_mapperObjects[$type] = &$mapper;
477  }
478  /**
479  * Store a connection for reuse
480  * @param initParams The initParams used to initialize the conenction
481  * @param connection A reference to the connection to save
482  */
483  function storeConnection($initParams, &$connection)
484  {
485  if ($connection != null)
486  {
487  $connectionKey = join(':', array_values($initParams));
488  $this->_dbConnections[$connectionKey] = &$connection;
489  }
490  }
491  /**
492  * Set state to readonly. If set to true, PersistenceFacade will return only immutable
493  * objects and save/delete methods are disabled.
494  * @param isReadOnly True/False whether objects should be readonly or not
495  */
496  function setReadOnly($isReadOnly)
497  {
498  $this->_isReadOnly = $isReadOnly;
499  }
500  /**
501  * Enable logging using a given OutputStrategy to log insert/update/delete actions to a file.
502  * @param logStrategy The OutputStrategy to use.
503  */
504  function enableLogging($logStrategy)
505  {
506  $this->_logStrategy = $logStrategy;
507  $mapperObjs = array_values($this->_mapperObjects);
508  for($i=0, $count=sizeof($mapperObjs); $i<$count; $i++) {
509  $mapperObjs[$i]->enableLogging($this->_logStrategy);
510  }
511  $this->_logging = true;
512  }
513  /**
514  * Disable logging.
515  */
516  function disableLogging()
517  {
518  $mapperObjs = array_values($this->_mapperObjects);
519  for($i=0, $count=sizeof($mapperObjs); $i<$count; $i++) {
520  $mapperObjs[$i]->disableLogging();
521  }
522  $this->_logging = false;
523  }
524  /**
525  * Check if the PersistenceMapper is logging.
526  * @return True/False whether the PersistenceMapper is logging.
527  */
528  function isLogging()
529  {
530  return $this->_logging;
531  }
532  /**
533  * Set state to caching. If set to true, PersistenceFacade will cache all loaded objects
534  * and returns cached instances when calling the PersistenceFacade::load method.
535  * @param isCaching True/False whether objects should be chached or not
536  */
537  function setCaching($isCaching)
538  {
539  $this->_isCaching = $isCaching;
540  }
541  /**
542  * Clear the object cache
543  */
544  function clearCache()
545  {
546  $this->_cache = array();
547  }
548  /**
549  * Get cache key from the given parameters
550  * @param oid OID of the object
551  * @param buildDepth One of the BUILDDEPTH constants
552  * @param buildAttribs An assoziative array (@see PersistenceFacade::load)
553  * @param buildTypes An array (@see PersistenceFacade::load)
554  * @return The cache key string
555  */
556  function getCacheKey($oid, $buildDepth, $buildAttribs, $buildTypes)
557  {
558  $key = $oid.':'.$buildDepth.':';
559  foreach($buildAttribs as $type => $attribs)
560  $key .= $type.'='.join(',', $attribs).':';
561  $key .= join(',', $buildTypes);
562  return $key;
563  }
564 
565  /**
566  * ChangeListener interface implementation
567  */
568 
569  /**
570  * @see ChangeListener::getId()
571  */
572  function getId()
573  {
574  return __CLASS__;
575  }
576  /**
577  * @see ChangeListener::valueChanged()
578  */
579  function valueChanged(&$object, $name, $type, $oldValue, $newValue) {}
580  /**
581  * @see ChangeListener::propertyChanged()
582  */
583  function propertyChanged(&$object, $name, $oldValue, $newValue) {}
584  /**
585  * @see ChangeListener::stateChanged()
586  */
587  function stateChanged(&$object, $oldValue, $newValue)
588  {
589  // store the object id in the internal registry if the object was saved after creation
590  if ($oldValue == STATE_NEW && $newValue == STATE_CLEAN)
591  {
592  $type = $object->getType();
593  if (!isset($this->_createdOIDs[$type]))
594  $this->_createdOIDs[$type] = array();
595  array_push($this->_createdOIDs[$type], $object->getOID());
596  }
597  }
598 }
599 ?>
ChangeListener defines an interface for classes that want to be notified when a value of an persisten...
error($message, $category)
Definition: class.Log.php:69
debug($message, $category)
Definition: class.Log.php:39
propertyChanged(&$object, $name, $oldValue, $newValue)
getOIDsEx($type, $where=null, $orderby=null, &$pagingInfo)
getFirstOID($type, $criteria=null, $orderby=null, &$pagingInfo)
& loadFirstObject($type, $buildDepth, $criteria=null, $orderby=null, &$pagingInfo, $buildAttribs=null, $buildTypes=null)
throwEx($message, $file='', $line='')
const STATE_CLEAN
PersistenceFacadeImpl delegates persistence operations to the type-specific PersistenceMappers.
const BUILDDEPTH_INFINITE
const STATE_NEW
decomposeOID($oid, $validate=true)
& load($oid, $buildDepth, $buildAttribs=null, $buildTypes=null)
loadObjects($type, $buildDepth, $criteria=null, $orderby=null, &$pagingInfo, $buildAttribs=null, $buildTypes=null)
stateChanged(&$object, $oldValue, $newValue)
const BUILDDEPTH_REQUIRED
storeConnection($initParams, &$connection)
getCacheKey($oid, $buildDepth, $buildAttribs, $buildTypes)
& create($type, $buildDepth, $buildAttribs=null)
getFirstOIDEx($type, $where=null, $orderby=null, &$pagingInfo)
valueChanged(&$object, $name, $type, $oldValue, $newValue)
const BUILDDEPTH_SINGLE
getOIDs($type, $criteria=null, $orderby=null, &$pagingInfo)