http://phing.info/

Source Code Coverage

Designed for use with PHPUnit2, Xdebug and Phing.

Methods: 21 LOC: 584 Statements: 175

Source file Statements Methods Total coverage
Abstract.php 93.1% 95.2% 93.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 116 2007-10-07 17:03:52Z 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
     * </dl>
104
     *
105
     * @var array
106
     */
107
    protected $_options = array();
108
109
    /**
110
     * The name of the table, defaults to entity name
111
     *
112
     * @var string
113
     */
114
    protected $_table = "";
115
116
    /**
117
     * Creates a new mapper
118
     *
119
     * Class authors can overwrite this, but <em>be sure to call the parent</em>
120
     *
121
     * @param Xyster_Orm_Mapper_Factory_Interface $factory
122
     */
123
    public function __construct( Xyster_Orm_Mapper_Factory_Interface $factory )
124
    {
125 214
        $this->_factory = $factory;
126
127 214
        $this->getEntityMeta(); // to assign the meta data to the entity class
128 214
        $this->getSet(); // to make sure the class is defined
129
    }
130
131
    /**
132
     * Allows for subclassing without overwriting constructor
133
     *
134
     * The mapper factory calls this method.  This is necessary because the init
135
     * method should contain the setup of relations, which might depend on the
136
     * mapper that's still being instantiated in the factory.
137
     */
138
    public function init()
139
    {
140
    }
141
142
    /**
143
     * Deletes an entity
144
     *
145
     * @param Xyster_Orm_Entity $entity The entity to delete
146
     */
147
    public function delete( Xyster_Orm_Entity $entity )
148
    {
149 6
        $broker = $this->_factory->getManager()->getPluginBroker();
150 6
        $broker->preDelete($entity);
151 6
        $this->_delete($entity->getPrimaryKeyAsCriterion());
152 6
        $broker->postDelete($entity);
153
154 6
        $relations = $this->getEntityMeta()->getRelations();
155 6
        foreach( $relations as $relation ) { /* @var $relation Xyster_Orm_Relation */
156 5
            if ( $relation->getType() == 'many' ) {
157 1
                $onDelete = $relation->getOnDelete();
158 1
                $map = $this->_factory->get($relation->getTo());
159 1
                $name = $relation->getName();
160 1
                $reverseName = $relation->hasBelongsTo() ?
161 1
                    $relation->getReverse()->getName() : null;
162 1
                $related = $entity->$name; /* @var $related Xyster_Orm_Set */
163
164
                // just remove the association
165 1
                if ( $onDelete == Xyster_Orm_Relation::ACTION_SET_NULL && $reverseName ) {
166 1
                    foreach( $related as $relatedEntity ) {
167 1
                        $relatedEntity->$reverseName = null;
168 1
                        $map->save($relatedEntity);
169 1
                    }
170
                // rely on the database to cascade the delete
171 1
                } else if ( $onDelete == Xyster_Orm_Relation::ACTION_CASCADE ) {
172 0
                    $this->_factory->getManager()->getRepository()->removeAll($related);
173
                // we have to delete every last one ourselves
174 1
                } else if ( $onDelete == Xyster_Orm_Relation::ACTION_REMOVE ) {
175 0
                    $this->_factory->getManager()->getRepository()->removeAll($related);
176 0
                    foreach( $related as $relatedEntity ) {
177 0
                        $map->delete($relatedEntity);
178 0
                    }
179 0
                }
180 1
            }
181 5
        }
182
    }
183
184
    /**
185
     * Gets an entity with the supplied identifier
186
     *
187
     * @param mixed $id  The id of the entity to get
188
     * @return Xyster_Orm_Entity  The data entity found, or null if none
189
     */
190
    final public function get( $id )
191
    {
192 31
        $keyNames = $this->getEntityMeta()->getPrimary();
193 31
        $keyValues = array();
194
195 31
	    if ( count($keyNames) > 1 ) {
196
197 5
    	    $this->_checkPrimaryKey($id);
198 3
    	    $keyValues = $id;
199
200 29
	    } else if ( is_array($id) ) {
201
202 17
	        $keyValues = array( current($keyNames) => current($id) );
203
204 17
	    } else {
205
206 9
	        $keyValues = array( $keyNames[0] => $id );
207
208
	    }
209
210 29
	    return $this->find($keyValues);
211
    }
