http://phing.info/

Source Code Coverage

Designed for use with PHPUnit2, Xdebug and Phing.

Methods: 24 LOC: 795 Statements: 367

Source file Statements Methods Total coverage
Mapper.php 94.0% 100.0% 94.4%
   
1
<?php
2
/**
3
 * Xyster Framework
4
 *
5
 * This source file is subject to the new BSD license that is bundled
6
 * with this package in the file LICENSE.txt.
7
 * It is also available through the world-wide-web at this URL:
8
 * http://www.opensource.org/licenses/bsd-license.php
9
 *
10
 * @category  Xyster
11
 * @package   Xyster_Orm
12
 * @copyright Copyright (c) 2007-2008 Irrational Logic (http://irrationallogic.net)
13
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
14
 * @version   $Id: Mapper.php 200 2008-01-20 15:43:42Z doublecompile $
15
 */
16
/**
17
 * @see Xyster_Orm_Mapper_Abstract
18
 */
19 1
require_once 'Xyster/Orm/Mapper/Abstract.php';
20
/**
21
 * We might as well require this now... sets use it as well as the reportQuery
22
 * @see Xyster_Data_Set
23
 */
24 1
require_once 'Xyster/Data/Set.php';
25
/**
26
 * A SQL implementation of the mapper interface
27
 *
28
 * @see       Xyster_Orm_Mapper_Interface
29
 * @category  Xyster
30
 * @package   Xyster_Orm
31
 * @copyright Copyright (c) 2007-2008 Irrational Logic (http://irrationallogic.net)
32
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
33
 */
