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 92.9% 100.0% 93.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 22
        if ( $cache ) {
67 22
            $this->_metadataCache = $cache;
68 22
        } else if ( array_key_exists('metadataCache', $this->_options) ) {
69 1
            $this->_metadataCache = self::_setupMetadataCache($this->_options['metadataCache']);
70 1
        }
71
72 22
        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 22
        require_once 'Zend/Registry.php';
88 22
        require_once 'Zend/Db.php';
89
90 22
        $adapter = ( $driver instanceof Zend_Db_Adapter_Abstract ) ?
91 22
            $driver : Zend_Db::factory($driver, $config);
92 22
        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 11
        $_criteria = $this->_buildCriteria($criteria);
104 11
		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 3
	    $select = $this->_buildSimpleSelect();
117 3
        $translator = $this->_buildTranslator();
118
119 3
        $token = $translator->translate($this->_buildCriteria($criteria));
120 3
		$select->where($token->getSql());
121 3
		$binds = $token->getBindValues();
122
123 3
	    if ( $sort !== null ) {
124 1
	        if ( !is_array($sort) ) {
125 1
	            $sort = array($sort);
126 1
	        }
127 1
		    foreach( $sort as $s ) {
128 1
		        if (! $s instanceof Xyster_Data_Sort ) {
129 1
		            require_once 'Xyster/Orm/Mapper/Exception.php';
130 1
                    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 1
		            $token = $translator->translateSort($s, false);
133 1
		            $select->order($token->getSql());
134
		        }
135 1
		    }
136 1
	    }
137
138 3
	    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 6
	    $orWhere = array();
150 6
        foreach( $ids as $id ) {
151 4
    	    $id = $this->_checkPrimaryKey($id);
152 4
            $orWhere[] = $this->_buildCriteria($id);
153 4
        }
154
155 6
	    $select = $this->_buildSimpleSelect();
156 6
        $binds = array();
157
158 6
        if ( count($orWhere) ) {
159 4
            require_once 'Xyster/Data/Junction.php';
160 4
            $translator = $this->_buildTranslator();
161 4
            $where = Xyster_Data_Junction::fromArray('OR', $orWhere);
162 4
            $token = $translator->translate($where);
163 4
    		$select->where($token->getSql());
164 4
		    $binds += $token->getBindValues();
165 4
        }
166
167 6
	    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 22
        if ( !$this->_metadata ) {
182
183 22
            $cache = $this->getMetadataCache();
184
185
            // If $this has a metadata cache
186 22
	        if (null !== $cache) {
187
	            // Define the cache identifier where the metadata are saved
188 22
	            $cacheId = md5($this->getTable());
189 22
	        }
190
191
	        // If $this has no metadata cache or metadata cache misses
192 22
	        if (null === $cache || !($metadata = $cache->load($cacheId))) {
193
	            // Fetch metadata from the adapter's describeTable() method
194 22
	            $metadata = $this->_getAdapter()->describeTable($this->getTable());
195
	            // If $this has a metadata cache, then cache the metadata
196 22
	            if (null !== $cache && !$cache->save($metadata, $cacheId)) {
197 1
	                require_once 'Xyster/Orm/Mapper/Exception.php';
198 1
	                throw new Xyster_Orm_Mapper_Exception('Failed saving metadata to metadataCache');
199 0
	            }
200 22
	        }
201
202
	        // Assign the metadata to $this
203 22
	        $this->_metadata = $metadata;
204 22
        }
205 22
        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 4
        $db = $this->_getAdapter();
218 4
        $rightMap = $this->_factory->get($relation->getTo());
219
220 4
        $targetTable = $rightMap->getTable();
221 4
		$targetTableAlias = 't2';
222 4
        $columns = array();
223 4
		foreach( $rightMap->getFields() as $name => $v ) {
224 4
			$alias = $rightMap->translateField($name);
225 4
			$columns[$alias] = $targetTableAlias.'.'.$name;
226 4
		}
227
228
		// get the join SQL for the left to the middle
229 4
		$firstCond = array();
