1
|
|
|
<?php |
2
|
|
|
//[PHPCOMPRESSOR(remove,start)] |
3
|
|
|
/** |
4
|
|
|
* Created by PhpStorm. |
5
|
|
|
* User: molodyko |
6
|
|
|
* Date: 13.02.2016 |
7
|
|
|
* Time: 23:55 |
8
|
|
|
*/ |
9
|
|
|
namespace samsoncms\api\generator; |
10
|
|
|
|
11
|
|
|
use samson\activerecord\dbMySQLConnector; |
12
|
|
|
use samsoncms\api\Field; |
13
|
|
|
use samsoncms\api\generator\exception\ParentEntityNotFound; |
14
|
|
|
use samsonframework\orm\DatabaseInterface; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Database analyzer and metadata creator. |
18
|
|
|
* |
19
|
|
|
* @package samsoncms\api\generator |
20
|
|
|
*/ |
21
|
|
|
class Analyzer |
22
|
|
|
{ |
23
|
|
|
/** @var DatabaseInterface */ |
24
|
|
|
protected $database; |
25
|
|
|
|
26
|
|
|
/** @var Metadata[] Collection of entities metadata */ |
27
|
|
|
protected $metadata = array(); |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Generator constructor. |
31
|
|
|
* @param DatabaseInterface $database Database instance |
32
|
|
|
* @throws ParentEntityNotFound |
33
|
|
|
* @throws \samsoncms\api\exception\AdditionalFieldTypeNotFound |
34
|
|
|
*/ |
35
|
|
|
public function __construct(DatabaseInterface $database) |
36
|
|
|
{ |
37
|
|
|
$this->database = $database; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Analyze database and fill in metadata by type. |
42
|
|
|
* |
43
|
|
|
* @param int $type Metadata type |
44
|
|
|
* @param null|callable $filter Filtering callback |
45
|
|
|
* |
46
|
|
|
* @return Metadata[] Gathered metadata collection |
47
|
|
|
* |
48
|
|
|
* @throws ParentEntityNotFound |
49
|
|
|
* @throws \samsoncms\api\exception\AdditionalFieldTypeNotFound |
50
|
|
|
*/ |
51
|
|
View Code Duplication |
public function analyze($type, $filter = null) |
|
|
|
|
52
|
|
|
{ |
53
|
|
|
$metadataCollection = []; |
54
|
|
|
|
55
|
|
|
// Iterate all structures, parents first |
56
|
|
|
foreach ($this->entityNavigations($type) as $structureRow) { |
57
|
|
|
// If filter is the function and filter return false then skip this structure |
58
|
|
|
if (is_callable($filter) && (false === $filter($structureRow))) { |
59
|
|
|
continue; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
// Fill in entity metadata |
63
|
|
|
$metadata = new Metadata($type); |
64
|
|
|
|
65
|
|
|
// Get CapsCase and transliterated entity name |
66
|
|
|
$metadata->entity = $this->entityName($structureRow['Name']); |
67
|
|
|
$metadata->entityClassName = $this->fullEntityName($metadata->entity); |
68
|
|
|
$metadata->entityRealName = $structureRow['Name']; |
69
|
|
|
$metadata->entityID = $structureRow['StructureID']; |
70
|
|
|
|
71
|
|
|
// Try to find entity parent identifier for building future relations |
72
|
|
|
$metadata->parentID = $this->entityParent($structureRow['StructureID']); |
73
|
|
|
// Generate application from current entity |
74
|
|
|
$metadata->generateApplication = $structureRow['applicationGenerate']; |
75
|
|
|
// Show application from current entity |
76
|
|
|
$metadata->showApplication = $structureRow['applicationOutput']; |
77
|
|
|
// Icon for application from current entity |
78
|
|
|
$metadata->iconApplication = $structureRow['applicationIcon']; |
79
|
|
|
// Render application on main page |
80
|
|
|
$metadata->renderMainApplication = $structureRow['applicationRenderMain']; |
81
|
|
|
|
82
|
|
|
// TODO: Add multiple parent and fetching their data in a loop |
83
|
|
|
|
84
|
|
|
// Set pointer to parent entity |
85
|
|
|
if (null !== $metadata->parentID && $metadata->type !== Metadata::TYPE_TABLE) { |
86
|
|
|
if (array_key_exists($metadata->parentID, $this->metadata)) { |
87
|
|
|
$metadata->parent = $this->metadata[$metadata->parentID]; |
88
|
|
|
// Add all parent metadata to current object |
89
|
|
|
$metadata->defaultValues = $metadata->parent->defaultValues; |
90
|
|
|
$metadata->realNames = $metadata->parent->realNames; |
91
|
|
|
$metadata->allFieldIDs = $metadata->parent->allFieldIDs; |
92
|
|
|
$metadata->allFieldNames = $metadata->parent->allFieldNames; |
93
|
|
|
$metadata->allFieldValueColumns = $metadata->parent->allFieldValueColumns; |
94
|
|
|
$metadata->allFieldTypes = $metadata->parent->allFieldTypes; |
95
|
|
|
$metadata->fieldDescriptions = $metadata->parent->fieldDescriptions; |
96
|
|
|
$metadata->localizedFieldIDs = $metadata->parent->localizedFieldIDs; |
97
|
|
|
$metadata->notLocalizedFieldIDs = $metadata->parent->notLocalizedFieldIDs; |
98
|
|
|
} else { |
99
|
|
|
throw new ParentEntityNotFound($metadata->parentID); |
100
|
|
|
} |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
// Get old AR collections of metadata |
104
|
|
|
$metadata->arSelect = \samson\activerecord\material::$_sql_select; |
105
|
|
|
$metadata->arAttributes = \samson\activerecord\material::$_attributes; |
106
|
|
|
$metadata->arMap = \samson\activerecord\material::$_map; |
107
|
|
|
$metadata->arFrom = \samson\activerecord\material::$_sql_from; |
108
|
|
|
$metadata->arGroup = \samson\activerecord\material::$_own_group; |
109
|
|
|
$metadata->arRelationAlias = \samson\activerecord\material::$_relation_alias; |
110
|
|
|
$metadata->arRelationType = \samson\activerecord\material::$_relation_type; |
111
|
|
|
$metadata->arRelations = \samson\activerecord\material::$_relations; |
112
|
|
|
|
113
|
|
|
// Add SamsonCMS material needed data |
114
|
|
|
$metadata->arSelect['this'] = ' STRAIGHT_JOIN ' . $metadata->arSelect['this']; |
115
|
|
|
$metadata->arFrom['this'] .= "\n" . |
116
|
|
|
'LEFT JOIN ' . dbMySQLConnector::$prefix . 'materialfield as _mf |
117
|
|
|
ON ' . dbMySQLConnector::$prefix . 'material.MaterialID = _mf.MaterialID'; |
118
|
|
|
$metadata->arGroup[] = dbMySQLConnector::$prefix . 'material.MaterialID'; |
119
|
|
|
|
120
|
|
|
// Iterate entity fields |
121
|
|
|
foreach ($this->navigationFields($structureRow['StructureID']) as $fieldID => $fieldRow) { |
122
|
|
|
// Get camelCase and transliterated field name |
123
|
|
|
$fieldName = $this->fieldName($fieldRow['Name']); |
124
|
|
|
|
125
|
|
|
// TODO: Set default for additional field storing type accordingly. |
126
|
|
|
|
127
|
|
|
// Store field metadata |
128
|
|
|
$metadata->realNames[$fieldRow['Name']] = $fieldName; |
129
|
|
|
$metadata->allFieldIDs[$fieldID] = $fieldName; |
130
|
|
|
$metadata->allFieldNames[$fieldName] = $fieldID; |
131
|
|
|
$metadata->allFieldValueColumns[$fieldID] = Field::valueColumn($fieldRow[Field::F_TYPE]); |
132
|
|
|
$metadata->allFieldTypes[$fieldID] = Field::phpType($fieldRow['Type']); |
133
|
|
|
$metadata->allFieldCmsTypes[$fieldID] = (int)$fieldRow['Type']; |
134
|
|
|
$metadata->fieldDescriptions[$fieldID] = $fieldRow['Description'] . ', ' . $fieldRow['Name'] . '#' . $fieldID; |
135
|
|
|
$metadata->fieldRawDescriptions[$fieldID] = $fieldRow['Description']; |
136
|
|
|
|
137
|
|
|
// Fill localization fields collections |
138
|
|
|
if ($fieldRow[Field::F_LOCALIZED] == 1) { |
139
|
|
|
$metadata->localizedFieldIDs[$fieldID] = $fieldName; |
140
|
|
|
} else { |
141
|
|
|
$metadata->notLocalizedFieldIDs[$fieldID] = $fieldName; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
// Fill all fields which should display in list |
145
|
|
|
if ($fieldRow['showInList'] == 1) { |
146
|
|
|
$metadata->showFieldsInList[] = $fieldID; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
// Save custom type |
150
|
|
|
$metadata->customTypeFields[$fieldID] = $fieldRow['customTypeName']; |
151
|
|
|
|
152
|
|
|
// Set old AR collections of metadata |
153
|
|
|
$metadata->arAttributes[$fieldName] = $fieldName; |
154
|
|
|
$metadata->arMap[$fieldName] = dbMySQLConnector::$prefix . 'material.' . $fieldName; |
155
|
|
|
|
156
|
|
|
// Add additional field column to entity query |
157
|
|
|
$equal = '((_mf.FieldID = ' . $fieldID . ')&&(_mf.locale ' . ($fieldRow['local'] ? ' = "@locale"' : 'IS NULL') . '))'; |
158
|
|
|
$metadata->arSelect['this'] .= "\n\t\t" . ',MAX(IF(' . $equal . ', _mf.`' . Field::valueColumn($fieldRow['Type']) . '`, NULL)) as `' . $fieldName . '`'; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
// Get id of child navigation |
162
|
|
|
foreach ($this->entityChildNavigation($structureRow['StructureID']) as $childNavigation) { |
163
|
|
|
$metadata->childNavigationIDs[] = $childNavigation['StructureID']; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
// Store metadata by entity identifier |
167
|
|
|
$metadataCollection[$structureRow['StructureID']] = $metadata; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
|
171
|
|
|
return $metadataCollection; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** @return array Get collection of navigation objects */ |
175
|
|
|
protected function entityNavigations($type = 0) |
176
|
|
|
{ |
177
|
|
|
return $this->database->fetch(' |
178
|
|
|
SELECT * FROM `structure` |
179
|
|
|
WHERE `Active` = "1" AND `Type` = "' . $type . '" |
180
|
|
|
ORDER BY `ParentID` ASC |
181
|
|
|
'); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Get correct entity name. |
186
|
|
|
* |
187
|
|
|
* @param string $navigationName Original navigation entity name |
188
|
|
|
* @return string Correct PHP-supported entity name |
189
|
|
|
*/ |
190
|
|
|
protected function entityName($navigationName) |
191
|
|
|
{ |
192
|
|
|
return ucfirst($this->getValidName($this->transliterated($navigationName))); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Remove all wrong characters from entity name |
197
|
|
|
* |
198
|
|
|
* @param string $navigationName Original navigation entity name |
199
|
|
|
* |
200
|
|
|
* @return string Correct PHP-supported entity name |
201
|
|
|
*/ |
202
|
|
|
protected function getValidName($navigationName) |
203
|
|
|
{ |
204
|
|
|
return preg_replace('/(^\d*)|([^\w\d_])/', '', $navigationName); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Transliterate string to english. |
209
|
|
|
* |
210
|
|
|
* @param string $string Source string |
211
|
|
|
* @return string Transliterated string |
212
|
|
|
*/ |
213
|
|
View Code Duplication |
protected function transliterated($string) |
|
|
|
|
214
|
|
|
{ |
215
|
|
|
return str_replace( |
216
|
|
|
' ', |
217
|
|
|
'', |
218
|
|
|
ucwords(iconv("UTF-8", "UTF-8//IGNORE", strtr($string, array( |
219
|
|
|
"'" => "", |
220
|
|
|
"`" => "", |
221
|
|
|
"-" => " ", |
222
|
|
|
"_" => " ", |
223
|
|
|
"а" => "a", "А" => "a", |
224
|
|
|
"б" => "b", "Б" => "b", |
225
|
|
|
"в" => "v", "В" => "v", |
226
|
|
|
"г" => "g", "Г" => "g", |
227
|
|
|
"д" => "d", "Д" => "d", |
228
|
|
|
"е" => "e", "Е" => "e", |
229
|
|
|
"ж" => "zh", "Ж" => "zh", |
230
|
|
|
"з" => "z", "З" => "z", |
231
|
|
|
"и" => "i", "И" => "i", |
232
|
|
|
"й" => "y", "Й" => "y", |
233
|
|
|
"к" => "k", "К" => "k", |
234
|
|
|
"л" => "l", "Л" => "l", |
235
|
|
|
"м" => "m", "М" => "m", |
236
|
|
|
"н" => "n", "Н" => "n", |
237
|
|
|
"о" => "o", "О" => "o", |
238
|
|
|
"п" => "p", "П" => "p", |
239
|
|
|
"р" => "r", "Р" => "r", |
240
|
|
|
"с" => "s", "С" => "s", |
241
|
|
|
"т" => "t", "Т" => "t", |
242
|
|
|
"у" => "u", "У" => "u", |
243
|
|
|
"ф" => "f", "Ф" => "f", |
244
|
|
|
"х" => "h", "Х" => "h", |
245
|
|
|
"ц" => "c", "Ц" => "c", |
246
|
|
|
"ч" => "ch", "Ч" => "ch", |
247
|
|
|
"ш" => "sh", "Ш" => "sh", |
248
|
|
|
"щ" => "sch", "Щ" => "sch", |
249
|
|
|
"ъ" => "", "Ъ" => "", |
250
|
|
|
"ы" => "y", "Ы" => "y", |
251
|
|
|
"ь" => "", "Ь" => "", |
252
|
|
|
"э" => "e", "Э" => "e", |
253
|
|
|
"ю" => "yu", "Ю" => "yu", |
254
|
|
|
"я" => "ya", "Я" => "ya", |
255
|
|
|
"і" => "i", "І" => "i", |
256
|
|
|
"ї" => "yi", "Ї" => "yi", |
257
|
|
|
"є" => "e", "Є" => "e" |
258
|
|
|
) |
259
|
|
|
) |
260
|
|
|
) |
261
|
|
|
) |
262
|
|
|
); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Find entity parent. |
267
|
|
|
* |
268
|
|
|
* @param $entityID |
269
|
|
|
* |
270
|
|
|
* @return null|int Parent entity identifier |
271
|
|
|
*/ |
272
|
|
View Code Duplication |
public function entityParent($entityID) |
|
|
|
|
273
|
|
|
{ |
274
|
|
|
$parentData = $this->database->fetch(' |
275
|
|
|
SELECT * |
276
|
|
|
FROM structure_relation as sm |
277
|
|
|
JOIN structure as s ON s.StructureID = sm.parent_id |
278
|
|
|
WHERE sm.child_id = "' . $entityID . '" |
279
|
|
|
AND s.StructureID != "' . $entityID . '" |
280
|
|
|
'); |
281
|
|
|
|
282
|
|
|
// Get parent entity identifier |
283
|
|
|
return count($parentData) ? $parentData[0]['StructureID'] : null; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** @return array Collection of navigation additional fields */ |
287
|
|
View Code Duplication |
protected function navigationFields($navigationID) |
|
|
|
|
288
|
|
|
{ |
289
|
|
|
$return = array(); |
290
|
|
|
// TODO: Optimize queries make one single query with only needed data |
291
|
|
|
foreach ($this->database->fetch('SELECT * FROM `structurefield` WHERE `StructureID` = "' . $navigationID . '" AND `Active` = "1"') as $fieldStructureRow) { |
292
|
|
|
foreach ($this->database->fetch('SELECT * FROM `field` WHERE `FieldID` = "' . $fieldStructureRow['FieldID'] . '"') as $fieldRow) { |
293
|
|
|
$return[$fieldRow['FieldID']] = $fieldRow; |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
return $return; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Get correct field name. |
302
|
|
|
* |
303
|
|
|
* @param string $fieldName Original field name |
304
|
|
|
* |
305
|
|
|
* @return string Correct PHP-supported field name |
306
|
|
|
*/ |
307
|
|
|
protected function fieldName($fieldName) |
308
|
|
|
{ |
309
|
|
|
return $fieldName = lcfirst($this->transliterated($fieldName)); |
|
|
|
|
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** @return array Get collection of child navigation objects */ |
313
|
|
|
protected function entityChildNavigation($parentId) |
314
|
|
|
{ |
315
|
|
|
return $this->database->fetch(' |
316
|
|
|
SELECT * FROM `structure` |
317
|
|
|
WHERE `Active` = "1" AND `ParentID` = ' . $parentId . ' |
318
|
|
|
ORDER BY `ParentID` ASC |
319
|
|
|
'); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** @return string Entity state hash */ |
323
|
|
|
public function entityHash() |
324
|
|
|
{ |
325
|
|
|
// Получим информацию о всех таблицах из БД |
326
|
|
|
return md5(serialize($this->database->fetch( |
327
|
|
|
'SELECT `TABLES`.`TABLE_NAME` as `TABLE_NAME` |
328
|
|
|
FROM `information_schema`.`TABLES` as `TABLES` |
329
|
|
|
WHERE `TABLES`.`TABLE_SCHEMA`="' . $this->database->database() . '";' |
330
|
|
|
))); |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* Make correct code formatting |
335
|
|
|
* |
336
|
|
|
* @param $code |
337
|
|
|
* |
338
|
|
|
* @return mixed |
339
|
|
|
*/ |
340
|
|
|
protected function formatTab($code) |
341
|
|
|
{ |
342
|
|
|
// Replace indentation |
343
|
|
|
return str_replace("\t", ' ', $code); |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Get correct full entity name with name space. |
348
|
|
|
* |
349
|
|
|
* @param string $navigationName Original navigation entity name |
350
|
|
|
* @param string $namespace Namespace |
351
|
|
|
* @return string Correct PHP-supported entity name |
352
|
|
|
*/ |
353
|
|
|
protected function fullEntityName($navigationName, $namespace = __NAMESPACE__) |
|
|
|
|
354
|
|
|
{ |
355
|
|
|
return '\samsoncms\api\generated\\'.$this->entityName($navigationName); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Get additional field type in form of Field constant name |
360
|
|
|
* by database additional field type identifier. |
361
|
|
|
* |
362
|
|
|
* @param integer $fieldType Additional field type identifier |
363
|
|
|
* @return string Additional field type constant |
364
|
|
|
*/ |
365
|
|
|
protected function additionalFieldType($fieldType) |
366
|
|
|
{ |
367
|
|
|
return 'Field::'.$this->constantNameByValue($fieldType); |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Get class constant name by its value. |
372
|
|
|
* |
373
|
|
|
* @param string $value Constant value |
374
|
|
|
* @param string $className Class name |
375
|
|
|
* |
376
|
|
|
* @return string Full constant name |
377
|
|
|
*/ |
378
|
|
View Code Duplication |
protected function constantNameByValue($value, $className = Field::ENTITY) |
|
|
|
|
379
|
|
|
{ |
380
|
|
|
// Get array where class constants are values and their values are keys |
381
|
|
|
$nameByValue = array_flip((new \ReflectionClass($className))->getConstants()); |
382
|
|
|
|
383
|
|
|
// Try to find constant by its value |
384
|
|
|
if (null !== $nameByValue[$value]) { |
385
|
|
|
// Return constant name |
386
|
|
|
return $nameByValue[$value]; |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
return ''; |
390
|
|
|
} |
391
|
|
|
} |
392
|
|
|
//[PHPCOMPRESSOR(remove,end)] |
393
|
|
|
|
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.