http://phing.info/

Source Code Coverage

Designed for use with PHPUnit2, Xdebug and Phing.

Methods: 17 LOC: 492 Statements: 150

Source file Statements Methods Total coverage
Relation.php 96.7% 100.0% 97.0%
   
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: Relation.php 202 2008-01-20 16:20:09Z doublecompile $
15
 */
16
/**
17
 * A factory and instance for entity relationships
18
 *
19
 * @category  Xyster
20
 * @package   Xyster_Orm
21
 * @copyright Copyright (c) 2007-2008 Irrational Logic (http://irrationallogic.net)
22
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
23
 */
24
class Xyster_Orm_Relation
25
{
26
    const ACTION_NONE = 0x000000;
27
    const ACTION_CASCADE = 0x000001;
28
    const ACTION_SET_NULL = 0x000002;
29
    const ACTION_REMOVE = 0x000003;
30
31
    /**
32
     * The type of relation; one of ('one','belongs','many','joined')
33
     *
34
     * @var string
35
     */
36
    protected $_type;
37
    /**
38
     * The name of the relation
39
     *
40
     * @var string
41
     */
42
    protected $_name;
43
    /**
44
     * The class that defines the relation
45
     *
46
     * @var string
47
     */
48
    protected $_from;
49
    /**
50
     * The class on the target end of the relation
51
     *
52
     * @var string
53
     */
54
    protected $_to;
55
    /**
56
     * The primary key field(s) involved in the relation
57
     *
58
     * @var array
59
     */
60
    protected $_id;
61
    /**
62
     * Any filters associated with the relation
63
     *
64
     * @var Xyster_Data_Criterion
65
     */
66
    protected $_filters;
67
    /**
68
     * The activity to perform on entity delete
69
     *
70
     * @var string
71
     */
72
    protected $_onDelete = self::ACTION_NONE;
73
    /**
74
     * The activity to perform when a primary key is changed
75
     *
76
     * @var string
77
     */
78
    protected $_onUpdate = self::ACTION_NONE;
79
    /**
80
     * In a join relationship, this is the table linking the two entities
81
     *
82
     * @var string
83
     */
84
    protected $_joinTable;
85
    /**
86
     * The join table field linking to the left entity's primary key fields
87
     *
88
     * @var array
89
     */
90
    protected $_joinLeft;
91
    /**
92
     * The join table field linking to the right entity's primary key fields
93
     *
94
     * @var array
95
     */
96
    protected $_joinRight;
97
98
    /**
99
     * The "belongs" relation opposite a "many"
100
     *
101
     * @var Xyster_Orm_Relation
102
     */
103
    protected $_reverse;
104
105
    /**
106
     * The mapper factory
107
     *
108
     * @var Xyster_Orm_Mapper_Factory_Interface
109
     */
110
    protected $_mapFactory;
111
112
    /**
113
     * The acceptable types
114
     *
115
     * @var array
116
     */
117
	static private $_types = array('belongs', 'one', 'many', 'joined');
118
119
    /**
120
	 * Create a new relationship
121
	 *
122
	 * @param Xyster_Orm_Mapper $map The mapper of the class owning the relation
123
	 * @param string $type  One of belongs, one, many, joined
124
	 * @param string $name  The name of the relationship
125
	 * @param array $options  An array of options
126
	 */
127
	public function __construct( Xyster_Orm_Entity_Meta $meta, $type, $name, array $options = array() )
128
	{
129 203
	    $declaringClass = $meta->getEntityName();
130 203
	    $this->_mapFactory = $meta->getMapperFactory();
131
132 203
		if ( !in_array($type, self::$_types) ) {
133 1
			require_once 'Xyster/Orm/Relation/Exception.php';
134 1
			throw new Xyster_Orm_Relation_Exception("'" . $type . "' is an invalid relationship type");
135
		}
136
137 203
		$class = ( array_key_exists('class', $options) ) ? $options['class'] : null;
138
		// determine the class if not provided
139 203
		if ( !$class && ( $type == 'one' || $type == 'belongs' ) ) {
140 1
			$class = ucfirst($name);
141 203
		} else if ( !$class && ( $type == 'many' || $type == 'joined' ) ) {
142 24
			$class = ( substr($name,-1) == 's' ) ? substr($name, 0, -1) : $name;
143 24
			$class = ucfirst($class);
144 24
		}
145
146 203
		$id = ( array_key_exists('id', $options) ) ? $options['id'] : null;
147
		// get the primary key from the mapper if not provided
148 203
		if ( !$id && $type != 'joined' ) {
149
		    // if it's a one-to-many, we're just using the declaring class' key
150
	        // if it's a one-to-one, we need the related class' key
151 4
		    $meta = ( $type == 'many' ) ? $meta :
152 4
		        $this->_mapFactory->getEntityMeta($class);
153 4
	        $id = $meta->getPrimary();
154 4
		}
155 203
		if ( $type != 'joined' && !is_array($id) ) {
156 203
		    $id = array($id);
157 203
		}
158
159 203
		$filters = ( array_key_exists('filters', $options) ) ?
160 203
		    $options['filters'] : null;
161 203
		if ( $filters ) {
162 48
		    require_once 'Xyster/Orm/Query/Parser.php';
163 48
		    $parser = new Xyster_Orm_Query_Parser($this->_mapFactory);
164 48
			$filters = $parser->parseCriterion($filters);
165 48
		}
166
167 203
	    $this->_name = $name;
168 203
		$this->_from = $declaringClass;
169 203
		$this->_to = $class;
170 203
		$this->_id = $id;
171 203
		$this->_filters = $filters;
172 203
		$this->_type = $type;
173
174 203
		if ( $this->isCollection() ) {
175 203
            $this->_onUpdate = ( isset($options['onUpdate']) ) ? $options['onUpdate'] : self::ACTION_NONE;
176 203
            $this->_onDelete = ( isset($options['onDelete']) ) ? $options['onDelete'] : self::ACTION_NONE;
177 203
		}
178
179 203
		if ( $type == 'joined' ) {
180 203
			$leftMap = $this->_mapFactory->get($declaringClass);
181 203
			$rightMap = $this->_mapFactory->get($class);
182 203
			$leftMeta = $leftMap->getEntityMeta();
183 203
			$rightMeta = $rightMap->getEntityMeta();
184
185 203
			$this->_joinTable = array_key_exists('table', $options) ?
186 203
			    $options['table'] : $leftMap->getTable().'_'.$rightMap->getTable();
187
188 203
			if ( isset($options['left']) ) {
189
			    // make sure number of fields matches number of primary keys
190 181
			    $leftCount = is_array($options['left']) ?
191 181
			        count($options['left']) : 1;
192 181
			    if ( $leftCount != count($leftMeta->getPrimary()) ) {
193 1
			        require_once 'Xyster/Orm/Relation/Exception.php';
194 1
			        throw new Xyster_Orm_Relation_Exception('Number of "left" keys do not match number of keys in left table');
195
			    }
196 181
			    $this->_joinLeft = (array) $options['left'];
197 181
			} else {
198 203
			    $this->_joinLeft = array_map(array($leftMap,'untranslateField'), $leftMeta->getPrimary());
199
			}
200
201 203
			if ( isset($options['right']) ) {
202
			    // make sure number of fields matches number of primary keys
203 181
			    $rightCount = is_array($options['right']) ?
204 181
			        count($options['right']) : 1;
205 181
			    if ( $rightCount != count($rightMeta->getPrimary()) ) {
206 1
			        require_once 'Xyster/Orm/Relation/Exception.php';
207 1
			        throw new Xyster_Orm_Relation_Exception('Number of "right" keys do not match number of keys in right table');
208
			    }
209 181
			    $this->_joinRight = (array) $options['right'];
210 181
			} else {
211 203
			    $this->_joinRight = array_map(array($rightMap,'untranslateField'), $rightMeta->getPrimary());
212
			}
213 203
		}
214
	}
215
216
    /**
217
     * Gets the filters
218
     *
219
     * @return Xyster_Data_Criterion
220
     */
221
    public function getFilters()
222
    {
223 4
        return $this->_filters;
224
    }
225
226
    /**
227
     * Gets the class that owns the relationship
228
     *
229
     * @return string
230
     */
231
    public function getFrom()
232
    {
233 2
        return $this->_from;
234
    }
235
236
    /**
237
     * Gets the ids involved
238
     *
239
     * @return array
240
     */
241
    public function getId()
242
    {
243 20
        return $this->_id;
244
    }
245
246
    /**
247
     * Gets the left entity column name in the join table
248
     *
249
     * @return array
250
     */
251
    public function getLeft()
252
    {
253 5
        return $this->_joinLeft;
254
    }
255
256
    /**
257
     * Gets the name of the relationship
258
     *
259
     * @return string
260
     */
261
    public function getName()
262
    {
263 30
        return $this->_name;
264
    }
265
266
    /**
267
     * Gets the action type to perform onDelete
268
     *
269
     * @return int
270
     */
271
    public function getOnDelete()
272
    {
273 3
        return $this->_onDelete;
274
    }
275
276
    /**
277
     * Gets the action type to perform onUpdate
278
     *
279
     * @return int
280
     */
281
    public function getOnUpdate()
282
    {
283 2
        return $this->_onUpdate;
284
    }
285
286
    /**
287
     * Gets the right entity column name in the join table
288
     *
289
     * @return array
290
     */
291
    public function getRight()
292
    {
293 5
        return $this->_joinRight;
294
    }
295
296
    /**
297
     * Gets the join table name
298
     *
299
     * @return string
300
     */
301
    public function getTable()
302
    {
303 5
        return $this->_joinTable;
304
    }
305
306
    /**
307
     * Gets the class of the relationship
308
     *
309
     * @return string
310
     */
311
    public function getTo()
312
    {
313 36
        return $this->_to;
314
    }
315
316
    /**
317
     * Gets the type of the relationship
318
     *
319
     * @return string
320
     */
321
    public function getType()
322
    {
323 27
        return $this->_type;
324
    }
325
326
    /**
327
     * If this relation is a 'many', this returns the 'belongs' relation
328
     *
329
     * @return Xyster_Orm_Relation
330
     * @throws Xyster_Orm_Relation_Exception
331
     */
332
    public function getReverse()
333
    {
334 16
        if ( $this->_type != 'many' ) {
335 1
            require_once 'Xyster/Orm/Relation/Exception.php';
336 1
            throw new Xyster_Orm_Relation_Exception('This method is only intended for "many" relations');
337 0
        }
338
339 15
        if ( $this->_reverse === null ) {
340 12
            $meta = $this->_mapFactory->getEntityMeta($this->_to);
341 12
	        foreach( $meta->getRelations() as $relation ) {
342
	            /* @var $relation Xyster_Orm_Relation */
343 11
	            if ( $relation->_type == 'belongs'
344 11
					&& $relation->_to == $this->_from
345 11
	                && $relation->_id == $this->_id ) {
346 11
	                $this->_reverse = $relation;
347 11
	                break;
348 0
	            }
349 7
	        }
350
	        // so we don't bother searching on subsequent calls
351 12
	        if ( $this->_reverse === null ) {
352 1
	            $this->_reverse = false;
353 1
	        }
354 12
        }
355
356 15
        return $this->_reverse;
357
    }
358
359
	/**
360
	 * Checks to see if the class referenced by this relationship has a belongs
361
	 *
362
	 * This relationship must be a 'many' to return true, and the target class
363
	 * must have a 'belongs' relationship pointing back to this declaring class
364
	 *
365
	 * @return boolean
366
	 */
367
	public function hasBelongsTo()
368
	{
369 13
		return $this->getReverse() instanceof self;
370
	}
371
372
    /**
373
     * This returns true if the type is 'many' or 'joined', false otherwise
374
     *
375
     * @return boolean
376
     */
377
    public function isCollection()
378
    {
379 203
        return $this->_type == 'many' || $this->_type == 'joined';
380
    }
381
382
383
	/**
384
	 * Loads the {@link Xyster_Orm_Entity} or {@link Xyster_Orm_Set}
385
	 *
386
	 * @param Xyster_Orm_Entity $entity The entity to use
387
	 * @param string $name
388
	 * @return mixed
389
	 */
390
	public function load( Xyster_Orm_Entity $entity )
391
	{
392 21
		$linked = null;
393 21
		$manager = $this->_mapFactory->getManager();
394
395 21
		if ( !$this->isCollection() ) {
396
397
		    /*
398
		     * A one-to-one
399
		     */
400 5
            $key = array();
401 5
            $keys = $this->_mapFactory->getEntityMeta($this->_to)->getPrimary();
402 5
            foreach( $this->_id as $i => $name ) {
403 5
                $key[$keys[$i]] = $entity->$name;
404 5
            }
405 5
		    $linked = $manager->get($this->_to, $key);
406
407 5
		} else {
408
409
		    /*
410
		     * A one-to-many or many-to-many with filters
411
		     * We will use the base primary key value
412
		     */
413 19
            $primaryKey = $entity->getPrimaryKey(true);
414
415 19
			if ( count($primaryKey) && current($primaryKey) ) {
416
417 12
			    if ( $this->_type == 'many' ) {
418
419 7
           		    $find = array();
420 7
        		    $id = (array) $this->_id;
421 7
        		    $i = 0;
422 7
                    require_once 'Xyster/Data/Expression.php';
423 7
        		    foreach( $primaryKey as $keyName => $keyValue ) {
424 7
                        $find[] = Xyster_Data_Expression::eq($id[$i], $keyValue);
425 7
                        $i++;
426 7
                    }
427 7
                    $criteria = Xyster_Data_Criterion::fromArray('AND', $find);
428
429 7
				    if ( $this->_filters ) {
430 6
				        require_once 'Xyster/Data/Junction.php';
431 6
				        $criteria = Xyster_Data_Junction::all($criteria,
432 6
				            $this->_filters);
433 6
				    }
434 7
					$linked = $manager->findAll($this->_to, $criteria);
435
436 12
				} else if ( $this->_type == 'joined' ) {
437
438 6
					$linked = $manager->getJoined($entity, $this);
439
440 6
				}
441
442 12
			} else {
443
444 7
			    $linked = $this->_mapFactory->get($this->_to)->getSet();
445
446
			}
447
448 19
			$linked->relateTo($this, $entity);
449
450
		}
451
452 21
		return $linked;
453
	}
454
455
    /**
456
     * Relates one entity to another (if one-to-one)
457
     *
458
     * @param Xyster_Orm_Entity $from The entity that owns the many set
459
     * @param Xyster_Orm_Entity $to An entity in the many set
460
     * @throws Xyster_Orm_Relation_Exception if $from or $to are of the wrong type
461
     */
462
    public function relate( Xyster_Orm_Entity $from, Xyster_Orm_Entity $to )
463
    {
464
        // make sure this relation type is 'many'
465 16
        if ( $this->_type != 'many' ) {
466 1
            require_once 'Xyster/Orm/Relation/Exception.php';
467 1
            throw new Xyster_Orm_Relation_Exception('This can only be done with "many" relations');
468 0
        }
469
470
        // make sure the $from object is the $this->_from class
471 15
        $fromClass = $this->_from;
472 15
        if (! $from instanceof $fromClass ) {
473 1
            require_once 'Xyster/Orm/Relation/Exception.php';
474 1
            throw new Xyster_Orm_Relation_Exception('$from must be an instance of '.$fromClass);
475 0
        }
476
477
        // make sure the $to object is the $this->_to class
478 14
        $toClass = $this->_to;
479 14
        if (! $to instanceof $toClass ) {
480 1
            require_once 'Xyster/Orm/Relation/Exception.php';
481 1
            throw new Xyster_Orm_Relation_Exception('$to must be an instance of '.$toClass);
482 0
        }
483
484
        // there's only work to do if there's a belongsTo relationship
485 13
        if ( $this->hasBelongsTo() ) {
486 13
            $relation = $this->getReverse();
487 13
            $name = $relation->getName();
488 13
            $to->$name = $from;
489
            // no need to set $to's foreign key - it will do that itself
490 13
        }
491
    }
492
}


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