Coverage for Doctrine_Import_Builder

Back to coverage report

1 <?php
2 /*
3  *  $Id: Builder.php 2838 2007-10-14 07:58:52Z phuson $
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  * Import builder is responsible of building Doctrine ActiveRecord classes
25  * based on a database schema.
26  *
27  * @package     Doctrine
28  * @subpackage  Import
29  * @link        www.phpdoctrine.com
30  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
31  * @since       1.0
32  * @version     $Revision: 2838 $
33  * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
34  * @author      Jukka Hassinen <Jukka.Hassinen@BrainAlliance.com>
35  * @author      Nicolas BĂ©rard-Nault <nicobn@php.net>
36  */
37 class Doctrine_Import_Builder
38 {
39     /**
40      * @var string $path    the path where imported files are being generated
41      */
42     private $path = '';
43
44     private $suffix = '.class.php';
45     
46     private $generateBaseClasses = false;
47     
48     private $baseClassesDirectory = 'generated';
49     
50     private static $tpl;
51     
52     public function __construct()
53     {
54         $this->loadTemplate();
55     }
56
57     /**
58      * setTargetPath
59      *
60      * @param string path   the path where imported files are being generated
61      * @return
62      */
63     public function setTargetPath($path)
64     {
65         if ( ! file_exists($path)) {
66             mkdir($path, 0777);
67         }
68
69         $this->path = $path;
70     }
71     
72     /**
73      * generateBaseClasses
74      *
75      * Specify whether or not to generate classes which extend from generated base classes
76      *
77      * @param string $bool
78      * @return void
79      * @author Jonathan H. Wage
80      */
81     public function generateBaseClasses($bool = null)
82     {
83       if ($bool !== null) {
84         $this->generateBaseClasses = $bool;
85       }
86       
87       return $this->generateBaseClasses;
88     }
89     
90     /**
91      * getTargetPath
92      *
93      * @return string       the path where imported files are being generated
94      */
95     public function getTargetPath()
96     {
97         return $this->path;
98     }
99
100     /**
101      * This is a template that was previously in Builder/Record.tpl. Due to the fact
102      * that it was not bundled when compiling, it had to be moved here.
103      *
104      * @return void
105      */
106     public function loadTemplate() 
107     {
108         if (isset(self::$tpl)) {
109             return;
110         }
111
112         self::$tpl =<<<END
113 /**
114  * This class has been auto-generated by the Doctrine ORM Framework
115  */
116 %sclass %s extends %s
117 {
118 %s
119 %s
120 }
121 END;
122
123     }
124
125     /*
126      * Build the table definition of a Doctrine_Record object
127      *
128      * @param  string $table
129      * @param  array  $tableColumns
130      */
131     public function buildTableDefinition(array $options, array $columns, array $relations, array $indexes)
132     {
133         $ret = array();
134         
135         $i = 0;
136         
137         if (isset($options['inheritance']['extends']) && !isset($options['override_parent'])) {
138             $ret[$i] = "\t\t\t\tparent::setTableDefinition();";
139             $i++;
140         }
141         
142         if (isset($options['tableName']) && !empty($options['tableName'])) {
143             $ret[$i] = str_repeat(' ', 8) . '$this->setTableName(\''. $options['tableName'].'\');';
144             
145             $i++;
146         }
147         
148         foreach ($columns as $name => $column) {
149             $ret[$i] = '        $this->hasColumn(\'' . $name . '\', \'' . $column['type'] . '\'';
150             
151             if ($column['length']) {
152                 $ret[$i] .= ', ' . $column['length'];
153             } else {
154                 $ret[$i] .= ', null';
155             }
156
157             $a = array();
158
159             if (isset($column['default']) && $column['default']) {
160                 $a[] = '\'default\' => ' . var_export($column['default'], true);
161             }
162             if (isset($column['notnull']) && $column['notnull']) {
163                 $a[] = '\'notnull\' => true';
164             }
165             if (isset($column['primary']) && $column['primary']) {
166                 $a[] = '\'primary\' => true';
167             }
168             if ((isset($column['autoinc']) && $column['autoinc']) || isset($column['autoincrement']) && $column['autoincrement']) {
169                 $a[] = '\'autoincrement\' => true';
170             }
171             if (isset($column['unique']) && $column['unique']) {
172                 $a[] = '\'unique\' => true';
173             }
174             if (isset($column['unsigned']) && $column['unsigned']) {
175                 $a[] = '\'unsigned\' => true';
176             }
177             if ($column['type'] == 'enum' && isset($column['values']) ) {
178                 $a[] = '\'values\' => array(\'' . implode('\',\'', $column['values']) . '\')';
179             }
180
181             if ( ! empty($a)) {
182                 $ret[$i] .= ', ' . 'array(';
183                 $length = strlen($ret[$i]);
184                 $ret[$i] .= implode(',' . PHP_EOL . str_repeat(' ', $length), $a) . ')';
185             }
186             
187             $ret[$i] .= ');';
188
189             if ($i < (count($columns) - 1)) {
190                 $ret[$i] .= PHP_EOL;
191             }
192             $i++;
193         }
194         
195         foreach ($indexes as $indexName => $definitions) {
196             $ret[$i] = "\n".'        $this->index(\'' . $indexName . '\', array(';
197             
198             foreach ($definitions as $name => $value) {
199               
200               // parse fields
201               if ($name === 'fields') {
202                 $ret[$i] .= '\'fields\' => array(';
203                 
204                 foreach ($value as $fieldName => $fieldValue) {
205                   $ret[$i] .= '\'' . $fieldName . '\' => array( ';
206                   
207                   // parse options { sorting, length, primary }
208                   if (isset($fieldValue) && $fieldValue) {
209                     foreach ($fieldValue as $optionName => $optionValue) {
210                       
211                       $ret[$i] .= '\'' . $optionName . '\' => ';
212                       
213                       // check primary option, mark either as true or false
214                       if ($optionName === 'primary') {
215                        $ret[$i] .= (($optionValue == 'true') ? 'true' : 'false') . ', ';
216                        continue;
217                       }
218                       
219                       // convert sorting option to uppercase, for instance, asc -> ASC
220                       if ($optionName === 'sorting') {
221                        $ret[$i] .= '\'' . strtoupper($optionValue) . '\', ';
222                        continue;
223                       }
224                       
225                       // check the rest of the options
226                       $ret[$i] .= '\'' . $optionValue . '\', ';
227                     }
228                   }
229                                     
230                   $ret[$i] .= '), ';
231                 }
232               }
233               
234               // parse index type option, 4 choices { unique, fulltext, gist, gin }
235               if ($name === 'type') {
236                $ret[$i] .= '), \'type\' => \'' . $value . '\'';
237               }
238               
239               // add extra ) if type definition is not declared
240               if (!isset($definitions['type'])) {
241                $ret[$i] .= ')';
242               }
243             }
244             
245             $ret[$i] .= '));';
246             $i++;
247         }
248         
249         if (!empty($ret)) {
250           return "\n\tpublic function setTableDefinition()"."\n\t{\n".implode("\n", $ret)."\n\t}";
251         }
252     }
253     public function buildSetUp(array $options, array $columns, array $relations)
254     {
255         $ret = array();
256         
257         $i = 0;
258         
259         if (isset($options['inheritance']['extends']) && !isset($options['override_parent'])) {
260             $ret[$i] = "\t\t\t\tparent::setUp();";
261             $i++;
262         }
263         
264         foreach ($relations as $name => $relation) {
265             $class = isset($relation['class']) ? $relation['class']:$name;
266             $alias = (isset($relation['alias']) && $relation['alias'] !== $relation['class']) ? ' as ' . $relation['alias'] : '';
267
268             if ( ! isset($relation['type'])) {
269                 $relation['type'] = Doctrine_Relation::ONE;
270             }
271
272             if ($relation['type'] === Doctrine_Relation::ONE || 
273                 $relation['type'] === Doctrine_Relation::ONE_COMPOSITE) {
274                 $ret[$i] = '        $this->hasOne(\'' . $class . $alias . '\'';
275             } else {
276                 $ret[$i] = '        $this->hasMany(\'' . $class . $alias . '\'';
277             }
278             
279             $a = array();
280
281             if (isset($relation['refClass'])) {
282                 $a[] = '\'refClass\' => ' . var_export($relation['refClass'], true);
283             }
284             
285             if (isset($relation['deferred']) && $relation['deferred']) {
286                 $a[] = '\'default\' => ' . var_export($relation['deferred'], true);
287             }
288             
289             if (isset($relation['local']) && $relation['local']) {
290                 $a[] = '\'local\' => ' . var_export($relation['local'], true);
291             }
292             
293             if (isset($relation['foreign']) && $relation['foreign']) {
294                 $a[] = '\'foreign\' => ' . var_export($relation['foreign'], true);
295             }
296             
297             if (isset($relation['onDelete']) && $relation['onDelete']) {
298                 $a[] = '\'onDelete\' => ' . var_export($relation['onDelete'], true);
299             }
300             
301             if (isset($relation['onUpdate']) && $relation['onUpdate']) {
302                 $a[] = '\'onUpdate\' => ' . var_export($relation['onUpdate'], true);
303             }
304             
305             if ( ! empty($a)) {
306                 $ret[$i] .= ', ' . 'array(';
307                 $length = strlen($ret[$i]);
308                 $ret[$i] .= implode(',' . PHP_EOL . str_repeat(' ', $length), $a) . ')';
309             }
310             
311             $ret[$i] .= ');';
312             $i++;
313         }
314         
315         if (isset($options['inheritance']['keyField']) && isset($options['inheritance']['keyValue'])) {
316             $i++;
317             $ret[$i] = "\t\t".'$this->setInheritanceMap(array(\''.$options['inheritance']['keyField'].'\' => '.$options['inheritance']['keyValue'].'));';
318         }
319         
320         if (!empty($ret)) {
321           return "\n\tpublic function setUp()\n\t{\n".implode("\n", $ret)."\n\t}";
322         }
323     }
324     
325     public function buildDefinition(array $options, array $columns, array $relations = array(), array $indexes = array())
326     {
327         if ( ! isset($options['className'])) {
328             throw new Doctrine_Import_Builder_Exception('Missing class name.');
329         }
330         
331         $abstract = isset($options['abstract']) ? 'abstract ':null;
332         $className = $options['className'];
333         $extends = isset($options['inheritance']['extends']) ? $options['inheritance']['extends']:'Doctrine_Record';
334         $definition = !isset($options['no_definition']) ? $this->buildTableDefinition($options, $columns, $relations, $indexes):null;
335         $setUp = !isset($options['no_definition']) ? $this->buildSetUp($options, $columns, $relations):null;
336         
337         $content = sprintf(self::$tpl, $abstract,
338                                        $className,
339                                        $extends,
340                                        $definition,
341                                        $setUp);
342         
343         return $content;
344     }
345
346     public function buildRecord(array $options, array $columns, array $relations = array(), array $indexes = array())
347     {
348         if ( !isset($options['className'])) {
349             throw new Doctrine_Import_Builder_Exception('Missing class name.');
350         }
351
352         if ( !isset($options['fileName'])) {
353             if (empty($this->path)) {
354                 throw new Doctrine_Import_Builder_Exception('No build target directory set.');
355             }
356             
357
358             if (is_writable($this->path) === false) {
359                 throw new Doctrine_Import_Builder_Exception('Build target directory ' . $this->path . ' is not writable.');
360             }
361
362             $options['fileName']  = $this->path . DIRECTORY_SEPARATOR . $options['className'] . $this->suffix;
363         }
364         
365         if ($this->generateBaseClasses()) {
366           
367           // We only want to generate this one if it doesn't already exist
368           if (!file_exists($options['fileName'])) {
369             $optionsBak = $options;
370             
371             unset($options['tableName']);
372             $options['inheritance']['extends'] = 'Base' . $options['className'];
373             $options['requires'] = array($this->baseClassesDirectory . DIRECTORY_SEPARATOR  . $options['inheritance']['extends'] . $this->suffix);
374             $options['no_definition'] = true;
375             
376             $this->writeDefinition($options, array(), array(), array());
377             
378             $options = $optionsBak;
379           }
380           
381           $generatedPath = $this->path . DIRECTORY_SEPARATOR . $this->baseClassesDirectory;
382           
383           if (!file_exists($generatedPath)) {
384             mkdir($generatedPath);
385           }
386           
387           $options['className'] = 'Base' . $options['className'];
388           $options['abstract'] = true;
389           $options['fileName']  = $generatedPath . DIRECTORY_SEPARATOR . $options['className'] . $this->suffix;
390           
391           $this->writeDefinition($options, $columns, $relations, $indexes);
392         } else {
393           $this->writeDefinition($options, $columns, $relations, $indexes);
394         }
395     }
396     
397     public function writeDefinition(array $options, array $columns, array $relations = array(), array $indexes = array())
398     {
399       $content = $this->buildDefinition($options, $columns, $relations, $indexes);
400       
401       $code = "<?php\n";
402       
403       if (isset($options['requires'])) {
404           if (!is_array($options['requires'])) {
405               $options['requires'] = array($options['requires']);
406           }
407           
408           foreach ($options['requires'] as $require) {
409               $code .= "require_once('".$require."');";
410           }
411       }
412       
413       $code .= PHP_EOL . $content;
414       
415       $bytes = file_put_contents($options['fileName'], $code);
416
417       if ($bytes === false) {
418           throw new Doctrine_Import_Builder_Exception("Couldn't write file " . $options['fileName']);
419       }
420     }
421 }