Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/*
3
 * $Id: Schema.php 1838 2007-06-26 00:58:21Z nicobn $
4
 *
5
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16
 *
17
 * This software consists of voluntary contributions made by many individuals
18
 * and is licensed under the LGPL. For more information, see
19
 * <http://www.doctrine-project.org>.
20
 */
21
 
22
/**
23
 * Doctrine_Import_Schema
24
 *
25
 * Class for importing Doctrine_Record classes from a yaml schema definition
26
 *
27
 * @package     Doctrine
28
 * @subpackage  Import
29
 * @link        www.doctrine-project.org
30
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
31
 * @version     $Revision: 1838 $
32
 * @author      Nicolas Bérard-Nault <nicobn@gmail.com>
33
 * @author      Jonathan H. Wage <jonwage@gmail.com>
34
 */
35
class Doctrine_Import_Schema
36
{
37
    /**
38
     * Schema definition keys that can be applied at the global level.
39
     *
40
     * @var array
41
     */
42
    protected static $_globalDefinitionKeys = array(
43
        'connection',
44
        'attributes',
45
        'templates',
46
        'actAs',
47
        'options',
48
        'package',
49
        'package_custom_path',
50
        'inheritance',
51
        'detect_relations');
52
 
53
    /**
54
     * _relations
55
     *
56
     * Array of all relationships parsed from all schema files
57
     *
58
     * @var array
59
     */
60
    protected $_relations = array();
61
 
62
    /**
63
     * _options
64
     *
65
     * Array of options used to configure the model generation from the parsed schema files
66
     * This array is forwarded to Doctrine_Import_Builder
67
     *
68
     * @var array
69
     */
70
    protected $_options = array('packagesPrefix'        =>  'Package',
71
                                'packagesPath'          =>  '',
72
                                'packagesFolderName'    =>  'packages',
73
                                'suffix'                =>  '.php',
74
                                'generateBaseClasses'   =>  true,
75
                                'generateTableClasses'  =>  false,
76
                                'generateAccessors'     =>  false,
77
                                'baseClassPrefix'       =>  'Base',
78
                                'baseClassesDirectory'  =>  'generated',
79
                                'baseClassName'         =>  'Doctrine_Record');
80
 
81
    /**
82
     * _validation
83
     *
84
     * Array used to validate schema element.
85
     * See: _validateSchemaElement
86
     *
87
     * @var array
88
     */
89
    protected $_validation = array('root'       =>  array('abstract',
90
                                                          'connection',
91
                                                          'className',
92
                                                          'tableName',
93
                                                          'connection',
94
                                                          'relations',
95
                                                          'columns',
96
                                                          'indexes',
97
                                                          'attributes',
98
                                                          'templates',
99
                                                          'actAs',
100
                                                          'options',
101
                                                          'package',
102
                                                          'package_custom_path',
103
                                                          'inheritance',
104
                                                          'detect_relations',
105
                                                          'listeners',
106
                                                          'checks',
107
                                                          'comment'),
108
 
109
                                   'column'     =>  array('name',
110
                                                          'format',
111
                                                          'fixed',
112
                                                          'primary',
113
                                                          'autoincrement',
114
                                                          'type',
115
                                                          'length',
116
                                                          'size',
117
                                                          'default',
118
                                                          'scale',
119
                                                          'values',
120
                                                          'comment',
121
                                                          'sequence',
122
                                                          'protected',
123
                                                          'zerofill',
124
                                                          'owner',
125
                                                          'extra',
126
                                                          'comment',
127
                                                          'charset',
128
                                                          'collation'),
129
 
130
                                   'relation'   =>  array('key',
131
                                                          'class',
132
                                                          'alias',
133
                                                          'type',
134
                                                          'refClass',
135
                                                          'local',
136
                                                          'foreign',
137
                                                          'foreignClass',
138
                                                          'foreignAlias',
139
                                                          'foreignType',
140
                                                          'autoComplete',
141
                                                          'cascade',
142
                                                          'onDelete',
143
                                                          'onUpdate',
144
                                                          'equal',
145
                                                          'owningSide',
146
                                                          'refClassRelationAlias',
147
                                                          'foreignKeyName',
148
                                                          'orderBy'),
149
 
150
                                   'inheritance'=>  array('type',
151
                                                          'extends',
152
                                                          'keyField',
153
                                                          'keyValue'));
154
 
155
    /**
156
     * Returns an array of definition keys that can be applied at the global level.
157
     *
158
     * @return array
159
     */
160
    public static function getGlobalDefinitionKeys()
161
    {
162
        return self::$_globalDefinitionKeys;
163
    }
164
 
165
    /**
166
     * getOption
167
     *
168
     * @param string $name
169
     * @return void
170
     */
171
    public function getOption($name)
172
    {
173
        if (isset($this->_options[$name]))   {
174
            return $this->_options[$name];
175
        }
176
    }
177
 
178
    /**
179
     * getOptions
180
     *
181
     * @return void
182
     */
183
    public function getOptions()
184
    {
185
        return $this->_options;
186
    }
187
 
188
    /**
189
     * setOption
190
     *
191
     * @param string $name
192
     * @param string $value
193
     * @return void
194
     */
195
    public function setOption($name, $value)
196
    {
197
        if (isset($this->_options[$name])) {
198
            $this->_options[$name] = $value;
199
        }
200
    }
201
 
202
    /**
203
     * setOptions
204
     *
205
     * @param string $options
206
     * @return void
207
     */
208
    public function setOptions($options)
209
    {
210
        if ( ! empty($options)) {
211
          $this->_options = $options;
212
        }
213
    }
214
 
215
    /**
216
     * buildSchema
217
     *
218
     * Loop throug directories of schema files and parse them all in to one complete array of schema information
219
     *
220
     * @param  string   $schema Array of schema files or single schema file. Array of directories with schema files or single directory
221
     * @param  string   $format Format of the files we are parsing and building from
222
     * @return array    $array
223
     */
224
    public function buildSchema($schema, $format)
225
    {
226
        $array = array();
227
 
228
        foreach ((array) $schema AS $s) {
229
            if (is_file($s)) {
230
                $e = explode('.', $s);
231
                if (end($e) === $format) {
232
                    $array = array_merge($array, $this->parseSchema($s, $format));
233
                }
234
            } else if (is_dir($s)) {
235
                $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($s),
236
                                                      RecursiveIteratorIterator::LEAVES_ONLY);
237
 
238
                foreach ($it as $file) {
239
                    $e = explode('.', $file->getFileName());
240
                    if (end($e) === $format) {
241
                        $array = array_merge($array, $this->parseSchema($file->getPathName(), $format));
242
                    }
243
                }
244
            } else {
245
              $array = array_merge($array, $this->parseSchema($s, $format));
246
            }
247
        }
248
 
249
        $array = $this->_buildRelationships($array);
250
        $array = $this->_processInheritance($array);
251
 
252
        return $array;
253
    }
