http://phing.info/

Source Code Coverage

Designed for use with PHPUnit2, Xdebug and Phing.

Methods: 19 LOC: 487 Statements: 127

Source file Statements Methods Total coverage
Meta.php 93.7% 100.0% 94.5%
   
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: Meta.php 220 2008-02-09 18:04:52Z doublecompile $
15
 */
16
/**
17
 * @see Xyster_Orm_Entity_Field
18
 */
19 1
require_once 'Xyster/Orm/Entity/Field.php';
20
/**
21
 * @see Xyster_Orm_Xsql
22
 */
23 1
require_once 'Xyster/Orm/Xsql.php';
24
/**
25
 * A helper for meta information about entities
26
 *
27
 * @category  Xyster
28
 * @package   Xyster_Orm
29
 * @copyright Copyright (c) 2007-2008 Irrational Logic (http://irrationallogic.net)
30
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
31
 */
32
class Xyster_Orm_Entity_Meta
33
{
34
	/**
35
	 * @var string
36
	 */
37
    protected $_class;
38
39
    /**
40
     * Cache for entity fields
41
     *
42
     * @var array
43
     */
44
    protected $_fields = array();
45
46
    /**
47
     * The mapper factory
48
     *
49
     * @var Xyster_Orm_Mapper_Factory_Interface
50
     */
51
    protected $_mapFactory;
52
53
    /**
54
     * A cache for all entity members
55
     *
56
     * @var array
57
     */
58
    protected $_members = array();
59
60
    /**
61
     * The field names of the primary key
62
     *
63
     * @var array
64
     */
65
    protected $_primary = array();
66
67
    /**
68
     * The relation objects
69
     *
70
     * @var array
71
     */
72
    protected $_relations = array();
73
74
    /**
75
     * The cache for runtime column lookups
76
     *
77
     * @var array
78
     */
79
    protected $_runtime = array();
80
81
    /**
82
     * Creates a new Entity Metadata helper
83
     *
84
     * @param Xyster_Orm_Mapper $map
85
     */
86
    public function __construct( Xyster_Orm_Mapper_Interface $map )
87
    {
88 286
        $this->_class = $map->getEntityName();
89 286
        $this->_mapFactory = $map->getFactory();
90
91 286
        foreach( $map->getFields() as $name => $values ) {
92 286
            $translated = $map->translateField($name);
93 286
            $field = new Xyster_Orm_Entity_Field($translated, $values);
94 286
            $this->_fields[$translated] = $field;
95 286
            if ( $field->isPrimary() ) {
96 286
                $this->_primary[] = $translated;
97 286
            }
98 286
        }
99
    }
100
101
    /**
102
     * Asserts a field's presence in the entity class' members
103
     *
104
     * @param string $field
105
     * @throws Xyster_Orm_Entity_Exception
106
     */
107
    public function assertValidField( $field )
108
    {
109 44
    	if ( $field instanceof Xyster_Data_Field ) {
110 41
    		$field = trim($field->getName());
111 41
    	} else {
112 15
    		$field = trim($field);
113 15
	        require_once 'Xyster/Data/Field/Aggregate.php';
114 15
	        $matches = Xyster_Data_Field_Aggregate::match($field);
115 15
	        if ( count($matches) ) {
116 1
	            $field = trim($matches["field"]);
117 1
	        }
118
    	}
119
120 44
        $calls = Xyster_Orm_Xsql::splitArrow($field);
121
        /*
122
            for composite references (i.e.  supervisor->name )
123
            - check each column exists in its container
124
        */
125 44
        if ( count($calls) > 1 ) {
126
127 13
            $container = $this->_class;
128 13
            $meta = $this;
129 13
            foreach( $calls as $k=>$v ) {
130 13
                $meta->assertValidField($v);
131 13
                if ( $meta->isRelation($v) ) {
132 13
                    $details = $meta->getRelation($v);
133 13
                    if ( !$details->isCollection() ) {
134 13
                        $container = $details->getTo();
135 13
                        $meta = $this->_mapFactory->getEntityMeta($container);
136 13
                    } else {
137 1
                        break;
138
                    }
139 13
                } else {
140 13
                    break;
141
                }
142 13
            }
143
144 13
        } else {
145
146
            /*
147
                for method calls
148
                - check method exists in class
149
                - check any method parameters that may themselves be members
150
            */
151 44
            if ( preg_match("/^(?P<name>[a-z0-9_]+)(?P<meth>\((?P<args>[\w\W]*)\))$/i", $field, $matches) ) {
152 19
                if ( !in_array($matches['name'], $this->getMembers()) ) {
153 1
                    require_once 'Xyster/Orm/Entity/Exception.php';
154 1
                    throw new Xyster_Orm_Entity_Exception($matches['name'] . ' is not a member of the ' . $this->_class . ' class' );
155 0
                }
156 19
                if ( strlen(trim($matches['args'])) ) {
157 9
                    foreach( Xyster_Orm_Xsql::splitComma($matches['args']) as $v ) {
158 9
                        if ( Xyster_Orm_Xsql::isValidField($v) ) {
159 1
                        	$this->assertValidField($v);
160 1
                        }
161 9
                    }
162 9
                }
163
            /*
164
                for properties and relationships
165
                - check column exists in class
166
            */
167 44
            } else if ( !in_array($field, $this->getMembers()) ) {
168 1
                require_once 'Xyster/Orm/Entity/Exception.php';
169 1
                throw new Xyster_Orm_Entity_Exception($field . ' is not a member of the ' . $this->_class . ' class');
170 0
            }
171
        }
172
    }
173
174
    /**
175
     * Creates a 'one to one' relationship for entities on the 'many' end of a 'one to many' relationship
176
     *
177
     * Options can contain the following values:
178
     *
179
     * <dl>
180
     * <dt>class</dt><dd>The foreign class. The relation name by default</dd>
181
     * <dt>id</dt><dd>The name of the foreign key field(s) on the declaring
182
     * entity.  This should either be an array (if multiple) or a string (if
183
     * one).  By default, this is <var>class</var>Id</dd>
184
     * <dt>filters</dt><dd>In XSQL, any Criteria that should be used
185
     * against the entity to be loaded</dd>
186
     * </dl>
187
     *
188
     * @param string $name  The name of the relationship
189
     * @param array $options  An array of options
190
     * @return Xyster_Orm_Relation  The relationship created
191
     */
192
    public function belongsTo( $name, array $options = array() )
193
    {
194 266
        return $this->_baseCreate('belongs', $name, $options);
195
    }
196
197
    /**
198
     * Gets the class name of the entity
199
     *
200
     * @return string The class name
201
     */
202
    public function getEntityName()
203
    {
204 286
        return $this->_class;
205
    }
206
207
    /**
208
     * Gets the names of the fields for the entity
209
     *
210
     * @return array An array of field names
211
     */
212
    public function getFieldNames()
213
    {
214 191
        return array_keys($this->_fields);
215
    }
216
217
    /**
218
     * Gets the fields for the entity
219
     *
220
     * @return array An array of {@link Xyster_Orm_Entity_Field} objects
221
     */
222
    public function getFields()
223
    {
224 27
        return $this->_fields;
225
    }
226
227
    /**
228
     * Gets the mapper factory
229
     *
230
     * @return Xyster_Orm_Mapper_Factory_Interface
231
     */
232
    public function getMapperFactory()
233
    {
234 266
        return $this->_mapFactory;
235
    }
236
237
    /**
238
     * Gets all available class members (fields, relations, and methods)
239
     *
240
     * @return array
241
     */
242
    public function getMembers()
243
    {
244 45
        if ( !$this->_members ) {
245 45
            $this->_members = array_merge($this->_members, array_keys($this->_fields));
246 45
            $this->_members = array_merge($this->_members, $this->getRelationNames());
247 45
            $this->_members = array_merge($this->_members, get_class_methods($this->_class));
248 45
        }
249 45
        return $this->_members;
250
    }
251
252
    /**
253
     * Gets an array containing the field name or names for the primary key
254
     *
255
     * @return array  The field names
256
     */
257
    public function getPrimary()
258
    {
259 274
        return array_values($this->_primary);
260
        // so current() can be called without reset()ing the array
261
    }
262
263
    /**
264
     * Gets the relationship by name
265
     *
266
     * @param string $name The name of the relationship
267
     * @return Xyster_Orm_Relation
268
     * @throws Xyster_Orm_Relation_Exception if the relationship name is invalid
269
     */
270
    public function getRelation( $name )
271
    {
272 84
        if ( !$this->isRelation($name) ) {
273 1
            require_once 'Xyster/Orm/Relation/Exception.php';
274 1
            throw new Xyster_Orm_Relation_Exception("'" . $name
275 1
                . "' is not a valid relation for the '" . $this->_class . "' class");
276 0
        }
277
278 83
        return $this->_relations[$name];
279
    }
280
281
    /**
282
     * Gets the names of all relations defined for this entity
283
     *
284
     * @return array The names of defined relations
285
     */
286
    public function getRelationNames()
287
    {
288 46
        return array_keys($this->_relations);
289
    }
290
291
    /**
292
     * Gets the relations for the entity
293
     *
294
     * @return array An array of {@link Xyster_Orm_Relation} objects
295
     */
296
    public function getRelations()
297
    {
298 66
        return $this->_relations;
299
    }
300
301
	/**
302
	 * Creates a 'one to one' relationship
303
	 *
304
	 * Options can contain the following values:
305
	 *
306
	 * <dl>
307
	 * <dt>class</dt><dd>The foreign class. The relation name by default</dd>
308
	 * <dt>id</dt><dd>The name of the foreign key field(s) on the declaring
309
	 * entity.  This should either be an array (if multiple) or a string (if
310
	 * one).  By default, this is <var>class</var>Id</dd>
311
	 * <dt>filters</dt><dd>In XSQL, any Criteria that should be used
312
	 * against the entity to be loaded</dd>
313
	 * </dl>
314
	 *
315
	 * @param string $name  The name of the relationship
316
	 * @param array $options  An array of options
317
	 * @return Xyster_Orm_Relation  The relationship created
318
	 */
319
	public function hasOne( $name, array $options = array() )
320
	{
321 1
		return $this->_baseCreate('one', $name, $options);
322
	}
323
324
	/**
325
	 * Creates a 'one to many' relationship
326
	 *
327
	 * Options can contain the following values:
328
	 *
329
	 * <dl>
330
	 * <dt>class</dt><dd>The foreign class.  The relation name minus a trailing
331
	 * 's' by default</dd>
332
	 * <dt>id</dt><dd>The name of the foreign key field(s) on the related
333
	 * entity.  This should either be an array (if multiple) or a string (if
334
	 * one).  By default, this is <var>class</var>Id</dd>
335
	 * <dt>filters</dt><dd>In XSQL, any Criteria that should be used
336
	 * against the entities to be loaded</dd>
337
	 * </dl>
338
	 *
339
	 * @param string $name  The name of the relationship
340
	 * @param array $options  An array of options
341
	 * @return Xyster_Orm_Relation  The relationship created
342
	 */
343
	public function hasMany( $name, array $options = array() )
344
	{
345 53
		return $this->_baseCreate('many', $name, $options);
346
	}
347
348
	/**
349
	 * Creates a 'many to many' relationship
350
	 *
351
	 * <dl>
352
	 * <dt>class</dt><dd>The class of entity to load through the join table. It
353
	 * will be the relationship name minus a trailing 's' by default.</dd>
354
	 * <dt>table</dt><dd>The join table name.  By default this will be
355
	 * <var>declaring_class</var>_<var>class</var></dd>
356
	 * <dt>left</dt><dd>The column(s) in the join table referencing the
357
	 * declaringClass entity. By default: <var>declaring_class</var>_id</dd>
358
	 * <dt>right</dt><dd>The column(s) in the join table referencing the
359
	 * foreign entity.  By default, it's <var>class_name</var>_id</dd>
360
	 * <dt>filters</dt><dd>In XSQL, any Criteria that should be used
361
	 * against the join table.  Column names should be specified in the format
362
	 * native to the data store (i.e. with underscores, not camelCase)</dd>
363
	 * </dl>
364
	 *
365
	 * @param string $name  The name of the relationship
366
	 * @param array $options  An array of options
367
	 * @return Xyster_Orm_Relation  The relationship created
368
	 */
369
	public function hasJoined( $name, array $options = array() )
370
	{
371 266
		return $this->_baseCreate('joined', $name, $options);
372
	}
373
374
	/**
375
     * Whether the entity has a relationship with the name supplied
376
     *
377
     * @param string $name The name of the relationship
378
     * @return boolean
379
     */
380
	public function isRelation( $name )
381
	{
382 86
	    return array_key_exists($name, $this->_relations);
383
	}
384
385
    /**
386
     * Verifies if a {@link Xyster_Data_Symbol} is runtime
387
     *
388
     * @param Xyster_Data_Symbol $object
389
     * @param string $class
390
     * @return boolean
391
     */
392
    public function isRuntime( Xyster_Data_Symbol $object )
393
    {
394 48
        if ( $object instanceof Xyster_Data_Criterion ) {
395
396 32
            foreach( Xyster_Data_Criterion::getFields($object) as $v ) {
397 32
                if ($this->isRuntime($v)) {
398 15
                    return true;
399 0
                }
400 28
            }
401 28
            return false;
402
403 48
        } else if ( $object instanceof Xyster_Data_Field ) {
404
405 47
            $name = $object->getName();
406 47
	        if ( !isset($this->_runtime[$name]) ) {
407 47
	            $this->_runtime[$name] = $this->_isRuntime($object);
408 47
	        }
409 47
	        return $this->_runtime[$name];
410
411 19
        } else if ( $object instanceof Xyster_Data_Sort ) {
412
413 18
            return $this->isRuntime($object->getField());
414
415 0
        }
416
417 1
        require_once 'Xyster/Orm/Exception.php';
418 1
        throw new Xyster_Orm_Exception('Unexpected type: ' . gettype($object));
419
    }
420
421
    /**
422
     * Base creator method
423
     *
424
     * @param string $type The type of the relationship
425
     * @param string $name The name of the relationship
426
     * @param array $options An array of options
427
     * @return Xyster_Orm_Relation
428
     * @throws Xyster_Orm_Relation_Exception if the relationship is already defined
429
     */
430
    protected function _baseCreate( $type, $name, array $options )
431
    {
432 266
        $declaringClass = $this->_class;
433
434 266
        if ( array_key_exists($name, $this->_relations) ) {
435 1
            require_once 'Xyster/Orm/Relation/Exception.php';
436 1
            throw new Xyster_Orm_Relation_Exception("The relationship '" . $name . "' already exists");
437 0
        }
438
439 266
        require_once 'Xyster/Orm/Relation.php';
440 266
        $this->_relations[$name] = new Xyster_Orm_Relation($this, $type, $name, $options);
441
442 266
        return $this->_relations[$name];
443
    }
444
445
    /**
446
     * Returns if a field is runtime
447
     *
448
     * @param Xyster_Data_Field $field
449
     * @return boolean
450
     */
451
    protected function _isRuntime( Xyster_Data_Field $field )
452
    {
453 47
    	$calls = Xyster_Orm_Xsql::splitArrow($field->getName());
454
455 47
        if ( count($calls) == 1 ) {
456
457 47
        	$field = $field->getName();
458
            // the call isn't composite - could be a member or a relation
459 47
            return ( Xyster_Orm_Xsql::isMethodCall($calls[0]) ) ? true :
460 44
                ( !in_array($field, $this->getFieldNames())
461 47
                    && !$this->isRelation($field->getName()) );
462
463 0
        } else {
464
465
            // the call is composite - loop through to see if we can figure
466
            // out the type bindings
467 13
            $container = $this->_class;
468 13
            $meta = $this;
469 13
            foreach( $calls as $call ) {
470 13
                if ( Xyster_Orm_Xsql::isMethodCall($call) ) {
471 1
                    return true;
472 0
                } else {
473 13
                    $isRel = $meta->isRelation($call);
474 13
                    if ( !in_array($call, array_keys($meta->getFields()))
475 13
                        && !$isRel ) {
476 1
                        return true;
477 13
                    } else if ( $isRel ) {
478 13
                        $container = $meta->getRelation($call)->getTo();
479 13
                        $meta = $this->_mapFactory->getEntityMeta($container);
480 13
                    }
481
                }
482 13
            }
483 13
            return false;
484
485
        }
486
    }
487
}


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