AbstractMapper.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2015 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  * AbstractMapper provides a basic implementation for other mapper classes.
32  * It handles authorization on entity level and calls the lifecycle callcacks
33  * of PersistentObject instances. Authorization on attribute level has to be
34  * implemented by subclasses.
35  *
36  * @author ingo herwig <ingo@wemove.com>
37  */
38 abstract class AbstractMapper implements PersistenceMapper {
39 
40  private $_logStrategy = null;
41 
42  private $_attributeNames = array();
43  private $_relationNames = array();
44 
45  private static $_logger = null;
46 
47  protected $_persistenceFacade = null;
48  protected $_permissionManager = null;
49  protected $_concurrencyManager = null;
50  protected $_eventManager = null;
51  protected $_message = null;
52 
53  /**
54  * Constructor
55  * @param $persistenceFacade
56  * @param $permissionManager
57  * @param $concurrencyManager
58  * @param $eventManager
59  * @param $message
60  */
61  public function __construct(PersistenceFacade $persistenceFacade,
62  PermissionManager $permissionManager,
63  ConcurrencyManager $concurrencyManager,
64  EventManager $eventManager,
65  Message $message) {
66  if (self::$_logger == null) {
67  self::$_logger = LogManager::getLogger(__CLASS__);
68  }
69  $this->_persistenceFacade = $persistenceFacade;
70  $this->_permissionManager = $permissionManager;
71  $this->_concurrencyManager = $concurrencyManager;
72  $this->_eventManager = $eventManager;
73  $this->_message = $message;
74  }
75 
76  /**
77  * Set the OutputStrategy used for logging persistence actions.
78  * @param $logStrategy
79  */
80  public function setLogStrategy(OutputStrategy $logStrategy) {
81  $this->_logStrategy = $logStrategy;
82  }
83 
84  /**
85  * @see PersistenceMapper::hasRelation()
86  */
87  public function hasRelation($roleName) {
88  if (isset($this->_relationNames[$roleName])) {
89  return true;
90  }
91  $relations = $this->getRelations();
92  foreach ($relations as $relation) {
93  if ($relation->getOtherRole() == $roleName) {
94  $this->_relationNames[$roleName] = true;
95  return true;
96  }
97  }
98  return false;
99  }
100 
101  /**
102  * @see PersistenceMapper::hasAttribute()
103  */
104  public function hasAttribute($name) {
105  if (isset($this->_attributeNames[$name])) {
106  return true;
107  }
108  $attributes = $this->getAttributes();
109  foreach ($attributes as $attribute) {
110  if ($attribute->getName() == $name) {
111  $this->_attributeNames[$name] = true;
112  return true;
113  }
114  }
115  return false;
116  }
117 
118  /**
119  * @see PersistenceMapper::load()
120  */
121  public function load(ObjectId $oid, $buildDepth=BuildDepth::SINGLE) {
122  if (!$this->checkAuthorization($oid, PersistenceAction::READ)) {
124  return;
125  }
126 
127  if (!ObjectId::isValid($oid) || $oid->containsDummyIds()) {
128  return null;
129  }
130  // load object
131  $object = $this->loadImpl($oid, $buildDepth);
132  if ($object != null) {
133  // call lifecycle callback
134  $object->afterLoad();
135  }
136  return $object;
137  }
138 
139  /**
140  * @see PersistenceMapper::create()
141  */
142  public function create($type, $buildDepth=BuildDepth::SINGLE) {
143  // Don't check rights here, because object creation may be needed
144  // for internal purposes. That newly created objects may not be saved
145  // to the storage unless they are valid and the user is authorized
146  // is assured by the save method.
147  $object = $this->createImpl($type, $buildDepth);
148 
149  // call lifecycle callback
150  $object->afterCreate();
151 
152  return $object;
153  }
154 
155  /**
156  * @see PersistenceMapper::save()
157  */
158  public function save(PersistentObject $object) {
159  $isDirty = ($object->getState() == PersistentObject::STATE_DIRTY);
160  $isNew = ($object->getState() == PersistentObject::STATE_NEW);
161 
162  $oid = $object->getOID();
163  if ($isDirty) {
164  // check permissions for changed attributes, because this
165  // also includes instance and type checks
166  $oidStr = $object->getOID()->__toString();
167  foreach ($object->getChangedValues() as $valueName) {
168  $resource = $oidStr.'.'.$valueName;
169  if (!$this->checkAuthorization($resource, PersistenceAction::UPDATE)) {
171  return;
172  }
173  }
174  }
175  elseif ($isNew && !$this->checkAuthorization($oid, PersistenceAction::CREATE)) {
177  return;
178  }
179 
180  // call lifecycle callback
181  if ($isDirty) {
182  $object->beforeUpdate();
183  }
184  elseif ($isNew) {
185  $object->beforeInsert();
186  }
187 
188  // validate object
189  $object->validateValues($this->_message);
190 
191  // check concurrency
192  $this->_concurrencyManager->checkPersist($object);
193 
194  // save object
195  $this->saveImpl($object);
196 
197  // update lock
198  $this->_concurrencyManager->updateLock($oid, $object);
199 
200  // call lifecycle callback
201  if ($isDirty) {
202  $object->afterUpdate();
203  $this->_eventManager->dispatch(PersistenceEvent::NAME,
205  }
206  elseif ($isNew) {
207  $object->afterInsert();
208  $this->_eventManager->dispatch(PersistenceEvent::NAME,
210  }
211  }
212 
213  /**
214  * @see PersistenceMapper::delete()
215  */
216  public function delete(PersistentObject $object) {
217  $oid = $object->getOID();
218  if (!$this->checkAuthorization($oid, PersistenceAction::DELETE)) {
220  return;
221  }
222 
223  if (!ObjectId::isValid($oid)) {
224  return false;
225  }
226  // call lifecycle callback
227  $object->beforeDelete();
228 
229  // check concurrency
230  $this->_concurrencyManager->checkPersist($object);
231 
232  // delete object
233  $result = $this->deleteImpl($object);
234  if ($result === true) {
235  // call lifecycle callback
236  $object->afterDelete();
237  $this->_eventManager->dispatch(PersistenceEvent::NAME,
239 
240  // release any locks on the object
241  $this->_concurrencyManager->releaseLocks($oid);
242  }
243  return $result;
244  }
245 
246  /**
247  * @see PersistenceMapper::getOIDs()
248  */
249  public function getOIDs($type, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null) {
250  $oids = $this->getOIDsImpl($type, $criteria, $orderby, $pagingInfo);
251  $tx = $this->_persistenceFacade->getTransaction();
252 
253  // remove oids for which the user is not authorized
254  $result = array();
255  for ($i=0, $count=sizeof($oids); $i<$count; $i++) {
256  $oid = $oids[$i];
257  if ($this->checkAuthorization($oid, PersistenceAction::READ)) {
258  $result[] = $oid;
259  }
260  else {
261  $tx->detach($oid);
262  }
263  }
264  return $result;
265  }
266 
267  /**
268  * @see PersistenceMapper::loadObjects()
269  */
270  public function loadObjects($type, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null,
271  PagingInfo $pagingInfo=null) {
272  $objects = $this->loadObjectsImpl($type, $buildDepth, $criteria, $orderby, $pagingInfo);
273  $tx = $this->_persistenceFacade->getTransaction();
274 
275  // remove objects for which the user is not authorized
276  $result = array();
277  for ($i=0, $count=sizeof($objects); $i<$count; $i++) {
278  $object = $objects[$i];
279  if ($this->checkAuthorization($object->getOID(), PersistenceAction::READ)) {
280  // call lifecycle callback
281  $object->afterLoad();
282  $result[] = $object;
283  }
284  else {
285  $tx->detach($object->getOID());
286  }
287  // TODO remove attribute values for which the user is not authorized
288  // should use some pre-check if restrictions on the entity type exist
289  }
290  return $result;
291  }
292 
293  /**
294  * @see PersistenceMapper::loadRelation()
295  */
296  public function loadRelation(array $objects, $role, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null,
297  PagingInfo $pagingInfo=null) {
298  $relatedObjects = $this->loadRelationImpl($objects, $role, $buildDepth, $criteria, $orderby, $pagingInfo);
299  $tx = $this->_persistenceFacade->getTransaction();
300 
301  // remove objects for which the user is not authorized
302  if ($relatedObjects != null) {
303  $result = array();
304  foreach ($relatedObjects as $oidStr => $curObjects) {
305  $curResult = array();
306  for ($i=0, $count=sizeof($curObjects); $i<$count; $i++) {
307  $object = $curObjects[$i];
308  if ($this->checkAuthorization($object->getOID(), PersistenceAction::READ)) {
309  $curResult[] = $object;
310  }
311  else {
312  $tx->detach($object->getOID());
313  }
314  }
315  $result[$oidStr] = $curObjects;
316  }
317  return $result;
318  }
319  return null;
320  }
321 
322  /**
323  * Log the state of the given object
324  * @param $obj PersistentObject instance
325  */
326  protected function logAction(PersistentObject $obj) {
327  if ($this->_logStrategy) {
328  $this->_logStrategy->writeObject($obj);
329  }
330  }
331 
332  /**
333  * Check authorization on a resource (type/instance/instance property) and a given action.
334  * @param $resource Resource to authorize
335  * @param $action Action to authorize
336  * @return Boolean depending on success of authorization
337  */
338  protected function checkAuthorization($resource, $action) {
339  return $this->_permissionManager->authorize($resource, '', $action);
340  }
341 
342  /**
343  * Handle an authorization error.
344  * @param $resource
345  * @param $action
346  * @throws AuthorizationException
347  */
348  protected function authorizationFailedError($resource, $action) {
349  // when reading only log the error to avoid errors on the display
350  $msg = $this->_message->getText("Authorization failed for action '%0%' on '%1%'.", array($action, $resource));
351  if ($action == PersistenceAction::READ) {
352  self::$_logger->error($msg."\n".ErrorHandler::getStackTrace());
353  }
354  else {
355  throw new AuthorizationException($msg);
356  }
357  }
358 
359  /**
360  * @see PersistenceFacade::load()
361  * @note Precondition: Object rights have been checked already
362  *
363  */
364  abstract protected function loadImpl(ObjectId $oid, $buildDepth=BuildDepth::SINGLE);
365 
366  /**
367  * @see PersistenceFacade::create()
368  * @note Precondition: Object rights have been checked already
369  */
370  abstract protected function createImpl($type, $buildDepth=BuildDepth::SINGLE);
371 
372  /**
373  * @see PersistenceMapper::save()
374  * @note Precondition: Object rights have been checked already
375  */
376  abstract protected function saveImpl(PersistentObject $object);
377 
378  /**
379  * @see PersistenceMapper::delete()
380  * @note Precondition: Object rights have been checked already
381  */
382  abstract protected function deleteImpl(PersistentObject $object);
383 
384  /**
385  * @see PersistenceMapper::getOIDs()
386  */
387  abstract protected function getOIDsImpl($type, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null);
388 
389  /**
390  * @see PersistenceMapper::loadObjects()
391  */
392  abstract protected function loadObjectsImpl($type, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null,
393  PagingInfo $pagingInfo=null);
394 
395  /**
396  * @see PersistenceMapper::loadRelation()
397  */
398  abstract protected function loadRelationImpl(array $objects, $role, $buildDepth=BuildDepth::SINGLE, $criteria=null,
399  $orderby=null, PagingInfo $pagingInfo=null);
400 }
401 ?>
loadObjectsImpl($type, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
getOID()
Get the object id of the PersistentObject.
beforeUpdate()
This method is called always before updating the modified object in the store.
authorizationFailedError($resource, $action)
Handle an authorization error.
saveImpl(PersistentObject $object)
getRelations($hierarchyType='all')
Get the relations for this type.
EventManager is responsible for dispatching events to registered listeners.
OutputStrategy defines the interface for classes that write an object's content to a destination (cal...
AuthorizationException signals an exception in authorization.
loadRelationImpl(array $objects, $role, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
__construct(PersistenceFacade $persistenceFacade, PermissionManager $permissionManager, ConcurrencyManager $concurrencyManager, EventManager $eventManager, Message $message)
Constructor.
afterUpdate()
This method is called always after updating the modified object in the store.
logAction(PersistentObject $obj)
Log the state of the given object.
ObjectId is the unique identifier of an object.
Definition: ObjectId.php:27
PersistenceMapper defines the interface for all mapper classes.
static getLogger($name)
Get the logger with the given name.
Definition: LogManager.php:35
loadImpl(ObjectId $oid, $buildDepth=BuildDepth::SINGLE)
deleteImpl(PersistentObject $object)
load(ObjectId $oid, $buildDepth=BuildDepth::SINGLE)
ConcurrencyManager is used to handle concurrency for objects.
getOIDsImpl($type, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
getOIDs($type, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
validateValues(Message $message)
Validate all values by calling PersistentObject::validateValue() Throws a ValidationException in case...
PagingInfo contains information about a paged list.
Definition: PagingInfo.php:18
loadObjects($type, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
Message is used to get localized messages to be used in the user interface.
Definition: Message.php:23
AbstractMapper provides a basic implementation for other mapper classes.
PersistentEvent signals create/update/delete operations on a persistent entity.
containsDummyIds()
Check if this object id contains a dummy id.
Definition: ObjectId.php:236
setLogStrategy(OutputStrategy $logStrategy)
Set the OutputStrategy used for logging persistence actions.
PermissionManager implementations are used to handle all authorization requests.
createImpl($type, $buildDepth=BuildDepth::SINGLE)
create($type, $buildDepth=BuildDepth::SINGLE)
static isValid($oid)
Check if a serialized ObjectId has a valid syntax, the type is known and if the number of primary key...
Definition: ObjectId.php:132
getAttributes(array $tags=array(), $matchMode='all')
PersistentObject values may be tagged with application specific tags.
afterInsert()
This method is called once after inserting the newly created object into the store.
PersistenceFacade defines the interface for PersistenceFacade implementations.
checkAuthorization($resource, $action)
Check authorization on a resource (type/instance/instance property) and a given action.
beforeInsert()
This method is called once before inserting the newly created object into the store.
loadRelation(array $objects, $role, $buildDepth=BuildDepth::SINGLE, $criteria=null, $orderby=null, PagingInfo $pagingInfo=null)
getState()
Get the object's state:
getChangedValues()
Get the list of changed attributes since creation, loading.
static getStackTrace()
Get the stack trace.
PersistentObject defines the interface of all persistent objects.