1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Admingenerator\GeneratorBundle\Guesser; |
4
|
|
|
|
5
|
|
|
use Admingenerator\GeneratorBundle\Exception\NotImplementedException; |
6
|
|
|
use Doctrine\Common\Util\Inflector; |
7
|
|
|
use Symfony\Component\HttpKernel\Kernel; |
8
|
|
|
|
9
|
|
|
class PropelORMFieldGuesser |
10
|
|
|
{ |
11
|
|
|
/** |
12
|
|
|
* @var boolean |
13
|
|
|
*/ |
14
|
|
|
private $guessRequired; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* @var boolean |
18
|
|
|
*/ |
19
|
|
|
private $defaultRequired; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var array |
23
|
|
|
*/ |
24
|
|
|
private $cache = array(); |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var array |
28
|
|
|
*/ |
29
|
|
|
private $formTypes; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var array |
33
|
|
|
*/ |
34
|
|
|
private $filterTypes; |
35
|
|
|
|
36
|
|
|
public function __construct(array $formTypes, array $filterTypes, $guessRequired, $defaultRequired) |
37
|
|
|
{ |
38
|
|
|
$this->formTypes = $formTypes; |
39
|
|
|
$this->filterTypes = $filterTypes; |
40
|
|
|
$this->guessRequired = $guessRequired; |
41
|
|
|
$this->defaultRequired = $defaultRequired; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @param $class |
46
|
|
|
* @return mixed |
47
|
|
|
*/ |
48
|
|
|
protected function getMetadatas($class) |
49
|
|
|
{ |
50
|
|
|
return $this->getTable($class); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @param $class |
55
|
|
|
* @return array |
56
|
|
|
*/ |
57
|
|
|
public function getAllFields($class) |
58
|
|
|
{ |
59
|
|
|
$return = array(); |
60
|
|
|
|
61
|
|
|
foreach ($this->getMetadatas($class)->getColumns() as $column) { |
62
|
|
|
$return[] = Inflector::tableize($column->getPhpName()); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
return $return; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
public function getManyToMany($model, $fieldPath) |
69
|
|
|
{ |
70
|
|
|
$resolved = $this->resolveRelatedField($model, $fieldPath); |
71
|
|
|
$relation = $this->getRelation($resolved['field'], $resolved['class']); |
72
|
|
|
|
73
|
|
|
if ($relation) { |
74
|
|
|
return \RelationMap::MANY_TO_MANY === $relation->getType(); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
return false; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Find out the database type for given model field path. |
82
|
|
|
* |
83
|
|
|
* @param string $model The starting model. |
84
|
|
|
* @param string $fieldPath The field path. |
85
|
|
|
* @return string The leaf field's primary key. |
86
|
|
|
*/ |
87
|
|
|
public function getDbType($model, $fieldPath) |
88
|
|
|
{ |
89
|
|
|
$resolved = $this->resolveRelatedField($model, $fieldPath); |
90
|
|
|
$class = $resolved['class']; |
91
|
|
|
$field = $resolved['field']; |
92
|
|
|
|
93
|
|
|
$relation = $this->getRelation($field, $class); |
94
|
|
|
|
95
|
|
|
if ($relation) { |
96
|
|
|
return \RelationMap::MANY_TO_ONE === $relation->getType() ? 'model' : 'collection'; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
$column = $this->getColumn($class, $field); |
100
|
|
|
|
101
|
|
|
return $column ? $column->getType() : 'virtual'; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* @param $fieldName |
106
|
|
|
* @param $class |
107
|
|
|
* @return object|false The relation object or false. |
108
|
|
|
*/ |
109
|
|
|
protected function getRelation($fieldName, $class) |
110
|
|
|
{ |
111
|
|
|
$table = $this->getMetadatas($class); |
112
|
|
|
$relName = Inflector::classify($fieldName); |
113
|
|
|
|
114
|
|
|
foreach ($table->getRelations() as $relation) { |
115
|
|
|
if ($relName === $relation->getName() || $relName === $relation->getPluralName()) { |
116
|
|
|
return $relation; |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
return false; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* @param $class |
125
|
|
|
* @param $fieldName |
126
|
|
|
* @return string|void |
127
|
|
|
*/ |
128
|
|
|
public function getPhpName($class, $fieldName) |
129
|
|
|
{ |
130
|
|
|
$column = $this->getColumn($class, $fieldName); |
131
|
|
|
|
132
|
|
|
if ($column) { |
133
|
|
|
return $column->getPhpName(); |
134
|
|
|
} |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @param $dbType |
139
|
|
|
* @return string |
140
|
|
|
*/ |
141
|
|
|
public function getSortType($dbType) |
142
|
|
|
{ |
143
|
|
|
$alphabeticTypes = array( |
144
|
|
|
\PropelColumnTypes::CHAR, |
145
|
|
|
\PropelColumnTypes::VARCHAR, |
146
|
|
|
\PropelColumnTypes::LONGVARCHAR, |
147
|
|
|
\PropelColumnTypes::BLOB, |
148
|
|
|
\PropelColumnTypes::CLOB, |
149
|
|
|
\PropelColumnTypes::CLOB_EMU, |
150
|
|
|
); |
151
|
|
|
|
152
|
|
|
$numericTypes = array( |
153
|
|
|
\PropelColumnTypes::FLOAT, |
154
|
|
|
\PropelColumnTypes::REAL, |
155
|
|
|
\PropelColumnTypes::DOUBLE, |
156
|
|
|
\PropelColumnTypes::DECIMAL, |
157
|
|
|
\PropelColumnTypes::TINYINT, |
158
|
|
|
\PropelColumnTypes::SMALLINT, |
159
|
|
|
\PropelColumnTypes::INTEGER, |
160
|
|
|
\PropelColumnTypes::BIGINT, |
161
|
|
|
\PropelColumnTypes::NUMERIC, |
162
|
|
|
); |
163
|
|
|
|
164
|
|
|
if (in_array($dbType, $alphabeticTypes)) { |
165
|
|
|
return 'alphabetic'; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
if (in_array($dbType, $numericTypes)) { |
169
|
|
|
return 'numeric'; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
return 'default'; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* @param $dbType |
177
|
|
|
* @param $class: for debug only |
178
|
|
|
* @param $columnName: for debug only |
179
|
|
|
* @return string |
180
|
|
|
*/ |
181
|
|
View Code Duplication |
public function getFormType($dbType, $class, $columnName) |
|
|
|
|
182
|
|
|
{ |
183
|
|
|
$formTypes = array(); |
184
|
|
|
|
185
|
|
|
foreach ($this->formTypes as $key => $value) { |
186
|
|
|
// if config is all uppercase use it to retrieve \PropelColumnTypes |
187
|
|
|
// constant, otherwise use it literally |
188
|
|
|
if ($key === strtoupper($key)) { |
189
|
|
|
$key = constant('\PropelColumnTypes::'.$key); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
$formTypes[$key] = $value; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
if (array_key_exists($dbType, $formTypes)) { |
196
|
|
|
return $formTypes[$dbType]; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
if ('virtual' === $dbType) { |
200
|
|
|
return 'virtual_form'; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
throw new NotImplementedException( |
204
|
|
|
'The dbType "'.$dbType.'" is not yet implemented ' |
205
|
|
|
.'(column "'.$columnName.'" in "'.$class.'")' |
206
|
|
|
); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* @param $dbType |
211
|
|
|
* @param $columnName |
212
|
|
|
* @return string |
213
|
|
|
*/ |
214
|
|
View Code Duplication |
public function getFilterType($dbType, $class, $columnName) |
|
|
|
|
215
|
|
|
{ |
216
|
|
|
$filterTypes = array(); |
217
|
|
|
|
218
|
|
|
foreach ($this->filterTypes as $key => $value) { |
219
|
|
|
// if config is all uppercase use it to retrieve \PropelColumnTypes |
220
|
|
|
// constant, otherwise use it literally |
221
|
|
|
if ($key === strtoupper($key)) { |
222
|
|
|
$key = constant('\PropelColumnTypes::'.$key); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
$filterTypes[$key] = $value; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
if (array_key_exists($dbType, $filterTypes)) { |
229
|
|
|
return $filterTypes[$dbType]; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
if ('virtual' === $dbType) { |
233
|
|
|
return 'virtual_filter'; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
throw new NotImplementedException( |
237
|
|
|
'The dbType "'.$dbType.'" is not yet implemented ' |
238
|
|
|
.'(column "'.$columnName.'" in "'.$class.'")' |
239
|
|
|
); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* @param $formType |
244
|
|
|
* @param $dbType |
245
|
|
|
* @param $model |
246
|
|
|
* @param $fieldPath |
247
|
|
|
* @return array |
248
|
|
|
*/ |
249
|
|
|
public function getFormOptions($formType, $dbType, $model, $fieldPath) |
250
|
|
|
{ |
251
|
|
|
return $this->getOptions($formType, $dbType, $model, $fieldPath, false); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* @param $filterType |
256
|
|
|
* @param $dbType |
257
|
|
|
* @param $model |
258
|
|
|
* @param $fieldPath |
259
|
|
|
* @return array |
260
|
|
|
*/ |
261
|
|
|
public function getFilterOptions($filterType, $dbType, $model, $fieldPath) |
262
|
|
|
{ |
263
|
|
|
return $this->getOptions($filterType, $dbType, $model, $fieldPath, true); |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* @param $type |
268
|
|
|
* @param $dbType |
269
|
|
|
* @param $model |
270
|
|
|
* @param $fieldPath |
271
|
|
|
* @param bool $filter |
272
|
|
|
* @return array |
273
|
|
|
*/ |
274
|
|
|
protected function getOptions($type, $dbType, $model, $fieldPath, $filter = false) |
275
|
|
|
{ |
276
|
|
|
if ('virtual' === $dbType) { |
277
|
|
|
return array(); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
$resolved = $this->resolveRelatedField($model, $fieldPath); |
281
|
|
|
$class = $resolved['class']; |
282
|
|
|
$columnName = $resolved['field']; |
283
|
|
|
|
284
|
|
|
if ((\PropelColumnTypes::BOOLEAN == $dbType || \PropelColumnTypes::BOOLEAN_EMU == $dbType) && |
285
|
|
|
preg_match("#ChoiceType$#i", $type)) { |
286
|
|
|
$options = array( |
287
|
|
|
'choices' => array( |
288
|
|
|
'boolean.no' => 0, |
289
|
|
|
'boolean.yes' => 1 |
290
|
|
|
), |
291
|
|
|
'placeholder' => 'boolean.yes_or_no', |
292
|
|
|
'translation_domain' => 'Admingenerator', |
293
|
|
|
'choice_translation_domain' => 'Admingenerator' |
294
|
|
|
); |
295
|
|
|
|
296
|
|
|
if (Kernel::MAJOR_VERSION < 3) { |
297
|
|
|
$options['choices_as_values'] = true; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
return $options; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
if (!$filter && |
304
|
|
|
(\PropelColumnTypes::BOOLEAN == $dbType || \PropelColumnTypes::BOOLEAN_EMU == $dbType) && |
305
|
|
|
preg_match("#CheckboxType#i", $type)) { |
306
|
|
|
return array( |
307
|
|
|
'required' => false |
308
|
|
|
); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
if (preg_match("#ModelType$#i", $type)) { |
312
|
|
|
$relation = $this->getRelation($columnName, $class); |
313
|
|
|
if ($relation) { |
314
|
|
|
if (\RelationMap::MANY_TO_ONE === $relation->getType()) { |
315
|
|
|
return array( |
316
|
|
|
'class' => $relation->getForeignTable()->getClassname(), |
317
|
|
|
'multiple' => false, |
318
|
|
|
); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
return array( |
322
|
|
|
'class' => $relation->getLocalTable()->getClassname(), |
323
|
|
|
'multiple' => false, |
324
|
|
|
); |
325
|
|
|
} |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
if (preg_match("#CollectionType$#i", $type)) { |
329
|
|
|
$relation = $this->getRelation($columnName, $class); |
330
|
|
|
|
331
|
|
|
if ($relation) { |
332
|
|
|
return array( |
333
|
|
|
'allow_add' => true, |
334
|
|
|
'allow_delete' => true, |
335
|
|
|
'by_reference' => false, |
336
|
|
|
'entry_type' => 'entity', |
337
|
|
|
'entry_options' => array( |
338
|
|
|
'class' => \RelationMap::MANY_TO_ONE === $relation->getType() ? $relation->getForeignTable()->getClassname() : $relation->getLocalTable()->getClassname() |
339
|
|
|
) |
340
|
|
|
); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
return array( |
344
|
|
|
'entry_type' => 'text', |
345
|
|
|
); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
if (\PropelColumnTypes::ENUM == $dbType) { |
349
|
|
|
$valueSet = $this->getMetadatas($class)->getColumn($columnName)->getValueSet(); |
350
|
|
|
|
351
|
|
|
return array( |
352
|
|
|
'required' => $filter ? false : $this->isRequired($class, $columnName), |
353
|
|
|
'choices' => array_combine($valueSet, $valueSet), |
354
|
|
|
); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
return array('required' => $filter ? false : $this->isRequired($class, $columnName)); |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
protected function isRequired($class, $fieldName) |
361
|
|
|
{ |
362
|
|
|
if (!$this->guessRequired) { |
363
|
|
|
return $this->defaultRequired; |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
$column = $this->getColumn($class, $fieldName); |
367
|
|
|
|
368
|
|
|
return $column ? $column->isNotNull() : false; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Find the pk name |
373
|
|
|
*/ |
374
|
|
|
public function getModelPrimaryKeyName($class) |
375
|
|
|
{ |
376
|
|
|
$pks = $this->getMetadatas($class)->getPrimaryKeyColumns(); |
377
|
|
|
|
378
|
|
|
if (count($pks) == 1) { |
379
|
|
|
return $pks[0]->getPhpName(); |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
throw new \LogicException('No valid primary keys found'); |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
protected function getTable($class) |
386
|
|
|
{ |
387
|
|
|
if (isset($this->cache[$class])) { |
388
|
|
|
return $this->cache[$class]; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
if (class_exists($queryClass = $class.'Query')) { |
392
|
|
|
$query = new $queryClass(); |
393
|
|
|
|
394
|
|
|
return $this->cache[$class] = $query->getTableMap(); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
throw new \LogicException('Can\'t find query class '.$queryClass); |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
protected function getColumn($class, $property) |
401
|
|
|
{ |
402
|
|
|
if (isset($this->cache[$class.'::'.$property])) { |
403
|
|
|
return $this->cache[$class.'::'.$property]; |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
$table = $this->getTable($class); |
407
|
|
|
|
408
|
|
|
if ($table && $table->hasColumn($property)) { |
409
|
|
|
return $this->cache[$class.'::'.$property] = $table->getColumn($property); |
410
|
|
|
} else { |
411
|
|
|
foreach ($table->getColumns() as $column) { |
412
|
|
|
$tabelized = Inflector::tableize($column->getPhpName()); |
413
|
|
|
if ($tabelized === $property || $column->getPhpName() === ucfirst($property)) { |
414
|
|
|
return $this->cache[$class.'::'.$property] = $column; |
415
|
|
|
} |
416
|
|
|
} |
417
|
|
|
} |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Find out the primary key for given model field path. |
422
|
|
|
* |
423
|
|
|
* @param string $model The starting model. |
424
|
|
|
* @param string $fieldPath The field path. |
425
|
|
|
* @return string The leaf field's primary key. |
426
|
|
|
*/ |
427
|
|
|
public function getPrimaryKeyFor($model, $fieldPath) |
428
|
|
|
{ |
429
|
|
|
$resolved = $this->resolveRelatedField($model, $fieldPath); |
430
|
|
|
$class = $resolved['class']; |
431
|
|
|
$field = $resolved['field']; |
432
|
|
|
|
433
|
|
|
if ($relation = $this->getRelation($field, $class)) { |
434
|
|
|
$class = $relation->getLocalTable()->getClassname(); |
435
|
|
|
|
436
|
|
|
return $this->getModelPrimaryKeyName($class); |
437
|
|
|
} else { |
438
|
|
|
// if the leaf node is not an association |
439
|
|
|
return null; |
440
|
|
|
} |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
/** |
444
|
|
|
* Resolve field path for given model to class and field name. |
445
|
|
|
* |
446
|
|
|
* @param string $model The starting model. |
447
|
|
|
* @param string $fieldPath The field path. |
448
|
|
|
* @return array An array containing field and class information. |
449
|
|
|
*/ |
450
|
|
|
private function resolveRelatedField($model, $fieldPath) |
451
|
|
|
{ |
452
|
|
|
$path = explode('.', $fieldPath); |
453
|
|
|
$field = array_pop($path); |
454
|
|
|
$class = $model; |
455
|
|
|
|
456
|
|
|
foreach ($path as $part) { |
457
|
|
|
if (!$relation = $this->getRelation($part, $class)) { |
458
|
|
|
throw new \LogicException('Field "'.$part.'" for class "'.$class.'" is not an association.'); |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
$class = $relation->getName(); |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
return array( |
465
|
|
|
'field' => $field, |
466
|
|
|
'class' => $class |
467
|
|
|
); |
468
|
|
|
} |
469
|
|
|
} |
470
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.