230 4
		$left = $relation->getLeft();
231 4
		foreach( $this->getEntityMeta()->getPrimary() as $k=>$primary ) {
232 4
		    $firstCond[] = $db->quoteIdentifier('t1') . '.'
233 4
		        . $db->quoteIdentifier($this->untranslateField($primary))
234 4
		        . ' = ' . $db->quoteIdentifier($relation->getTable()) . '.'
235 4
		        . $db->quoteIdentifier($left[$k]);
236 4
		}
237 4
		$firstCond = implode(' AND ', $firstCond);
238
239
		// get the join SQL for the middle to the right
240 4
		$secondCond = array();
241 4
		$right = $relation->getRight();
242 4
		foreach( $rightMap->getEntityMeta()->getPrimary() as $k=>$primary ) {
243 4
		    $secondCond[] = $db->quoteIdentifier($relation->getTable()) . '.'
244 4
		        . $db->quoteIdentifier($right[$k]) . ' = '
245 4
		        . $db->quoteIdentifier($targetTableAlias) . '.'
246 4
		        . $db->quoteIdentifier($rightMap->untranslateField($primary));
247 4
		}
248 4
		$secondCond = implode(' AND ', $secondCond);
249
250 4
		$select = $this->_getAdapter()->select();
251
252 4
		$translator = $this->_buildTranslator();
253 4
		$translator->setTable('t1');
254 4
		$token = $translator->translate($entity->getPrimaryKeyAsCriterion());
255
256 4
		$binds = $token->getBindValues();
257 4
		$select->from(array('t1'=>$this->getTable()), array())
258 4
		    ->join($relation->getTable(), $firstCond, array())
259 4
		    ->join(array($targetTableAlias=>$targetTable), $secondCond, $columns)
260 4
		    ->where($token->getSql());
261
262 4
		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 22
        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 3
        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 4
	    $db = $this->_getAdapter();
294
295 4
        require_once 'Xyster/Orm/Mapper/Translator.php';
296 4
		$translator = new Xyster_Orm_Mapper_Translator($db,
297 4
		    $this->getEntityName(), $this->_factory);
298
299 4
		$select = $db->select();
300 4
		$binds = array();
301
302
		// apply the where clause that can be run on the database
303 4
		foreach( $query->getBackendWhere() as $criterion ) {
304 4
			$whereToken = $translator->translateCriterion($criterion);
305 4
			$select->where( $whereToken->getSql() );
306 4
			$binds += $whereToken->getBindValues();
307 4
		}
308
309
		// apply the order by clause that can be run on the database
310 4
		if ( !$query->hasRuntimeOrder() ) {
311 4
			foreach( $query->getOrder() as $sort ) {
312 2
				$select->order($translator->translateSort($sort, false)->getSql());
313 2
			}
314 4
		}
315
316
		// if the query is entirely against the database, add limit & offset
317 4
		if ( !$query->isRuntime() && $query->getLimit() ) {
318 2
		    $select->limit($query->getLimit(), $query->getOffset());
319 2
		}
