DefaultPersistenceFacade.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2020 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of the MIT License.
7  *
8  * See the LICENSE file distributed with this work for
9  * additional information.
10  */
12 
29 
30 /**
31  * Default PersistenceFacade implementation.
32  *
33  * @author ingo herwig <ingo@wemove.com>
34  */
36 
37  private $mappers = [];
38  private $simpleToFqNames = [];
39  private $createdOIDs = [];
40  private $eventManager = null;
41  private $currentTransaction = null;
42  private $logStrategy = null;
43 
44  /**
45  * Constructor
46  * @param $eventManager
47  * @param $logStrategy OutputStrategy used for logging persistence actions.
48  */
49  public function __construct(EventManager $eventManager,
50  OutputStrategy $logStrategy) {
51  $this->eventManager = $eventManager;
52  $this->logStrategy = $logStrategy;
53  // register as change listener to track the created oids, after save
54  $this->eventManager->addListener(StateChangeEvent::NAME,
55  [$this, 'stateChanged']);
56  }
57 
58  /**
59  * Destructor
60  */
61  public function __destruct() {
62  $this->eventManager->removeListener(StateChangeEvent::NAME,
63  [$this, 'stateChanged']);
64  }
65 
66  /**
67  * Set the PersistentMapper instances.
68  * @param $mappers Associative array with the fully qualified
69  * mapped class names as keys and the mapper instances as values
70  */
71  public function setMappers($mappers) {
72  $this->mappers = $mappers;
73  foreach ($mappers as $fqName => $mapper) {
74  // register simple type names
75  $name = $this->calculateSimpleType($fqName);
76  if (!isset($this->mappers[$name])) {
77  $this->mappers[$name] = $mapper;
78  if (!isset($this->simpleToFqNames[$name])) {
79  $this->simpleToFqNames[$name] = $fqName;
80  }
81  else {
82  // if the simple type name already exists, we remove
83  // it in order to prevent collisions with the new type
84  unset($this->simpleToFqNames[$name]);
85  }
86  }
87  // set logging strategy
88  $mapper->setLogStrategy($this->logStrategy);
89  }
90  }
91 
92  /**
93  * @see PersistenceFacade::getKnownTypes()
94  */
95  public function getKnownTypes() {
96  return array_values($this->simpleToFqNames);
97  }
98 
99  /**
100  * @see PersistenceFacade::isKnownType()
101  */
102  public function isKnownType($type) {
103  return (isset($this->mappers[$type]));
104  }
105 
106  /**
107  * @see PersistenceFacade::getFullyQualifiedType()
108  */
109  public function getFullyQualifiedType($type) {
110  if (isset($this->simpleToFqNames[$type])) {
111  return $this->simpleToFqNames[$type];
112  }
113  if ($this->isKnownType($type)) {
114  return $type;
115  }
116  throw new ConfigurationException("Type '".$type."' is unknown.");
117  }
118 
119  /**
120  * @see PersistenceFacade::getSimpleType()
121  */
122  public function getSimpleType($type) {
123  $simpleType = $this->calculateSimpleType($type);
124  // if there is a entry for the type name but not for the simple type name,
125  // the type is ambiquous and we return the type name
126  return (isset($this->mappers[$type]) && !isset($this->simpleToFqNames[$simpleType])) ?
127  $type : $simpleType;
128  }
129 
130  /**
131  * @see PersistenceFacade::load()
132  */
133  public function load(ObjectId $oid, $buildDepth=BuildDepth::SINGLE) {
134  if ($buildDepth < 0 && !in_array($buildDepth, [BuildDepth::INFINITE, BuildDepth::SINGLE])) {
135  throw new IllegalArgumentException("Build depth not supported: $buildDepth");
136  }
137  // check if the object is already part of the transaction
138  $transaction = $this->getTransaction();
139  $obj = $transaction->getLoaded($oid);
140 
141  // if not cached or build depth requested, load
142  if ($obj == null || $buildDepth != BuildDepth::SINGLE) {
143  $mapper = $this->getMapper($oid->getType());
144  $obj = $mapper->load($oid, $buildDepth);
145  }
146  return $obj;
147  }
148 
149  /**
150  * @see PersistenceFacade::create()
151  */
152  public function create($type, $buildDepth=BuildDepth::SINGLE) {
153  if ($buildDepth < 0 && !in_array($buildDepth, [BuildDepth::INFINITE, BuildDepth::SINGLE, BuildDepth::REQUIRED])) {
154  throw new IllegalArgumentException("Build depth not supported: $buildDepth");
155  }
156 
157  $mapper = $this->getMapper($type);
158  $obj = $mapper->create($type, $buildDepth);
159 
160  // attach the object to the transaction
161  $attachedObject = $this->getTransaction()->attach($obj);
162 
163  return $attachedObject;
164  }
165 
166  /**
167  * @see PersistenceFacade::getLastCreatedOID()
168  */
169  public function getLastCreatedOID($type) {
170  $fqType = $this->getFullyQualifiedType($type);
171  if (isset($this->createdOIDs[$fqType]) && sizeof($this->createdOIDs[$fqType]) > 0) {
172  return $this->createdOIDs[$fqType][sizeof($this->createdOIDs[$fqType])-1];
173  }
174  return null;
175  }
176 
177  /**
178  * @see PersistenceFacade::getOIDs()
179  */
180  public function getOIDs($type, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
181  $this->checkArrayParameter($criteria, 'criteria', 'wcmf\lib\persistence\Criteria');
182  $this->checkArrayParameter($orderby, 'orderby');
183 
184  $mapper = $this->getMapper($type);
185  $result = $mapper->getOIDs($type, $criteria, $orderby, $pagingInfo);
186  return $result;
187  }
188 
189  /**
190  * @see PersistenceFacade::getFirstOID()
191  */
192  public function getFirstOID($type, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
193  if ($pagingInfo == null) {
194  $pagingInfo = new PagingInfo(1, true);
195  }
196  $oids = $this->getOIDs($type, $criteria, $orderby, $pagingInfo);
197  if (sizeof($oids) > 0) {
198  return $oids[0];
199  }
200  else {
201  return null;
202  }
203  }
204 
205  /**
206  * @see PersistenceFacade::loadObjects()
207  */
208  public function loadObjects($typeOrTypes, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
209  $this->checkArrayParameter($criteria, 'criteria', 'wcmf\lib\persistence\Criteria');
210  $this->checkArrayParameter($orderby, 'orderby');
211 
212  if (!is_array($typeOrTypes)) {
213  // single type
214  $mapper = $this->getMapper($typeOrTypes);
215  $result = $mapper->loadObjects($typeOrTypes, $buildDepth, $criteria, $orderby, $pagingInfo);
216  }
217  else {
218  $queryProvider = new DefaultUnionQueryProvider($typeOrTypes, $criteria);
219  $result = UnionQuery::execute($queryProvider, $buildDepth, $orderby, $pagingInfo);
220  }
221  return $result;
222  }
223 
224  /**
225  * @see PersistenceFacade::loadFirstObject()
226  */
227  public function loadFirstObject($typeOrTypes, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
228  if ($pagingInfo == null) {
229  $pagingInfo = new PagingInfo(1, true);
230  }
231  $objects = $this->loadObjects($typeOrTypes, $buildDepth, $criteria, $orderby, $pagingInfo);
232  if (sizeof($objects) > 0) {
233  return $objects[0];
234  }
235  else {
236  return null;
237  }
238  }
239 
240  /**
241  * @see PersistenceFacade::getTransaction()
242  */
243  public function getTransaction() {
244  if ($this->currentTransaction == null) {
245  $this->currentTransaction = ObjectFactory::getInstance('transaction');
246  }
247  return $this->currentTransaction;
248  }
249 
250  /**
251  * @see PersistenceFacade::getMapper()
252  */
253  public function getMapper($type) {
254  if ($this->isKnownType($type)) {
255  $mapper = $this->mappers[$type];
256  return $mapper;
257  }
258  else {
259  throw new ConfigurationException("No PersistenceMapper found for type '".$type."'");
260  }
261  }
262 
263  /**
264  * @see PersistenceFacade::setMapper()
265  */
266  public function setMapper($type, PersistenceMapper $mapper) {
267  $this->mappers[$type] = $mapper;
268  }
269 
270  /**
271  * Check if the given value is either null or an array and
272  * throw an exception if not
273  * @param $param The parameter
274  * @param $paramName The name of the parameter (used in the exception text)
275  * @param $className Class name to match if, instances of a specific type are expected (optional)
276  */
277  private function checkArrayParameter($param, $paramName, $className=null) {
278  if ($param == null) {
279  return;
280  }
281  if (!is_array($param)) {
282  throw new IllegalArgumentException("The parameter '".$paramName.
283  "' is expected to be null or an array");
284  }
285  if ($className != null) {
286  foreach ($param as $instance) {
287  if (!($instance instanceof $className)) {
288  throw new IllegalArgumentException("The parameter '".$paramName.
289  "' is expected to contain only instances of '".$className."'");
290  }
291  }
292  }
293  }
294 
295  /**
296  * Listen to StateChangeEvents
297  * @param $event StateChangeEvent instance
298  */
299  public function stateChanged(StateChangeEvent $event) {
300  $oldState = $event->getOldValue();
301  $newState = $event->getNewValue();
302  // store the object id in the internal registry if the object was saved after creation
303  if ($oldState == PersistentObject::STATE_NEW && $newState == PersistentObject::STATE_CLEAN) {
304  $object = $event->getObject();
305  $type = $object->getType();
306  if (!array_key_exists($type, $this->createdOIDs)) {
307  $this->createdOIDs[$type] = [];
308  }
309  $this->createdOIDs[$type][] = $object->getOID();
310  }
311  }
312 
313  /**
314  * Calculate the simple type name for a given fully qualified type name.
315  * @param $type Type name with namespace
316  * @return Simple type name (without namespace)
317  */
318  protected function calculateSimpleType($type) {
319  $pos = strrpos($type, '.');
320  if ($pos !== false) {
321  return substr($type, $pos+1);
322  }
323  return $type;
324  }
325 }
326 ?>
OutputStrategy defines the interface for classes that write an object's content to a destination (cal...
EventManager is responsible for dispatching events to registered listeners.
getObject()
Get the object whose state has changed.
calculateSimpleType($type)
Calculate the simple type name for a given fully qualified type name.
loadFirstObject($typeOrTypes, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
IllegalArgumentException signals an exception in method arguments.
StateChangeEvent signals a change of the state of a PersistentObject instance.
PersistenceMapper defines the interface for all mapper classes.
getFirstOID($type, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
ObjectId is the unique identifier of an object.
Definition: ObjectId.php:28
UnionQueryProvider is used to provide queries to a union query.
BuildDepth values are used to define the depth when loading object trees.
Definition: BuildDepth.php:19
ConfigurationException signals an exception in the configuration.
PersistenceFacade defines the interface for PersistenceFacade implementations.
getType()
Get the type (including namespace)
Definition: ObjectId.php:97
getOIDs($type, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
setMappers($mappers)
Set the PersistentMapper instances.
__construct(EventManager $eventManager, OutputStrategy $logStrategy)
Constructor.
stateChanged(StateChangeEvent $event)
Listen to StateChangeEvents.
static getInstance($name, $dynamicConfiguration=[])
UnionQuery combines multiple query results to allow for sorting and paginating over different queries...
Definition: UnionQuery.php:23
PagingInfo contains information about a paged list.
Definition: PagingInfo.php:18
PersistentObject defines the interface of all persistent objects.
static execute(UnionQueryProvider $queryProvider, $buildDepth=BuildDepth::SINGLE, $orderby=null, PagingInfo $pagingInfo=null)
Execute the provided queries.
Definition: UnionQuery.php:33
ObjectFactory implements the service locator pattern by wrapping a Factory instance and providing sta...
loadObjects($typeOrTypes, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
ObjectComparator is used to compare persistent objects by given criterias.
DefaultUnionQueryProvider provides queries for multiple types using the PersistentFacade::loadObjects...
load(ObjectId $oid, $buildDepth=BuildDepth::SINGLE)