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: Table.php 1397 2007-05-19 19:54:15Z zYne $
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_Relation_Parser
24
 *
25
 * @package     Doctrine
26
 * @subpackage  Relation
27
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
28
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
29
 * @version     $Revision: 1397 $
30
 * @link        www.doctrine-project.org
31
 * @since       1.0
32
 * @todo Composite key support?
33
 */
34
class Doctrine_Relation_Parser
35
{
36
    /**
37
     * @var Doctrine_Table $_table          the table object this parser belongs to
38
     */
39
    protected $_table;
40
 
41
    /**
42
     * @var array $_relations               an array containing all the Doctrine_Relation objects for this table
43
     */
44
    protected $_relations = array();
45
 
46
    /**
47
     * @var array $_pending                 relations waiting for parsing
48
     */
49
    protected $_pending   = array();
50
 
51
    /**
52
     * constructor
53
     *
54
     * @param Doctrine_Table $table         the table object this parser belongs to
55
     */
56
    public function __construct(Doctrine_Table $table)
57
    {
58
        $this->_table = $table;
59
    }
60
 
61
    /**
62
     * getTable
63
     *
64
     * @return Doctrine_Table   the table object this parser belongs to
65
     */
66
    public function getTable()
67
    {
68
        return $this->_table;
69
    }
70
 
71
    /**
72
     * getPendingRelation
73
     *
74
     * @return array            an array defining a pending relation
75
     */
76
    public function getPendingRelation($name)
77
    {
78
        if ( ! isset($this->_pending[$name])) {
79
            throw new Doctrine_Relation_Exception('Unknown pending relation ' . $name);
80
        }
81
 
82
        return $this->_pending[$name];
83
    }
84
 
85
    /**
86
     * getPendingRelations
87
     *
88
     * @return array            an array containing all the pending relations
89
     */
90
    public function getPendingRelations()
91
    {
92
        return $this->_pending;
93
    }
94
 
95
    /**
96
     * unsetPendingRelations
97
     * Removes a relation. Warning: this only affects pending relations
98
     *
99
     * @param string            relation to remove
100
     */
101
    public function unsetPendingRelations($name)
102
    {
103
       unset($this->_pending[$name]);
104
    }
105
 
106
    /**
107
     * Check if a relation alias exists
108
     *
109
     * @param string $name
110
     * @return boolean $bool
111
     */
112
    public function hasRelation($name)
113
    {
114
        if ( ! isset($this->_pending[$name]) && ! isset($this->_relations[$name])) {
115
            return false;
116
        }
117
 
118
        return true;
119
    }
120
 
121
    /**
122
     * binds a relation
123
     *
124
     * @param string $name
125
     * @param string $field
126
     * @return void
127
     */
128
    public function bind($name, $options = array())
129
    {
130
        $e    = explode(' as ', $name);
131
        $e    = array_map('trim', $e);
132
        $name = $e[0];
133
        $alias = isset($e[1]) ? $e[1] : $name;
134
 
135
        if ( ! isset($options['type'])) {
136
            throw new Doctrine_Relation_Exception('Relation type not set.');
137
        }
138
 
139
        if ($this->hasRelation($alias)) {
140
            unset($this->_relations[$alias]);
141
            unset($this->_pending[$alias]);
142
        }
143
 
144
        $this->_pending[$alias] = array_merge($options, array('class' => $name, 'alias' => $alias));
145
 
146
        return $this->_pending[$alias];
147
    }
148
 
149
    /**
150
     * getRelation
151
     *
152
     * @param string $alias      relation alias
153
     */
154
    public function getRelation($alias, $recursive = true)
155
    {
156
        if (isset($this->_relations[$alias])) {
157
            return $this->_relations[$alias];
158
        }
159
 
160
        if (isset($this->_pending[$alias])) {
161
            $def = $this->_pending[$alias];
162
            $identifierColumnNames = $this->_table->getIdentifierColumnNames();
163
            $idColumnName = array_pop($identifierColumnNames);
164
 
165
            // check if reference class name exists
166
            // if it does we are dealing with association relation
167
            if (isset($def['refClass'])) {
168
                $def = $this->completeAssocDefinition($def);
169
                $localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));
170
 
171
                $backRefRelationName = isset($def['refClassRelationAlias']) ?
172
                        $def['refClassRelationAlias'] : $def['refClass'];
173
                if ( ! isset($this->_pending[$backRefRelationName]) && ! isset($this->_relations[$backRefRelationName])) {
174
 
175
                    $parser = $def['refTable']->getRelationParser();
176
 
177
                    if ( ! $parser->hasRelation($this->_table->getComponentName())) {
178
                        $parser->bind($this->_table->getComponentName(),
179
                                      array('type'    => Doctrine_Relation::ONE,
180
                                            'local'   => $def['local'],
181
                                            'foreign' => $idColumnName,
182
                                            'localKey' => true,
183
                                            ));
184
                    }
185
 
186
                    if ( ! $this->hasRelation($backRefRelationName)) {
187
                        if (in_array($def['class'], $localClasses)) {
188
                            $this->bind($def['refClass'] . " as " . $backRefRelationName, array(
189
                                    'type' => Doctrine_Relation::MANY,
190
                                    'foreign' => $def['foreign'],
191
                                    'local'   => $idColumnName));
192
                        } else {
193
                            $this->bind($def['refClass'] . " as " . $backRefRelationName, array(
194
                                    'type' => Doctrine_Relation::MANY,
195
                                    'foreign' => $def['local'],
196
                                    'local'   => $idColumnName));
197
                        }
198
                    }
199
                }
200
                if (in_array($def['class'], $localClasses)) {
201
                    $rel = new Doctrine_Relation_Nest($def);
202
                } else {
203
                    $rel = new Doctrine_Relation_Association($def);
204
                }
205
            } else {
206
                // simple foreign key relation
207
                $def = $this->completeDefinition($def);
208
 
209
                if (isset($def['localKey']) && $def['localKey']) {
210
                    $rel = new Doctrine_Relation_LocalKey($def);
211
 
212
                    // Automatically index for foreign keys
213
                    $foreign = (array) $def['foreign'];
214
 
215
                    foreach ($foreign as $fk) {
216
                        // Check if its already not indexed (primary key)
217
                        if ( ! $rel['table']->isIdentifier($rel['table']->getFieldName($fk))) {
218
                            $rel['table']->addIndex($fk, array('fields' => array($fk)));
219
                        }
220
                    }
221
                } else {
222
                    $rel = new Doctrine_Relation_ForeignKey($def);
223
                }
224
            }
225
            if (isset($rel)) {
226
                // unset pending relation
227
                unset($this->_pending[$alias]);
228
                $this->_relations[$alias] = $rel;
229
                return $rel;
230
            }
231
        }