34
abstract class Xyster_Orm_Mapper extends Xyster_Orm_Mapper_Abstract
35
{
36
    /**
37
     * The data adapter
38
     *
39
     * @var Zend_Db_Adapter_Abstract
40
     */
41
    protected $_db;
42
43
    /**
44
     * Information provided by the getFields() method
45
     *
46
     * @var array
47
     * @see getFields()
48
     */
49
    protected $_metadata = array();
50
51
    /**
52
     * Cache for information provided the backend's getFields method
53
     *
54
     * @var Zend_Cache_Core
55
     */
56
    protected $_metadataCache;
57
58
    /**
59
     * Creates a new mapper
60
     *
61
     * @param Xyster_Orm_Mapper_Factory_Interface $factory
62
     * @param Zend_Cache_Core $cache
63
     */
64
    final public function __construct( Xyster_Orm_Mapper_Factory_Interface $factory, Zend_Cache_Core $cache = null )
65
    {
66 87
        if ( $cache ) {
67 87
            $this->_metadataCache = $cache;
68 87
        } else if ( array_key_exists('metadataCache', $this->_options) ) {
69 3
            $this->_metadataCache = self::_setupMetadataCache($this->_options['metadataCache']);
70 3
        }
71
72 87
        parent::__construct($factory);
73
    }
74
75
    /**
76
     * Sets up a nickname for a database adapter
77
     *
78
     * This method adds a Zend_Db_Adapter_Abstract to the Zend_Registry so it
79
     * can be retrieved later.
80
     *
81
     * @param string $dsn
82
     * @param string $driver
83
     * @param array $config
84
     */
85
    static public function dsn( $dsn, $driver, array $config = array() )
86
    {
87 87
        require_once 'Zend/Registry.php';
88 87
        require_once 'Zend/Db.php';
89
90 87
        $adapter = ( $driver instanceof Zend_Db_Adapter_Abstract ) ?
91 87
            $driver : Zend_Db::factory($driver, $config);
92 87
        Zend_Registry::set(md5($dsn), $adapter);
93
    }
94
95
    /**
96
     * Gets the first entity from the data store matching the criteria
97
     *
98
     * @param mixed $criteria
99
     * @return Xyster_Orm_Entity  The entity found
100
     */
101
    final public function find( $criteria )
102
    {
103 44
        $_criteria = $this->_buildCriteria($criteria);
104 44
		return $this->_mapEntity($this->_fetchOne($_criteria));
105
    }
106
107
    /**
108
     * Gets all entities from the data store matching the criteria
109
     *
110
     * @param mixed $criteria
111
     * @param mixed $sort
112
     * @return Xyster_Orm_Set  A collection of the entities
113
     */
114
    final public function findAll( $criteria, $sort = null )
115
    {
116 12
	    $select = $this->_buildSimpleSelect();
117 12
        $translator = $this->_buildTranslator();
118
119 12
        $token = $translator->translate($this->_buildCriteria($criteria));
120 12
		$select->where($token->getSql());
121 12
		$binds = $token->getBindValues();
122
123 12
	    if ( $sort !== null ) {
124 4
	        if ( !is_array($sort) ) {
125 4
	            $sort = array($sort);
126 4
	        }
127 4
		    foreach( $sort as $s ) {
128 4
		        if (! $s instanceof Xyster_Data_Sort ) {
129 4
		            require_once 'Xyster/Orm/Mapper/Exception.php';
130 4
                    throw new Xyster_Orm_Mapper_Exception("The sort parameter must be a single Xyster_Data_Sort or an array with multiple");
131 0
		        } else {
132 4
		            $token = $translator->translateSort($s, false);
133 4
		            $select->order($token->getSql());
134
		        }
135 4
		    }
136 4
	    }
137
138 12
	    return $this->_mapSet($this->_getAdapter()->query($select, $binds));
139
    }
140
141
    /**
142
     * Gets all entities from the data store
143
     *
144
     * @param array $ids  An array of ids for which entities to retrieve
145
     * @return Xyster_Orm_Set  A collection of the entities
146
     */
147
    final public function getAll( array $ids = array() )
148
    {
149 24
	    $orWhere = array();
150 24
        foreach( $ids as $id ) {
151 16
    	    $id = $this->_checkPrimaryKey($id);
152 16
            $orWhere[] = $this->_buildCriteria($id);
153 16
        }
154
155 24
	    $select = $this->_buildSimpleSelect();
156 24
        $binds = array();
157
158 24
        if ( count($orWhere) ) {
159 16
            require_once 'Xyster/Data/Junction.php';
160 16
            $translator = $this->_buildTranslator();
161 16
            $where = Xyster_Data_Junction::fromArray('OR', $orWhere);
162 16
            $token = $translator->translate($where);
163 16
    		$select->where($token->getSql());
164 16
		    $binds += $token->getBindValues();
165 16
        }
166
167 24
	    return $this->_mapSet($this->_getAdapter()->query($select, $binds));
168
    }
169
170
    /**
171
	 * Gets the fields for an entity as they appear in the backend
172
	 *
173
	 * The array should come in the format of the describeTable method of the
174
	 * Zend_Db_Adapter_Abstract class.
175
	 *
176
	 * @see Zend_Db_Adapter_Abstract::describeTable
177
	 * @return array
178
	 */
179
    final public function getFields()
180
    {
181 87
        if ( !$this->_metadata ) {
182
183 87
            $cache = $this->getMetadataCache();
184
185
            // If $this has a metadata cache
186 87
	        if (null !== $cache) {
187
	            // Define the cache identifier where the metadata are saved
188 87
	            $cacheId = md5($this->getTable());
189 87
	        }
190
191
	        // If $this has no metadata cache or metadata cache misses
192 87
	        if (null === $cache || !($metadata = $cache->load($cacheId))) {
193
	            // Fetch metadata from the adapter's describeTable() method
194 87
	            $metadata = $this->_getAdapter()->describeTable($this->getTable());
195
	            // If $this has a metadata cache, then cache the metadata
196 87
	            if (null !== $cache && !$cache->save($metadata, $cacheId)) {
197 4
	                require_once 'Xyster/Orm/Mapper/Exception.php';
198 4
	                throw new Xyster_Orm_Mapper_Exception('Failed saving metadata to metadataCache');
199 0
	            }
200 87
	        }
201
202
	        // Assign the metadata to $this
203 87
	        $this->_metadata = $metadata;
204 87
        }
205 87
        return $this->_metadata;
206
    }
207
208
    /**
209
     * Gets entities via a many-to-many table
210
     *
211
     * @param Xyster_Orm_Entity $entity
212
     * @param Xyster_Orm_Relation $relation
213
     * @return Xyster_Orm_Set
214
     */
215
    public function getJoined( Xyster_Orm_Entity $entity, Xyster_Orm_Relation $relation )
216
    {
217 16
        $db = $this->_getAdapter();
218 16
        $rightMap = $this->_factory->get($relation->getTo());
219
220 16
        $targetTable = $rightMap->getTable();
221 16
		$targetTableAlias = 't2';
222 16
        $columns = array();
223 16
		foreach( $rightMap->getFields() as $name => $v ) {
224 16
			$alias = $rightMap->translateField($name);
225 16
			$columns[$alias] = $targetTableAlias.'.'.$name;
226 16
		}
227
228
		// get the join SQL for the left to the middle
229 16
		$firstCond = array();
230 16
		$left = $relation->getLeft();
231 16
		foreach( $this->getEntityMeta()->getPrimary() as $k=>$primary ) {
232 16
		    $firstCond[] = $db->quoteIdentifier('t1') . '.'
233 16
		        . $db->quoteIdentifier($this->untranslateField($primary))
234 16
		        . ' = ' . $db->quoteIdentifier($relation->getTable()) . '.'
235 16
		        . $db->quoteIdentifier($left[$k]);
236 16
		}
237 16
		$firstCond = implode(' AND ', $firstCond);
238
239
		// get the join SQL for the middle to the right
240 16
		$secondCond = array();
241 16
		$right = $relation->getRight();
242 16
		foreach( $rightMap->getEntityMeta()->getPrimary() as $k=>$primary ) {
243 16
		    $secondCond[] = $db->quoteIdentifier($relation->getTable()) . '.'
244 16
		        . $db->quoteIdentifier($right[$k]) . ' = '
245 16
		        . $db->quoteIdentifier($targetTableAlias) . '.'
246 16
		        . $db->quoteIdentifier($rightMap->untranslateField($primary));
247 16
		}
248 16
		$secondCond = implode(' AND ', $secondCond);
249
250 16
		$select = $this->_getAdapter()->select();
251
252 16
		$translator = $this->_buildTranslator();
253 16
		$translator->setTable('t1');
254 16
		$token = $translator->translate($entity->getPrimaryKeyAsCriterion());
255
256 16
		$binds = $token->getBindValues();
257 16
		$select->from(array('t1'=>$this->getTable()), array())
258 16
		    ->join($relation->getTable(), $firstCond, array())
259 16
		    ->join(array($targetTableAlias=>$targetTable), $secondCond, $columns)
260 16
		    ->where($token->getSql());
261
262 16
		return $rightMap->_mapSet($this->_getAdapter()->query($select, $binds));
263
    }
264
265
    /**
266
     * Gets the metadata cache
267
     *
268
     * @return Zend_Cache_Core
269
     */
270
    final public function getMetadataCache()
271
    {
272 87
        return $this->_metadataCache;
273
    }
274
275
    /**
276
     * Gets the sequence of this table
277
     *
278
     * @return string The sequence
279
     */
280
    final public function getSequence()
281
    {
282 12
        return $this->getOption('sequence');
283
    }
284
285
    /**
286
	 * Performs a query
287
	 *
288
	 * @param Xyster_Orm_Query $query  The query details
289
	 * @return Xyster_Data_Set
290
	 */
291
	public function query( Xyster_Orm_Query $query )
292
	{
293 16
	    $db = $this->_getAdapter();
294
295 16
        require_once 'Xyster/Orm/Mapper/Translator.php';
296 16
		$translator = new Xyster_Orm_Mapper_Translator($db,
297 16
		    $this->getEntityName(), $this->_factory);
298
299 16
		$select = $db->select();
300 16
		$binds = array();
301
302
		// apply the where clause that can be run on the database
303 16
		foreach( $query->getBackendWhere() as $criterion ) {
304 16
			$whereToken = $translator->translateCriterion($criterion);
305 16
			$select->where( $whereToken->getSql() );
306 16
			$binds += $whereToken->getBindValues();
307 16
		}
308
309
		// apply the order by clause that can be run on the database
310 16
		if ( !$query->hasRuntimeOrder() ) {
311 16
			foreach( $query->getOrder() as $sort ) {
312 8
				$select->order($translator->translateSort($sort, false)->getSql());
313 8
			}
314 16
		}
315
316
		// if the query is entirely against the database, add limit & offset
317 16
		if ( !$query->isRuntime() && $query->getLimit() ) {
318 8
		    $select->limit($query->getLimit(), $query->getOffset());
319 8
		}
320
321 16
		if (! $query instanceof Xyster_Orm_Query_Report ) {
322
323
		    // add the from clause, joins, and columns
324 4
			$select->from(array($translator->getMain() => $this->getTable()), $this->_selectColumns());
325 4
			foreach( $translator->getFromClause() as $table => $joinToken ) {
326 4
			    $select->joinLeft($table, $joinToken->getSql(), array());
327 4
			    $binds += $joinToken->getBindValues();
328 4
			}
329
330 4
			return $this->_mapSet($db->query($select, $binds));
331
332 0
		} else {
333
334
		    // pretty self explanitory...
335 12
            $select->distinct($query->isDistinct());
336
337 12
	        $fields = array();
338 12
			if ( !$query->isRuntime() ) {
339
340 8
				foreach( $query->getFields() as $k=>$field ) {
341
				    // we want to quote the field names for aggregates!
342
	                // Zend_Db_Select does not do this for functions
343 8
				    $quote = ($field instanceof Xyster_Data_Field_Aggregate);
344 8
				    $fieldName = $translator->translateField($field, $quote)->getSql();
345 8
				    if ( $field->getAlias() == $field->getName() ) {
346 4
				        $fields[] = $fieldName;
347 4
				    } else {
348 8
				        $fields[$field->getAlias()] = $fieldName;
349
				    }
350 8
				}
351
352 8
				if ( count($query->getGroup()) ) {
353
				    // add the group clause
354 4
					foreach( $query->getGroup() as $k=>$grp ) {
355 4
						$fieldName = $translator->translateField($grp, false)->getSql();
356 4
						if ( $grp->getAlias() == $grp->getName() ) {
357 4
						    $fields[] = $fieldName;
358 4
						} else {
359 4
						    $fields[$grp->getAlias()] = $fieldName;
360
						}
361 4
						$select->group($fieldName);
362 4
					}
363
					// add the having clause
364 4
					foreach( $query->getHaving() as $k=>$crit ) {
365 4
						$whereToken = $translator->translateCriterion($crit);
366 4
						$select->having($whereToken->getSql());
367 4
						$binds += $whereToken->getBindValues();
368 4
					}
369 4
				}
370
371 8
			} else {
372
			    // it's runtime, just pull back all fields in the main table
373 4
			    $fields = $this->_selectColumns();
374
			}
375
376
			// add the from clause, joins, and columns
377 12
		    $select->from(array($translator->getMain() => $this->getTable()), $fields);
378 12
			foreach( $translator->getFromClause() as $table => $joinToken ) {
379 4
			    $select->joinLeft($table, $joinToken->getSql(), array());
380 4
			    $binds += $joinToken->getBindValues();
381 4
			}
382
383 12
			if ( !$query->isRuntime() ) {
384 8
			    $result = $db->query($select, $binds)->fetchAll(Zend_Db::FETCH_ASSOC);
385 8
				return new Xyster_Data_Set(Xyster_Collection::using($result));
386 0
			} else {
387 4
			    return $this->_mapSet($db->query($select, $binds));
388
			}
389
		}
390
	}
391
392
    /**
393
	 * Reloads an entity's values with fresh ones from the backend
394
	 *
395
	 * @param Xyster_Orm_Entity $entity  The entity to refresh
396
	 */
397
	public function refresh( Xyster_Orm_Entity $entity )
398
	{
399 36
	    $this->_mapEntity($this->_fetchOne($entity->getPrimaryKeyAsCriterion()), $entity);
400
	}
401
402
    /**
403
     * Gets a simple Select object for this table
404
     *
405
     * @return Zend_Db_Select
406
     */
407
    protected function _buildSimpleSelect()
408
    {
409 60
        $select = $this->_getAdapter()->select();
410 60
		$select->from(array('t1' => $this->getTable()), $this->_selectColumns());
411
412 60
		return $select;
413
    }
414
415
    /**
416
     * Gets a Mapper Translator object
417
     *
418
     * @return Xyster_Db_Translator
419
     */
420
    protected function _buildTranslator()
421
    {
422 56
        require_once 'Xyster/Db/Translator.php';
423 56
		$translator = new Xyster_Db_Translator($this->_getAdapter());
424 56
		$translator->setRenameCallback(array($this, 'untranslateField'));
425 56
		return $translator;
426
    }
427
428
	/**
429
	 * Removes entities from the backend
430
	 *
431
	 * @param Xyster_Data_Criterion $where The criteria on which to remove entities
432
	 * @return int The number of rows deleted
433
	 */
434
	protected function _delete( Xyster_Data_Criterion $where )
435
	{
436 8
	    $translator = $this->_buildTranslator();
437 8
		$token = $translator->translateCriterion($where);
438
439 8
		$stmt = $this->_getAdapter()->prepare('DELETE FROM '
440 8
			. $this->_getAdapter()->quoteIdentifier($this->getTable())
441 8
		    . ' WHERE ' . $token->getSql());
442 8
		$stmt->execute($token->getBindValues());
443 8
		return $stmt->rowCount();
444
	}
445
446
	/**
447
	 * Fetches one record
448
	 *
449
	 * @param Xyster_Data_Criterion $where
450
	 * @return Zend_Db_Statement_Interface
451
	 */
452
	protected function _fetchOne( Xyster_Data_Criterion $where )
453
	{
454 48
	    $select = $this->_buildSimpleSelect();
455 48
		$select->limit(1);
456
457 48
		$translator = $this->_buildTranslator();
458 48
	    $token = $translator->translate($where);
459 48
		$select->where($token->getSql());
460
461 48
		return $this->_getAdapter()->query($select, $token->getBindValues());
462
	}
463
464
	/**
465
	 * Gets a connection to the database
466
	 *
467
	 * @return Zend_Db_Adapter_Abstract A connection to the database
468
	 * @throws Xyster_Orm_Mapper_Exception
469
	 */
470
	final protected function _getAdapter()
471
	{
472 87
	    if (! $this->_db instanceof Zend_Db_Adapter_Abstract ) {
473 87
            $key = md5($this->getDomain());
474 87
            require_once 'Zend/Registry.php';
475 87
            $db = Zend_Registry::isRegistered($key) ? Zend_Registry::get($key) : null;
476
477 87
            if (!$db instanceof Zend_Db_Adapter_Abstract) {
478 4
                require_once 'Xyster/Orm/Mapper/Exception.php';
479 4
                throw new Xyster_Orm_Mapper_Exception('A database connection has not been defined.  Please call the static "dsn" method to do so.');
480 0
            }
481 87
            $this->_db = $db;
482 87
	    }
483
484 87
        return $this->_db;
485
    }
486
487
    /**
488
	 * Saves a new entity into the backend
489
	 *
490
	 * @param Xyster_Orm_Entity $entity  The entity to insert
491
	 * @return mixed  The new primary key
492
	 */
493
	protected function _insert( Xyster_Orm_Entity $entity )
494
	{
495 12
	    $db = $this->_getAdapter();
496
497 12
	    $data = array();
498 12
	    foreach( $entity->toArray() as $name => $value ) {
499 12
	        $data[ $this->untranslateField($name) ] = $value;
500 12
	    }
501
502
        /**
503
         * This class assumes that if you have a compound primary key
504
         * and one of the columns in the key uses a sequence,
505
         * it's the _first_ column in the compound key.
506
         */
507 12
        $primary = array_map(array($this, 'untranslateField'),
508 12
            $this->getEntityMeta()->getPrimary());
509 12
        $pkIdentity = $primary[0];
510 12
        if ( count($primary) > 0 ) {
511 12
	        $fields = $this->getEntityMeta()->getFields();
512 12
	        foreach( $fields as $field ) {
513
	            /* @var $field Xyster_Orm_Entity_Field */
514 12
	            if ( $field->isIdentity() ) {
515 8
	                $posn = $field->getPrimaryPosition() - 1;
516 8
	                $pkIdentity = $primary[ $posn ];
517 8
	            }
518 12
	        }
519 12
        }
520
521 12
        $sequence = $this->getSequence();
522 12
        if ( !$sequence && $db instanceof Zend_Db_Adapter_Pdo_Pgsql ) {
523 2
            $sequence = $this->getTable() . "_" . $pkIdentity . "_seq";
524 2
        }
525
526
        /**
527
         * If this table uses a database sequence object and the data does not
528
         * specify a value, then get the next ID from the sequence and add it
529
         * to the row.  We assume that only the first column in a compound
530
         * primary key takes a value from a sequence.
531
         */
532 12
        if ( $sequence && !$data[$pkIdentity]) {
533 2
            $data[$pkIdentity] = $db->nextSequenceId($sequence);
534 2
        }
535
536
	    /**
537
         * If the primary key can be generated automatically, and no value was
538
         * specified in the user-supplied data, then omit it from the tuple
539
         */
540 12
        if ( array_key_exists($pkIdentity, $data) && $data[$pkIdentity] === null ) {
541 6
            unset($data[$pkIdentity]);
542 6
        }
543
544 12
        $db->insert($this->getTable(), $data);
545
546 12
        $primaryKey = null;
547 12
        if ( isset($data[$pkIdentity]) ) {
548
            /**
549
             * Return the primary key value or array of values if the
550
             * primary key is compound.  This handles:
551
             * - natural keys
552
             * - sequence-driven keys
553
             * - auto-increment keys when the user specifies a value
554
             */
555 9
            $pkData = array_intersect_key($data, array_flip($primary));
556 9
            if (count($primary) == 1) {
557 5
                $primaryKey = current($pkData);
558 5
            } else {
559 4
                $primaryKey = $pkData;
560
            }
561 9
        }
562
563 12
        if (!$sequence) {
564
            /**
565
             * Return the most recent ID generated by an auto-increment column
566
             */
567 9
            $primaryKey = $db->lastInsertId();
568 9
        }
569
570
        /**
571
         * Normalize the result to an array indexed by primary key column(s).
572
         */
573 12
        $newPrimaryKey = is_array($primaryKey) ?
574 12
            $primaryKey : array(current($primary) => $primaryKey);
575
576 12
        foreach( $newPrimaryKey as $name => $value ) {
577 12
            $field = $this->translateField($name);
578 12
            $entity->$field = $value;
579 12
        }
580
581 12
    	return $newPrimaryKey;
582
	}
583
584
    /**
585
     * Adds the entities to the many-to-many join
586
     *
587
     * @param Xyster_Orm_Set $set
588
     */
589
    protected function _joinedInsert( Xyster_Orm_Set $set )
590
    {
591 12
        $entity = $set->getRelatedEntity();
592 12
        $relation = $set->getRelation();
593
594 12
        $leftValues = array();
595 12
        $left = $relation->getLeft();
596 12
        foreach( $this->getEntityMeta()->getPrimary() as $k=>$primary ) {
597 12
            $leftValues[$left[$k]] = $entity->$primary;
598 12
        }
599
600 12
        $rightMap = $this->getFactory()->get($relation->getTo());
601 12
        $right = $relation->getRight();
602 12
        $rightPrimary = $rightMap->getEntityMeta()->getPrimary();
603
604 12
        foreach( $set->getDiffAdded() as $added ) {
605
            /* @var $added Xyster_Orm_Entity */
606 12
            if ( !$added->getBase() ) {
607 4
                $rightMap->save($added);
608 4
            }
609 12
            $values = $leftValues;
610 12
            foreach( $rightPrimary as $k=>$primary ) {
611 12
                $values[$right[$k]] = $added->$primary;
612 12
            }
613 12
            $this->_getAdapter()->insert($relation->getTable(), $values);
614 12
        }
615
    }
616
617
    /**
618
     * Removes the entities from the many-to-many join
619
     *
620
     * @param Xyster_Orm_Set $set
621
     */
622
    protected function _joinedDelete( Xyster_Orm_Set $set )
623
    {
624 12
        $entity = $set->getRelatedEntity();
625 12
        $relation = $set->getRelation();
626
627 12
		$firstCond = array();
628 12
		$left = $relation->getLeft();
629 12
		foreach( $this->getEntityMeta()->getPrimary() as $k=>$primary ) {
630 12
		    $firstCond[] = Xyster_Data_Expression::eq($left[$k], $entity->$primary);
631 12
		}
632 12
		$leftCriteria = Xyster_Data_Criterion::fromArray('AND', $firstCond);
633
634 12
        $rightMap = $this->getFactory()->get($relation->getTo());
635 12
		$right = $relation->getRight();
636 12
		$rightPrimary = $rightMap->getEntityMeta()->getPrimary();
637
638 12
		$diffRemoved = $set->getDiffRemoved();
639 12
		if ( !count($diffRemoved) ) {
640 8
		    return;
641 0
		}
642 4
		$secondCriteria = array();
643 4
		foreach( $diffRemoved as $removed ) {
644 4
		    $secondCond = array();
645 4
		    foreach( $rightPrimary as $k=>$primary ) {
646 4
    		    $secondCond[] = Xyster_Data_Expression::eq($right[$k], $removed->$primary);
647 4
    		}
648 4
    		$secondCriteria[] = Xyster_Data_Criterion::fromArray('AND', $secondCond);
649 4
		}
650 4
		$allSecondCriteria = Xyster_Data_Criterion::fromArray('OR', $secondCriteria);
651
652 4
		$where = Xyster_Data_Junction::all($leftCriteria, $allSecondCriteria);
653 4
		$translator = new Xyster_Db_Translator($this->_getAdapter());
654 4
		$token = $translator->translate($where);
655
656 4
		$stmt = $this->_getAdapter()->prepare('DELETE FROM '
657 4
		    . $this->_getAdapter()->quoteIdentifier($relation->getTable())
658 4
		    . ' WHERE ' . $token->getSql());
659
660 4
		$stmt->execute($token->getBindValues());
661
    }
662
663
	/**
664
	 * Translates the first row of a database recordset into an entity
665
	 *
666
	 * @param Zend_Db_Statement_Interface $stmt A statement containing the row to translate
667
	 * @param Xyster_Orm_Entity $entity  Optional.  An entity to refresh
668
	 * @return Xyster_Orm_Entity  The translated entity
669
	 */
670
	protected function _mapEntity( Zend_Db_Statement_Interface $stmt, Xyster_Orm_Entity $entity = null )
671
	{
672 48
	    $return = null;
673
674 48
		if ( $row = $stmt->fetch(Zend_Db::FETCH_ASSOC) ) {
675 45
			$this->_checkPropertyNames($row);
676 45
			$stmt->closeCursor();
677 45
			if ( $entity instanceof Xyster_Orm_Entity ) {
678 33
			    $return = $entity->import($row);
679 33
			} else {
680 44
			    $return = $this->_create($row);
681
			}
682 45
		}
683
684 48
		return $return;
685
	}
686
687
    /**
688
	 * Translates a database recordset into an entity set
689
	 *
690
	 * @param Zend_Db_Statement_Interface $stmt A statement containing rows to translate
691
	 * @return Xyster_Orm_Set  The translated set
692
	 */
693
	protected function _mapSet( Zend_Db_Statement_Interface $stmt )
694
	{
695 40
		$entities = array();
696 40
		$stmt->setFetchMode(Zend_Db::FETCH_ASSOC);
697 40
		foreach( $stmt->fetchAll() as $k => $row ) {
698 40
			if ( $k<1 ) {
699 40
				$this->_checkPropertyNames($row);
700 40
			}
701 40
			$entities[] = $this->_create($row);
702 40
		}
703 40
		$stmt->closeCursor();
704
705 40
		return $this->getSet(Xyster_Collection::using($entities));
706
	}
707
708
	/**
709
	 * Gets the columns to select
710
	 *
711
	 * @return array
712
	 */
713
	protected function _selectColumns()
714
	{
715 64
	    $columns = array();
716 64
		foreach( $this->getFields() as $name => $v ) {
717 64
			$alias = $this->translateField($name);
718 64
			$columns[$alias] = $name;
719 64
		}
720 64
		return $columns;
721
	}
722
723
    /**
724
     * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key
725
     * @return Zend_Cache_Core
726
     * @throws Xyster_Orm_Mapper_Exception
727
     */
728
    protected final function _setupMetadataCache($metadataCache)
729
    {
730 3
        if (is_string($metadataCache)) {
731 3
            require_once 'Zend/Registry.php';
732 3
            $metadataCache = Zend_Registry::get($metadataCache);
733 3
        }
734
735 3
        if ($metadataCache === null || $metadataCache instanceof Zend_Cache_Core) {
736 3
            return $metadataCache;
737 0
        }
738
739 3
        require_once 'Xyster/Orm/Mapper/Exception.php';
740 3
        throw new Xyster_Orm_Mapper_Exception('Argument must be of type Zend_Cache_Core, or a Registry key where a Zend_Cache_Core object is stored');
741
    }
742
743
	/**
744
	 * Updates the values of an entity in the backend
745
	 *
746
	 * @param Xyster_Orm_Entity $entity The entity to update
747
     * @throws Xyster_Orm_Mapper_Exception if the record was modified or deleted
748
	 */
749
	protected function _update( Xyster_Orm_Entity $entity )
750
	{
751 24
	    $db = $this->_getAdapter();
752
753 24
    	$values = array();
754 24
    	foreach( $entity->getDirtyFields() as $name => $value ) {
755 12
    	    $values[ $this->untranslateField($name) ] = $value;
756 12
    	}
757
758 24
	    $keyNames = $this->getEntityMeta()->getPrimary();
759 24
	    $key = $entity->getPrimaryKey(true);
760
761 24
	    $where = array();
762 24
	    foreach( $keyNames as $name ) {
763 24
	        $sql = $db->quoteIdentifier($this->untranslateField($name)) . ' = ?';
764 24
	        $where[] = $db->quoteInto($sql, $key[$name]);
765 24
    	}
766
767
        // optimistic locking
768 24
    	$lockingField = $this->getOption('locking');
769 24
    	$lockingVersion = 0;
770 24
    	if ( $lockingField ) {
771 0
    		$dbLockingField = $this->untranslateField($lockingField);
772 0
    		$lockingVersion = $entity->$lockingField;
773 0
    		$sql = $db->quoteIdentifier($dbLockingField) . ' = ?';
774 0
            $where[] = $db->quoteInto($sql, $lockingVersion);
775 0
            if ( count($values) > 0 ) {
776
            	// if we have dirty values, increase the version number
777 0
            	$values[$dbLockingField] = $entity->$lockingField++;
778 0
            }
779 0
    	}
780
781 24
    	if ( count($values) > 0 ) {
782 12
    	    $rows = $this->_getAdapter()->update($this->getTable(), $values, $where);
783
784
    	    // optimistic locking
785 12
    	    if ( $lockingField && !$rows ) {
786 0
    	    	require_once 'Xyster/Orm/Mapper/Exception.php';
787 0
   	    		throw new Xyster_Orm_Mapper_Exception("Could not update the '" .
788 0
   	    		  $this->getEntityName() . "' with ID '" .
789 0
   	    		  $entity->getPrimaryKeyAsString(true) . "' and version #" .
790 0
   	    		  $lockingVersion .
791 0
   	    		  ".  The database record was either deleted or modified.");
792 0
    	    }
793 12
    	}
794
	}
795
}


Report generated at 2008-03-05T18:27:43-05:00