212
213
    /**
214
     * Gets the name of the domain to which this mapper belongs
215
     *
216
     * @return string  The domain
217
     */
218
    final public function getDomain()
219
    {
220 39
        return $this->_domain;
221
    }
222
223
    /**
224
     * Gets the entity metadata
225
     *
226
     * @return Xyster_Orm_Entity_Meta
227
     */
228
    final public function getEntityMeta()
229
    {
230 214
        if ( !$this->_meta ) {
231 214
            $this->_meta = new Xyster_Orm_Entity_Meta($this);
232 214
            Xyster_Orm_Entity::setMeta($this->_meta);
233 214
        }
234 214
        return $this->_meta;
235
    }
236
237
    /**
238
     * Gets the class name of the entity
239
     *
240
     * Class authors should overwrite this method if their entity name isn't
241
     * the same as the mapper name.
242
     *
243
     * @return string  The class name of the entity
244
     */
245
    public function getEntityName()
246
    {
247 214
        return substr(get_class($this),0,-6);
248
    }
249
250
    /**
251
     * Gets the factory that created this mapper
252
     *
253
     * @return Xyster_Orm_Mapper_Factory_Interface
254
     */
255
    public function getFactory()
256
    {
257 214
        return $this->_factory;
258
    }
259
260
    /**
261
     * Gets the columns that should be used to index the entity
262
     *
263
     * The array consists of index names as keys and arrays of the columns
264
     * contained within as values.
265
     *
266
     * @return array
267
     */
268
    final public function getIndex()
269
    {
270 46
        return $this->_index;
271
    }
272
273
	/**
274
	 * Gets the time in seconds an entity should be cached
275
	 *
276
	 * @return int
277
	 */
278
	final public function getLifetime()
279
	{
280 29
		return $this->_lifetime;
281
	}
282
283
    /**
284
     * Gets the value of an option
285
     *
286
     * @param string $name The name of the option
287
     * @return mixed The option value
288
     */
289
    final public function getOption( $name )
290
    {
291 4
        return array_key_exists($name, $this->_options) ?
292 4
            $this->_options[$name] : null;
293
    }
294
295
    /**
296
     * Gets the options for this mapper
297
     *
298
     * @return array
299
     */
300
    final public function getOptions()
301
    {
302 1
        return $this->_options;
303
    }
304
305
    /**
306
     * Gets an empty entity set for the mapper's entity type
307
     *
308
     * @return Xyster_Orm_Set An empty set
309
     */
310
    public function getSet( Xyster_Collection_Interface $entities = null )
311
    {
312 214
        $set = Xyster_Orm_Loader::loadSetClass($this->getEntityName());
313
314 214
        return new $set($entities);
315
    }
316
317
    /**
318
     * Gets the table from which an entity comes
319
     *
320
     * It is up to the Xyster_Orm_Backend to do something with this value.
321
     *
322
     * @return string The table name
323
     */
324
    public function getTable()
325
    {
326 25
        if ( !$this->_table ) {
327 1
            require_once 'Xyster/String.php';
328 1
            $this->_table = Xyster_String::toUnderscores($this->getEntityName());
329 1
        }
330 25
        return $this->_table;
331
    }
332
333
    /**
334
     * Saves an entity (insert or update)
335
     *
336
     * @param Xyster_Orm_Entity $entity  The entity to save
337
     */
338
    public function save( Xyster_Orm_Entity $entity )