232
        if ($recursive) {
233
            $this->getRelations();
234
 
235
            return $this->getRelation($alias, false);
236
        } else {
237
            throw new Doctrine_Table_Exception('Unknown relation alias ' . $alias);
238
        }
239
    }
240
 
241
    /**
242
     * getRelations
243
     * returns an array containing all relation objects
244
     *
245
     * @return array        an array of Doctrine_Relation objects
246
     */
247
    public function getRelations()
248
    {
249
        foreach ($this->_pending as $k => $v) {
250
            $this->getRelation($k);
251
        }
252
 
253
        return $this->_relations;
254
    }
255
 
256
    /**
257
     * getImpl
258
     * returns the table class of the concrete implementation for given template
259
     * if the given template is not a template then this method just returns the
260
     * table class for the given record
261
     *
262
     * @param string $template
263
     */
264
    public function getImpl($template)
265
    {
266
        $conn = $this->_table->getConnection();
267
 
268
        if (class_exists($template) && in_array('Doctrine_Template', class_parents($template))) {
269
            $impl = $this->_table->getImpl($template);
270
 
271
            if ($impl === null) {
272
                throw new Doctrine_Relation_Parser_Exception("Couldn't find concrete implementation for template " . $template);
273
            }
274
        } else {
275
            $impl = $template;
276
        }
277
 
278
        return $conn->getTable($impl);
279
    }
280
 
281
    /**
282
     * Completes the given association definition
283
     *
284
     * @param array $def    definition array to be completed
285
     * @return array        completed definition array
286
     */
287
    public function completeAssocDefinition($def)