254
 
255
    /**
256
     * importSchema
257
     *
258
     * A method to import a Schema and translate it into a Doctrine_Record object
259
     *
260
     * @param  string $schema       The file containing the XML schema
261
     * @param  string $format       Format of the schema file
262
     * @param  string $directory    The directory where the Doctrine_Record class will be written
263
     * @param  array  $models       Optional array of models to import
264
     *
265
     * @return void
266
     */
267
    public function importSchema($schema, $format = 'yml', $directory = null, $models = array())
268
    {
269
        $schema = (array) $schema;
270
        $builder = new Doctrine_Import_Builder();
271
        $builder->setTargetPath($directory);
272
        $builder->setOptions($this->getOptions());
273
 
274
        $array = $this->buildSchema($schema, $format);
275
 
276
        if (count($array) == 0) {
277
            throw new Doctrine_Import_Exception(
278
                sprintf('No ' . $format . ' schema found in ' . implode(", ", $schema))
279
            );
280
        }
281
 
282
        foreach ($array as $name => $definition) {
283
            if ( ! empty($models) && !in_array($definition['className'], $models)) {
284
                continue;
285
            }
286
 
287
            $builder->buildRecord($definition);
288
        }
289
    }
290
 
291
    /**
292
     * parseSchema
293
     *
294
     * A method to parse a Schema and translate it into a property array.
295
     * The function returns that property array.
296
     *
297
     * @param  string $schema   Path to the file containing the schema
298
     * @param  string $type     Format type of the schema we are parsing
299
     * @return array  $build    Built array of schema information
300
     */
