http://phing.info/

Source Code Coverage

Designed for use with PHPUnit2, Xdebug and Phing.

Methods: 22 LOC: 625 Statements: 197

Source file Statements Methods Total coverage
Abstract.php 95.4% 95.5% 95.4%
   
1
<?php
2
/**
3
 * Xyster Framework
4
 *
5
 * LICENSE
6
 *
7
 * This source file is subject to the new BSD license that is bundled
8
 * with this package in the file LICENSE.txt.
9
 * It is also available through the world-wide-web at this URL:
10
 * http://www.opensource.org/licenses/bsd-license.php
11
 * If you did not receive a copy of the license and are unable to
12
 * obtain it through the world-wide-web, please send an email
13
 * to xyster@devweblog.org so we can send you a copy immediately.
14
 *
15
 * @category  Xyster
16
 * @package   Xyster_Orm
17
 * @copyright Copyright (c) 2007 Irrational Logic (http://devweblog.org)
18
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
19
 * @version   $Id: Abstract.php 125 2007-10-16 22:02:45Z doublecompile $
20
 */
21
/**
22
 * @see Xyster_Orm_Loader
23
 */
24 1
require_once 'Xyster/Orm/Loader.php';
25
/**
26
 * @see Xyster_Orm_Mapper_Interface
27
 */
28 1
require_once 'Xyster/Orm/Mapper/Interface.php';
29
/**
30
 * @see Xyster_Orm_Entity
31
 */
32 1
require_once 'Xyster/Orm/Entity.php';
33
/**
34
 * @see Xyster_Orm_Entity_Meta
35
 */
36 1
require_once 'Xyster/Orm/Entity/Meta.php';
37
/**
38
 * An abstract implementation of the mapper interface
39
 *
40
 * This class allows for a more simple implementation of the mapper interface,
41
 * taking care of common logic.
42
 *
43
 * @category  Xyster
44
 * @package   Xyster_Orm
45
 * @copyright Copyright (c) 2007 Irrational Logic (http://devweblog.org)
46
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
47
 */
