[+FEATURE] Extbase (Persistence): Implemented support for mm_opposite_field now....
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Backend.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
6 * All rights reserved
7 *
8 * This class is a backport of the corresponding class of FLOW3.
9 * All credits go to the v5 team.
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * A persistence backend. This backend maps objects to the relational model of the storage backend.
30 * It persists all added, removed and changed objects.
31 *
32 * @package Extbase
33 * @subpackage Persistence
34 * @version $Id: Backend.php 2183 2009-04-24 14:28:37Z k-fish $
35 */
36 class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendInterface, t3lib_Singleton {
37
38 /**
39 * @var Tx_Extbase_Persistence_Session
40 */
41 protected $session;
42
43 /**
44 * @var Tx_Extbase_Persistence_ObjectStorage
45 */
46 protected $aggregateRootObjects;
47
48 /**
49 * @var Tx_Extbase_Persistence_IdentityMap
50 **/
51 protected $identityMap;
52
53 /**
54 * @var Tx_Extbase_Persistence_QOM_QueryObjectModelFactoryInterface
55 */
56 protected $QOMFactory;
57
58 /**
59 * @var Tx_Extbase_Persistence_ValueFactoryInterface
60 */
61 protected $valueFactory;
62
63 /**
64 * @var Tx_Extbase_Persistence_Storage_BackendInterface
65 */
66 protected $storageBackend;
67
68 /**
69 * @var Tx_Extbase_Persistence_DataMapper
70 */
71 protected $dataMapper;
72
73 /**
74 * The TYPO3 reference index object
75 *
76 * @var t3lib_refindex
77 **/
78 protected $referenceIndex;
79
80 /**
81 * Storage page ID used to store records. Set by the Dispatcher.
82 * @var integer
83 */
84 protected $storagePageId;
85
86 /**
87 * Constructs the backend
88 *
89 * @param Tx_Extbase_Persistence_Session $session The persistence session used to persist data
90 */
91 public function __construct(Tx_Extbase_Persistence_Session $session, Tx_Extbase_Persistence_Storage_BackendInterface $storageBackend) {
92 $this->session = $session;
93 $this->storageBackend = $storageBackend;
94 $this->referenceIndex = t3lib_div::makeInstance('t3lib_refindex');
95 $this->aggregateRootObjects = new Tx_Extbase_Persistence_ObjectStorage();
96 $this->persistenceBackend = $GLOBALS['TYPO3_DB']; // FIXME This is just an intermediate solution
97 $extbaseSettings = Tx_Extbase_Dispatcher::getSettings();
98 $this->storagePageId = $extbaseSettings['storagePid'];
99 }
100
101 /**
102 * Injects the DataMapper to map nodes to objects
103 *
104 * @param Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper
105 * @return void
106 */
107 public function injectDataMapper(Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper) {
108 $this->dataMapper = $dataMapper;
109 }
110
111 /**
112 * Injects the identity map
113 *
114 * @param Tx_Extbase_Persistence_IdentityMap $identityMap
115 * @return void
116 * @internal
117 */
118 public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap $identityMap) {
119 $this->identityMap = $identityMap;
120 }
121
122 /**
123 * Injects the QueryObjectModelFactory
124 *
125 * @param Tx_Extbase_Persistence_QOM_QueryObjectModelFactoryInterface $dataMapper
126 * @return void
127 */
128 public function injectQOMFactory(Tx_Extbase_Persistence_QOM_QueryObjectModelFactoryInterface $QOMFactory) {
129 $this->QOMFactory = $QOMFactory;
130 }
131
132 /**
133 * Injects the ValueFactory
134 *
135 * @param Tx_Extbase_Persistence_ValueFactoryInterface $valueFactory
136 * @return void
137 */
138 public function injectValueFactory(Tx_Extbase_Persistence_ValueFactoryInterface $valueFactory) {
139 $this->valueFactory = $valueFactory;
140 }
141
142 /**
143 * Returns the repository session
144 *
145 * @return Tx_Extbase_Persistence_Session
146 */
147 public function getSession() {
148 return $this->session;
149 }
150
151 /**
152 * Returns the current QOM factory
153 *
154 * @return Tx_Extbase_Persistence_QOM_QueryObjectModelFactoryInterface
155 * @internal
156 */
157 public function getQOMFactory() {
158 return $this->QOMFactory;
159 }
160
161 /**
162 * Returns the current value factory
163 *
164 * @return Tx_Extbase_Persistence_ValueFactoryInterface
165 * @internal
166 */
167 public function getValueFactory() {
168 return $this->valueFactory;
169 }
170
171 /**
172 * Returns the current identityMap
173 *
174 * @return Tx_Extbase_Persistence_IdentityMap
175 * @internal
176 */
177 public function getIdentityMap() {
178 return $this->identityMap;
179 }
180
181 /**
182 * Returns the (internal) identifier for the object, if it is known to the
183 * backend. Otherwise NULL is returned.
184 *
185 * @param object $object
186 * @return string The identifier for the object if it is known, or NULL
187 */
188 public function getUidByObject($object) {
189 if ($this->identityMap->hasObject($object)) {
190 return $this->identityMap->getUidByObject($object);
191 } else {
192 return NULL;
193 }
194 }
195
196 /**
197 * Checks if the given object has ever been persisted.
198 *
199 * @param object $object The object to check
200 * @return boolean TRUE if the object is new, FALSE if the object exists in the repository
201 */
202 public function isNewObject($object) {
203 return ($this->getUidByObject($object) === NULL);
204 }
205
206 /**
207 * Replaces the given object by the second object.
208 *
209 * This method will unregister the existing object at the identity map and
210 * register the new object instead. The existing object must therefore
211 * already be registered at the identity map which is the case for all
212 * reconstituted objects.
213 *
214 * The new object will be identified by the uid which formerly belonged
215 * to the existing object. The existing object looses its uid.
216 *
217 * @param object $existingObject The existing object
218 * @param object $newObject The new object
219 * @return void
220 */
221 public function replaceObject($existingObject, $newObject) {
222 $existingUid = $this->getUidByObject($existingObject);
223 if ($existingUid === NULL) throw new Tx_Extbase_Persistence_Exception_UnknownObject('The given object is unknown to this persistence backend.', 1238070163);
224
225 $this->identityMap->unregisterObject($existingObject);
226 $this->identityMap->registerObject($newObject, $existingUid);
227 }
228
229 /**
230 * Sets the aggregate root objects
231 *
232 * @param Tx_Extbase_Persistence_ObjectStorage $objects
233 * @return void
234 */
235 public function setAggregateRootObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
236 $this->aggregateRootObjects = $objects;
237 }
238
239 /**
240 * Sets the deleted objects
241 *
242 * @param Tx_Extbase_Persistence_ObjectStorage $objects
243 * @return void
244 */
245 public function setDeletedObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
246 $this->deletedObjects = $objects;
247 }
248
249 /**
250 * Commits the current persistence session.
251 *
252 * @return void
253 */
254 public function commit() {
255 $this->persistObjects();
256 $this->processDeletedObjects();
257 }
258
259 /**
260 * Traverse and persist all aggregate roots and their object graph.
261 *
262 * @return void
263 */
264 protected function persistObjects() {
265 foreach ($this->aggregateRootObjects as $object) {
266 $this->persistObject($object);
267 }
268 }
269
270 /**
271 * Inserts an objects corresponding row into the database. If the object is a value object an
272 * existing instance will be looked up.
273 *
274 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be inserted
275 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
276 * @param string $parentPropertyName The name of the property the object is stored in
277 * @return void
278 */
279 protected function persistObject($object, $parentObject = NULL, $parentPropertyName = NULL, $processQueue = TRUE) {
280 $row = array();
281 $queue = array();
282 $className = get_class($object);
283 $dataMap = $this->dataMapper->getDataMap($className);
284 $properties = $object->_getProperties();
285
286 if ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject) {
287 $this->checkForAlreadyPersistedValueObject($object);
288 }
289
290 // Fill up $row[$columnName] array with changed values which need to be stored
291 foreach ($properties as $propertyName => $propertyValue) {
292 if (!$dataMap->isPersistableProperty($propertyName) || ($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy)) {
293 continue;
294 }
295
296 $columnMap = $dataMap->getColumnMap($propertyName);
297 $columnName = $columnMap->getColumnName();
298 if ($object->_isNew() || $object->_isDirty($propertyName)) {
299 if ($columnMap->isRelation()) {
300 if (($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) || ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY)) {
301 $row[$columnName] = count($properties[$propertyName]);
302 foreach ($propertyValue as $containedObject) {
303 $queue[] = array($propertyName => $containedObject);
304 }
305 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
306 // TODO Handle Value Objects different
307 // SK: this is the case RELATION_HAS_ONE, correct?
308 if ($propertyValue->_isNew() || $propertyValue->_isDirty()) {
309 // SK: What happens if the value is not new, but changed?
310 $this->persistObject($propertyValue);
311 }
312 $row[$columnName] = $propertyValue->getUid();
313 }
314 } else {
315 // Not an relation, this means it is a simple type such as STRING or Integer
316 $row[$columnName] = $dataMap->convertPropertyValueToFieldValue($properties[$propertyName]);
317 }
318 }
319 } // end property iteration for loop
320
321 if ($object->_isNew()) {
322 $this->insertObject($object, $parentObject, $parentPropertyName, $row);
323 } elseif ($object->_isDirty()) {
324 $this->updateObject($object, $parentObject, $parentPropertyName, $row);
325 }
326
327 // SK: I need to check the code below more thoroughly
328 if ($parentObject instanceof Tx_Extbase_DomainObject_DomainObjectInterface && !empty($parentPropertyName)) {
329 $parentClassName = get_class($parentObject);
330 $parentDataMap = $this->dataMapper->getDataMap($parentClassName);
331 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
332
333 if (($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY)) {
334 $this->insertRelation($object, $parentObject, $parentPropertyName);
335 }
336 }
337
338 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
339 $object->_memorizeCleanState();
340 }
341
342 // SK: Where does $queue come from? Do we need the code below?
343 if ($processQueue === TRUE) {
344 foreach ($queue as $queuedObjects) {
345 foreach($queuedObjects as $propertyName => $queuedObject) {
346 $this->persistObject($queuedObject, $object, $propertyName);
347 }
348 }
349 }
350
351 }
352
353 /*
354 * Tests, if the given Domain Object already exists in the storage backend
355 *
356 * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
357 */
358 protected function checkForAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
359 $dataMap = $this->dataMapper->getDataMap(get_class($object));
360 $properties = $object->_getProperties();
361 $result = $this->storageBackend->hasValueObject($properties, $dataMap);
362 if ($result !== FALSE) {
363 $object->_setProperty('uid', $result);
364 }
365 }
366
367 /**
368 * Inserts an object in the storage
369 *
370 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
371 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
372 * @param string|NULL $parentPropertyName The name of the property
373 * @param array $row The $row
374 */
375 protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject = NULL, $parentPropertyName = NULL, array &$row) {
376 $className = get_class($object);
377 $dataMap = $this->dataMapper->getDataMap($className);
378 $tableName = $dataMap->getTableName();
379 $this->addCommonFieldsToRow($object, $parentObject, $parentPropertyName, $row);
380 $uid = $this->storageBackend->addRow(
381 $tableName,
382 $row
383 );
384 $object->_setProperty('uid', $uid);
385 $this->referenceIndex->updateRefIndexTable($tableName, $uid);
386 }
387
388 /**
389 * Inserts mm-relation into a relation table
390 *
391 * @param Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject The related object
392 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
393 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
394 * @return void
395 */
396 protected function insertRelation(Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
397 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
398 $columnMap = $dataMap->getColumnMap($parentPropertyName);
399 $row = array(
400 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
401 $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid(),
402 'tablenames' => $dataMap->getTableName(),
403 'sorting' => 9999 // TODO sorting of mm table items
404 );
405 $res = $this->storageBackend->addRow(
406 $columnMap->getRelationTableName(),
407 $row,
408 TRUE);
409 return $res;
410 }
411
412 /**
413 * Updates a given object in the storage
414 *
415 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
416 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
417 * @param string|NULL $parentPropertyName The name of the property
418 * @param array $row The $row
419 */
420 protected function updateObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, array &$row) {
421 $className = get_class($object);
422 $dataMap = $this->dataMapper->getDataMap($className);
423 $tableName = $dataMap->getTableName();
424 $this->addCommonFieldsToRow($object, $parentObject, $parentPropertyName, $row);
425 $uid = $object->getUid();
426 $row['uid'] = $uid;
427 $res = $this->storageBackend->updateRow(
428 $tableName,
429 $row
430 );
431 $this->referenceIndex->updateRefIndexTable($tableName, $uid);
432 return $res;
433 }
434
435 /**
436 * Returns a table row to be inserted or updated in the database
437 *
438 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The appropriate data map representing a database table
439 * @param array $properties The properties of the object
440 * @return array A single row to be inserted in the database
441 */
442 // TODO Should we pass an extra flag (UPDATE/INSERT)?
443 protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, array &$row) {
444 $className = get_class($object);
445 $dataMap = $this->dataMapper->getDataMap($className);
446 if ($dataMap->hasCreationDateColumn() && $object->_isNew()) {
447 $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
448 }
449 if ($dataMap->hasTimestampColumn()) {
450 $row[$dataMap->getTimestampColumnName()] = $GLOBALS['EXEC_TIME'];
451 }
452 if ($dataMap->hasPidColumn() && !isset($row['pid'])) {
453 $row['pid'] = $this->storagePageId;
454 }
455 if ($parentObject instanceof Tx_Extbase_DomainObject_DomainObjectInterface && !empty($parentPropertyName)) {
456 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
457 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
458 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
459 if ($parentKeyFieldName !== NULL) {
460 $row[$parentKeyFieldName] = $parentObject->getUid();
461 }
462 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
463 if ($parentTableFieldName !== NULL) {
464 $row[$parentTableFieldName] = $parentDataMap->getTableName();
465 }
466 }
467 }
468
469 /**
470 * Inserts and updates all relations of an object. It also inserts and updates data in relation tables.
471 *
472 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object for which the relations should be updated
473 * @param string $propertyName The name of the property holding the related child objects
474 * @param array $relations The queued relations
475 * @return void
476 */
477 protected function persistRelations(Tx_Extbase_DomainObject_DomainObjectInterface $object, $propertyName, array $relations) {
478 $dataMap = $this->dataMapper->getDataMap(get_class($object));
479 foreach ($relations as $propertyName => $relatedObjects) {
480 if (!empty($relatedObjects)) {
481 $typeOfRelation = $dataMap->getColumnMap($propertyName)->getTypeOfRelation();
482 foreach ($relatedObjects as $relatedObject) {
483 if ($relatedObject->_isNew()) {
484 $this->persistObject($relatedObject, $object, $propertyName);
485 if ($typeOfRelation === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
486 $this->insertRelationInRelationTable($relatedObject, $object, $propertyName);
487 }
488 } elseif ($relatedObject->_isDirty()) {
489 $this->persistObject($relatedObject, $object, $propertyName);
490 }
491 }
492 }
493 }
494 }
495
496 /**
497 * Iterate over deleted objects and process them
498 *
499 * @return void
500 */
501 protected function processDeletedObjects() {
502 foreach ($this->deletedObjects as $object) {
503 $this->deleteObject($object);
504 if ($this->identityMap->hasObject($object)) {
505 $this->session->registerRemovedObject($object);
506 $this->identityMap->unregisterObject($object);
507 }
508 }
509 $this->deletedObjects = new Tx_Extbase_Persistence_ObjectStorage();
510 }
511
512 /**
513 * Deletes an object, it's 1:n related objects, and the m:n relations in relation tables (but not the m:n related objects!)
514 *
515 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
516 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
517 * @param string|NULL $parentPropertyName The name of the property
518 * @param bool $markAsDeleted Shold we only mark the row as deleted instead of deleting (TRUE by default)?
519 * @param bool $recurseIntoRelations Shold we delete also dependant aggregates (FALSE by default)?
520 * @return void
521 */
522 protected function deleteObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, $markAsDeleted = TRUE, $recurseIntoRelations = FALSE) {
523 // TODO Implement recursive deletions
524 $dataMap = $this->dataMapper->getDataMap(get_class($object));
525 $tableName = $dataMap->getTableName();
526 if ($markAsDeleted === TRUE && $dataMap->hasDeletedColumn()) {
527 $deletedColumnName = $dataMap->getDeletedColumnName();
528 $res = $this->storageBackend->updateRow(
529 $tableName,
530 array(
531 'uid' => $object->getUid(),
532 $deletedColumnName => 1
533 )
534 );
535 } else {
536 $res = $this->storageBackend->removeRow(
537 $tableName,
538 $object->getUid()
539 );
540 }
541 $this->referenceIndex->updateRefIndexTable($tableName, $uid);
542 }
543
544 /**
545 * Deletes all relations of an object.
546 *
547 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object for which the relations should be updated
548 * @param string $propertyName The name of the property holding the related child objects
549 * @param array $relations The queued relations
550 * @return void
551 */
552 // SK: The below method is never called.
553 // SK: I think there is still a problem with deleted objects and deleted relations.
554 // SK: I am not yet sure where deleted relations ae handled. Need to check more thoroughly!
555 protected function deleteRelatedObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object, array $relations) {
556 $dataMap = $this->dataMapper->getDataMap(get_class($object));
557 foreach ($relations as $propertyName => $relatedObjects) {
558 if (is_array($relatedObjects)) {
559 foreach ($relatedObjects as $relatedObject) {
560 $this->deleteObject($relatedObject, $object, $propertyName);
561 if ($dataMap->getColumnMap($propertyName)->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
562 // SK: This method does IMHO not exist.
563 $this->deleteRelationInRelationTable($relatedObject, $object, $propertyName);
564 }
565 }
566 }
567 }
568 }
569
570 /**
571 * Update relations in a relation table
572 *
573 * @param array $relatedObjects An array of related objects
574 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
575 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
576 * @return void
577 */
578 protected function deleteRelationInRelationTable($relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
579 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
580 $columnMap = $dataMap->getColumnMap($parentPropertyName);
581 // TODO Remove dependency to the t3lib_db instance
582 $res = $this->persistenceBackend->exec_SELECTquery(
583 $columnMap->getChildKeyFieldName(),
584 $tableName,
585 $columnMap->getParentKeyFieldName() . $parentObject->getUid()
586 );
587 $existingRelations = array();
588 while($row = $this->persistenceBackend->sql_fetch_assoc($res)) {
589 $existingRelations[current($row)] = current($row);
590 }
591 $relationsToDelete = $existingRelations;
592 if (is_array($relatedObject)) {
593 foreach ($relatedObject as $relatedObject) {
594 $relatedObjectUid = $relatedObject->getUid();
595 if (array_key_exists($relatedObjectUid, $relationsToDelete)) {
596 unset($relationsToDelete[$relatedObjectUid]);
597 }
598 }
599 }
600 if (count($relationsToDelete) > 0) {
601 $relationsToDeleteList = implode(',', $relationsToDelete);
602 $res = $this->persistenceBackend->exec_DELETEquery(
603 $columnMap->getRelationTableName(),
604 $columnMap->getParentKeyFieldName() . '=' . $parentObject->getUid() . ' AND ' . $columnMap->getChildKeyFieldName() . ' IN (' . $relationsToDeleteList . ')'
605 );
606 }
607 }
608
609 /**
610 * Delegates the call to the Data Map.
611 * Returns TRUE if the property is persistable (configured in $TCA)
612 *
613 * @param string $className The property name
614 * @param string $propertyName The property name
615 * @return boolean TRUE if the property is persistable (configured in $TCA)
616 */
617 public function isPersistableProperty($className, $propertyName) {
618 $dataMap = $this->dataMapper->getDataMap($className);
619 return $dataMap->isPersistableProperty($propertyName);
620 }
621
622 }
623
624 ?>