339
    {
340
        /*
341
		 * Step 1: Sets ids for any single-entity relationships
342
		 */
343 12
		foreach( $this->getEntityMeta()->getRelations() as $k=>$v ) {
344 9
			if ( !$v->isCollection() && $entity->isLoaded($k) && $entity->$k !== null ) {
345 2
				$linked = $entity->$k;
346
				// get the original primary key, in case it's not auto-generated
347 2
				$key = $linked->getPrimaryKey(true);
348 2
				if ( !count($key) || !current($key) ) {
349 2
					$this->_factory->get($v->getTo())->save($linked);
350 2
					$key = $linked->getPrimaryKey();
351 2
				}
352 2
				$keyNames = array_keys($key);
353 2
				$foreignKey = $v->getId();
354 2
				for( $i=0; $i<count($key); $i++ ) {
355 2
				    $field = $foreignKey[$i];
356 2
				    $keyName = $keyNames[$i];
357 2
				    $entity->$field = $linked->$keyName;
358 2
				}
359 2
			}
360 9
		}
361
362 12
		$broker = $this->_factory->getManager()->getPluginBroker();
363
		/*
364
		 * Step 2: Save actual entity
365
		 */
366 12
        if ( !$entity->getBase() ) {
367 7
            $broker->preInsert($entity);
368 7
            $this->_insert($entity);
369 7
            $broker->postInsert($entity);
370
371 7
        } else {
372 9
            $broker->preUpdate($entity);
373 9
            $this->_update($entity);
374 9
            $broker->postUpdate($entity);
375
        }
376
377
    	// this is in case any triggers in the db, etc. have changed the record
378 12
    	$this->refresh($entity);
379
380
        /*
381
		 * Step 3: work with many and joined relationships
382
		 */
383 12
		foreach( $this->getEntityMeta()->getRelations() as $k=>$relation ) {
384
		    /* @var $relation Xyster_Orm_Relation */
385 9
			if ( $relation->isCollection() && $entity->isLoaded($k) ) {
386 4
				$set = $entity->$k;
387
388 4
				$added = $set->getDiffAdded();
389 4
				$removed = $set->getDiffRemoved();
390 4
				if ( !$added && !$removed ) {
391 1
					continue;
392 0
				}
393
394 4
				if ( $relation->getType() == 'joined' ) {
395
396 3
				    $this->_joinedInsert($set);
397 3
				    $this->_joinedDelete($set);
398
399 4
				} else if ( $relation->getType() == 'many' ) {
400
401 1
    				$map = $this->_factory->get($relation->getTo());
402 1
    				array_walk($added, array($map, 'save'));
403 1
    				array_walk($removed, array($map, 'delete'));
404
405 1
				}
406
407 4
				$set->baseline();
408 4
			}
409 9
		}
410
    }
411
412
    /**
413
     *
414
     * @param string $field
415
     * @return string
416
     */
417
    public function translateField( $field )
418
    {
419 214
        require_once 'Xyster/String.php';
420 214
        return Xyster_String::toCamel($field);
421
    }
422
423
    /**
424
     *
425
     * @param string $field
426
     * @return string
427
     */
428
    public function untranslateField( $field )
429
    {
430 195
        require_once 'Xyster/String.php';
431 195
        return Xyster_String::toUnderscores($field);
432
    }
433
434
    /**
435
     * Removes entities from the backend
436
     *
437
     * @param Xyster_Data_Criterion $where  The criteria on which to remove entities
438
     */
439
    abstract protected function _delete( Xyster_Data_Criterion $where );
440
441
    /**
442
     * Saves a new entity into the backend
443
     *
444
     * @param Xyster_Orm_Entity $entity  The entity to insert
445
     * @return mixed  The new primary key
446
     */
447
    abstract protected function _insert( Xyster_Orm_Entity $entity );
448
449
    /**
450
     * Adds the entity to the many-to-many join
451
     *
452
     * @param Xyster_Orm_Set $set
453
     */
454
    abstract protected function _joinedInsert( Xyster_Orm_Set $set );
455
456
    /**
457
     * Removes the entity from the many-to-many join
458
     *
459
     * @param Xyster_Orm_Set $set
460
     */
461
    abstract protected function _joinedDelete( Xyster_Orm_Set $set );
462
463
    /**
464
     * Updates the values of an entity in the backend
465
     *
466
     * @param Xyster_Orm_Entity $entity  The entity to update
467
     */
468
    abstract protected function _update( Xyster_Orm_Entity $entity );
469
470
    /**
471
     * Ensures the parameter passed is a Criterion
472
     *
473
     * @param Xyster_Data_Criterion|array $criteria
474
     * @return Xyster_Data_Criterion
475
     */
476
    protected function _buildCriteria( $criteria )