48
abstract class Xyster_Orm_Mapper_Abstract implements Xyster_Orm_Mapper_Interface
49
{
50
    /**
51
     * The domain associated with the entity
52
     *
53
     * @var string
54
     */
55
    protected $_domain = "";
56
57
    /**
58
     * The factory that created this mapper
59
     *
60
     * @var Xyster_Orm_Mapper_Factory_Interface
61
     */
62
    protected $_factory;
63
64
    /**
65
     * An array of properties used to index the entity by value
66
     *
67
     * The array consists of index names as keys and arrays of the columns
68
     * contained within as values.
69
     *
70
     * <code>array(
71
     *     'name_index' => array( 'name' ),
72
     *  'multi_index' => array( 'transactionNumber', 'transactionDate' )
73
     * );</code>
74
     *
75
     * @var array
76
     */
77
    protected $_index = array();
78
79
    /**
80
     * The period of time entities should persist in the secondary cache
81
     *
82
     * A value of -1 means the entity shouldn't be added to secondary cache. A
83
     * value of 0 means the entity should be stored indefinitely.
84
     *
85
     * @var int
86
     */
87
    protected $_lifetime = 60;
88
89
    /**
90
     * The class meta data
91
     *
92
     * @var Xyster_Orm_Entity_Meta
93
     */
94
    private $_meta;
95
96
    /**
97
     * Any additional options
98
     *
99
     * <dl>
100
     * <dt>metadataCache</dt><dd>The name of the Zend_Registry key to find a
101
     * Zend_Cache_Core object for caching metadata information.  If not
102
     * specified, the mapper will use the defaultMetadataCache.</dd>
103
     * <dt>doNotRefreshAfterSave</dt><dd>This will cause the mapper not to
104
     * refresh the entity after it's inserted or updated.</dd>
105
     * </dl>
106
     *
107
     * @var array
108
     */
109
    protected $_options = array();
110
111
    /**
112
     * The name of the table, defaults to entity name
113
     *
114
     * @var string
115
     */
116
    protected $_table = "";
117
118
    /**
119
     * Creates a new mapper
120
     *
121
     * Class authors can overwrite this, but <em>be sure to call the parent</em>
122
     *
123
     * @param Xyster_Orm_Mapper_Factory_Interface $factory
124
     */
125
    public function __construct( Xyster_Orm_Mapper_Factory_Interface $factory )
126
    {
127 221
        $this->_factory = $factory;
128
129 221
        $this->getEntityMeta(); // to assign the meta data to the entity class
130 221
        $this->getSet(); // to make sure the class is defined
131
    }
132
133
    /**
134
     * Allows for subclassing without overwriting constructor
135
     *
136
     * The mapper factory calls this method.  This is necessary because the init
137
     * method should contain the setup of relations, which might depend on the
138
     * mapper that's still being instantiated in the factory.
139
     */
140
    public function init()
141
    {
142
    }
143
144
    /**
145
     * Deletes an entity
146
     *
147
     * @param Xyster_Orm_Entity $entity The entity to delete
148
     */
149
    public function delete( Xyster_Orm_Entity $entity )
150
    {
151 7
        $this->_assertThisEntityName($entity);
152
153 7
        $manager = $this->_factory->getManager();
154 7
        $broker = $manager->getPluginBroker();
155 7
        $broker->preDelete($entity);
156 7
        $this->_delete($entity->getPrimaryKeyAsCriterion());
157 7
        $broker->postDelete($entity);
158
159 7
        $relations = $this->getEntityMeta()->getRelations();
160 7
        foreach( $relations as $relation ) { /* @var $relation Xyster_Orm_Relation */
161 6
            if ( $relation->getType() == 'many' ) {
162 2
                $onDelete = $relation->getOnDelete();
163 2
                $map = $this->_factory->get($relation->getTo());
164 2
                $name = $relation->getName();
165 2
                $reverseName = $relation->hasBelongsTo() ?
166 2
                    $relation->getReverse()->getName() : null;
167 2
                $related = $entity->$name; /* @var $related Xyster_Orm_Set */
168
169
                // just remove the association
170 2
                if ( $onDelete == Xyster_Orm_Relation::ACTION_SET_NULL && $reverseName ) {
171 1
                    foreach( $related as $relatedEntity ) {
172 1
                        $relatedEntity->$reverseName = null;
173 1
                        $map->save($relatedEntity);
174 1
                    }
175
                // rely on the database to cascade the delete
176 2
                } else if ( $onDelete == Xyster_Orm_Relation::ACTION_CASCADE ) {
177 1
                    $manager->getRepository()->removeAll($related);
178
                // we have to delete every last one ourselves
179 2
                } else if ( $onDelete == Xyster_Orm_Relation::ACTION_REMOVE ) {
180 1
                    $manager->getRepository()->removeAll($related);
181 1
                    foreach( $related as $relatedEntity ) {
182 1
                        $map->delete($relatedEntity);
183 1
                    }
184 1
                }
185 2
            }
186 6
        }
187
    }
188
189
    /**
190
     * Gets an entity with the supplied identifier
191
     *
192
     * @param mixed $id  The id of the entity to get
193
     * @return Xyster_Orm_Entity  The data entity found, or null if none
194
     */
195
    final public function get( $id )
196
    {
197 35
        $keyNames = $this->getEntityMeta()->getPrimary();
198 35
        $keyValues = array();
199
200 35
	    if ( count($keyNames) > 1 ) {
201
202 5
    	    $this->_checkPrimaryKey($id);
203 3
    	    $keyValues = $id;
204
205 33
	    } else if ( is_array($id) ) {
206
207 19
	        $keyValues = array( current($keyNames) => current($id) );
208
209 19
	    } else {
210
211 11
	        $keyValues = array( $keyNames[0] => $id );
212
213
	    }
214
215 33
	    return $this->find($keyValues);
216
    }
217
218
    /**
219
     * Gets the name of the domain to which this mapper belongs
220
     *
221
     * @return string  The domain
222
     */
223
    final public function getDomain()
224
    {
225 40
        return $this->_domain;
226
    }
227
228
    /**
229
     * Gets the entity metadata
230
     *
231
     * @return Xyster_Orm_Entity_Meta
232
     */
233
    final public function getEntityMeta()
234
    {
235 221
        if ( !$this->_meta ) {
236 221
            $this->_meta = new Xyster_Orm_Entity_Meta($this);
237 221
            Xyster_Orm_Entity::setMeta($this->_meta);
238 221
        }
239 221
        return $this->_meta;
240
    }
241
242
    /**
243
     * Gets the class name of the entity
244
     *
245
     * Class authors should overwrite this method if their entity name isn't
246
     * the same as the mapper name.
247
     *
248
     * @return string  The class name of the entity
249
     */
250
    public function getEntityName()
251
    {
252 221
        return substr(get_class($this),0,-6);
253
    }
254
255
    /**
256
     * Gets the factory that created this mapper
257
     *
258
     * @return Xyster_Orm_Mapper_Factory_Interface
259
     */
260
    public function getFactory()
261
    {
262 221
        return $this->_factory;
263
    }
264
265
    /**
266
     * Gets the columns that should be used to index the entity
267
     *
268
     * The array consists of index names as keys and arrays of the columns
269
     * contained within as values.
270
     *
271
     * @return array
272
     */
273
    final public function getIndex()
274
    {
275 50
        return $this->_index;
276
    }
277
278
	/**
279
	 * Gets the time in seconds an entity should be cached
280
	 *
281
	 * @return int
282
	 */
283
	final public function getLifetime()
284
	{
285 34
		return $this->_lifetime;
286
	}
287
288
    /**
289
     * Gets the value of an option
290
     *
291
     * @param string $name The name of the option
292
     * @return mixed The option value
293
     */
294
    final public function getOption( $name )
295
    {
296 15
        return array_key_exists($name, $this->_options) ?
297 15
            $this->_options[$name] : null;
298
    }
299
300
    /**
301
     * Gets the options for this mapper
302
     *
303
     * @return array
304
     */
305
    final public function getOptions()
306
    {
307 1
        return $this->_options;
308
    }
309
310
    /**
311
     * Gets an empty entity set for the mapper's entity type
312
     *
313
     * @return Xyster_Orm_Set An empty set
314
     */
315
    public function getSet( Xyster_Collection_Interface $entities = null )
316
    {
317 221
        $set = Xyster_Orm_Loader::loadSetClass($this->getEntityName());
318
319 221
        return new $set($entities);
320
    }
321
322
    /**
323
     * Gets the table from which an entity comes
324
     *
325
     * It is up to the Xyster_Orm_Backend to do something with this value.
326
     *
327
     * @return string The table name
328
     */
329
    public function getTable()
330
    {
331 26
        if ( !$this->_table ) {
332 1
            require_once 'Xyster/String.php';
333 1
            $this->_table = Xyster_String::toUnderscores($this->getEntityName());
334 1
        }
335 26
        return $this->_table;
336
    }
337
338
    /**
339
     * Saves an entity (insert or update)
340
     *
341
     * @param Xyster_Orm_Entity $entity  The entity to save
342
     */
343
    public function save( Xyster_Orm_Entity $entity )
344
    {
345 14
        $this->_assertThisEntityName($entity);
346
347
        /*
348
		 * Step 1: Sets ids for any single-entity relationships
349
		 */
350 14
		foreach( $this->getEntityMeta()->getRelations() as $k=>$v ) {
351
		    /* @var $v Xyster_Orm_Relation */
352 11
			if ( !$v->isCollection() && $entity->isLoaded($k) && $entity->$k !== null ) {
353 4
				$linked = $entity->$k; /* @var $linked Xyster_Orm_Entity */
354
				// get the original primary key, in case it's not auto-generated
355 4
				$key = $linked->getPrimaryKey(true);
356 4
				if ( !$linked->getBase() ) {
357 2
					$this->_factory->get($v->getTo())->save($linked);
358 2
					$key = $linked->getPrimaryKey();
359 4
				} else if ( $key != $linked->getPrimaryKey() ) {
360 1
				    $key = $linked->getPrimaryKey();
361 1
				}
362 4
				$keyNames = array_keys($key);
363 4
				$foreignKey = $v->getId();
364 4
				for( $i=0; $i<count($key); $i++ ) {
365 4
				    $field = $foreignKey[$i];
366 4
				    $keyName = $keyNames[$i];
367 4
				    $entity->$field = $linked->$keyName;
368 4
				}
369 4
			}
370 11
		}
371
372 14
		$broker = $this->_factory->getManager()->getPluginBroker();
373 14
		$updatedKey = false;
374
		/*
375
		 * Step 2: Save actual entity
376
		 */
377 14
        if ( !$entity->getBase() ) {
378 7
            $broker->preInsert($entity);
379 7
            $this->_insert($entity);
380 7
            $broker->postInsert($entity);
381
382 7
        } else {
383 11
            $updatedKey = $entity->getPrimaryKey() != $entity->getPrimaryKey(true);
384 11
            $broker->preUpdate($entity);
385 11
            $this->_update($entity);
386 11
            $broker->postUpdate($entity);
387
        }
388
389
        // this is in case any triggers in the db, etc. have changed the record
390 14
        if ( !$this->getOption('doNotRefreshAfterSave') ) {
391 12
    	   $this->refresh($entity);
392 12
        }
393
394
        /*
395
		 * Step 3: work with many and joined relationships
396
		 */
397 14
		foreach( $this->getEntityMeta()->getRelations() as $k=>$relation ) {
398
		    /* @var $relation Xyster_Orm_Relation */
399 11
            if ( $relation->isCollection() && ( $updatedKey
400 11
			    || $entity->isLoaded($k) ) ) {
401
402 6
				$set = $entity->$k;
403 6
				$cascadeUpdate = $updatedKey &&
404 6
				    $relation->getOnUpdate() == Xyster_Orm_Relation::ACTION_CASCADE;
405
406 6
				$added = $set->getDiffAdded();
407 6
				$removed = $set->getDiffRemoved();
408 6
				if ( !$added && !$removed && !$cascadeUpdate ) {
409 1
					continue;
410 0
				}
411
412 5
				if ( $relation->getType() == 'joined' ) {
413
414 3
				    $this->_joinedInsert($set);
415 3
				    $this->_joinedDelete($set);
416
417 5
				} else if ( $relation->getType() == 'many' ) {
418
419 2
    				$map = $this->_factory->get($relation->getTo());
420 2
    				if ( $cascadeUpdate ) {
421
    				    // if we should cascade changed primary keys
422 1
    				    foreach( $set as $setEntity ) {
423 1
    				        $map->save($setEntity);
424 1
    				    }
425 1
       				} else {
426
    				    // no cascade, just save newly added to set
427 1
    				    array_walk($added, array($map, 'save'));
428
    				}
429 2
    				array_walk($removed, array($map, 'delete'));
430
431 2
				}
432
433 5
				$set->baseline();
434 5
			}
435 11
		}
436
    }
437
438
    /**
439
     *
440
     * @param string $field
441
     * @return string
442
     */
443
    public function translateField( $field )
444
    {
445 221
        require_once 'Xyster/String.php';
446 221
        return Xyster_String::toCamel($field);
447
    }
448
449
    /**
450
     *
451
     * @param string $field
452
     * @return string
453
     */
454
    public function untranslateField( $field )
455
    {
456 202
        require_once 'Xyster/String.php';
457 202
        return Xyster_String::toUnderscores($field);
458
    }
459
460
    /**
461
     * Removes entities from the backend
462
     *
463
     * @param Xyster_Data_Criterion $where  The criteria on which to remove entities
464
     */
465
    abstract protected function _delete( Xyster_Data_Criterion $where );
466
467
    /**
468
     * Saves a new entity into the backend
469
     *
470
     * @param Xyster_Orm_Entity $entity  The entity to insert
471
     * @return mixed  The new primary key
472
     */
473
    abstract protected function _insert( Xyster_Orm_Entity $entity );
474
475
    /**
476
     * Adds the entity to the many-to-many join
477
     *
478
     * @param Xyster_Orm_Set $set
479
     */
480
    abstract protected function _joinedInsert( Xyster_Orm_Set $set );
481
482
    /**
483
     * Removes the entity from the many-to-many join
484
     *
485
     * @param Xyster_Orm_Set $set
486
     */
487
    abstract protected function _joinedDelete( Xyster_Orm_Set $set );
488
489
    /**
490
     * Updates the values of an entity in the backend
491
     *
492
     * @param Xyster_Orm_Entity $entity  The entity to update
493
     */
494
    abstract protected function _update( Xyster_Orm_Entity $entity );
495
496
    /**
497
     * A convenience method to assert entity type
498
     *
499
     * @param Xyster_Orm_Entity $entity
500
     * @throws Xyster_Orm_Mapper_Exception if the entity supplied is of the wrong type
501
     */
502
    final protected function _assertThisEntityName( Xyster_Orm_Entity $entity )
503
    {
504 17
        $name = $this->getEntityName();
505 17
        if ( ! $entity instanceof $name ) {
506 0
            require_once 'Xyster/Orm/Mapper/Exception.php';
507 0
            throw new Xyster_Orm_Mapper_Exception('This mapper only accepts entities of type ' . $name);
508 0
        }
509
    }
510
511
    /**
512
     * Ensures the parameter passed is a Criterion
513
     *
514
     * @param Xyster_Data_Criterion|array $criteria
515
     * @return Xyster_Data_Criterion
516
     */
517
    protected function _buildCriteria( $criteria )
518
    {
519 48
        if ( !is_array($criteria) &&
520 10
            ! $criteria instanceof Xyster_Data_Criterion &&
521 48
            $criteria !== null ) {
522 2
            require_once 'Xyster/Orm/Mapper/Exception.php';
523 2
            throw new Xyster_Orm_Mapper_Exception('Invalid criteria: ' . gettype($criteria));
524 0
        }
525
526 46
        $_criteria = null;
527
528 46
        if ( is_array($criteria) ) {
529 45
            $this->_checkPropertyNames($criteria);
530 44
            foreach( $criteria as $name => $value ) {
531 44
                require_once 'Xyster/Data/Expression.php';
532 44
                $thiskey = Xyster_Data_Expression::eq($name,$value);
533 44
                if ( !$_criteria ) {
534 44
                    $_criteria = $thiskey;
535 44
                } else if ( $_criteria instanceof Xyster_Data_Expression ) {
536 4
                    require_once 'Xyster/Data/Junction.php';
537 4
                    $_criteria = Xyster_Data_Junction::all( $_criteria, $thiskey );
538 4
                } else if ( $_criteria instanceof Xyster_Data_Junction ) {
539 4
                    $_criteria->add($thiskey);
540 4
                }
541 44
            }
542 44
        } else {
543 8
            $_criteria = $criteria;
544
        }
545
546 45
        return $_criteria;
547
    }
548
549
    /**
550
     * Checks an array for correct primary key names
551
     *
552
     * @param mixed $key
553
     */
554
    protected function _checkPrimaryKey( $id )
555
    {
556 11
        $keyNames = $this->getEntityMeta()->getPrimary();
557
558 11
        if ( (!is_array($id) && count($keyNames) > 1) || count($id) != count($keyNames)) {
559 1
            require_once 'Xyster/Orm/Mapper/Exception.php';
560 1
            throw new Xyster_Orm_Mapper_Exception("Missing value(s) for the primary key");
561 0
        }
562
563 10
        if ( !is_array($id) ) {
564 5
            $id = array( $keyNames[0] => $id );
565 5
        }
566
567 10
        foreach( array_keys($id) as $name ) {
568 10
            if ( !in_array($name, $keyNames) ) {
569 1
                require_once 'Xyster/Orm/Mapper/Exception.php';
570 1
                throw new Xyster_Orm_Mapper_Exception("'$name' is not a primary key");
571 0
            }
572 10
        }
573
574 9
        return $id;
575
    }
576
577
    /**
578
     * Asserts the correct property names in a criteria array
579
     *
580
     * @param array $criteria
581
     * @throws Xyster_Orm_Mapper_Exception if one of the field names is invalid
582
     */
583
    protected function _checkPropertyNames( array $criteria )
584
    {
585
        // get the array of Xyster_Orm_Entity_Field objects
586 47
        $fields = $this->getEntityMeta()->getFieldNames();
587
588 47
        foreach( $criteria as $k => $v ) {
589 47
            if ( !in_array($k, $fields) ) {
590 1
                require_once 'Xyster/Orm/Mapper/Exception.php';
591 1
                throw new Xyster_Orm_Mapper_Exception("'" . $k
592 1
                    . "' is not a valid field for "
593 1
                    . $this->getEntityName() );
594 0
            }
595 46
        }
596
    }
597
598
    /**
599
     * Creates an entity from the row supplied
600
     *
601
     * If the row has already been loaded and the entity that represents the row
602
     * is in the repository, this method will return that exact instance instead
603
     * of creating a new one.
604
     *
605
     * @param array $row
606
     * @return Xyster_Orm_Entity  The entity created
607
     */
608
    protected function _create( $row )
609
    {
610 58
        $entityName = $this->getEntityName();
611
        // this class should already be loaded by the class' mapper
612
613 58
        $manager = $this->_factory->getManager();
614 58
        $primary = array_intersect_key($row,
615 58
            array_flip($this->getEntityMeta()->getPrimary()));
616 58
        $loaded = $manager->getFromCache($entityName, $primary);
617 58
        if ( $loaded instanceof Xyster_Orm_Entity ) {
618 16
            return $loaded;
619 0
        }
620
621 58
        $entity = new $entityName($row);
622 58
        $manager->getPluginBroker()->postLoad($entity);
623 58
        return $entity;
624
    }
625
}


Report generated at 2007-11-05T09:09:01-05:00