288
    {
289
        $conn = $this->_table->getConnection();
290
        $def['table'] = $this->getImpl($def['class']);
291
        $def['localTable'] = $this->_table;
292
        $def['class'] = $def['table']->getComponentName();
293
        $def['refTable'] = $this->getImpl($def['refClass']);
294
 
295
        $id = $def['refTable']->getIdentifierColumnNames();
296
 
297
        if (count($id) > 1) {
298
            if ( ! isset($def['foreign'])) {
299
                // foreign key not set
300
                // try to guess the foreign key
301
 
302
                $def['foreign'] = ($def['local'] === $id[0]) ? $id[1] : $id[0];
303
            }
304
            if ( ! isset($def['local'])) {
305
                // foreign key not set
306
                // try to guess the foreign key
307
                $def['local'] = ($def['foreign'] === $id[0]) ? $id[1] : $id[0];
308
            }
309
        } else {
310
 
311
            if ( ! isset($def['foreign'])) {
312
                // foreign key not set
313
                // try to guess the foreign key
314
 
315
                $columns = $this->getIdentifiers($def['table']);
316
 
317
                $def['foreign'] = $columns;
318
            }
319
            if ( ! isset($def['local'])) {
320
                // local key not set
321
                // try to guess the local key
322
                $columns = $this->getIdentifiers($this->_table);
323
 
324
                $def['local'] = $columns;
325
            }
326
        }
327
        return $def;
328
    }
329
 
330
    /**
331
     * getIdentifiers
332
     * gives a list of identifiers from given table
333
     *
334
     * the identifiers are in format:
335
     * [componentName].[identifier]
336
     *
337
     * @param Doctrine_Table $table     table object to retrieve identifiers from
338
     */
339
    public function getIdentifiers(Doctrine_Table $table)
340
    {
341
        $componentNameToLower = strtolower($table->getComponentName());
342
        if (is_array($table->getIdentifier())) {
343
            $columns = array();
344
            foreach ((array) $table->getIdentifierColumnNames() as $identColName) {
345
                $columns[] = $componentNameToLower . '_' . $identColName;
346
            }
347
        } else {
348
            $columns = $componentNameToLower . '_' . $table->getColumnName(
349
                    $table->getIdentifier());
350
        }
351
 
352
        return $columns;
353
    }
354
 
355
    /**
356
     * guessColumns
357
     *
358
     * @param array $classes                    an array of class names
359
     * @param Doctrine_Table $foreignTable      foreign table object
360
     * @return array                            an array of column names
361
     */
362
    public function guessColumns(array $classes, Doctrine_Table $foreignTable)
363
    {
364
        $conn = $this->_table->getConnection();
365
 
366
        foreach ($classes as $class) {
367
            try {
368
                $table   = $conn->getTable($class);
369
            } catch (Doctrine_Table_Exception $e) {
370
                continue;
371
            }
372
            $columns = $this->getIdentifiers($table);
373
            $found   = true;
374
 
375
            foreach ((array) $columns as $column) {
376
                if ( ! $foreignTable->hasColumn($column)) {
377
                    $found = false;
378
                    break;
379
                }
380
            }
381
            if ($found) {
382
                break;
383
            }
384
        }
385
 
386
        if ( ! $found) {
387
            throw new Doctrine_Relation_Exception("Couldn't find columns.");
388
        }
389
 
390
        return $columns;
391
    }
392
 
393
    /**
394
     * Completes the given definition
395
     *
396
     * @param array $def    definition array to be completed
397
     * @return array        completed definition array
398
     * @todo Description: What does it mean to complete a definition? What is done (not how)?
399
     *       Refactor (too long & nesting level)
400
     */
401
    public function completeDefinition($def)
402
    {
403
        $conn = $this->_table->getConnection();
404
        $def['table'] = $this->getImpl($def['class']);
405
        $def['localTable'] = $this->_table;
406
        $def['class'] = $def['table']->getComponentName();
407
 
408
        $foreignClasses = array_merge($def['table']->getOption('parents'), array($def['class']));
409
        $localClasses   = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));
410
 
411
        $localIdentifierColumnNames = $this->_table->getIdentifierColumnNames();
412
        $localIdentifierCount = count($localIdentifierColumnNames);
413
        $localIdColumnName = array_pop($localIdentifierColumnNames);
414
        $foreignIdentifierColumnNames = $def['table']->getIdentifierColumnNames();
415
        $foreignIdColumnName = array_pop($foreignIdentifierColumnNames);
416
 