301
    public function parseSchema($schema, $type)
302
    {
303
        $defaults = array('abstract'            =>  false,
304
                          'className'           =>  null,
305
                          'tableName'           =>  null,
306
                          'connection'          =>  null,
307
                          'relations'           =>  array(),
308
                          'indexes'             =>  array(),
309
                          'attributes'          =>  array(),
310
                          'templates'           =>  array(),
311
                          'actAs'               =>  array(),
312
                          'options'             =>  array(),
313
                          'package'             =>  null,
314
                          'inheritance'         =>  array(),
315
                          'detect_relations'    =>  false);
316
 
317
        $array = Doctrine_Parser::load($schema, $type);
318
 
319
        // Loop over and build up all the global values and remove them from the array
320
        $globals = array();
321
        foreach ($array as $key => $value) {
322
            if (in_array($key, self::$_globalDefinitionKeys)) {
323
                unset($array[$key]);
324
                $globals[$key] = $value;
325
            }
326
        }
327
 
328
        // Merge the globals that aren't specifically set to each class
329
        foreach ($array as $className => $table) {
330
            $array[$className] = Doctrine_Lib::arrayDeepMerge($globals, $array[$className]);
331
        }
332
 
333
        $build = array();
334
 
335
        foreach ($array as $className => $table) {
336
            $table = (array) $table;
337
            $this->_validateSchemaElement('root', array_keys($table), $className);
338
 
339
            $columns = array();
340
 
341
            $className = isset($table['className']) ? (string) $table['className']:(string) $className;
342
 
343
            if (isset($table['inheritance']['keyField']) || isset($table['inheritance']['keyValue'])) {
344
                $table['inheritance']['type'] = 'column_aggregation';
345
            }
346
 
347
            if (isset($table['tableName']) && $table['tableName']) {
348
                $tableName = $table['tableName'];
349
            } else {
350
                if (isset($table['inheritance']['type']) && ($table['inheritance']['type'] == 'column_aggregation')) {
351
                    $tableName = null;
352
                } else {
353
                    $tableName = Doctrine_Inflector::tableize($className);
354
                }
355
            }
356
 
357
            $connection = isset($table['connection']) ? $table['connection']:'current';
358
 
359
            $columns = isset($table['columns']) ? $table['columns']:array();
360
 
361
            if ( ! empty($columns)) {
362
                foreach ($columns as $columnName => $field) {
363
 
364
                    // Support short syntax: my_column: integer(4)
365
                    if ( ! is_array($field)) {
366
                        $original = $field;
367
                        $field = array();
368
                        $field['type'] = $original;
369
                    }
370
 
371
                    $colDesc = array();
372
                    if (isset($field['name'])) {
373
                        $colDesc['name'] = $field['name'];
374
                    } else {
375
                        $colDesc['name'] = $columnName;
376
                    }
377
 
378
                    $this->_validateSchemaElement('column', array_keys($field), $className . '->columns->' . $colDesc['name']);
379
 
380
                    // Support short type(length) syntax: my_column: { type: integer(4) }
381
                    $e = explode('(', $field['type']);
382
                    if (isset($e[0]) && isset($e[1])) {
383
                        $colDesc['type'] = $e[0];
384
                        $value = substr($e[1], 0, strlen($e[1]) - 1);
385
                        $e = explode(',', $value);
386
                        $colDesc['length'] = $e[0];
387
                        if (isset($e[1]) && $e[1]) {
388
                            $colDesc['scale'] = $e[1];
389
                        }
390
                    } else {
391
                        $colDesc['type'] = isset($field['type']) ? (string) $field['type']:null;
392
                        $colDesc['length'] = isset($field['length']) ? (int) $field['length']:null;
393
                        $colDesc['length'] = isset($field['size']) ? (int) $field['size']:$colDesc['length'];
394
                    }
395
 
396
                    $colDesc['fixed'] = isset($field['fixed']) ? (int) $field['fixed']:null;
397
                    $colDesc['primary'] = isset($field['primary']) ? (bool) (isset($field['primary']) && $field['primary']):null;
398
                    $colDesc['default'] = isset($field['default']) ? $field['default']:null;
399
                    $colDesc['autoincrement'] = isset($field['autoincrement']) ? (bool) (isset($field['autoincrement']) && $field['autoincrement']):null;
400
 
401
                    if (isset($field['sequence'])) {
402
                        if (true === $field['sequence']) {
403
                            $colDesc['sequence'] = $tableName;
404
                        } else {
405
                            $colDesc['sequence'] = (string) $field['sequence'];
406
                        }
407
                    } else {
408
                        $colDesc['sequence'] = null;
409
                    }
410
 
411
                    $colDesc['values'] = isset($field['values']) ? (array) $field['values']:null;
412
 
413
                    // Include all the specified and valid validators in the colDesc
414
                    $validators = Doctrine_Manager::getInstance()->getValidators();
415
 
416
                    foreach ($validators as $validator) {
417
                        if (isset($field[$validator])) {
418
                            $colDesc[$validator] = $field[$validator];
419
                        }
420
                    }
421
 
422
                    $columns[(string) $columnName] = $colDesc;
423
                }
424
            }
425
 
426
            // Apply the default values
427
            foreach ($defaults as $key => $defaultValue) {
428
                if (isset($table[$key]) && ! isset($build[$className][$key])) {
429
                    $build[$className][$key] = $table[$key];
430
                } else {
431
                    $build[$className][$key] = isset($build[$className][$key]) ? $build[$className][$key]:$defaultValue;
432
                }
433
            }
434
 
435
            $build[$className]['className'] = $className;
436
            $build[$className]['tableName'] = $tableName;
437
            $build[$className]['columns'] = $columns;
438
 
439
            // Make sure that anything else that is specified in the schema makes it to the final array
440
            $build[$className] = Doctrine_Lib::arrayDeepMerge($table, $build[$className]);
441
 
442
            // We need to keep track of the className for the connection
443
            $build[$className]['connectionClassName'] = $build[$className]['className'];
444
        }
445
 
446
        return $build;
447
    }