320
321 4
		if (! $query instanceof Xyster_Orm_Query_Report ) {
322
323
		    // add the from clause, joins, and columns
324 1
			$select->from(array($translator->getMain() => $this->getTable()), $this->_selectColumns());
325 1
			foreach( $translator->getFromClause() as $table => $joinToken ) {
326 1
			    $select->joinLeft($table, $joinToken->getSql(), array());
327 1
			    $binds += $joinToken->getBindValues();
328 1
			}
329
330 1
			return $this->_mapSet($db->query($select, $binds));
331
332 0
		} else {
333
334
		    // pretty self explanitory...
335 3
            $select->distinct($query->isDistinct());
336
337 3
	        $fields = array();
338 3
			if ( !$query->isRuntime() ) {
339
340 2
				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 2
				    $quote = ($field instanceof Xyster_Data_Field_Aggregate);
344 2
				    $fieldName = $translator->translateField($field, $quote)->getSql();
345 2
				    if ( $field->getAlias() == $field->getName() ) {
346 1
				        $fields[] = $fieldName;
347 1
				    } else {
348 2
				        $fields[$field->getAlias()] = $fieldName;
349
				    }
350 2
				}
351
352 2
				if ( count($query->getGroup()) ) {
353
				    // add the group clause
354 1
					foreach( $query->getGroup() as $k=>$grp ) {
355 1
						$fieldName = $translator->translateField($grp, false)->getSql();
356 1
						if ( $grp->getAlias() == $grp->getName() ) {
357 1
						    $fields[] = $fieldName;
358 1
						} else {
359 1
						    $fields[$grp->getAlias()] = $fieldName;
360
						}
361 1
						$select->group($fieldName);
362 1
					}
363
					// add the having clause
364 1
					foreach( $query->getHaving() as $k=>$crit ) {
365 1
						$whereToken = $translator->translateCriterion($crit);
366 1
						$select->having($whereToken->getSql());
367 1
						$binds += $whereToken->getBindValues();
368 1
					}
369 1
				}
370
371 2
			} else {
372
			    // it's runtime, just pull back all fields in the main table
373 1
			    $fields = $this->_selectColumns();
374
			}
375
376
			// add the from clause, joins, and columns
377 3
		    $select->from(array($translator->getMain() => $this->getTable()), $fields);
378 3
			foreach( $translator->getFromClause() as $table => $joinToken ) {
379 1
			    $select->joinLeft($table, $joinToken->getSql(), array());
380 1
			    $binds += $joinToken->getBindValues();
381 1
			}
382
383 3
			if ( !$query->isRuntime() ) {
384 2
			    $result = $db->query($select, $binds)->fetchAll(Zend_Db::FETCH_ASSOC);
385 2
				return new Xyster_Data_Set(Xyster_Collection::using($result));
386 0
			} else {
387 1
			    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 9
	    $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 15
        $select = $this->_getAdapter()->select();
410 15
		$select->from(array('t1' => $this->getTable()), $this->_selectColumns());
411
412 15
		return $select;
413
    }
414
415
    /**
416
     * Gets a Mapper Translator object
417
     *
418
     * @return Xyster_Db_Translator
419
     */
420
    protected function _buildTranslator()
