Coverage for Doctrine_Import_Builder

Back to coverage report

1 <?php
2 /*
3  *  $Id: Builder.php 2963 2007-10-21 06:23:59Z Jonathan.Wage $
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  * Doctrine_Import_Builder
24  *
25  * Import builder is responsible of building Doctrine_Record classes
26  * based on a database schema.
27  *
28  * @package     Doctrine
29  * @subpackage  Import
30  * @link        www.phpdoctrine.com
31  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
32  * @since       1.0
33  * @version     $Revision: 2963 $
34  * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
35  * @author      Jukka Hassinen <Jukka.Hassinen@BrainAlliance.com>
36  * @author      Nicolas BĂ©rard-Nault <nicobn@php.net>
37  * @author      Jonathan H. Wage <jwage@mac.com>
38  */
39 class Doctrine_Import_Builder
40 {
41     /**
42      * Path
43      * 
44      * the path where imported files are being generated
45      *
46      * @var string $path
47      */
48     private $path = '';
49
50     /**
51      * suffix
52      * 
53      * File suffix to use when writing class definitions
54      *
55      * @var string $suffix
56      */
57     private $suffix = '.class.php';
58
59     /**
60      * generateBaseClasses
61      * 
62      * Bool true/false for whether or not to generate base classes
63      *
64      * @var string $suffix
65      */
66     private $generateBaseClasses = false;
67
68     /**
69      * baseClassesDirectory
70      * 
71      * Directory to put the generate base classes in
72      *
73      * @var string $suffix
74      */
75     private $baseClassesDirectory = 'generated';
76
77     /**
78      * tpl
79      *
80      * Class template used for writing classes
81      *
82      * @var $tpl
83      */
84     private static $tpl;
85
86     /**
87      * __construct
88      *
89      * @return void
90      */
91     public function __construct()
92     {
93         $this->loadTemplate();
94     }
95
96     /**
97      * setTargetPath
98      *
99      * @param string path   the path where imported files are being generated
100      * @return
101      */
102     public function setTargetPath($path)
103     {
104         if ( ! file_exists($path)) {
105             mkdir($path, 0777);
106         }
107
108         $this->path = $path;
109     }
110
111     /**
112      * generateBaseClasses
113      *
114      * Specify whether or not to generate classes which extend from generated base classes
115      *
116      * @param string $bool
117      * @return void
118      * @author Jonathan H. Wage
119      */
120     public function generateBaseClasses($bool = null)
121     {
122       if ($bool !== null) {
123         $this->generateBaseClasses = $bool;
124       }
125       
126       return $this->generateBaseClasses;
127     }
128
129     /**
130      * getTargetPath
131      *
132      * @return string       the path where imported files are being generated
133      */
134     public function getTargetPath()
135     {
136         return $this->path;
137     }
138
139     /**
140      * loadTemplate
141      * 
142      * Loads the class template used for generating classes
143      *
144      * @return void
145      */
146     public function loadTemplate() 
147     {
148         if (isset(self::$tpl)) {
149             return;
150         }
151
152         self::$tpl =<<<END
153 /**
154  * This class has been auto-generated by the Doctrine ORM Framework
155  */
156 %sclass %s extends %s
157 {
158 %s
159 %s
160 %s
161 }
162 END;
163     }
164
165     /*
166      * Build the accessors
167      *
168      * @param  string $table
169      * @param  array  $columns
170      */
171     public function buildAccessors(array $options, array $columns)
172     {
173         $ret = '';
174         foreach ($columns as $name => $column) {
175             // getters
176             $ret .= "\n\tpublic function get".Doctrine::classify($name)."(\$load = true)\n";
177             $ret .= "\t{\n";
178             $ret .= "\t\treturn \$this->get('{$name}', \$load);\n";
179             $ret .= "\t}\n";
180
181             // setters
182             $ret .= "\n\tpublic function set".Doctrine::classify($name)."(\${$name}, \$load = true)\n";
183             $ret .= "\t{\n";
184             $ret .= "\t\treturn \$this->set('{$name}', \${$name}, \$load);\n";
185             $ret .= "\t}\n";
186         }
187
188         return $ret;
189     }
190
191     /*
192      * Build the table definition of a Doctrine_Record object
193      *
194      * @param  string $table
195      * @param  array  $tableColumns
196      */
197     public function buildTableDefinition(array $options, array $columns, array $relations, array $indexes, array $attributes, array $templates, array $actAs)
198     {
199         $ret = array();
200         
201         $i = 0;
202         
203         if (isset($options['inheritance']['extends']) && !isset($options['override_parent'])) {
204             $ret[$i] = "\t\tparent::setTableDefinition();";
205             $i++;
206         }
207         
208         if (isset($options['tableName']) && !empty($options['tableName'])) {
209             $ret[$i] = "\t\t".'$this->setTableName(\''. $options['tableName'].'\');';
210             
211             $i++;
212         }
213         
214         foreach ($columns as $name => $column) {
215             $ret[$i] = "\t\t".'$this->hasColumn(\'' . $name . '\', \'' . $column['type'] . '\'';
216             
217             if ($column['length']) {
218                 $ret[$i] .= ', ' . $column['length'];
219             } else {
220                 $ret[$i] .= ', null';
221             }
222
223             $a = array();
224
225             if (isset($column['default'])) {
226                 $a[] = '\'default\' => ' . var_export($column['default'], true);
227             }
228             if (isset($column['notnull']) && $column['notnull']) {
229                 $a[] = '\'notnull\' => true';
230             }
231             if (isset($column['primary']) && $column['primary']) {
232                 $a[] = '\'primary\' => true';
233             }
234             if ((isset($column['autoinc']) && $column['autoinc']) || isset($column['autoincrement']) && $column['autoincrement']) {
235                 $a[] = '\'autoincrement\' => true';
236             }
237             if (isset($column['unique']) && $column['unique']) {
238                 $a[] = '\'unique\' => true';
239             }
240             if (isset($column['unsigned']) && $column['unsigned']) {
241                 $a[] = '\'unsigned\' => true';
242             }
243             if ($column['type'] == 'enum' && isset($column['values']) ) {
244                 $a[] = '\'values\' => array(\'' . implode('\',\'', $column['values']) . '\')';
245             }
246
247             if ( ! empty($a)) {
248                 $ret[$i] .= ', ' . 'array(';
249                 $length = strlen($ret[$i]);
250                 $ret[$i] .= implode(',' . PHP_EOL . str_repeat(' ', $length), $a) . ')';
251             }
252             
253             $ret[$i] .= ');';
254
255             if ($i < (count($columns) - 1)) {
256                 $ret[$i] .= PHP_EOL;
257             }
258             $i++;
259         }
260         
261         $ret[$i] = $this->buildIndexes($indexes);
262         $i++;
263         
264         $ret[$i] = $this->buildAttributes($attributes);
265         $i++;
266         
267         $ret[$i] = $this->buildTemplates($templates);
268         $i++;
269         
270         $ret[$i] = $this->buildActAs($actAs);
271         
272         if ( ! empty($ret)) {
273           return "\n\tpublic function setTableDefinition()"."\n\t{\n".implode("\n", $ret)."\n\t}";
274         }
275     }
276
277     /**
278      * buildTemplates
279      *
280      * @param string $array 
281      * @return void
282      */
283     public function buildTemplates(array $templates)
284     {
285         $build = '';
286         foreach ($templates as $name => $options) {
287             
288             if (is_array($options) && !empty($options)) {
289                 $optionsPhp = $this->arrayToPhpArrayCode($options);
290             
291                 $build .= "\t\t\$this->loadTemplate('" . $name . "', " . $optionsPhp . ");\n";
292             } else {
293                 if (isset($templates[0])) {
294                     $build .= "\t\t\$this->loadTemplate('" . $options . "');\n";
295                 } else {
296                     $build .= "\t\t\$this->loadTemplate('" . $name . "');\n";
297                 }
298             }
299         }
300         
301         return $build;
302     }
303
304     /**
305      * buildActAs
306      *
307      * @param string $array 
308      * @return void
309      */
310     public function buildActAs(array $actAs)
311     {
312         $build = '';
313         foreach ($actAs as $name => $options) {
314             if (is_array($options) && !empty($options)) {
315                 $optionsPhp = $this->arrayToPhp($options);
316                 
317                 $build .= "\t\t\$this->actAs('" . $name . "', " . $optionsPhp . ");\n";
318             } else {
319                 if (isset($actAs[0])) {
320                     $build .= "\t\t\$this->actAs('" . $options . "');\n";
321                 } else {
322                     $build .= "\t\t\$this->actAs('" . $name . "');\n";
323                 }
324             }
325         }
326         
327         return $build;
328     }
329
330     /**
331      * arrayToPhp
332      *
333      * @param string $array 
334      * @return void
335      */
336     protected function arrayToPhp(array $array)
337     {
338         ob_start();
339         var_export($array);
340         $php = ob_get_contents();
341         ob_end_clean();
342         
343         return $php;
344     }
345
346     /**
347      * buildAttributes
348      *
349      * @param string $array 
350      * @return void
351      */
352     public function buildAttributes(array $attributes)
353     {
354         $build = "\n";
355         foreach ($attributes as $key => $value) {
356             if ( ! is_array($value)) {
357                 $value = array($value);
358             }
359             
360             $values = '';
361             foreach ($value as $attr) {
362                 $values .= "Doctrine::" . strtoupper($key) . "_" . strtoupper($attr) . ' ^ ';
363             }
364             
365             // Trim last ^
366             $values = substr($values, 0, strlen($values) - 3);
367             
368             $build .= "\t\t\$this->setAttribute(Doctrine::ATTR_" . strtoupper($key) . ", " . $values . ");\n";
369         }
370         
371         return $build;
372     }
373
374     /**
375      * buildIndexes
376      *
377      * @param string $array 
378      * @return void
379      */
380     public function buildIndexes(array $indexes)
381     {
382       $build = '';
383
384       foreach ($indexes as $indexName => $definitions) {
385           $build = "\n\t\t".'$this->index(\'' . $indexName . '\', array(';
386
387           foreach ($definitions as $name => $value) {
388
389             // parse fields
390             if ($name === 'fields' || $name === 'columns') {
391               $build .= '\'fields\' => array(';
392
393               foreach ($value as $fieldName => $fieldValue) {
394                 $build .= '\'' . $fieldName . '\' => array( ';
395
396                 // parse options { sorting, length, primary }
397                 if (isset($fieldValue) && $fieldValue) {
398                   foreach ($fieldValue as $optionName => $optionValue) {
399
400                     $build .= '\'' . $optionName . '\' => ';
401
402                     // check primary option, mark either as true or false
403                     if ($optionName === 'primary') {
404                      $build .= (($optionValue == 'true') ? 'true' : 'false') . ', ';
405                      continue;
406                     }
407
408                     // convert sorting option to uppercase, for instance, asc -> ASC
409                     if ($optionName === 'sorting') {
410                      $build .= '\'' . strtoupper($optionValue) . '\', ';
411                      continue;
412                     }
413
414                     // check the rest of the options
415                     $build .= '\'' . $optionValue . '\', ';
416                   }
417                 }
418
419                 $build .= '), ';
420               }
421             }
422
423             // parse index type option, 4 choices { unique, fulltext, gist, gin }
424             if ($name === 'type') {
425              $build .= '), \'type\' => \'' . $value . '\'';
426             }
427
428             // add extra ) if type definition is not declared
429             if ( ! isset($definitions['type'])) {
430              $build .= ')';
431             }
432           }
433
434           $build .= '));';
435       }
436
437       return $build;
438     }
439
440     /**
441      * buildSetUp
442      *
443      * @param  array $options 
444      * @param  array $columns 
445      * @param  array $relations 
446      * @return string
447      */
448     public function buildSetUp(array $options, array $columns, array $relations)
449     {
450         $ret = array();
451         $i = 0;
452         
453         if ( !  (isset($options['override_parent']) && $options['override_parent'] === true)) {
454             $ret[$i] = "\t\tparent::setUp();";
455             $i++;
456         }
457         
458         foreach ($relations as $name => $relation) {
459             $class = isset($relation['class']) ? $relation['class']:$name;
460             $alias = (isset($relation['alias']) && $relation['alias'] !== $relation['class']) ? ' as ' . $relation['alias'] : '';
461
462             if ( ! isset($relation['type'])) {
463                 $relation['type'] = Doctrine_Relation::ONE;
464             }
465
466             if ($relation['type'] === Doctrine_Relation::ONE || 
467                 $relation['type'] === Doctrine_Relation::ONE_COMPOSITE) {
468                 $ret[$i] = "\t\t".'$this->hasOne(\'' . $class . $alias . '\'';
469             } else {
470                 $ret[$i] = "\t\t".'$this->hasMany(\'' . $class . $alias . '\'';
471             }
472             
473             $a = array();
474
475             if (isset($relation['refClass'])) {
476                 $a[] = '\'refClass\' => ' . var_export($relation['refClass'], true);
477             }
478             
479             if (isset($relation['deferred']) && $relation['deferred']) {
480                 $a[] = '\'default\' => ' . var_export($relation['deferred'], true);
481             }
482             
483             if (isset($relation['local']) && $relation['local']) {
484                 $a[] = '\'local\' => ' . var_export($relation['local'], true);
485             }
486             
487             if (isset($relation['foreign']) && $relation['foreign']) {
488                 $a[] = '\'foreign\' => ' . var_export($relation['foreign'], true);
489             }
490             
491             if (isset($relation['onDelete']) && $relation['onDelete']) {
492                 $a[] = '\'onDelete\' => ' . var_export($relation['onDelete'], true);
493             }
494             
495             if (isset($relation['onUpdate']) && $relation['onUpdate']) {
496                 $a[] = '\'onUpdate\' => ' . var_export($relation['onUpdate'], true);
497             }
498             
499             if (isset($relation['equal']) && $relation['equal']) { 
500                 $a[] = '\'equal\' => ' . var_export($relation['equal'], true); 
501             }
502             
503             if ( ! empty($a)) {
504                 $ret[$i] .= ', ' . 'array(';
505                 $length = strlen($ret[$i]);
506                 $ret[$i] .= implode(',' . PHP_EOL . str_repeat(' ', $length), $a) . ')';
507             }
508             
509             $ret[$i] .= ');'."\n";
510             $i++;
511         }
512         
513         if (isset($options['inheritance']['keyField']) && isset($options['inheritance']['keyValue'])) {
514             $i++;
515             $ret[$i] = "\t\t".'$this->setInheritanceMap(array(\''.$options['inheritance']['keyField'].'\' => '.$options['inheritance']['keyValue'].'));';
516         }
517         
518         if ( ! empty($ret)) {
519           return "\n\tpublic function setUp()\n\t{\n".implode("\n", $ret)."\n\t}";
520         }
521     }
522
523     /**
524      * buildDefinition
525      *
526      * @param array $options 
527      * @param array $columns 
528      * @param array $relations 
529      * @param array $indexes 
530      * @param array $attributes 
531      * @param array $templates 
532      * @param array $actAs 
533      * @return string
534      */
535     public function buildDefinition(array $options, array $columns, array $relations = array(), array $indexes = array(), $attributes = array(), array $templates = array(), array $actAs = array())
536     {
537         if ( ! isset($options['className'])) {
538             throw new Doctrine_Import_Builder_Exception('Missing class name.');
539         }
540
541         $abstract = isset($options['abstract']) && $options['abstract'] === true ? 'abstract ':null;
542         $className = $options['className'];
543         $extends = isset($options['inheritance']['extends']) ? $options['inheritance']['extends']:'Doctrine_Record';
544
545         if ( ! (isset($options['no_definition']) && $options['no_definition'] === true)) {
546             $definition = $this->buildTableDefinition($options, $columns, $relations, $indexes, $attributes, $templates, $actAs);
547             $setUp = $this->buildSetUp($options, $columns, $relations);
548         } else {
549             $definition = null;
550             $setUp = null;
551         }
552         
553         $accessors = (isset($options['generate_accessors']) && $options['generate_accessors'] === true) ? $this->buildAccessors($options, $columns):null;
554         
555         $content = sprintf(self::$tpl, $abstract,
556                                        $className,
557                                        $extends,
558                                        $definition,
559                                        $setUp,
560                                        $accessors);
561         
562         return $content;
563     }
564
565     /**
566      * buildRecord
567      *
568      * @param array $options 
569      * @param array $columns 
570      * @param array $relations 
571      * @param array $indexes 
572      * @param array $attributes 
573      * @param array $templates 
574      * @param array $actAs 
575      * @return void=
576      */
577     public function buildRecord(array $options, array $columns, array $relations = array(), array $indexes = array(), array $attributes = array(), array $templates = array(), array $actAs = array())
578     {
579         if ( !isset($options['className'])) {
580             throw new Doctrine_Import_Builder_Exception('Missing class name.');
581         }
582
583         if ( !isset($options['fileName'])) {
584             if (empty($this->path)) {
585                 throw new Doctrine_Import_Builder_Exception('No build target directory set.');
586             }
587             
588
589             if (is_writable($this->path) === false) {
590                 throw new Doctrine_Import_Builder_Exception('Build target directory ' . $this->path . ' is not writable.');
591             }
592
593             $options['fileName']  = $this->path . DIRECTORY_SEPARATOR . $options['className'] . $this->suffix;
594         }
595         
596         if ($this->generateBaseClasses()) {
597           
598           // We only want to generate this one if it doesn't already exist
599           if ( ! file_exists($options['fileName'])) {
600             $optionsBak = $options;
601             
602             unset($options['tableName']);
603             $options['inheritance']['extends'] = 'Base' . $options['className'];
604             $options['requires'] = array($this->baseClassesDirectory . DIRECTORY_SEPARATOR  . $options['inheritance']['extends'] . $this->suffix);
605             $options['no_definition'] = true;
606             
607             $this->writeDefinition($options);
608             
609             $options = $optionsBak;
610           }
611           
612           $generatedPath = $this->path . DIRECTORY_SEPARATOR . $this->baseClassesDirectory;
613           
614           if ( ! file_exists($generatedPath)) {
615             mkdir($generatedPath);
616           }
617           
618           $options['className'] = 'Base' . $options['className'];
619           $options['abstract'] = true;
620           $options['fileName']  = $generatedPath . DIRECTORY_SEPARATOR . $options['className'] . $this->suffix;
621           $options['override_parent'] = true;
622           
623           $this->writeDefinition($options, $columns, $relations, $indexes, $attributes, $templates, $actAs);
624         } else {
625           $this->writeDefinition($options, $columns, $relations, $indexes, $attributes, $templates, $actAs);
626         }
627     }
628
629     /**
630      * writeDefinition
631      *
632      * @param array $options 
633      * @param array $columns 
634      * @param array $relations 
635      * @param array $indexes 
636      * @param array $attributes 
637      * @param array $templates 
638      * @param array $actAs 
639      * @return void
640      */
641     public function writeDefinition(array $options, array $columns = array(), array $relations = array(), array $indexes = array(), array $attributes = array(), array $templates = array(), array $actAs = array())
642     {
643         $content = $this->buildDefinition($options, $columns, $relations, $indexes, $attributes, $templates, $actAs);
644         $code = "<?php\n";
645         
646         if (isset($options['requires'])) {
647             if ( ! is_array($options['requires'])) {
648                 $options['requires'] = array($options['requires']);
649             }
650
651             foreach ($options['requires'] as $require) {
652                 $code .= "require_once('".$require."');\n";
653             }
654         }
655         
656         if (isset($options['connection']) && $options['connection']) {
657             $code .= "// Connection Component Binding\n";
658             $code .= "Doctrine_Manager::getInstance()->bindComponent('" . $options['connectionClassName'] . "', '" . $options['connection'] . "');\n";
659         }
660         
661         $code .= PHP_EOL . $content;
662
663         $bytes = file_put_contents($options['fileName'], $code);
664
665         if ($bytes === false) {
666             throw new Doctrine_Import_Builder_Exception("Couldn't write file " . $options['fileName']);
667         }
668     }
669 }