448
 
449
    /**
450
     * _processInheritance
451
     *
452
     * Perform some processing on inheritance.
453
     * Sets the default type and sets some default values for certain types
454
     *
455
     * @param string $array
456
     * @return void
457
     */
458
    protected function _processInheritance($array)
459
    {
460
        // Apply default inheritance configuration
461
        foreach ($array as $className => $definition) {
462
            if ( ! empty($array[$className]['inheritance'])) {
463
                $this->_validateSchemaElement('inheritance', array_keys($definition['inheritance']), $className . '->inheritance');
464
 
465
                // Default inheritance to concrete inheritance
466
                if ( ! isset($array[$className]['inheritance']['type'])) {
467
                    $array[$className]['inheritance']['type'] = 'concrete';
468
                }
469
 
470
                // Some magic for setting up the keyField and keyValue column aggregation options
471
                // Adds keyField to the parent class automatically
472
                if ($array[$className]['inheritance']['type'] == 'column_aggregation') {
473
                    // Set the keyField to 'type' by default
474
                    if ( ! isset($array[$className]['inheritance']['keyField'])) {
475
                        $array[$className]['inheritance']['keyField'] = 'type';
476
                    }
477
 
478
                    // Set the keyValue to the name of the child class if it does not exist
479
                    if ( ! isset($array[$className]['inheritance']['keyValue'])) {
480
                        $array[$className]['inheritance']['keyValue'] = $className;
481
                    }
482
 
483
                    $parent = $this->_findBaseSuperClass($array, $definition['className']);
484
                    // Add the keyType column to the parent if a definition does not already exist
485
                    if ( ! isset($array[$parent]['columns'][$array[$className]['inheritance']['keyField']])) {
486
                        $array[$parent]['columns'][$array[$className]['inheritance']['keyField']] = array('name' => $array[$className]['inheritance']['keyField'], 'type' => 'string', 'length' => 255);
487
                    }
488
                }
489
            }
490
        }
491
 
492
        // Array of the array keys to move to the parent, and the value to default the child definition to
493
        // after moving it. Will also populate the subclasses array for the inheritance parent
494
        $moves = array('columns' => array(),
495
                       'indexes' => array(),
496
                       'attributes' => array(),
497
                       'options' => array(),
498
                       'checks' => array());
499
 
500
        foreach ($array as $className => $definition) {
501
            // Move any definitions on the schema to the parent
502
            if (isset($definition['inheritance']['extends']) && isset($definition['inheritance']['type']) && ($definition['inheritance']['type'] == 'simple' || $definition['inheritance']['type'] == 'column_aggregation')) {
503
                $parent = $this->_findBaseSuperClass($array, $definition['className']);
504
                foreach ($moves as $move => $resetValue) {
505
                    if (isset($array[$parent][$move]) && isset($definition[$move])) {
506
                        $array[$parent][$move] = Doctrine_Lib::arrayDeepMerge($array[$parent][$move], $definition[$move]);
507
                        $array[$definition['className']][$move] = $resetValue;
508
                    }
509
                }
510
 
511
                // Populate the parents subclasses
512
                if ($definition['inheritance']['type'] == 'column_aggregation') {
513
                    // Fix for 2015: loop through superclasses' inheritance to the base-superclass to
514
                    // make sure we collect all keyFields needed (and not only the first)
515
                    $inheritanceFields = array($definition['inheritance']['keyField'] => $definition['inheritance']['keyValue']);
516
 
517
                    $superClass = $definition['inheritance']['extends'];
518
                    $multiInheritanceDef = $array[$superClass];
519
 
520
                    while (count($multiInheritanceDef['inheritance']) > 0 && array_key_exists('extends', $multiInheritanceDef['inheritance']) && $multiInheritanceDef['inheritance']['type'] == 'column_aggregation') {
521
                        $superClass = $multiInheritanceDef['inheritance']['extends'];
522
 
523
                        // keep original keyField with it's keyValue
524
                        if ( ! isset($inheritanceFields[$multiInheritanceDef['inheritance']['keyField']])) {
525
                            $inheritanceFields[$multiInheritanceDef['inheritance']['keyField']] = $multiInheritanceDef['inheritance']['keyValue'];
526
                        }
527
                        $multiInheritanceDef = $array[$superClass];
528
                    }
529
 
530
                    $array[$parent]['inheritance']['subclasses'][$definition['className']] = $inheritanceFields;
531
                }
532
            }
533
        }
534
 
535
        return $array;
536
    }
