Coverage for Doctrine_Import_Schema

Back to coverage report

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.phpdoctrine.com>.
20  */
21
22 /**
23  * class Doctrine_Import_Schema
24  *
25  * Different methods to import a XML schema. The logic behind using two different
26  * methods is simple. Some people will like the idea of producing Doctrine_Record
27  * objects directly, which is totally fine. But in fast and growing application,
28  * table definitions tend to be a little bit more volatile. importArr() can be used
29  * to output a table definition in a PHP file. This file can then be stored
30  * independantly from the object itself.
31  *
32  * @package     Doctrine
33  * @subpackage  Import
34  * @link        www.phpdoctrine.com
35  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
36  * @version     $Revision: 1838 $
37  * @author      Nicolas BĂ©rard-Nault <nicobn@gmail.com>
38  * @author      Jonathan H. Wage <jonwage@gmail.com>
39  */
40 class Doctrine_Import_Schema
41 {
42     public $relations = array();
43     public $generateBaseClasses = false;
44
45     /**
46      * generateBaseClasses
47      *
48      * Specify whether or not to generate base classes with the model definition in it. The base is generated everytime
49      * But another child class that extends the base is only generated once. Allowing you to customize your models
50      * Without losing the changes when you regenerate
51      *
52      * @param   string $bool 
53      * @return  bool   $generateBaseClasses
54      */
55     public function generateBaseClasses($bool = null)
56     {
57         if ($bool !== null) {
58             $this->generateBaseClasses = $bool;
59         }
60         
61         return $this->generateBaseClasses;
62     }
63
64     /**
65      * buildSchema
66      *
67      * Loop throug directories of schema files and part them all in to one complete array of schema information
68      *
69      * @param  string   $schema Array of schema files or single schema file. Array of directories with schema files or single directory
70      * @param  string   $format Format of the files we are parsing and building from
71      * @return array    $array
72      */
73     public function buildSchema($schema, $format)
74     {
75         $array = array();
76         
77         foreach ((array) $schema AS $s) {
78             if (is_file($s)) {
79                 $array = array_merge($array, $this->parseSchema($s, $format));
80             } else if (is_dir($s)) {
81                 $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($s),
82                                                       RecursiveIteratorIterator::LEAVES_ONLY);
83             
84                 foreach ($it as $file) {
85                     $e = explode('.', $file->getFileName());
86                     if (end($e) === $format) {
87                         $array = array_merge($array, $this->parseSchema($file->getPathName(), $format));
88                     }
89                 }
90             }
91         }
92         
93         $this->buildRelationships($array);
94         
95         return array('schema' => $array, 'relations' => $this->relations);
96     }
97
98     /**
99      * importSchema
100      *
101      * A method to import a Schema and translate it into a Doctrine_Record object
102      *
103      * @param  string $schema       The file containing the XML schema
104      * @param  string $directory    The directory where the Doctrine_Record class will be written
105      * @param  array $models        Optional array of models to import
106      *
107      * @return void
108      */
109     public function importSchema($schema, $format = 'yml', $directory = null, $models = array())
110     {
111         $builder = new Doctrine_Import_Builder();
112         $builder->setTargetPath($directory);
113         $builder->generateBaseClasses($this->generateBaseClasses());
114         
115         $schema = $this->buildSchema($schema, $format);
116         
117         $array = $schema['schema'];
118         
119         foreach ($array as $name => $properties) {
120             if ( ! empty($models) && !in_array($properties['className'], $models)) {
121                 continue;
122             }
123             
124             $options = $this->getOptions($properties, $directory);
125             $columns = $this->getColumns($properties);
126             $relations = $this->getRelations($properties);
127             $indexes = $this->getIndexes($properties);
128             $attributes = $this->getAttributes($properties);
129             $templates = $this->getTemplates($properties);
130             $actAs = $this->getActAs($properties);
131             
132             $builder->buildRecord($options, $columns, $relations, $indexes, $attributes, $templates, $actAs);
133         }
134     }
135
136     /**
137      * getOptions
138      *
139      * FIXME: Directory argument needs to be removed
140      *
141      * @param string $properties Array of table properties
142      * @param string $directory  Directory we are writing the class to
143      * @return array $options    Array of options from a parse schemas properties
144      */
145     public function getOptions($properties, $directory)
146     {
147         $options = array();
148         $options['className'] = $properties['className'];
149         $options['fileName'] = $directory.DIRECTORY_SEPARATOR.$properties['className'].'.class.php';
150         $options['tableName'] = isset($properties['tableName']) ? $properties['tableName']:null;
151         $options['connection'] = isset($properties['connection']) ? $properties['connection']:null;
152         $options['connectionClassName'] = isset($properties['connection']) ? $properties['className']:null;
153         
154         if (isset($properties['inheritance'])) {
155             $options['inheritance'] = $properties['inheritance'];
156         }
157
158         return $options;
159     }
160
161     /**
162      * getColumns
163      *
164      * Get array of columns from table properties
165      *
166      * @param  string $properties Array of table properties
167      * @return array  $columns    Array of columns
168      */
169     public function getColumns($properties)
170     {
171         return isset($properties['columns']) ? $properties['columns']:array();
172     }
173
174     /**
175      * getRelations
176      * 
177      * Get array of relations from table properties
178      *
179      * @param  string $properties Array of tables properties
180      * @return array  $relations  Array of relations
181      */
182     public function getRelations($properties)
183     {
184         return isset($this->relations[$properties['className']]) ? $this->relations[$properties['className']]:array();
185     }
186
187     /**
188      * getIndexes
189      *
190      * Get array of indexes from table properties
191      *
192      * @param  string $properties Array of table properties
193      * @return array  $index
194      */
195     public function getIndexes($properties)
196     {
197         return isset($properties['indexes']) ? $properties['indexes']:array();;
198     }
199
200     /**
201      * getAttributes
202      *
203      * Get array of attributes from table properties
204      *
205      * @param  string $properties Array of tables properties 
206      * @return array  $attributes
207      */
208     public function getAttributes($properties)
209     {
210         return isset($properties['attributes']) ? $properties['attributes']:array();
211     }
212
213     /**
214      * getTemplates
215      *
216      * Get array of templates from table properties
217      *
218      * @param  string $properties Array of table properties
219      * @return array  $templates  Array of table templates
220      */
221     public function getTemplates($properties)
222     {
223         return isset($properties['templates']) ? $properties['templates']:array();
224     }
225
226     /**
227      * getActAs
228      *
229      * Get array of actAs definitions from table properties
230      *
231      * @param  string $properties Array of table properties
232      * @return array  $actAs      Array of actAs definitions from table properties
233      */
234     public function getActAs($properties)
235     {
236         return isset($properties['actAs']) ? $properties['actAs']:array();
237     }
238
239     /**
240      * parseSchema
241      *
242      * A method to parse a Schema and translate it into a property array.
243      * The function returns that property array.
244      *
245      * @param  string $schema   Path to the file containing the schema
246      * @return array  $build    Built array of schema information
247      */
248     public function parseSchema($schema, $type)
249     {
250         $array = Doctrine_Parser::load($schema, $type);
251         
252         $build = array();
253         
254         foreach ($array as $className => $table) {
255             $columns = array();
256             
257             $className = isset($table['className']) ? (string) $table['className']:(string) $className;
258             $tableName = isset($table['tableName']) ? (string) $table['tableName']:(string) Doctrine::tableize($className);
259             
260             $columns = isset($table['columns']) ? $table['columns']:array();
261             $columns = isset($table['fields']) ? $table['fields']:$columns;
262             
263             if ( ! empty($columns)) {
264                 foreach ($columns as $columnName => $field) {
265                     $colDesc = array();
266                     $colDesc['name'] = isset($field['name']) ? (string) $field['name']:$columnName;
267                     $colDesc['type'] = isset($field['type']) ? (string) $field['type']:null;
268                     $colDesc['ptype'] = isset($field['ptype']) ? (string) $field['ptype']:(string) $colDesc['type'];
269                     $colDesc['length'] = isset($field['length']) ? (int) $field['length']:null;
270                     $colDesc['length'] = isset($field['size']) ? (int) $field['size']:$colDesc['length'];
271                     $colDesc['fixed'] = isset($field['fixed']) ? (int) $field['fixed']:null;
272                     $colDesc['unsigned'] = isset($field['unsigned']) ? (bool) $field['unsigned']:null;
273                     $colDesc['primary'] = isset($field['primary']) ? (bool) (isset($field['primary']) && $field['primary']):null;
274                     $colDesc['default'] = isset($field['default']) ? (string) $field['default']:null;
275                     $colDesc['notnull'] = isset($field['notnull']) ? (bool) (isset($field['notnull']) && $field['notnull']):null;
276                     $colDesc['autoincrement'] = isset($field['autoincrement']) ? (bool) (isset($field['autoincrement']) && $field['autoincrement']):null;
277                     $colDesc['autoincrement'] = isset($field['autoinc']) ? (bool) (isset($field['autoinc']) && $field['autoinc']):$colDesc['autoincrement'];
278                     $colDesc['unique'] = isset($field['unique']) ? (bool) (isset($field['unique']) && $field['unique']):null;
279                     $colDesc['values'] = isset($field['values']) ? (array) $field['values']: null;
280
281                     $columns[(string) $colDesc['name']] = $colDesc;
282                 }
283                 
284                 $build[$className]['connection'] = isset($table['connection']) ? $table['connection']:null;
285                 $build[$className]['className'] = $className;
286                 $build[$className]['tableName'] = $tableName;
287                 $build[$className]['columns'] = $columns;
288                 $build[$className]['relations'] = isset($table['relations']) ? $table['relations']:array();
289                 $build[$className]['indexes'] = isset($table['indexes']) ? $table['indexes']:array();
290                 $build[$className]['attributes'] = isset($table['attributes']) ? $table['attributes']:array();
291                 $build[$className]['templates'] = isset($table['templates']) ? $table['templates']:array();
292                 $build[$className]['actAs'] = isset($table['actAs']) ? $table['actAs']:array();
293             }
294             
295             if (isset($table['inheritance'])) {
296                 $build[$className]['inheritance'] = $table['inheritance'];
297             }
298         }
299         
300         return $build;
301     }
302
303     /**
304      * buildRelationships
305      *
306      * Loop through an array of schema information and build all the necessary relationship information
307      * Will attempt to auto complete relationships and simplify the amount of information required for defining a relationship
308      *
309      * @param  string $array 
310      * @return void
311      */
312     protected function buildRelationships(&$array)
313     {
314         foreach ($array as $name => $properties) {
315             if ( ! isset($properties['relations'])) {
316                 continue;
317             }
318             
319             $className = $properties['className'];
320             $relations = $properties['relations'];
321             
322             foreach ($relations as $alias => $relation) {
323                 $class = isset($relation['class']) ? $relation['class']:$alias;
324                 
325                 // Attempt to guess the local and foreign
326                 if (isset($relation['refClass'])) {
327                     $relation['local'] = isset($relation['local']) ? $relation['local']:Doctrine::tableize($name) . '_id';
328                     $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:Doctrine::tableize($class) . '_id';
329                 } else {
330                     $relation['local'] = isset($relation['local']) ? $relation['local']:Doctrine::tableize($class) . '_id';
331                     $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:'id';
332                 }
333             
334                 $relation['alias'] = isset($relation['alias']) ? $relation['alias'] : $alias;
335                 $relation['class'] = $class;
336                 
337                 if (isset($relation['refClass'])) {
338                     $relation['type'] = 'many';
339                 }
340                 
341                 if (isset($relation['type']) && $relation['type']) {
342                     $relation['type'] = $relation['type'] === 'one' ? Doctrine_Relation::ONE:Doctrine_Relation::MANY;
343                 } else {
344                     $relation['type'] = Doctrine_Relation::ONE;
345                 }
346
347                 if (isset($relation['foreignType']) && $relation['foreignType']) {
348                     $relation['foreignType'] = $relation['foreignType'] === 'one' ? Doctrine_Relation::ONE:Doctrine_Relation::MANY;
349                 }
350                 
351                 if(isset($relation['refClass']) && !empty($relation['refClass'])  && ( ! isset($array[$relation['refClass']]['relations']) || empty($array[$relation['refClass']]['relations']))) {
352                     
353                     if ( ! isset($array[$relation['refClass']]['relations'][$className]['local'])) {
354                         $array[$relation['refClass']]['relations'][$className]['local'] = $relation['local'];
355                     }
356                     
357                     if ( ! isset($array[$relation['refClass']]['relations'][$className]['foreign'])) {
358                         $array[$relation['refClass']]['relations'][$className]['foreign'] = $relation['foreign'];
359                     }
360                     
361                     $array[$relation['refClass']]['relations'][$className]['ignore'] = true;
362                     
363                     if ( ! isset($array[$relation['refClass']]['relations'][$relation['class']]['local'])) {
364                         $array[$relation['refClass']]['relations'][$relation['class']]['local'] = $relation['local'];
365                     }
366                     
367                     if ( ! isset($array[$relation['refClass']]['relations'][$relation['class']]['foreign'])) {
368                         $array[$relation['refClass']]['relations'][$relation['class']]['foreign'] = $relation['foreign'];
369                     }
370                     
371                     $array[$relation['refClass']]['relations'][$relation['class']]['ignore'] = true;
372                     
373                     if(isset($relation['foreignAlias'])) {
374                         $array[$relation['class']]['relations'][$relation['foreignAlias']] = array('type'=>$relation['type'],'local'=>$relation['foreign'],'foreign'=>$relation['local'],'refClass'=>$relation['refClass'],'class'=>$className);
375                     }
376                 }
377                 
378                 $this->relations[$className][$alias] = $relation;
379             }
380         }
381         
382         // Now we fix all the relationships and auto-complete opposite ends of relationships
383         $this->fixRelationships();
384     }
385
386     /**
387      * fixRelationships
388      *
389      * Loop through all relationships building the opposite ends of each relationship
390      *
391      * @return void
392      */
393     protected function fixRelationships()
394     {
395         foreach($this->relations as $className => $relations) {
396             foreach ($relations AS $alias => $relation) {
397                 if(isset($relation['ignore']) && $relation['ignore'] || isset($relation['refClass']) || isset($this->relations[$relation['class']]['relations'][$className])) {
398                     continue;
399                 }
400                     
401                 $newRelation = array();
402                 $newRelation['foreign'] = $relation['local'];
403                 $newRelation['local'] = $relation['foreign'];
404                 $newRelation['class'] = $className;
405                 $newRelation['alias'] = isset($relation['foreignAlias'])?$relation['foreignAlias']:$className;
406                 
407                 if(isset($relation['foreignType'])) {
408                     $newRelation['type'] = $relation['foreignType'];
409                 } else {
410                     $newRelation['type'] = $relation['type'] === Doctrine_Relation::ONE ? Doctrine_Relation::MANY:Doctrine_Relation::ONE;
411                 }
412                 
413                 if( isset($this->relations[$relation['class']]) && is_array($this->relations[$relation['class']]) ) {
414                     foreach($this->relations[$relation['class']] as $otherRelation) {
415                         // skip fully defined m2m relationships
416                         if(isset($otherRelation['refClass']) && $otherRelation['refClass'] == $className) {
417                             continue(2);
418                         }
419                     }
420                 }
421                 
422                 $this->relations[$relation['class']][$newRelation['alias']] = $newRelation;
423             }
424         }
425     }
426 }