| 1 |
lars |
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.doctrine-project.org>.
|
|
|
20 |
*/
|
|
|
21 |
|
|
|
22 |
/**
|
|
|
23 |
* Doctrine_Import_Schema
|
|
|
24 |
*
|
|
|
25 |
* Class for importing Doctrine_Record classes from a yaml schema definition
|
|
|
26 |
*
|
|
|
27 |
* @package Doctrine
|
|
|
28 |
* @subpackage Import
|
|
|
29 |
* @link www.doctrine-project.org
|
|
|
30 |
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
|
|
31 |
* @version $Revision: 1838 $
|
|
|
32 |
* @author Nicolas Bérard-Nault <nicobn@gmail.com>
|
|
|
33 |
* @author Jonathan H. Wage <jonwage@gmail.com>
|
|
|
34 |
*/
|
|
|
35 |
class Doctrine_Import_Schema
|
|
|
36 |
{
|
|
|
37 |
/**
|
|
|
38 |
* Schema definition keys that can be applied at the global level.
|
|
|
39 |
*
|
|
|
40 |
* @var array
|
|
|
41 |
*/
|
|
|
42 |
protected static $_globalDefinitionKeys = array(
|
|
|
43 |
'connection',
|
|
|
44 |
'attributes',
|
|
|
45 |
'templates',
|
|
|
46 |
'actAs',
|
|
|
47 |
'options',
|
|
|
48 |
'package',
|
|
|
49 |
'package_custom_path',
|
|
|
50 |
'inheritance',
|
|
|
51 |
'detect_relations');
|
|
|
52 |
|
|
|
53 |
/**
|
|
|
54 |
* _relations
|
|
|
55 |
*
|
|
|
56 |
* Array of all relationships parsed from all schema files
|
|
|
57 |
*
|
|
|
58 |
* @var array
|
|
|
59 |
*/
|
|
|
60 |
protected $_relations = array();
|
|
|
61 |
|
|
|
62 |
/**
|
|
|
63 |
* _options
|
|
|
64 |
*
|
|
|
65 |
* Array of options used to configure the model generation from the parsed schema files
|
|
|
66 |
* This array is forwarded to Doctrine_Import_Builder
|
|
|
67 |
*
|
|
|
68 |
* @var array
|
|
|
69 |
*/
|
|
|
70 |
protected $_options = array('packagesPrefix' => 'Package',
|
|
|
71 |
'packagesPath' => '',
|
|
|
72 |
'packagesFolderName' => 'packages',
|
|
|
73 |
'suffix' => '.php',
|
|
|
74 |
'generateBaseClasses' => true,
|
|
|
75 |
'generateTableClasses' => false,
|
|
|
76 |
'generateAccessors' => false,
|
|
|
77 |
'baseClassPrefix' => 'Base',
|
|
|
78 |
'baseClassesDirectory' => 'generated',
|
|
|
79 |
'baseClassName' => 'Doctrine_Record');
|
|
|
80 |
|
|
|
81 |
/**
|
|
|
82 |
* _validation
|
|
|
83 |
*
|
|
|
84 |
* Array used to validate schema element.
|
|
|
85 |
* See: _validateSchemaElement
|
|
|
86 |
*
|
|
|
87 |
* @var array
|
|
|
88 |
*/
|
|
|
89 |
protected $_validation = array('root' => array('abstract',
|
|
|
90 |
'connection',
|
|
|
91 |
'className',
|
|
|
92 |
'tableName',
|
|
|
93 |
'connection',
|
|
|
94 |
'relations',
|
|
|
95 |
'columns',
|
|
|
96 |
'indexes',
|
|
|
97 |
'attributes',
|
|
|
98 |
'templates',
|
|
|
99 |
'actAs',
|
|
|
100 |
'options',
|
|
|
101 |
'package',
|
|
|
102 |
'package_custom_path',
|
|
|
103 |
'inheritance',
|
|
|
104 |
'detect_relations',
|
|
|
105 |
'listeners',
|
|
|
106 |
'checks',
|
|
|
107 |
'comment'),
|
|
|
108 |
|
|
|
109 |
'column' => array('name',
|
|
|
110 |
'format',
|
|
|
111 |
'fixed',
|
|
|
112 |
'primary',
|
|
|
113 |
'autoincrement',
|
|
|
114 |
'type',
|
|
|
115 |
'length',
|
|
|
116 |
'size',
|
|
|
117 |
'default',
|
|
|
118 |
'scale',
|
|
|
119 |
'values',
|
|
|
120 |
'comment',
|
|
|
121 |
'sequence',
|
|
|
122 |
'protected',
|
|
|
123 |
'zerofill',
|
|
|
124 |
'owner',
|
|
|
125 |
'extra',
|
|
|
126 |
'comment',
|
|
|
127 |
'charset',
|
|
|
128 |
'collation'),
|
|
|
129 |
|
|
|
130 |
'relation' => array('key',
|
|
|
131 |
'class',
|
|
|
132 |
'alias',
|
|
|
133 |
'type',
|
|
|
134 |
'refClass',
|
|
|
135 |
'local',
|
|
|
136 |
'foreign',
|
|
|
137 |
'foreignClass',
|
|
|
138 |
'foreignAlias',
|
|
|
139 |
'foreignType',
|
|
|
140 |
'autoComplete',
|
|
|
141 |
'cascade',
|
|
|
142 |
'onDelete',
|
|
|
143 |
'onUpdate',
|
|
|
144 |
'equal',
|
|
|
145 |
'owningSide',
|
|
|
146 |
'refClassRelationAlias',
|
|
|
147 |
'foreignKeyName',
|
|
|
148 |
'orderBy'),
|
|
|
149 |
|
|
|
150 |
'inheritance'=> array('type',
|
|
|
151 |
'extends',
|
|
|
152 |
'keyField',
|
|
|
153 |
'keyValue'));
|
|
|
154 |
|
|
|
155 |
/**
|
|
|
156 |
* Returns an array of definition keys that can be applied at the global level.
|
|
|
157 |
*
|
|
|
158 |
* @return array
|
|
|
159 |
*/
|
|
|
160 |
public static function getGlobalDefinitionKeys()
|
|
|
161 |
{
|
|
|
162 |
return self::$_globalDefinitionKeys;
|
|
|
163 |
}
|
|
|
164 |
|
|
|
165 |
/**
|
|
|
166 |
* getOption
|
|
|
167 |
*
|
|
|
168 |
* @param string $name
|
|
|
169 |
* @return void
|
|
|
170 |
*/
|
|
|
171 |
public function getOption($name)
|
|
|
172 |
{
|
|
|
173 |
if (isset($this->_options[$name])) {
|
|
|
174 |
return $this->_options[$name];
|
|
|
175 |
}
|
|
|
176 |
}
|
|
|
177 |
|
|
|
178 |
/**
|
|
|
179 |
* getOptions
|
|
|
180 |
*
|
|
|
181 |
* @return void
|
|
|
182 |
*/
|
|
|
183 |
public function getOptions()
|
|
|
184 |
{
|
|
|
185 |
return $this->_options;
|
|
|
186 |
}
|
|
|
187 |
|
|
|
188 |
/**
|
|
|
189 |
* setOption
|
|
|
190 |
*
|
|
|
191 |
* @param string $name
|
|
|
192 |
* @param string $value
|
|
|
193 |
* @return void
|
|
|
194 |
*/
|
|
|
195 |
public function setOption($name, $value)
|
|
|
196 |
{
|
|
|
197 |
if (isset($this->_options[$name])) {
|
|
|
198 |
$this->_options[$name] = $value;
|
|
|
199 |
}
|
|
|
200 |
}
|
|
|
201 |
|
|
|
202 |
/**
|
|
|
203 |
* setOptions
|
|
|
204 |
*
|
|
|
205 |
* @param string $options
|
|
|
206 |
* @return void
|
|
|
207 |
*/
|
|
|
208 |
public function setOptions($options)
|
|
|
209 |
{
|
|
|
210 |
if ( ! empty($options)) {
|
|
|
211 |
$this->_options = $options;
|
|
|
212 |
}
|
|
|
213 |
}
|
|
|
214 |
|
|
|
215 |
/**
|
|
|
216 |
* buildSchema
|
|
|
217 |
*
|
|
|
218 |
* Loop throug directories of schema files and parse them all in to one complete array of schema information
|
|
|
219 |
*
|
|
|
220 |
* @param string $schema Array of schema files or single schema file. Array of directories with schema files or single directory
|
|
|
221 |
* @param string $format Format of the files we are parsing and building from
|
|
|
222 |
* @return array $array
|
|
|
223 |
*/
|
|
|
224 |
public function buildSchema($schema, $format)
|
|
|
225 |
{
|
|
|
226 |
$array = array();
|
|
|
227 |
|
|
|
228 |
foreach ((array) $schema AS $s) {
|
|
|
229 |
if (is_file($s)) {
|
|
|
230 |
$e = explode('.', $s);
|
|
|
231 |
if (end($e) === $format) {
|
|
|
232 |
$array = array_merge($array, $this->parseSchema($s, $format));
|
|
|
233 |
}
|
|
|
234 |
} else if (is_dir($s)) {
|
|
|
235 |
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($s),
|
|
|
236 |
RecursiveIteratorIterator::LEAVES_ONLY);
|
|
|
237 |
|
|
|
238 |
foreach ($it as $file) {
|
|
|
239 |
$e = explode('.', $file->getFileName());
|
|
|
240 |
if (end($e) === $format) {
|
|
|
241 |
$array = array_merge($array, $this->parseSchema($file->getPathName(), $format));
|
|
|
242 |
}
|
|
|
243 |
}
|
|
|
244 |
} else {
|
|
|
245 |
$array = array_merge($array, $this->parseSchema($s, $format));
|
|
|
246 |
}
|
|
|
247 |
}
|
|
|
248 |
|
|
|
249 |
$array = $this->_buildRelationships($array);
|
|
|
250 |
$array = $this->_processInheritance($array);
|
|
|
251 |
|
|
|
252 |
return $array;
|
|
|
253 |
}
|
|
|
254 |
|
|
|
255 |
/**
|
|
|
256 |
* importSchema
|
|
|
257 |
*
|
|
|
258 |
* A method to import a Schema and translate it into a Doctrine_Record object
|
|
|
259 |
*
|
|
|
260 |
* @param string $schema The file containing the XML schema
|
|
|
261 |
* @param string $format Format of the schema file
|
|
|
262 |
* @param string $directory The directory where the Doctrine_Record class will be written
|
|
|
263 |
* @param array $models Optional array of models to import
|
|
|
264 |
*
|
|
|
265 |
* @return void
|
|
|
266 |
*/
|
|
|
267 |
public function importSchema($schema, $format = 'yml', $directory = null, $models = array())
|
|
|
268 |
{
|
|
|
269 |
$schema = (array) $schema;
|
|
|
270 |
$builder = new Doctrine_Import_Builder();
|
|
|
271 |
$builder->setTargetPath($directory);
|
|
|
272 |
$builder->setOptions($this->getOptions());
|
|
|
273 |
|
|
|
274 |
$array = $this->buildSchema($schema, $format);
|
|
|
275 |
|
|
|
276 |
if (count($array) == 0) {
|
|
|
277 |
throw new Doctrine_Import_Exception(
|
|
|
278 |
sprintf('No ' . $format . ' schema found in ' . implode(", ", $schema))
|
|
|
279 |
);
|
|
|
280 |
}
|
|
|
281 |
|
|
|
282 |
foreach ($array as $name => $definition) {
|
|
|
283 |
if ( ! empty($models) && !in_array($definition['className'], $models)) {
|
|
|
284 |
continue;
|
|
|
285 |
}
|
|
|
286 |
|
|
|
287 |
$builder->buildRecord($definition);
|
|
|
288 |
}
|
|
|
289 |
}
|
|
|
290 |
|
|
|
291 |
/**
|
|
|
292 |
* parseSchema
|
|
|
293 |
*
|
|
|
294 |
* A method to parse a Schema and translate it into a property array.
|
|
|
295 |
* The function returns that property array.
|
|
|
296 |
*
|
|
|
297 |
* @param string $schema Path to the file containing the schema
|
|
|
298 |
* @param string $type Format type of the schema we are parsing
|
|
|
299 |
* @return array $build Built array of schema information
|
|
|
300 |
*/
|
|
|
301 |
public function parseSchema($schema, $type)
|
|
|
302 |
{
|
|
|
303 |
$defaults = array('abstract' => false,
|
|
|
304 |
'className' => null,
|
|
|
305 |
'tableName' => null,
|
|
|
306 |
'connection' => null,
|
|
|
307 |
'relations' => array(),
|
|
|
308 |
'indexes' => array(),
|
|
|
309 |
'attributes' => array(),
|
|
|
310 |
'templates' => array(),
|
|
|
311 |
'actAs' => array(),
|
|
|
312 |
'options' => array(),
|
|
|
313 |
'package' => null,
|
|
|
314 |
'inheritance' => array(),
|
|
|
315 |
'detect_relations' => false);
|
|
|
316 |
|
|
|
317 |
$array = Doctrine_Parser::load($schema, $type);
|
|
|
318 |
|
|
|
319 |
// Loop over and build up all the global values and remove them from the array
|
|
|
320 |
$globals = array();
|
|
|
321 |
foreach ($array as $key => $value) {
|
|
|
322 |
if (in_array($key, self::$_globalDefinitionKeys)) {
|
|
|
323 |
unset($array[$key]);
|
|
|
324 |
$globals[$key] = $value;
|
|
|
325 |
}
|
|
|
326 |
}
|
|
|
327 |
|
|
|
328 |
// Merge the globals that aren't specifically set to each class
|
|
|
329 |
foreach ($array as $className => $table) {
|
|
|
330 |
$array[$className] = Doctrine_Lib::arrayDeepMerge($globals, $array[$className]);
|
|
|
331 |
}
|
|
|
332 |
|
|
|
333 |
$build = array();
|
|
|
334 |
|
|
|
335 |
foreach ($array as $className => $table) {
|
|
|
336 |
$table = (array) $table;
|
|
|
337 |
$this->_validateSchemaElement('root', array_keys($table), $className);
|
|
|
338 |
|
|
|
339 |
$columns = array();
|
|
|
340 |
|
|
|
341 |
$className = isset($table['className']) ? (string) $table['className']:(string) $className;
|
|
|
342 |
|
|
|
343 |
if (isset($table['inheritance']['keyField']) || isset($table['inheritance']['keyValue'])) {
|
|
|
344 |
$table['inheritance']['type'] = 'column_aggregation';
|
|
|
345 |
}
|
|
|
346 |
|
|
|
347 |
if (isset($table['tableName']) && $table['tableName']) {
|
|
|
348 |
$tableName = $table['tableName'];
|
|
|
349 |
} else {
|
|
|
350 |
if (isset($table['inheritance']['type']) && ($table['inheritance']['type'] == 'column_aggregation')) {
|
|
|
351 |
$tableName = null;
|
|
|
352 |
} else {
|
|
|
353 |
$tableName = Doctrine_Inflector::tableize($className);
|
|
|
354 |
}
|
|
|
355 |
}
|
|
|
356 |
|
|
|
357 |
$connection = isset($table['connection']) ? $table['connection']:'current';
|
|
|
358 |
|
|
|
359 |
$columns = isset($table['columns']) ? $table['columns']:array();
|
|
|
360 |
|
|
|
361 |
if ( ! empty($columns)) {
|
|
|
362 |
foreach ($columns as $columnName => $field) {
|
|
|
363 |
|
|
|
364 |
// Support short syntax: my_column: integer(4)
|
|
|
365 |
if ( ! is_array($field)) {
|
|
|
366 |
$original = $field;
|
|
|
367 |
$field = array();
|
|
|
368 |
$field['type'] = $original;
|
|
|
369 |
}
|
|
|
370 |
|
|
|
371 |
$colDesc = array();
|
|
|
372 |
if (isset($field['name'])) {
|
|
|
373 |
$colDesc['name'] = $field['name'];
|
|
|
374 |
} else {
|
|
|
375 |
$colDesc['name'] = $columnName;
|
|
|
376 |
}
|
|
|
377 |
|
|
|
378 |
$this->_validateSchemaElement('column', array_keys($field), $className . '->columns->' . $colDesc['name']);
|
|
|
379 |
|
|
|
380 |
// Support short type(length) syntax: my_column: { type: integer(4) }
|
|
|
381 |
$e = explode('(', $field['type']);
|
|
|
382 |
if (isset($e[0]) && isset($e[1])) {
|
|
|
383 |
$colDesc['type'] = $e[0];
|
|
|
384 |
$value = substr($e[1], 0, strlen($e[1]) - 1);
|
|
|
385 |
$e = explode(',', $value);
|
|
|
386 |
$colDesc['length'] = $e[0];
|
|
|
387 |
if (isset($e[1]) && $e[1]) {
|
|
|
388 |
$colDesc['scale'] = $e[1];
|
|
|
389 |
}
|
|
|
390 |
} else {
|
|
|
391 |
$colDesc['type'] = isset($field['type']) ? (string) $field['type']:null;
|
|
|
392 |
$colDesc['length'] = isset($field['length']) ? (int) $field['length']:null;
|
|
|
393 |
$colDesc['length'] = isset($field['size']) ? (int) $field['size']:$colDesc['length'];
|
|
|
394 |
}
|
|
|
395 |
|
|
|
396 |
$colDesc['fixed'] = isset($field['fixed']) ? (int) $field['fixed']:null;
|
|
|
397 |
$colDesc['primary'] = isset($field['primary']) ? (bool) (isset($field['primary']) && $field['primary']):null;
|
|
|
398 |
$colDesc['default'] = isset($field['default']) ? $field['default']:null;
|
|
|
399 |
$colDesc['autoincrement'] = isset($field['autoincrement']) ? (bool) (isset($field['autoincrement']) && $field['autoincrement']):null;
|
|
|
400 |
|
|
|
401 |
if (isset($field['sequence'])) {
|
|
|
402 |
if (true === $field['sequence']) {
|
|
|
403 |
$colDesc['sequence'] = $tableName;
|
|
|
404 |
} else {
|
|
|
405 |
$colDesc['sequence'] = (string) $field['sequence'];
|
|
|
406 |
}
|
|
|
407 |
} else {
|
|
|
408 |
$colDesc['sequence'] = null;
|
|
|
409 |
}
|
|
|
410 |
|
|
|
411 |
$colDesc['values'] = isset($field['values']) ? (array) $field['values']:null;
|
|
|
412 |
|
|
|
413 |
// Include all the specified and valid validators in the colDesc
|
|
|
414 |
$validators = Doctrine_Manager::getInstance()->getValidators();
|
|
|
415 |
|
|
|
416 |
foreach ($validators as $validator) {
|
|
|
417 |
if (isset($field[$validator])) {
|
|
|
418 |
$colDesc[$validator] = $field[$validator];
|
|
|
419 |
}
|
|
|
420 |
}
|
|
|
421 |
|
|
|
422 |
$columns[(string) $columnName] = $colDesc;
|
|
|
423 |
}
|
|
|
424 |
}
|
|
|
425 |
|
|
|
426 |
// Apply the default values
|
|
|
427 |
foreach ($defaults as $key => $defaultValue) {
|
|
|
428 |
if (isset($table[$key]) && ! isset($build[$className][$key])) {
|
|
|
429 |
$build[$className][$key] = $table[$key];
|
|
|
430 |
} else {
|
|
|
431 |
$build[$className][$key] = isset($build[$className][$key]) ? $build[$className][$key]:$defaultValue;
|
|
|
432 |
}
|
|
|
433 |
}
|
|
|
434 |
|
|
|
435 |
$build[$className]['className'] = $className;
|
|
|
436 |
$build[$className]['tableName'] = $tableName;
|
|
|
437 |
$build[$className]['columns'] = $columns;
|
|
|
438 |
|
|
|
439 |
// Make sure that anything else that is specified in the schema makes it to the final array
|
|
|
440 |
$build[$className] = Doctrine_Lib::arrayDeepMerge($table, $build[$className]);
|
|
|
441 |
|
|
|
442 |
// We need to keep track of the className for the connection
|
|
|
443 |
$build[$className]['connectionClassName'] = $build[$className]['className'];
|
|
|
444 |
}
|
|
|
445 |
|
|
|
446 |
return $build;
|
|
|
447 |
}
|
|
|
448 |
|
|
|
449 |
/**
|
|
|
450 |
* _processInheritance
|
|
|
451 |
*
|
|
|
452 |
* Perform some processing on inheritance.
|
|
|
453 |
* Sets the default type and sets some default values for certain types
|
|
|
454 |
*
|
|
|
455 |
* @param string $array
|
|
|
456 |
* @return void
|
|
|
457 |
*/
|
|
|
458 |
protected function _processInheritance($array)
|
|
|
459 |
{
|
|
|
460 |
// Apply default inheritance configuration
|
|
|
461 |
foreach ($array as $className => $definition) {
|
|
|
462 |
if ( ! empty($array[$className]['inheritance'])) {
|
|
|
463 |
$this->_validateSchemaElement('inheritance', array_keys($definition['inheritance']), $className . '->inheritance');
|
|
|
464 |
|
|
|
465 |
// Default inheritance to concrete inheritance
|
|
|
466 |
if ( ! isset($array[$className]['inheritance']['type'])) {
|
|
|
467 |
$array[$className]['inheritance']['type'] = 'concrete';
|
|
|
468 |
}
|
|
|
469 |
|
|
|
470 |
// Some magic for setting up the keyField and keyValue column aggregation options
|
|
|
471 |
// Adds keyField to the parent class automatically
|
|
|
472 |
if ($array[$className]['inheritance']['type'] == 'column_aggregation') {
|
|
|
473 |
// Set the keyField to 'type' by default
|
|
|
474 |
if ( ! isset($array[$className]['inheritance']['keyField'])) {
|
|
|
475 |
$array[$className]['inheritance']['keyField'] = 'type';
|
|
|
476 |
}
|
|
|
477 |
|
|
|
478 |
// Set the keyValue to the name of the child class if it does not exist
|
|
|
479 |
if ( ! isset($array[$className]['inheritance']['keyValue'])) {
|
|
|
480 |
$array[$className]['inheritance']['keyValue'] = $className;
|
|
|
481 |
}
|
|
|
482 |
|
|
|
483 |
$parent = $this->_findBaseSuperClass($array, $definition['className']);
|
|
|
484 |
// Add the keyType column to the parent if a definition does not already exist
|
|
|
485 |
if ( ! isset($array[$parent]['columns'][$array[$className]['inheritance']['keyField']])) {
|
|
|
486 |
$array[$parent]['columns'][$array[$className]['inheritance']['keyField']] = array('name' => $array[$className]['inheritance']['keyField'], 'type' => 'string', 'length' => 255);
|
|
|
487 |
}
|
|
|
488 |
}
|
|
|
489 |
}
|
|
|
490 |
}
|
|
|
491 |
|
|
|
492 |
// Array of the array keys to move to the parent, and the value to default the child definition to
|
|
|
493 |
// after moving it. Will also populate the subclasses array for the inheritance parent
|
|
|
494 |
$moves = array('columns' => array(),
|
|
|
495 |
'indexes' => array(),
|
|
|
496 |
'attributes' => array(),
|
|
|
497 |
'options' => array(),
|
|
|
498 |
'checks' => array());
|
|
|
499 |
|
|
|
500 |
foreach ($array as $className => $definition) {
|
|
|
501 |
// Move any definitions on the schema to the parent
|
|
|
502 |
if (isset($definition['inheritance']['extends']) && isset($definition['inheritance']['type']) && ($definition['inheritance']['type'] == 'simple' || $definition['inheritance']['type'] == 'column_aggregation')) {
|
|
|
503 |
$parent = $this->_findBaseSuperClass($array, $definition['className']);
|
|
|
504 |
foreach ($moves as $move => $resetValue) {
|
|
|
505 |
if (isset($array[$parent][$move]) && isset($definition[$move])) {
|
|
|
506 |
$array[$parent][$move] = Doctrine_Lib::arrayDeepMerge($array[$parent][$move], $definition[$move]);
|
|
|
507 |
$array[$definition['className']][$move] = $resetValue;
|
|
|
508 |
}
|
|
|
509 |
}
|
|
|
510 |
|
|
|
511 |
// Populate the parents subclasses
|
|
|
512 |
if ($definition['inheritance']['type'] == 'column_aggregation') {
|
|
|
513 |
// Fix for 2015: loop through superclasses' inheritance to the base-superclass to
|
|
|
514 |
// make sure we collect all keyFields needed (and not only the first)
|
|
|
515 |
$inheritanceFields = array($definition['inheritance']['keyField'] => $definition['inheritance']['keyValue']);
|
|
|
516 |
|
|
|
517 |
$superClass = $definition['inheritance']['extends'];
|
|
|
518 |
$multiInheritanceDef = $array[$superClass];
|
|
|
519 |
|
|
|
520 |
while (count($multiInheritanceDef['inheritance']) > 0 && array_key_exists('extends', $multiInheritanceDef['inheritance']) && $multiInheritanceDef['inheritance']['type'] == 'column_aggregation') {
|
|
|
521 |
$superClass = $multiInheritanceDef['inheritance']['extends'];
|
|
|
522 |
|
|
|
523 |
// keep original keyField with it's keyValue
|
|
|
524 |
if ( ! isset($inheritanceFields[$multiInheritanceDef['inheritance']['keyField']])) {
|
|
|
525 |
$inheritanceFields[$multiInheritanceDef['inheritance']['keyField']] = $multiInheritanceDef['inheritance']['keyValue'];
|
|
|
526 |
}
|
|
|
527 |
$multiInheritanceDef = $array[$superClass];
|
|
|
528 |
}
|
|
|
529 |
|
|
|
530 |
$array[$parent]['inheritance']['subclasses'][$definition['className']] = $inheritanceFields;
|
|
|
531 |
}
|
|
|
532 |
}
|
|
|
533 |
}
|
|
|
534 |
|
|
|
535 |
return $array;
|
|
|
536 |
}
|
|
|
537 |
|
|
|
538 |
/**
|
|
|
539 |
* Find the base super class for this inheritance child. We need to move all levels of children to the
|
|
|
540 |
* top most parent.
|
|
|
541 |
*
|
|
|
542 |
* @param array $array Array of schema information
|
|
|
543 |
* @return string $class Class to get find the parent for
|
|
|
544 |
*/
|
|
|
545 |
protected function _findBaseSuperClass($array, $class)
|
|
|
546 |
{
|
|
|
547 |
if (isset($array[$class]['inheritance']['extends']) && isset($array[$class]['inheritance']['type']) && ($array[$class]['inheritance']['type'] == 'simple' || $array[$class]['inheritance']['type'] == 'column_aggregation')) {
|
|
|
548 |
return $this->_findBaseSuperClass($array, $array[$class]['inheritance']['extends']);
|
|
|
549 |
} else {
|
|
|
550 |
return $class;
|
|
|
551 |
}
|
|
|
552 |
}
|
|
|
553 |
|
|
|
554 |
/**
|
|
|
555 |
* buildRelationships
|
|
|
556 |
*
|
|
|
557 |
* Loop through an array of schema information and build all the necessary relationship information
|
|
|
558 |
* Will attempt to auto complete relationships and simplify the amount of information required
|
|
|
559 |
* for defining a relationship
|
|
|
560 |
*
|
|
|
561 |
* @param string $array
|
|
|
562 |
* @return void
|
|
|
563 |
*/
|
|
|
564 |
protected function _buildRelationships($array)
|
|
|
565 |
{
|
|
|
566 |
// Handle auto detecting relations by the names of columns
|
|
|
567 |
// User.contact_id will automatically create User hasOne Contact local => contact_id, foreign => id
|
|
|
568 |
foreach ($array as $className => $properties) {
|
|
|
569 |
if (isset($properties['columns']) && ! empty($properties['columns']) && isset($properties['detect_relations']) && $properties['detect_relations']) {
|
|
|
570 |
foreach ($properties['columns'] as $column) {
|
|
|
571 |
// Check if the column we are inflecting has a _id on the end of it before trying to inflect it and find
|
|
|
572 |
// the class name for the column
|
|
|
573 |
if (strpos($column['name'], '_id')) {
|
|
|
574 |
$columnClassName = Doctrine_Inflector::classify(str_replace('_id', '', $column['name']));
|
|
|
575 |
if (isset($array[$columnClassName]) && !isset($array[$className]['relations'][$columnClassName])) {
|
|
|
576 |
$array[$className]['relations'][$columnClassName] = array();
|
|
|
577 |
|
|
|
578 |
// Set the detected foreign key type and length to the same as the primary key
|
|
|
579 |
// of the related table
|
|
|
580 |
$type = isset($array[$columnClassName]['columns']['id']['type']) ? $array[$columnClassName]['columns']['id']['type']:'integer';
|
|
|
581 |
$length = isset($array[$columnClassName]['columns']['id']['length']) ? $array[$columnClassName]['columns']['id']['length']:8;
|
|
|
582 |
$array[$className]['columns'][$column['name']]['type'] = $type;
|
|
|
583 |
$array[$className]['columns'][$column['name']]['length'] = $length;
|
|
|
584 |
}
|
|
|
585 |
}
|
|
|
586 |
}
|
|
|
587 |
}
|
|
|
588 |
}
|
|
|
589 |
|
|
|
590 |
foreach ($array as $name => $properties) {
|
|
|
591 |
if ( ! isset($properties['relations'])) {
|
|
|
592 |
continue;
|
|
|
593 |
}
|
|
|
594 |
|
|
|
595 |
$className = $properties['className'];
|
|
|
596 |
$relations = $properties['relations'];
|
|
|
597 |
|
|
|
598 |
foreach ($relations as $alias => $relation) {
|
|
|
599 |
$class = isset($relation['class']) ? $relation['class']:$alias;
|
|
|
600 |
if ( ! isset($array[$class])) {
|
|
|
601 |
continue;
|
|
|
602 |
}
|
|
|
603 |
$relation['class'] = $class;
|
|
|
604 |
$relation['alias'] = isset($relation['alias']) ? $relation['alias'] : $alias;
|
|
|
605 |
|
|
|
606 |
// Attempt to guess the local and foreign
|
|
|
607 |
if (isset($relation['refClass'])) {
|
|
|
608 |
$relation['local'] = isset($relation['local']) ? $relation['local']:Doctrine_Inflector::tableize($name) . '_id';
|
|
|
609 |
$relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:Doctrine_Inflector::tableize($class) . '_id';
|
|
|
610 |
} else {
|
|
|
611 |
$relation['local'] = isset($relation['local']) ? $relation['local']:Doctrine_Inflector::tableize($relation['class']) . '_id';
|
|
|
612 |
$relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:'id';
|
|
|
613 |
}
|
|
|
614 |
|
|
|
615 |
if (isset($relation['refClass'])) {
|
|
|
616 |
$relation['type'] = 'many';
|
|
|
617 |
}
|
|
|
618 |
|
|
|
619 |
if (isset($relation['type']) && $relation['type']) {
|
|
|
620 |
$relation['type'] = $relation['type'] === 'one' ? Doctrine_Relation::ONE:Doctrine_Relation::MANY;
|
|
|
621 |
} else {
|
|
|
622 |
$relation['type'] = Doctrine_Relation::ONE;
|
|
|
623 |
}
|
|
|
624 |
|
|
|
625 |
if (isset($relation['foreignType']) && $relation['foreignType']) {
|
|
|
626 |
$relation['foreignType'] = $relation['foreignType'] === 'one' ? Doctrine_Relation::ONE:Doctrine_Relation::MANY;
|
|
|
627 |
}
|
|
|
628 |
|
|
|
629 |
$relation['key'] = $this->_buildUniqueRelationKey($relation);
|
|
|
630 |
|
|
|
631 |
$this->_validateSchemaElement('relation', array_keys($relation), $className . '->relation->' . $relation['alias']);
|
|
|
632 |
|
|
|
633 |
$this->_relations[$className][$alias] = $relation;
|
|
|
634 |
}
|
|
|
635 |
}
|
|
|
636 |
|
|
|
637 |
// Now we auto-complete opposite ends of relationships
|
|
|
638 |
$this->_autoCompleteOppositeRelations();
|
|
|
639 |
|
|
|
640 |
// Make sure we do not have any duplicate relations
|
|
|
641 |
$this->_fixDuplicateRelations();
|
|
|
642 |
|
|
|
643 |
// Set the full array of relationships for each class to the final array
|
|
|
644 |
foreach ($this->_relations as $className => $relations) {
|
|
|
645 |
$array[$className]['relations'] = $relations;
|
|
|
646 |
}
|
|
|
647 |
|
|
|
648 |
return $array;
|
|
|
649 |
}
|
|
|
650 |
|
|
|
651 |
/**
|
|
|
652 |
* fixRelationships
|
|
|
653 |
*
|
|
|
654 |
* Loop through all relationships building the opposite ends of each relationship
|
|
|
655 |
* and make sure no duplicate relations exist
|
|
|
656 |
*
|
|
|
657 |
* @return void
|
|
|
658 |
*/
|
|
|
659 |
protected function _autoCompleteOppositeRelations()
|
|
|
660 |
{
|
|
|
661 |
foreach($this->_relations as $className => $relations) {
|
|
|
662 |
foreach ($relations AS $alias => $relation) {
|
|
|
663 |
if ((isset($relation['equal']) && $relation['equal']) || (isset($relation['autoComplete']) && $relation['autoComplete'] === false)) {
|
|
|
664 |
continue;
|
|
|
665 |
}
|
|
|
666 |
|
|
|
667 |
$newRelation = array();
|
|
|
668 |
$newRelation['foreign'] = $relation['local'];
|
|
|
669 |
$newRelation['local'] = $relation['foreign'];
|
|
|
670 |
$newRelation['class'] = isset($relation['foreignClass']) ? $relation['foreignClass']:$className;
|
|
|
671 |
$newRelation['alias'] = isset($relation['foreignAlias']) ? $relation['foreignAlias']:$className;
|
|
|
672 |
$newRelation['foreignAlias'] = $alias;
|
|
|
673 |
|
|
|
674 |
// this is so that we know that this relation was autogenerated and
|
|
|
675 |
// that we do not need to include it if it is explicitly declared in the schema by the users.
|
|
|
676 |
$newRelation['autogenerated'] = true;
|
|
|
677 |
|
|
|
678 |
if (isset($relation['refClass'])) {
|
|
|
679 |
$newRelation['refClass'] = $relation['refClass'];
|
|
|
680 |
$newRelation['type'] = isset($relation['foreignType']) ? $relation['foreignType']:$relation['type'];
|
|
|
681 |
} else {
|
|
|
682 |
if (isset($relation['foreignType'])) {
|
|
|
683 |
$newRelation['type'] = $relation['foreignType'];
|
|
|
684 |
} else {
|
|
|
685 |
$newRelation['type'] = $relation['type'] === Doctrine_Relation::ONE ? Doctrine_Relation::MANY:Doctrine_Relation::ONE;
|
|
|
686 |
}
|
|
|
687 |
}
|
|
|
688 |
|
|
|
689 |
// Make sure it doesn't already exist
|
|
|
690 |
if ( ! isset($this->_relations[$relation['class']][$newRelation['alias']])) {
|
|
|
691 |
$newRelation['key'] = $this->_buildUniqueRelationKey($newRelation);
|
|
|
692 |
$this->_relations[$relation['class']][$newRelation['alias']] = $newRelation;
|
|
|
693 |
}
|
|
|
694 |
}
|
|
|
695 |
}
|
|
|
696 |
}
|
|
|
697 |
|
|
|
698 |
/**
|
|
|
699 |
* _fixDuplicateRelations
|
|
|
700 |
*
|
|
|
701 |
* Ensure the relations for each class are unique and that no duplicated relations exist from the auto generated relations
|
|
|
702 |
* and the user explicitely defining the opposite end
|
|
|
703 |
*
|
|
|
704 |
* @return void
|
|
|
705 |
*/
|
|
|
706 |
protected function _fixDuplicateRelations()
|
|
|
707 |
{
|
|
|
708 |
foreach($this->_relations as $className => $relations) {
|
|
|
709 |
// This is for checking for duplicates between alias-relations and a auto-generated relations to ensure the result set of unique relations
|
|
|
710 |
$existingRelations = array();
|
|
|
711 |
$uniqueRelations = array();
|
|
|
712 |
foreach ($relations as $relation) {
|
|
|
713 |
if ( ! in_array($relation['key'], $existingRelations)) {
|
|
|
714 |
$existingRelations[] = $relation['key'];
|
|
|
715 |
$uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation));
|
|
|
716 |
} else {
|
|
|
717 |
// check to see if this relationship is not autogenerated, if it's not, then the user must have explicitly declared it
|
|
|
718 |
if ( ! isset($relation['autogenerated']) || $relation['autogenerated'] != true) {
|
|
|
719 |
$uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation));
|
|
|
720 |
}
|
|
|
721 |
}
|
|
|
722 |
}
|
|
|
723 |
|
|
|
724 |
$this->_relations[$className] = $uniqueRelations;
|
|
|
725 |
}
|
|
|
726 |
}
|
|
|
727 |
|
|
|
728 |
/**
|
|
|
729 |
* _buildUniqueRelationKey
|
|
|
730 |
*
|
|
|
731 |
* Build a unique key to identify a relationship by
|
|
|
732 |
* Md5 hash of all the relationship parameters
|
|
|
733 |
*
|
|
|
734 |
* @param string $relation
|
|
|
735 |
* @return void
|
|
|
736 |
*/
|
|
|
737 |
protected function _buildUniqueRelationKey($relation)
|
|
|
738 |
{
|
|
|
739 |
return md5($relation['local'].$relation['foreign'].$relation['class'].(isset($relation['refClass']) ? $relation['refClass']:null));
|
|
|
740 |
}
|
|
|
741 |
|
|
|
742 |
/**
|
|
|
743 |
* _validateSchemaElement
|
|
|
744 |
*
|
|
|
745 |
* @param string $name
|
|
|
746 |
* @param string $value
|
|
|
747 |
* @return void
|
|
|
748 |
*/
|
|
|
749 |
protected function _validateSchemaElement($name, $element, $path)
|
|
|
750 |
{
|
|
|
751 |
$element = (array) $element;
|
|
|
752 |
|
|
|
753 |
$validation = $this->_validation[$name];
|
|
|
754 |
|
|
|
755 |
// Validators are a part of the column validation
|
|
|
756 |
// This should be fixed, made cleaner
|
|
|
757 |
if ($name == 'column') {
|
|
|
758 |
$validators = Doctrine_Manager::getInstance()->getValidators();
|
|
|
759 |
$validation = array_merge($validation, $validators);
|
|
|
760 |
}
|
|
|
761 |
|
|
|
762 |
$validation = array_flip($validation);
|
|
|
763 |
foreach ($element as $key => $value) {
|
|
|
764 |
if ( ! isset($validation[$value])) {
|
|
|
765 |
throw new Doctrine_Import_Exception(
|
|
|
766 |
sprintf('Invalid schema element named "' . $value . '" at path "' . $path . '"')
|
|
|
767 |
);
|
|
|
768 |
}
|
|
|
769 |
}
|
|
|
770 |
}
|
|
|
771 |
}
|