# 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.org>.
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.org
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 | protected $_relations = array();
43 | protected $_options = array('packagesPrefix' => 'Package',
44 | 'packagesPath' => '',
45 | 'generateBaseClasses' => true,
46 | 'generateTableClasses' => true,
47 | 'baseClassesDirectory' => 'generated',
48 | 'baseClassName' => 'Doctrine_Record',
49 | 'suffix' => '.php');
50 |
51 | /**
52 | * getOption
53 | *
54 | * @param string $name
55 | * @return void
56 | */
57 | public function getOption($name)
58 | {
59 | if (isset($this->_options[$name])) {
60 | return $this->_options[$name];
61 | }
62 | }
63 |
64 | /**
65 | * setOption
66 | *
67 | * @param string $name
68 | * @param string $value
69 | * @return void
70 | */
71 | public function setOption($name, $value)
72 | {
73 | if (isset($this->_options[$name])) {
74 | $this->_options[$name] = $value;
75 | }
76 | }
77 |
78 | /**
79 | * setOptions
80 | *
81 | * @param string $options
82 | * @return void
83 | */
84 | public function setOptions($options)
85 | {
86 | if (!empty($options)) {
87 | $this->_options = $options;
88 | }
89 | }
90 |
91 | /**
92 | * buildSchema
93 | *
94 | * Loop throug directories of schema files and part them all in to one complete array of schema information
95 | *
96 | * @param string $schema Array of schema files or single schema file. Array of directories with schema files or single directory
97 | * @param string $format Format of the files we are parsing and building from
98 | * @return array $array
99 | */
100 | public function buildSchema($schema, $format)
101 | {
102 | $array = array();
103 |
104 | foreach ((array) $schema AS $s) {
105 | if (is_file($s)) {
106 | $array = array_merge($array, $this->parseSchema($s, $format));
107 | } else if (is_dir($s)) {
108 | $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($s),
109 | RecursiveIteratorIterator::LEAVES_ONLY);
110 |
111 | foreach ($it as $file) {
112 | $e = explode('.', $file->getFileName());
113 | if (end($e) === $format) {
114 | $array = array_merge($array, $this->parseSchema($file->getPathName(), $format));
115 | }
116 | }
117 | } else {
118 | $array = array_merge($array, $this->parseSchema($s, $format));
119 | }
120 | }
121 |
122 | $this->_buildRelationships($array);
123 |
124 | return $array;
125 | }
126 |
127 | /**
128 | * importSchema
129 | *
130 | * A method to import a Schema and translate it into a Doctrine_Record object
131 | *
132 | * @param string $schema The file containing the XML schema
133 | * @param string $directory The directory where the Doctrine_Record class will be written
134 | * @param array $models Optional array of models to import
135 | *
136 | * @return void
137 | */
138 | public function importSchema($schema, $format = 'yml', $directory = null, $models = array())
139 | {
140 | $builder = new Doctrine_Import_Builder();
141 | $builder->setTargetPath($directory);
142 |
143 | foreach ($this->_options as $key => $value) {
144 | if ($value) {
145 | $builder->setOption($key, $value);
146 | }
147 | }
148 |
149 | $array = $this->buildSchema($schema, $format);
150 |
151 | foreach ($array as $name => $properties) {
152 | if ( ! empty($models) && !in_array($properties['className'], $models)) {
153 | continue;
154 | }
155 |
156 | $options = $this->getOptions($properties);
157 | $columns = $this->getColumns($properties);
158 | $relations = $this->getRelations($properties);
159 | $indexes = $this->getIndexes($properties);
160 | $attributes = $this->getAttributes($properties);
161 | $templates = $this->getTemplates($properties);
162 | $actAs = $this->getActAs($properties);
163 | $tableOptions = $this->getTableOptions($properties);
164 |
165 | $builder->buildRecord($options, $columns, $relations, $indexes, $attributes, $templates, $actAs, $tableOptions);
166 | }
167 | }
168 |
169 | /**
170 | * getOptions
171 | *
172 | * @param string $properties Array of table properties
173 | * @param string $directory Directory we are writing the class to
174 | * @return array $options Array of options from a parse schemas properties
175 | */
176 | public function getOptions($properties)
177 | {
178 | $options = array();
179 | $options['className'] = $properties['className'];
180 | $options['tableName'] = isset($properties['tableName']) ? $properties['tableName']:null;
181 | $options['connection'] = isset($properties['connection']) ? $properties['connection']:null;
182 | $options['connectionClassName'] = isset($properties['connection']) ? $properties['className']:null;
183 | $options['package'] = $properties['package'];
184 |
185 | if (isset($properties['inheritance'])) {
186 | $options['inheritance'] = $properties['inheritance'];
187 | }
188 |
189 | return $options;
190 | }
191 |
192 | /**
193 | * getColumns
194 | *
195 | * Get array of columns from table properties
196 | *
197 | * @param string $properties Array of table properties
198 | * @return array $columns Array of columns
199 | */
200 | public function getColumns($properties)
201 | {
202 | return isset($properties['columns']) ? $properties['columns']:array();
203 | }
204 |
205 | /**
206 | * getRelations
207 | *
208 | * Get array of relations from table properties
209 | *
210 | * @param string $properties Array of tables properties
211 | * @return array $relations Array of relations
212 | */
213 | public function getRelations($properties)
214 | {
215 | $all_relations = isset($this->_relations[$properties['className']]) ? $this->_relations[$properties['className']]:array();
216 |
217 | // This is for checking for duplicates between alias-relations and a auto-generated relations to ensure the result set of unique relations
218 | $exist_relations = array();
219 | $unique_relations = array();
220 | foreach ($all_relations as $relation) {
221 | if (!in_array($relation['class'], $exist_relations)) {
222 | $exist_relations[] = $relation['class'];
223 | $unique_relations = array_merge($unique_relations, array($relation['alias'] => $relation));
224 | } else {
225 | // check to see if this relationship is not autogenerated, if it's not, then the user must have explicitly declared it
226 | if (!isset($relation['autogenerated']) || $relation['autogenerated'] != true) {
227 | $unique_relations = array_merge($unique_relations, array($relation['alias'] => $relation));
228 | }
229 | }
230 | }
231 |
232 | return $unique_relations;
233 | }
234 |
235 | /**
236 | * getIndexes
237 | *
238 | * Get array of indexes from table properties
239 | *
240 | * @param string $properties Array of table properties
241 | * @return array $index
242 | */
243 | public function getIndexes($properties)
244 | {
245 | return isset($properties['indexes']) ? $properties['indexes']:array();;
246 | }
247 |
248 | /**
249 | * getAttributes
250 | *
251 | * Get array of attributes from table properties
252 | *
253 | * @param string $properties Array of tables properties
254 | * @return array $attributes
255 | */
256 | public function getAttributes($properties)
257 | {
258 | return isset($properties['attributes']) ? $properties['attributes']:array();
259 | }
260 |
261 | /**
262 | * getTemplates
263 | *
264 | * Get array of templates from table properties
265 | *
266 | * @param string $properties Array of table properties
267 | * @return array $templates Array of table templates
268 | */
269 | public function getTemplates($properties)
270 | {
271 | return isset($properties['templates']) ? $properties['templates']:array();
272 | }
273 |
274 | /**
275 | * getActAs
276 | *
277 | * Get array of actAs definitions from table properties
278 | *
279 | * @param string $properties Array of table properties
280 | * @return array $actAs Array of actAs definitions from table properties
281 | */
282 | public function getActAs($properties)
283 | {
284 | return isset($properties['actAs']) ? $properties['actAs']:array();
285 | }
286 |
287 | /**
288 | * getTableOptions
289 | *
290 | * @param string $properties
291 | * @return void
292 | */
293 | public function getTableOptions($properties)
294 | {
295 | return isset($properties['options']) ? $properties['options']:array();
296 | }
297 |
298 | /**
299 | * parseSchema
300 | *
301 | * A method to parse a Schema and translate it into a property array.
302 | * The function returns that property array.
303 | *
304 | * @param string $schema Path to the file containing the schema
305 | * @return array $build Built array of schema information
306 | */
307 | public function parseSchema($schema, $type)
308 | {
309 | $array = Doctrine_Parser::load($schema, $type);
310 |
311 | $build = array();
312 |
313 | foreach ($array as $className => $table) {
314 | $columns = array();
315 |
316 | $className = isset($table['className']) ? (string) $table['className']:(string) $className;
317 |
318 | if (isset($table['tableName']) && $table['tableName']) {
319 | $tableName = $table['tableName'];
320 | } else {
321 | if (isset($table['inheritance']['extends']) && isset($table['inheritance']['extends']['keyType']) && isset($table['inheritance']['extends']['keyValue'])) {
322 | $tableName = null;
323 | } else {
324 | $tableName = Doctrine::tableize($className);
325 | }
326 | }
327 |
328 | $columns = isset($table['columns']) ? $table['columns']:array();
329 | $columns = isset($table['fields']) ? $table['fields']:$columns;
330 |
331 | if ( ! empty($columns)) {
332 | foreach ($columns as $columnName => $field) {
333 | $colDesc = array();
334 | $colDesc['name'] = $columnName;
335 |
336 | $e = explode('(', $field['type']);
337 | if (isset($e[0]) && isset($e[1])) {
338 | $colDesc['type'] = $e[0];
339 | $colDesc['length'] = substr($e[1], 0, strlen($e[1]) - 1);
340 | } else {
341 | $colDesc['type'] = isset($field['type']) ? (string) $field['type']:null;
342 | $colDesc['length'] = isset($field['length']) ? (int) $field['length']:null;
343 | $colDesc['length'] = isset($field['size']) ? (int) $field['size']:$colDesc['length'];
344 | }
345 |
346 | $colDesc['ptype'] = isset($field['ptype']) ? (string) $field['ptype']:(string) $colDesc['type'];
347 | $colDesc['fixed'] = isset($field['fixed']) ? (int) $field['fixed']:null;
348 | $colDesc['primary'] = isset($field['primary']) ? (bool) (isset($field['primary']) && $field['primary']):null;
349 | $colDesc['default'] = isset($field['default']) ? $field['default']:null;
350 | $colDesc['autoincrement'] = isset($field['autoincrement']) ? (bool) (isset($field['autoincrement']) && $field['autoincrement']):null;
351 | $colDesc['autoincrement'] = isset($field['autoinc']) ? (bool) (isset($field['autoinc']) && $field['autoinc']):$colDesc['autoincrement'];
352 | $colDesc['values'] = isset($field['values']) ? (array) $field['values']:null;
353 |
354 | $validators = Doctrine::getValidators();
355 |
356 | foreach ($validators as $validator) {
357 | if (isset($field[$validator])) {
358 | $colDesc[$validator] = $field[$validator];
359 | }
360 | }
361 |
362 | $columns[(string) $colDesc['name']] = $colDesc;
363 | }
364 | }
365 |
366 | $build[$className]['connection'] = isset($table['connection']) ? $table['connection']:null;
367 | $build[$className]['className'] = $className;
368 | $build[$className]['tableName'] = $tableName;
369 | $build[$className]['columns'] = $columns;
370 | $build[$className]['relations'] = isset($table['relations']) ? $table['relations']:array();
371 | $build[$className]['indexes'] = isset($table['indexes']) ? $table['indexes']:array();
372 | $build[$className]['attributes'] = isset($table['attributes']) ? $table['attributes']:array();
373 | $build[$className]['templates'] = isset($table['templates']) ? $table['templates']:array();
374 | $build[$className]['actAs'] = isset($table['actAs']) ? $table['actAs']:array();
375 | $build[$className]['options'] = isset($table['options']) ? $table['options']:array();
376 | $build[$className]['package'] = isset($table['package']) ? $table['package']:null;
377 |
378 | if (isset($table['inheritance'])) {
379 | $build[$className]['inheritance'] = $table['inheritance'];
380 | }
381 | }
382 |
383 | return $build;
384 | }
385 |
386 | /**
387 | * buildRelationships
388 | *
389 | * Loop through an array of schema information and build all the necessary relationship information
390 | * Will attempt to auto complete relationships and simplify the amount of information required for defining a relationship
391 | *
392 | * @param string $array
393 | * @return void
394 | */
395 | protected function _buildRelationships(&$array)
396 | {
397 | foreach ($array as $name => $properties) {
398 | if ( ! isset($properties['relations'])) {
399 | continue;
400 | }
401 |
402 | $className = $properties['className'];
403 | $relations = $properties['relations'];
404 |
405 | foreach ($relations as $alias => $relation) {
406 | $class = isset($relation['class']) ? $relation['class']:$alias;
407 |
408 | // Attempt to guess the local and foreign
409 | if (isset($relation['refClass'])) {
410 | $relation['local'] = isset($relation['local']) ? $relation['local']:Doctrine::tableize($name) . '_id';
411 | $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:Doctrine::tableize($class) . '_id';
412 | } else {
413 | $relation['local'] = isset($relation['local']) ? $relation['local']:Doctrine::tableize($class) . '_id';
414 | $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:'id';
415 | }
416 |
417 | $relation['alias'] = isset($relation['alias']) ? $relation['alias'] : $alias;
418 | $relation['class'] = $class;
419 |
420 | if (isset($relation['refClass'])) {
421 | $relation['type'] = 'many';
422 | }
423 |
424 | if (isset($relation['type']) && $relation['type']) {
425 | $relation['type'] = $relation['type'] === 'one' ? Doctrine_Relation::ONE:Doctrine_Relation::MANY;
426 | } else {
427 | $relation['type'] = Doctrine_Relation::ONE;
428 | }
429 |
430 | if (isset($relation['foreignType']) && $relation['foreignType']) {
431 | $relation['foreignType'] = $relation['foreignType'] === 'one' ? Doctrine_Relation::ONE:Doctrine_Relation::MANY;
432 | }
433 |
434 | $this->_relations[$className][$alias] = $relation;
435 | }
436 | }
437 |
438 | // Now we fix all the relationships and auto-complete opposite ends of relationships
439 | $this->_fixRelationships();
440 | }
441 |
442 | /**
443 | * fixRelationships
444 | *
445 | * Loop through all relationships building the opposite ends of each relationship
446 | *
447 | * @return void
448 | */
449 | protected function _fixRelationships()
450 | {
451 | foreach($this->_relations as $className => $relations) {
452 | foreach ($relations AS $alias => $relation) {
453 | $newRelation = array();
454 | $newRelation['foreign'] = $relation['local'];
455 | $newRelation['local'] = $relation['foreign'];
456 | $newRelation['class'] = isset($relation['foreignClass']) ? $relation['foreignClass']:$className;
457 | $newRelation['alias'] = isset($relation['foreignAlias']) ? $relation['foreignAlias']:$className;
458 |
459 | // this is so that we know that this relation was autogenerated and
460 | // that we do not need to include it if it is explicitly declared in the schema by the users.
461 | $newRelation['autogenerated'] = true;
462 |
463 | if (isset($relation['refClass'])) {
464 | $newRelation['refClass'] = $relation['refClass'];
465 | $newRelation['type'] = isset($relation['foreignType']) ? $relation['foreignType']:$relation['type'];
466 | } else {
467 | if(isset($relation['foreignType'])) {
468 | $newRelation['type'] = $relation['foreignType'];
469 | } else {
470 | $newRelation['type'] = $relation['type'] === Doctrine_Relation::ONE ? Doctrine_Relation::MANY:Doctrine_Relation::ONE;
471 | }
472 | }
473 |
474 | if (!isset($this->_relations[$relation['class']][$newRelation['alias']])) {
475 | $this->_relations[$relation['class']][$newRelation['alias']] = $newRelation;
476 | }
477 | }
478 | }
479 | }
480 | }