537
 
538
    /**
539
     * Find the base super class for this inheritance child. We need to move all levels of children to the
540
     * top most parent.
541
     *
542
     * @param  array  $array  Array of schema information
543
     * @return string $class  Class to get find the parent for
544
     */
545
    protected function _findBaseSuperClass($array, $class)
546
    {
547
        if (isset($array[$class]['inheritance']['extends']) && isset($array[$class]['inheritance']['type']) && ($array[$class]['inheritance']['type'] == 'simple' || $array[$class]['inheritance']['type'] == 'column_aggregation')) {
548
            return $this->_findBaseSuperClass($array, $array[$class]['inheritance']['extends']);
549
        } else {
550
            return $class;
551
        }
552
    }
553
 
554
    /**
555
     * buildRelationships
556
     *
557
     * Loop through an array of schema information and build all the necessary relationship information
558
     * Will attempt to auto complete relationships and simplify the amount of information required
559
     * for defining a relationship
560
     *
561
     * @param  string $array
562
     * @return void
563
     */
564
    protected function _buildRelationships($array)
565
    {
566
        // Handle auto detecting relations by the names of columns
567
        // User.contact_id will automatically create User hasOne Contact local => contact_id, foreign => id
568
        foreach ($array as $className => $properties) {
569
            if (isset($properties['columns']) && ! empty($properties['columns']) && isset($properties['detect_relations']) && $properties['detect_relations']) {
570
                foreach ($properties['columns'] as $column) {
571
                    // Check if the column we are inflecting has a _id on the end of it before trying to inflect it and find
572
                    // the class name for the column
573
                    if (strpos($column['name'], '_id')) {
574
                        $columnClassName = Doctrine_Inflector::classify(str_replace('_id', '', $column['name']));
575
                        if (isset($array[$columnClassName]) && !isset($array[$className]['relations'][$columnClassName])) {
576
                            $array[$className]['relations'][$columnClassName] = array();
577
 
578
                            // Set the detected foreign key type and length to the same as the primary key
579
                            // of the related table
580
                            $type = isset($array[$columnClassName]['columns']['id']['type']) ? $array[$columnClassName]['columns']['id']['type']:'integer';
581
                            $length = isset($array[$columnClassName]['columns']['id']['length']) ? $array[$columnClassName]['columns']['id']['length']:8;
582
                            $array[$className]['columns'][$column['name']]['type'] = $type;
583
                            $array[$className]['columns'][$column['name']]['length'] = $length;
584
                        }
585
                    }
586
                }
587
            }
588
        }
589
 
590
        foreach ($array as $name => $properties) {
591
            if ( ! isset($properties['relations'])) {
592
                continue;
593
            }
594
 
595
            $className = $properties['className'];
596
            $relations = $properties['relations'];
597
 
598
            foreach ($relations as $alias => $relation) {
599
                $class = isset($relation['class']) ? $relation['class']:$alias;
600
                if ( ! isset($array[$class])) {
601
                    continue;
602
                }
603
                $relation['class'] = $class;
604
                $relation['alias'] = isset($relation['alias']) ? $relation['alias'] : $alias;
605
 
606
                // Attempt to guess the local and foreign
607
                if (isset($relation['refClass'])) {
608
                    $relation['local'] = isset($relation['local']) ? $relation['local']:Doctrine_Inflector::tableize($name) . '_id';
609
                    $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:Doctrine_Inflector::tableize($class) . '_id';
610
                } else {
611
                    $relation['local'] = isset($relation['local']) ? $relation['local']:Doctrine_Inflector::tableize($relation['class']) . '_id';
612
                    $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:'id';
613
                }
614
 
615
                if (isset($relation['refClass'])) {
616
                    $relation['type'] = 'many';
617
                }
618
 
619
                if (isset($relation['type']) && $relation['type']) {
620
                    $relation['type'] = $relation['type'] === 'one' ? Doctrine_Relation::ONE:Doctrine_Relation::MANY;
621
                } else {
622
                    $relation['type'] = Doctrine_Relation::ONE;
623
                }
624
 
625
                if (isset($relation['foreignType']) && $relation['foreignType']) {
626
                    $relation['foreignType'] = $relation['foreignType'] === 'one' ? Doctrine_Relation::ONE:Doctrine_Relation::MANY;
627
                }
628
 
629
                $relation['key'] = $this->_buildUniqueRelationKey($relation);
630
 
631
                $this->_validateSchemaElement('relation', array_keys($relation), $className . '->relation->' . $relation['alias']);
632
 
633
                $this->_relations[$className][$alias] = $relation;
634
            }
635
        }
636
 
637
        // Now we auto-complete opposite ends of relationships
638
        $this->_autoCompleteOppositeRelations();
639
 
640
        // Make sure we do not have any duplicate relations
641
        $this->_fixDuplicateRelations();
642
 
643
        // Set the full array of relationships for each class to the final array
644
        foreach ($this->_relations as $className => $relations) {
645
            $array[$className]['relations'] = $relations;
646
        }
647
 
648
        return $array;
649
    }
