http://phing.info/

Source Code Coverage

Designed for use with PHPUnit2, Xdebug and Phing.

Methods: 17 LOC: 507 Statements: 163

Source file Statements Methods Total coverage
Relation.php 95.1% 100.0% 95.6%
   
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 115 2007-10-06 22:42:44Z 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 194
	    $declaringClass = $meta->getEntityName();
135 194
	    $this->_mapFactory = $meta->getMapperFactory();
136
137 194
		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 0
		}
141
142 194
		$class = ( array_key_exists('class', $options) ) ? $options['class'] : null;
143
		// determine the class if not provided
144 194
		if ( !$class && ( $type == 'one' || $type == 'belongs' ) ) {
145 1
			$class = ucfirst($name);
146 194
		} else if ( !$class && ( $type == 'many' || $type == 'joined' ) ) {
147 23
			$class = ( substr($name,-1) == 's' ) ? substr($name, 0, -1) : $name;
148 23
			$class = ucfirst($class);
149 23
		}
150
151 194
		$id = ( array_key_exists('id', $options) ) ? $options['id'] : null;
152
		// get the primary key from the mapper if not provided
153 194
		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 194
		if ( $type != 'joined' && !is_array($id) ) {
161 194
		    $id = array($id);
162 194
		}
163
164 194
		$filters = ( array_key_exists('filters', $options) ) ?
165 194
		    $options['filters'] : null;
166 194
		if ( $filters ) {
167 44
		    require_once 'Xyster/Orm/Query/Parser.php';
168 44
		    $parser = new Xyster_Orm_Query_Parser($this->_mapFactory);
169 44
			$filters = $parser->parseCriterion($filters);
170 44
		}
171
172 194
	    $this->_name = $name;
173 194
		$this->_from = $declaringClass;
174 194
		$this->_to = $class;
175 194
		$this->_id = $id;
176 194
		$this->_filters = $filters;
177 194
		$this->_type = $type;
178
179 194
		if ( $this->isCollection() ) {
180 194
            $this->_onUpdate = ( isset($options['onUpdate']) ) ? $options['onUpdate'] : self::ACTION_NONE;
181 194
            $this->_onDelete = ( isset($options['onDelete']) ) ? $options['onDelete'] : self::ACTION_NONE;
182 194
		}
183
184 194
		if ( $type == 'joined' ) {
185 194
			$leftMap = $this->_mapFactory->get($declaringClass);
186 194
			$rightMap = $this->_mapFactory->get($class);
187 194
			$leftMeta = $leftMap->getEntityMeta();
188 194
			$rightMeta = $rightMap->getEntityMeta();
189
190 194
			$this->_joinTable = array_key_exists('table', $options) ?
191 194
			    $options['table'] : $leftMap->getTable().'_'.$rightMap->getTable();
192
193 194
			if ( isset($options['left']) ) {
194
			    // make sure number of fields matches number of primary keys
195 173
			    $leftCount = is_array($options['left']) ?
196 173
			        count($options['left']) : 1;
197 173
			    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 0
			    }
201 173
			    $this->_joinLeft = (array) $options['left'];
202 173
			} else {
203 194
			    $this->_joinLeft = array_map(array($leftMap,'untranslateField'), $leftMeta->getPrimary());
204
			}
205
206 194
			if ( isset($options['right']) ) {
207
			    // make sure number of fields matches number of primary keys
208 173
			    $rightCount = is_array($options['right']) ?
209 173
			        count($options['right']) : 1;
210 173
			    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 0
			    }
214 173
			    $this->_joinRight = (array) $options['right'];
215 173
			} else {
216 194
			    $this->_joinRight = array_map(array($rightMap,'untranslateField'), $rightMeta->getPrimary());
217
			}
218 194
		}
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 16
        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 4
        return $this->_joinLeft;
259
    }
260
261
    /**
262
     * Gets the name of the relationship
263
     *
264
     * @return string
265
     */
266
    public function getName()
267
    {
268 12
        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 2
        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 1
        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 4
        return $this->_joinRight;
299
    }
300
301
    /**
302
     * Gets the join table name
303
     *
304
     * @return string
305
     */