477
    {
478 43
        if ( !is_array($criteria) &&
479 6
            ! $criteria instanceof Xyster_Data_Criterion &&
480 43
            $criteria !== null ) {
481 2
            require_once 'Xyster/Orm/Mapper/Exception.php';
482 2
            throw new Xyster_Orm_Mapper_Exception('Invalid criteria: ' . gettype($criteria) );
483 0
        }
484
485 41
        $_criteria = null;
486
487 41
        if ( is_array($criteria) ) {
488 41
            $this->_checkPropertyNames($criteria);
489 40
            foreach( $criteria as $name => $value ) {
490 40
                require_once 'Xyster/Data/Expression.php';
491 40
                $thiskey = Xyster_Data_Expression::eq($name,$value);
492 40
                if ( !$_criteria ) {
493 40
                    $_criteria = $thiskey;
494 40
                } else if ( $_criteria instanceof Xyster_Data_Expression ) {
495 4
                    require_once 'Xyster/Data/Junction.php';
496 4
                    $_criteria = Xyster_Data_Junction::all( $_criteria, $thiskey );
497 4
                } else if ( $_criteria instanceof Xyster_Data_Junction ) {
498 4
                    $_criteria->add($thiskey);
499 4
                }
500 40
            }
501 40
        } else {
502 4
            $_criteria = $criteria;
503
        }
504
505 40
        return $_criteria;
506
    }
507
508
    /**
509
     * Checks an array for correct primary key names
510
     *
511
     * @param mixed $key
512
     */
513
    protected function _checkPrimaryKey( $id )
514
    {
515 10
        $keyNames = $this->getEntityMeta()->getPrimary();
516
517 10
        if ( (!is_array($id) && count($keyNames) > 1) || count($id) != count($keyNames)) {
518 1
            require_once 'Xyster/Orm/Mapper/Exception.php';
519 1
            throw new Xyster_Orm_Mapper_Exception("Missing value(s) for the primary key");
520 0
        }
521
522 9
        if ( !is_array($id) ) {
523 5
            $id = array( $keyNames[0] => $id );
524 5
        }
525
526 9
        foreach( array_keys($id) as $name ) {
527 9
            if ( !in_array($name, $keyNames) ) {
528 1
                require_once 'Xyster/Orm/Mapper/Exception.php';
529 1
                throw new Xyster_Orm_Mapper_Exception("'$name' is not a primary key");
530 0
            }
531 9
        }
532
533 8
        return $id;
534
    }
535
536
    /**
537
     * Asserts the correct property names in a criteria array
538
     *
539
     * @param array $criteria
540
     * @throws Xyster_Orm_Mapper_Exception if one of the field names is invalid
541
     */
542
    protected function _checkPropertyNames( array $criteria )
543
    {
544
        // get the array of Xyster_Orm_Entity_Field objects
545 43
        $fields = $this->getEntityMeta()->getFieldNames();
546
547 43
        foreach( $criteria as $k => $v ) {
548 43
            if ( !in_array($k, $fields) ) {
549 1
                require_once 'Xyster/Orm/Mapper/Exception.php';
550 1
                throw new Xyster_Orm_Mapper_Exception("'" . $k
551 1
                    . "' is not a valid field for "
552 1
                    . $this->getEntityName() );
553 0
            }
554 42
        }
555
    }
556
557
    /**
558
     * Creates an entity from the row supplied
559
     *
560
     * If the row has already been loaded and the entity that represents the row
561
     * is in the repository, this method will return that exact instance instead
562
     * of creating a new one.
563
     *
564
     * @param array $row
565
     * @return Xyster_Orm_Entity  The entity created
566
     */
567
    protected function _create( $row )
568
    {
569 54
        $entityName = $this->getEntityName();
570
        // this class should already be loaded by the class' mapper
571
572 54
        $manager = $this->_factory->getManager();
573 54
        $primary = array_intersect_key($row,
574 54
            array_flip($this->getEntityMeta()->getPrimary()));
575 54
        $loaded = $manager->getFromCache($entityName, $primary);
576 54
        if ( $loaded instanceof Xyster_Orm_Entity ) {
577 14
            return $loaded;
578 0
        }
579
580 54
        $entity = new $entityName($row);
581 54
        $manager->getPluginBroker()->postLoad($entity);
582 54
        return $entity;
583
    }
584
}


Report generated at 2007-10-08T19:32:23-05:00