650
 
651
    /**
652
     * fixRelationships
653
     *
654
     * Loop through all relationships building the opposite ends of each relationship
655
     * and make sure no duplicate relations exist
656
     *
657
     * @return void
658
     */
659
    protected function _autoCompleteOppositeRelations()
660
    {
661
        foreach($this->_relations as $className => $relations) {
662
            foreach ($relations AS $alias => $relation) {
663
                if ((isset($relation['equal']) && $relation['equal']) || (isset($relation['autoComplete']) && $relation['autoComplete'] === false)) {
664
                    continue;
665
                }
666
 
667
                $newRelation = array();
668
                $newRelation['foreign'] = $relation['local'];
669
                $newRelation['local'] = $relation['foreign'];
670
                $newRelation['class'] = isset($relation['foreignClass']) ? $relation['foreignClass']:$className;
671
                $newRelation['alias'] = isset($relation['foreignAlias']) ? $relation['foreignAlias']:$className;
672
                $newRelation['foreignAlias'] = $alias;
673
 
674
                // this is so that we know that this relation was autogenerated and
675
                // that we do not need to include it if it is explicitly declared in the schema by the users.
676
                $newRelation['autogenerated'] = true;
677
 
678
                if (isset($relation['refClass'])) {
679
                    $newRelation['refClass'] = $relation['refClass'];
680
                    $newRelation['type'] = isset($relation['foreignType']) ? $relation['foreignType']:$relation['type'];
681
                } else {
682
                    if (isset($relation['foreignType'])) {
683
                        $newRelation['type'] = $relation['foreignType'];
684
                    } else {
685
                        $newRelation['type'] = $relation['type'] === Doctrine_Relation::ONE ? Doctrine_Relation::MANY:Doctrine_Relation::ONE;
686
                    }
687
                }
688
 
689
                // Make sure it doesn't already exist
690
                if ( ! isset($this->_relations[$relation['class']][$newRelation['alias']])) {
691
                    $newRelation['key'] = $this->_buildUniqueRelationKey($newRelation);
692
                    $this->_relations[$relation['class']][$newRelation['alias']] = $newRelation;
693
                }
694
            }
695
        }
696
    }