417
        if (isset($def['local'])) {
418
            $def['local'] = $def['localTable']->getColumnName($def['local']);
419
 
420
            if ( ! isset($def['foreign'])) {
421
                // local key is set, but foreign key is not
422
                // try to guess the foreign key
423
 
424
                if ($def['local'] === $localIdColumnName) {
425
                    $def['foreign'] = $this->guessColumns($localClasses, $def['table']);
426
                } else {
427
                    // the foreign field is likely to be the
428
                    // identifier of the foreign class
429
                    $def['foreign'] = $foreignIdColumnName;
430
                    $def['localKey'] = true;
431
                }
432
            } else {
433
                $def['foreign'] = $def['table']->getColumnName($def['foreign']);
434
 
435
                if ($localIdentifierCount == 1) {
436
                    if ($def['local'] == $localIdColumnName && isset($def['owningSide'])
437
                            && $def['owningSide'] === true) {
438
                        $def['localKey'] = true;
439
                    } else if (($def['local'] !== $localIdColumnName && $def['type'] == Doctrine_Relation::ONE)) {
440
                        $def['localKey'] = true;
441
                    }
442
                } else if ($localIdentifierCount > 1 && ! isset($def['localKey'])) {
443
                    // It's a composite key and since 'foreign' can not point to a composite
444
                    // key currently, we know that 'local' must be the foreign key.
445
                    $def['localKey'] = true;
446
                }
447
            }
448
        } else {
449
            if (isset($def['foreign'])) {
450
                $def['foreign'] = $def['table']->getColumnName($def['foreign']);
451
 
452
                // local key not set, but foreign key is set
453
                // try to guess the local key
454
                if ($def['foreign'] === $foreignIdColumnName) {
455
                    $def['localKey'] = true;
456
                    try {
457
                        $def['local'] = $this->guessColumns($foreignClasses, $this->_table);
458
                    } catch (Doctrine_Relation_Exception $e) {
459
                        $def['local'] = $localIdColumnName;
460
                    }
461
                } else {
462
                    $def['local'] = $localIdColumnName;
463
                }
464
            } else {
465
                // neither local or foreign key is being set
466
                // try to guess both keys
467
 
468
                $conn = $this->_table->getConnection();
469
 
470
                // the following loops are needed for covering inheritance
471
                foreach ($localClasses as $class) {
472
                    $table = $conn->getTable($class);
473
                    $identifierColumnNames = $table->getIdentifierColumnNames();
474
                    $idColumnName = array_pop($identifierColumnNames);
475
                    $column = strtolower($table->getComponentName())
476
                            . '_' . $idColumnName;
477
 
478
                    foreach ($foreignClasses as $class2) {
479
                        $table2 = $conn->getTable($class2);
480
                        if ($table2->hasColumn($column)) {
481
                            $def['foreign'] = $column;
482
                            $def['local'] = $idColumnName;
483
                            return $def;
484
                        }
485
                    }
486
                }
487
 
488
                foreach ($foreignClasses as $class) {
489
                    $table  = $conn->getTable($class);
490
                    $identifierColumnNames = $table->getIdentifierColumnNames();
491
                    $idColumnName = array_pop($identifierColumnNames);
492
                    $column = strtolower($table->getComponentName())
493
                            . '_' . $idColumnName;
494
 
495
                    foreach ($localClasses as $class2) {
496
                        $table2 = $conn->getTable($class2);
497
                        if ($table2->hasColumn($column)) {
498
                            $def['foreign']  = $idColumnName;
499
                            $def['local']    = $column;
500
                            $def['localKey'] = true;
501
                            return $def;
502
                        }
503
                    }
504
                }
505
 
506
                // auto-add columns and auto-build relation
507
                $columns = array();
508
                foreach ((array) $this->_table->getIdentifierColumnNames() as $id) {
509
                    // ?? should this not be $this->_table->getComponentName() ??
510
                    $column = strtolower($table->getComponentName())
511
                            . '_' . $id;
512
 
513
                    $col = $this->_table->getColumnDefinition($id);
514
                    $type = $col['type'];
515
                    $length = $col['length'];
516
 
517
                    unset($col['type']);
518
                    unset($col['length']);
519
                    unset($col['autoincrement']);
520
                    unset($col['sequence']);
521
                    unset($col['primary']);
522
 
523
                    $def['table']->setColumn($column, $type, $length, $col);
524
 
525
                    $columns[] = $column;
526
                }
527
                if (count($columns) > 1) {
528
                    $def['foreign'] = $columns;
529
                } else {
530
                    $def['foreign'] = $columns[0];
531
                }
532
                $def['local'] = $localIdColumnName;
533
            }
534
        }
535
        return $def;
536
    }
537
}