http://phing.info/

Source Code Coverage

Designed for use with PHPUnit2, Xdebug and Phing.

Methods: 17 LOC: 497 Statements: 150

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


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