306
    public function getTable()
307
    {
308 4
        return $this->_joinTable;
309
    }
310
311
    /**
312
     * Gets the class of the relationship
313
     *
314
     * @return string
315
     */
316
    public function getTo()
317
    {
318 31
        return $this->_to;
319
    }
320
321
    /**
322
     * Gets the type of the relationship
323
     *
324
     * @return string
325
     */
326
    public function getType()
327
    {
328 22
        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 12
        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 11
        if ( $this->_reverse === null ) {
345 8
            $meta = $this->_mapFactory->getEntityMeta($this->_to);
346 8
	        foreach( $meta->getRelations() as $relation ) {
347
	            /* @var $relation Xyster_Orm_Relation */
348 7
	            if ( $relation->_type == 'belongs'
349 7
					&& $relation->_to == $this->_from
350 7
	                && $relation->_id == $this->_id ) {
351 7
	                $this->_reverse = $relation;
352 7
	                break;
353 0
	            }
354 3
	        }
355
	        // so we don't bother searching on subsequent calls
356 8
	        if ( $this->_reverse === null ) {
357 1
	            $this->_reverse = false;
358 1
	        }
359 8
        }
360
361 11
        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 9
		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 194
        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 16
		$linked = null;
398 16
		$manager = $this->_mapFactory->getManager();
399
400 16
		if ( !$this->isCollection() ) {
401
402
		    /*
403
		     * A one-to-one
404
		     */
405 3
            $key = array();
406 3
            $keys = $this->_mapFactory->getEntityMeta($this->_to)->getPrimary();
407 3
            foreach( $this->_id as $i => $name ) {
408 3
                $key[$keys[$i]] = $entity->$name;
409 3
            }
410 3
		    $linked = $manager->get($this->_to, $key);
411
412 3
		} 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 14
            $primaryKey = $entity->getPrimaryKey(true);
419
420 14
			if ( count($primaryKey) && current($primaryKey) ) {
421
422 7
			    if ( $this->_type == 'many' ) {
423
424 3
           		    $find = array();
425 3
        		    $id = (array) $this->_id;
426 3
        		    $i = 0;
427 3
                    require_once 'Xyster/Data/Expression.php';
428 3
        		    foreach( $primaryKey as $keyName => $keyValue ) {
429 3
                        $find[] = Xyster_Data_Expression::eq($id[$i], $keyValue);
430 3
                        $i++;
431 3
                    }
432 3
                    $criteria = Xyster_Data_Criterion::fromArray('AND', $find);
433
434 3
				    if ( $this->_filters ) {
435 3
				        require_once 'Xyster/Data/Junction.php';
436 3
				        $criteria = Xyster_Data_Junction::all($criteria,
437 3
				            $this->_filters);
438 3
				    }
439 3
					$linked = $manager->findAll($this->_to, $criteria);
440
441 7
				} else if ( $this->_type == 'joined' ) {
442
443 4
					$linked = $manager->getJoined($entity, $this);
444
445 4
				}
446
447 7
			} else {
448
449 7
			    $linked = $this->_mapFactory->get($this->_to)->getSet();
450
451
			}
452
453 14
			$linked->relateTo($this, $entity);
454
455
		}
456
457 16
		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 12
        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 11
        $fromClass = $this->_from;
477 11
        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 10
        $toClass = $this->_to;
484 10
        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 9
        if ( $this->hasBelongsTo() ) {
491 9
            $relation = $this->getReverse();
492 9
            $name = $relation->getName();
493 9
            $to->$name = $from;
494
495 9
            $primary = $from->getPrimaryKey(true);
496 9
            if ( $primary ) {
497 4
                $keyNames = array_keys($primary);
498 4
                $foreignNames = $relation->getId();
499 4
                for( $i=0; $i<count($keyNames); $i++ ) {
500 4
                    $keyName = $keyNames[$i];
501 4
                    $foreignName = $foreignNames[$i];
502 4
                    $to->$foreignName = $from->$keyName;
503 4
                }
504 4
            }
505 9
        }
506
    }
507
}


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