697
 
698
    /**
699
     * _fixDuplicateRelations
700
     *
701
     * Ensure the relations for each class are unique and that no duplicated relations exist from the auto generated relations
702
     * and the user explicitely defining the opposite end
703
     *
704
     * @return void
705
     */
706
    protected function _fixDuplicateRelations()
707
    {
708
        foreach($this->_relations as $className => $relations) {
709
            // This is for checking for duplicates between alias-relations and a auto-generated relations to ensure the result set of unique relations
710
            $existingRelations = array();
711
            $uniqueRelations = array();
712
            foreach ($relations as $relation) {
713
                if ( ! in_array($relation['key'], $existingRelations)) {
714
                    $existingRelations[] = $relation['key'];
715
                    $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation));
716
                } else {
717
                    // check to see if this relationship is not autogenerated, if it's not, then the user must have explicitly declared it
718
                    if ( ! isset($relation['autogenerated']) || $relation['autogenerated'] != true) {
719
                        $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation));
720
                    }
721
                }
722
            }
723
 
724
            $this->_relations[$className] = $uniqueRelations;
725
        }
726
    }
727
 
728
    /**
729
     * _buildUniqueRelationKey
730
     *
731
     * Build a unique key to identify a relationship by
732
     * Md5 hash of all the relationship parameters
733
     *
734
     * @param string $relation
735
     * @return void
736
     */
737
    protected function _buildUniqueRelationKey($relation)
738
    {
739
        return md5($relation['local'].$relation['foreign'].$relation['class'].(isset($relation['refClass']) ? $relation['refClass']:null));
740
    }
741
 
742
    /**
743
     * _validateSchemaElement
744
     *
745
     * @param string $name
746
     * @param string $value
747
     * @return void
748
     */
749
    protected function _validateSchemaElement($name, $element, $path)
750
    {
751
        $element = (array) $element;
752
 
753
        $validation = $this->_validation[$name];
754
 
755
        // Validators are a part of the column validation
756
        // This should be fixed, made cleaner
757
        if ($name == 'column') {
758
            $validators = Doctrine_Manager::getInstance()->getValidators();
759
            $validation = array_merge($validation, $validators);
760
        }
761
 
762
        $validation = array_flip($validation);
763
        foreach ($element as $key => $value) {
764
            if ( ! isset($validation[$value])) {
765
                throw new Doctrine_Import_Exception(
766
                    sprintf('Invalid schema element named "' . $value . '" at path "' . $path . '"')
767
                );
768
            }
769
        }
770
    }
771
}