421
    {
422 14
        require_once 'Xyster/Db/Translator.php';
423 14
		$translator = new Xyster_Db_Translator($this->_getAdapter());
424 14
		$translator->setRenameCallback(array($this, 'untranslateField'));
425 14
		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 2
	    $translator = $this->_buildTranslator();
437 2
		$token = $translator->translateCriterion($where);
438
439 2
		$stmt = $this->_getAdapter()->prepare('DELETE FROM '
440 2
			. $this->_getAdapter()->quoteIdentifier($this->getTable())
441 2
		    . ' WHERE ' . $token->getSql());
442 2
		$stmt->execute($token->getBindValues());
443 2
		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 12
	    $select = $this->_buildSimpleSelect();
455 12
		$select->limit(1);
456
457 12
		$translator = $this->_buildTranslator();
458 12
	    $token = $translator->translate($where);
459 12
		$select->where($token->getSql());
460
461 12
		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 22
	    if (! $this->_db instanceof Zend_Db_Adapter_Abstract ) {
473 22
            $key = md5($this->getDomain());
474 22
            require_once 'Zend/Registry.php';
475 22
            $db = Zend_Registry::isRegistered($key) ? Zend_Registry::get($key) : null;
476
477 22
            if (!$db instanceof Zend_Db_Adapter_Abstract) {
478 1
                require_once 'Xyster/Orm/Mapper/Exception.php';
479 1
                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 22
            $this->_db = $db;
482 22
	    }
483
484 22
        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 3
	    $db = $this->_getAdapter();
496
497 3
	    $data = array();
498 3
	    foreach( $entity->toArray() as $name => $value ) {
499 3
	        $data[ $this->untranslateField($name) ] = $value;
500 3
	    }
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 3
        $primary = array_map(array($this, 'untranslateField'),
508 3
            $this->getEntityMeta()->getPrimary());
509 3
        $pkIdentity = $primary[0];
510 3
        if ( count($primary) > 0 ) {
511 3
	        $fields = $this->getEntityMeta()->getFields();
512 3
	        foreach( $fields as $field ) {
513
	            /* @var $field Xyster_Orm_Entity_Field */
514 3
	            if ( $field->isIdentity() ) {
515 3
	                $posn = $field->getPrimaryPosition() - 1;
516 3
	                $pkIdentity = $primary[ $posn ];
517 3
	            }
518 3
	        }
519 3
        }
520
521 3
        $sequence = $this->getSequence();
522 3
        if ( !$sequence && $db instanceof Zend_Db_Adapter_Pdo_Pgsql ) {
523 0
            $sequence = $this->getTable() . "_" . $pkIdentity . "_seq";
524 0
        }
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 3
        if ( $sequence && !$data[$pkIdentity]) {
533 0
            $data[$pkIdentity] = $db->nextSequenceId($sequence);
534 0
        }
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 3
        if ( array_key_exists($pkIdentity, $data) && $data[$pkIdentity] === null ) {
541 2
            unset($data[$pkIdentity]);
542 2
        }
543
544 3
        $db->insert($this->getTable(), $data);
545
546 3
        $primaryKey = null;
547 3
        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 2
            $pkData = array_intersect_key($data, array_flip($primary));
556 2
            if (count($primary) == 1) {
557 1
                $primaryKey = current($pkData);
558 1
            } else {
559 1
                $primaryKey = $pkData;
560
            }
561 2
        }
562
563 3
        if (!$sequence) {
564
            /**
565
             * Return the most recent ID generated by an auto-increment column
566
             */
567 3
            $primaryKey = $db->lastInsertId();
568 3
        }
569
570
        /**
571
         * Normalize the result to an array indexed by primary key column(s).
572
         */
573 3
        $newPrimaryKey = is_array($primaryKey) ?
574 3
            $primaryKey : array(current($primary) => $primaryKey);
575
576 3
        foreach( $newPrimaryKey as $name => $value ) {
577 3
            $field = $this->translateField($name);
578 3
            $entity->$field = $value;
579 3
        }
580
581 3
    	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 3
        $entity = $set->getRelatedEntity();
592 3
        $relation = $set->getRelation();
593
594 3
        $leftValues = array();
595 3
        $left = $relation->getLeft();
596 3
        foreach( $this->getEntityMeta()->getPrimary() as $k=>$primary ) {
597 3
            $leftValues[$left[$k]] = $entity->$primary;
598 3
        }
599
600 3
        $rightMap = $this->getFactory()->get($relation->getTo());
601 3
        $right = $relation->getRight();
602 3
        $rightPrimary = $rightMap->getEntityMeta()->getPrimary();
603
604 3
        foreach( $set->getDiffAdded() as $added ) {
605
            /* @var $added Xyster_Orm_Entity */
606 3
            if ( !$added->getBase() ) {
607 1
                $rightMap->save($added);
608 1
            }
609 3
            $values = $leftValues;
610 3
            foreach( $rightPrimary as $k=>$primary ) {
611 3
                $values[$right[$k]] = $added->$primary;
612 3
            }
613 3
            $this->_getAdapter()->insert($relation->getTable(), $values);
614 3
        }
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 3
        $entity = $set->getRelatedEntity();
625 3
        $relation = $set->getRelation();
626
627 3
		$firstCond = array();
628 3
		$left = $relation->getLeft();
629 3
		foreach( $this->getEntityMeta()->getPrimary() as $k=>$primary ) {
630 3
		    $firstCond[] = Xyster_Data_Expression::eq($left[$k], $entity->$primary);
631 3
		}
632 3
		$leftCriteria = Xyster_Data_Criterion::fromArray('AND', $firstCond);
633
634 3
        $rightMap = $this->getFactory()->get($relation->getTo());
635 3
		$right = $relation->getRight();
636 3
		$rightPrimary = $rightMap->getEntityMeta()->getPrimary();
637
638 3
		$diffRemoved = $set->getDiffRemoved();
639 3
		if ( !count($diffRemoved) ) {
640 2
		    return;
641 0
		}
642 1
		$secondCriteria = array();
643 1
		foreach( $diffRemoved as $removed ) {
644 1
		    $secondCond = array();
645 1
		    foreach( $rightPrimary as $k=>$primary ) {
646 1
    		    $secondCond[] = Xyster_Data_Expression::eq($right[$k], $removed->$primary);
647 1
    		}
648 1
    		$secondCriteria[] = Xyster_Data_Criterion::fromArray('AND', $secondCond);
649 1
		}
650 1
		$allSecondCriteria = Xyster_Data_Criterion::fromArray('OR', $secondCriteria);
651
652 1
		$where = Xyster_Data_Junction::all($leftCriteria, $allSecondCriteria);
653 1
		$translator = new Xyster_Db_Translator($this->_getAdapter());
654 1
		$token = $translator->translate($where);
655
656 1
		$stmt = $this->_getAdapter()->prepare('DELETE FROM '
657 1
		    . $this->_getAdapter()->quoteIdentifier($relation->getTable())
658 1
		    . ' WHERE ' . $token->getSql());
659
660 1
		$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 12
	    $return = null;
673
674 12
		if ( $row = $stmt->fetch(Zend_Db::FETCH_ASSOC) ) {
675 11
			$this->_checkPropertyNames($row);
676 11
			$stmt->closeCursor();
677 11
			if ( $entity instanceof Xyster_Orm_Entity ) {
678 8
			    $return = $entity->import($row);
679 8
			} else {
680 11
			    $return = $this->_create($row);
681
			}
682 11
		}
683
684 12
		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 10
		$entities = array();
696 10
		$stmt->setFetchMode(Zend_Db::FETCH_ASSOC);
697 10
		foreach( $stmt->fetchAll() as $k => $row ) {
698 10
			if ( $k<1 ) {
699 10
				$this->_checkPropertyNames($row);
700 10
			}
701 10
			$entities[] = $this->_create($row);
702 10
		}
703 10
		$stmt->closeCursor();
704
705 10
		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 16
	    $columns = array();
716 16
		foreach( $this->getFields() as $name => $v ) {
717 16
			$alias = $this->translateField($name);
718 16
			$columns[$alias] = $name;
719 16
		}
720 16
		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 1
        if (is_string($metadataCache)) {
731 1
            require_once 'Zend/Registry.php';
732 1
            $metadataCache = Zend_Registry::get($metadataCache);
733 1
        }
734
735 1
        if ($metadataCache === null || $metadataCache instanceof Zend_Cache_Core) {
736 1
            return $metadataCache;
737 0
        }
738
739 1
        require_once 'Xyster/Orm/Mapper/Exception.php';
740 1
        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 6
	    $db = $this->_getAdapter();
752
753 6
    	$values = array();
754 6
    	foreach( $entity->getDirtyFields() as $name => $value ) {
755 3
    	    $values[ $this->untranslateField($name) ] = $value;
756 3
    	}
757
758 6
	    $keyNames = $this->getEntityMeta()->getPrimary();
759 6
	    $key = $entity->getPrimaryKey(true);
760
761 6
	    $where = array();
762 6
	    foreach( $keyNames as $name ) {
763 6
	        $sql = $db->quoteIdentifier($this->untranslateField($name)) . ' = ?';
764 6
	        $where[] = $db->quoteInto($sql, $key[$name]);
765 6
    	}
766
767
        // optimistic locking
768 6
    	$lockingField = $this->getOption('locking');
769 6
    	$lockingVersion = 0;
770 6
    	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 6
    	if ( count($values) > 0 ) {
782 3
    	    $rows = $this->_getAdapter()->update($this->getTable(), $values, $where);
783
784
    	    // optimistic locking
785 3
    	    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 3
    	}
794
	}
795
}


Report generated at 2008-01-20T12:13:38-05:00