Completed
Push — master ( dc1c30...cbe355 )
by Vitaly
03:00
created

Generator::createTableClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 49
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 3
Metric Value
c 3
b 0
f 3
dl 0
loc 49
rs 9.2258
cc 2
eloc 39
nc 2
nop 4
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: VITALYIEGOROV
5
 * Date: 09.12.15
6
 * Time: 14:34
7
 */
8
namespace samsoncms\api;
9
10
use samsoncms\api\query\Generic;
11
use samsonframework\orm\DatabaseInterface;
12
13
/**
14
 * Entity classes generator.
15
 * @package samsoncms\api
16
 */
17
class Generator
18
{
19
    /** @var DatabaseInterface */
20
    protected $database;
21
22
    /**
23
     * Transliterate string to english.
24
     *
25
     * @param string $string Source string
26
     * @return string Transliterated string
27
     */
28
    protected function transliterated($string)
29
    {
30
        return str_replace(
31
            ' ',
32
            '',
33
            ucwords(iconv("UTF-8", "UTF-8//IGNORE", strtr($string, array(
34
                            "'" => "",
35
                            "`" => "",
36
                            "-" => " ",
37
                            "_" => " ",
38
                            "а" => "a", "А" => "a",
39
                            "б" => "b", "Б" => "b",
40
                            "в" => "v", "В" => "v",
41
                            "г" => "g", "Г" => "g",
42
                            "д" => "d", "Д" => "d",
43
                            "е" => "e", "Е" => "e",
44
                            "ж" => "zh", "Ж" => "zh",
45
                            "з" => "z", "З" => "z",
46
                            "и" => "i", "И" => "i",
47
                            "й" => "y", "Й" => "y",
48
                            "к" => "k", "К" => "k",
49
                            "л" => "l", "Л" => "l",
50
                            "м" => "m", "М" => "m",
51
                            "н" => "n", "Н" => "n",
52
                            "о" => "o", "О" => "o",
53
                            "п" => "p", "П" => "p",
54
                            "р" => "r", "Р" => "r",
55
                            "с" => "s", "С" => "s",
56
                            "т" => "t", "Т" => "t",
57
                            "у" => "u", "У" => "u",
58
                            "ф" => "f", "Ф" => "f",
59
                            "х" => "h", "Х" => "h",
60
                            "ц" => "c", "Ц" => "c",
61
                            "ч" => "ch", "Ч" => "ch",
62
                            "ш" => "sh", "Ш" => "sh",
63
                            "щ" => "sch", "Щ" => "sch",
64
                            "ъ" => "", "Ъ" => "",
65
                            "ы" => "y", "Ы" => "y",
66
                            "ь" => "", "Ь" => "",
67
                            "э" => "e", "Э" => "e",
68
                            "ю" => "yu", "Ю" => "yu",
69
                            "я" => "ya", "Я" => "ya",
70
                            "і" => "i", "І" => "i",
71
                            "ї" => "yi", "Ї" => "yi",
72
                            "є" => "e", "Є" => "e"
73
                        )
74
                    )
75
                )
76
            )
77
        );
78
    }
79
80
    /**
81
     * Get class constant name by its value.
82
     *
83
     * @param string $value Constant value
84
     * @param string $className Class name
85
     * @return string Full constant name
86
     */
87
    protected function constantNameByValue($value, $className = Field::ENTITY)
88
    {
89
        // Get array where class constants are values and their values are keys
90
        $nameByValue = array_flip((new \ReflectionClass($className))->getConstants());
91
92
        // Try to find constant by its value
93
        if (isset($nameByValue[$value])) {
94
            // Return constant name
95
            return $nameByValue[$value];
96
        }
97
    }
98
99
    /**
100
     * Get correct entity name.
101
     *
102
     * @param string $navigationName Original navigation entity name
103
     * @return string Correct PHP-supported entity name
104
     */
105
    protected function entityName($navigationName)
106
    {
107
        return ucfirst($this->transliterated($navigationName));
108
    }
109
110
    /**
111
     * Get correct full entity name with name space.
112
     *
113
     * @param string $navigationName Original navigation entity name
114
     * @param string $namespace Namespace
115
     * @return string Correct PHP-supported entity name
116
     */
117
    protected function fullEntityName($navigationName, $namespace = __NAMESPACE__)
118
    {
119
        return str_replace('\\', '\\\\' , '\\'.$namespace.'\\'.$this->entityName($navigationName));
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
120
    }
121
122
    /**
123
     * Get correct field name.
124
     *
125
     * @param string $fieldName Original field name
126
     * @return string Correct PHP-supported field name
127
     */
128
    protected function fieldName($fieldName)
129
    {
130
        return $fieldName = lcfirst($this->transliterated($fieldName));
0 ignored issues
show
Unused Code introduced by
$fieldName is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
131
    }
132
133
    /**
134
     * Get additional field type in form of Field constant name
135
     * by database additional field type identifier.
136
     *
137
     * @param integer $fieldType Additional field type identifier
138
     * @return string Additional field type constant
139
     */
140
    protected function additionalFieldType($fieldType)
141
    {
142
        return 'Field::'.$this->constantNameByValue($fieldType);
143
    }
144
145
    /**
146
     * Generate Query::where() analog for specific field.
147
     *
148
     * @param string $fieldName Field name
149
     * @param string $fieldId Field primary identifier
150
     * @param string $fieldType Field PHP type
151
     * @return string Generated PHP method code
152
     */
153
    protected function generateFieldConditionMethod($fieldName, $fieldId, $fieldType)
154
    {
155
        $code = "\n\t" . '/**';
156
        $code .= "\n\t" . ' * Add '.$fieldName.'(#' . $fieldId . ') field query condition.';
157
        $code .= "\n\t" . ' * @param '.Field::phpType($fieldType).' $value Field value';
158
        $code .= "\n\t" . ' * @return self Chaining';
159
        $code .= "\n\t" . ' * @see Generic::where()';
160
        $code .= "\n\t" . ' */';
161
        $code .= "\n\t" . 'public function ' . $fieldName . '($value)';
162
        $code .= "\n\t" . "{";
163
        $code .= "\n\t\t" . 'return $this->where("'.$fieldName.'", $value);';
164
165
        return $code . "\n\t" . "}"."\n";
166
    }
167
168
    /**
169
     * Create entity PHP class code.
170
     *
171
     * @param string $navigationName Original entity name
172
     * @param string $entityName PHP entity name
173
     * @param array $navigationFields Collection of entity additional fields
174
     * @return string Generated entity query PHP class code
175
     */
176
    protected function createEntityClass($navigationName, $entityName, $navigationFields)
177
    {
178
        $class = "\n\n" . '/** Class for getting "'.$navigationName.'" instances from database */';
179
        $class .= "\n" . 'class ' . $entityName . ' extends Entity';
180
        $class .= "\n" . '{';
181
182
        // Iterate additional fields
183
        $constants = '';
184
        $constants .= "\n\t" .'/** Entity full class name */';
185
        $constants .= "\n\t" . 'const ENTITY = "'.$this->fullEntityName($entityName).'";';
186
        $variables = '';
187
        $methods = '';
0 ignored issues
show
Unused Code introduced by
$methods is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
188
        foreach ($navigationFields as $fieldID => $fieldRow) {
189
            $fieldName = $this->fieldName($fieldRow['Name']);
190
191
            $constants .= "\n\t" . '/** ' . Field::phpType($fieldRow['Type']) . ' '.$fieldRow['Description'].' Field #' . $fieldID . ' variable name */';
192
            $constants .= "\n\t" . 'const F_' . strtoupper($fieldName) . ' = "'.$fieldName.'";';
193
194
            $variables .= "\n\t" . '/** @var ' . Field::phpType($fieldRow['Type']) . ' '.$fieldRow['Description'].' Field #' . $fieldID . '*/';
195
            $variables .= "\n\t" . 'public $' . $fieldName . ';';
196
        }
197
198
        $class .= $constants;
199
        $class .= "\n\t";
200
        $class .= "\n\t" . '/** @var string Not transliterated entity name */';
201
        $class .= "\n\t" . 'protected static $viewName = "' . $navigationName . '";';
202
        $class .= "\n\t";
203
        $class .= $variables;
204
205
        $class .= "\n" . '}';
206
207
        return $class;
208
    }
209
210
    /**
211
     * Generate FieldsTable::values() analog for specific field.
212
     *
213
     * @param string $fieldName Field name
214
     * @param string $fieldId Field primary identifier
215
     * @param string $fieldType Field PHP type
216
     * @return string Generated PHP method code
217
     */
218
    protected function generateTableFieldMethod($fieldName, $fieldId, $fieldType)
219
    {
220
        $code = "\n\t" . '/**';
221
        $code .= "\n\t" . ' * Get table column '.$fieldName.'(#' . $fieldId . ') values.';
222
        $code .= "\n\t" . ' * @return array Collection('.Field::phpType($fieldType).') of table column values';
223
        $code .= "\n\t" . ' */';
224
        $code .= "\n\t" . 'public function ' . $fieldName . '()';
225
        $code .= "\n\t" . "{";
226
        $code .= "\n\t\t" . 'return $this->values('.$fieldId.');';
227
228
        return $code . "\n\t" . "}"."\n";
229
    }
230
231
    /**
232
     * Create fields table PHP class code.
233
     *
234
     * @param integer $navigationID Entity navigation identifier
235
     * @param string $navigationName Original entity name
236
     * @param string $entityName PHP entity name
237
     * @param array $navigationFields Collection of entity additional fields
238
     * @return string Generated entity query PHP class code
239
     */
240
    protected function createTableClass($navigationID, $navigationName, $entityName, $navigationFields)
241
    {
242
        $class = "\n";
243
        $class .= "\n" . '/**';
244
        $class .= "\n" . ' * Class for getting "'.$navigationName.'" fields table';
245
        $class .= "\n" . ' */';
246
        $class .= "\n" . 'class ' . $entityName . ' extends FieldsTable';
247
        $class .= "\n" . '{';
248
249
        // Iterate additional fields
250
        $constants = '';
251
        $variables = '';
252
        $methods = '';
253
        foreach ($navigationFields as $fieldID => $fieldRow) {
254
            $fieldName = $this->fieldName($fieldRow['Name']);
255
256
            $methods .= $this->generateTableFieldMethod(
257
                $fieldName,
258
                $fieldRow[Field::F_PRIMARY],
259
                $fieldRow[Field::F_TYPE]
260
            );
261
            $constants .= "\n\t" . '/** ' . Field::phpType($fieldRow['Type']) . ' '.$fieldRow['Description'].' Field #' . $fieldID . ' variable name */';
262
            $constants .= "\n\t" . 'const F_' . strtoupper($fieldName) . ' = "'.$fieldName.'";';
263
264
            $variables .= "\n\t" . '/** @var array Collection of '.$fieldRow['Description'].' Field #' . $fieldID . ' values */';
265
            $variables .= "\n\t" . 'protected $' . $fieldName . ';';
266
        }
267
268
        $class .= $constants;
269
        $class .= "\n\t";
270
        $class .= "\n\t" . '/** @var array Collection of navigation identifiers */';
271
        $class .= "\n\t" . 'protected static $navigationIDs = array(' . $navigationID . ');';
272
        $class .= "\n\t";
273
        $class .= $variables;
274
        $class .= "\n\t";
275
        $class .= $methods;
276
        $class .= "\n\t".'/**';
277
        $class .= "\n\t".' * @param QueryInterface $query Database query instance';
278
        $class .= "\n\t".' * @param integer $entityID Entity identifier to whom this table belongs';
279
        $class .= "\n\t".' * @param string $locale Localization identifier';
280
        $class .= "\n\t".' */';
281
        $class .= "\n\t".'public function __construct(QueryInterface $query, $entityID, $locale = "")';
282
        $class .= "\n\t".'{';
283
        $class .= "\n\t\t".'parent::__construct($query, static::$navigationIDs, $entityID, $locale);';
284
        $class .= "\n\t".'}';
285
        $class .= "\n" . '}';
286
287
        return $class;
288
    }
289
290
    /**
291
     * Create entity query PHP class code.
292
     *
293
     * @param integer $navigationID Entity navigation identifier
294
     * @param string $navigationName Original entity name
295
     * @param string $entityName PHP entity name
296
     * @param array $navigationFields Collection of entity additional fields
297
     * @return string Generated entity query PHP class code
298
     */
299
    protected function createQueryClass($navigationID, $navigationName, $entityName, $navigationFields)
300
    {
301
        $class = "\n";
302
        $class .= "\n" . '/**';
303
        $class .= "\n" . ' * Class for getting "'.$navigationName.'" instances from database';
304
        $class .= "\n" . ' * @method '.$this->entityName($navigationName).'[] find() Get entities collection';
305
        $class .= "\n" . ' * @method '.$this->entityName($navigationName).' first() Get entity';
306
        $class .= "\n" . ' * @method '.$entityName.' where($fieldName, $fieldValue = null, $fieldRelation = ArgumentInterface::EQUAL)';
307
        $class .= "\n" . ' * @method '.$entityName.' primary($value) Query for chaining';
308
        $class .= "\n" . ' * @method '.$entityName.' identifier($value) Query for chaining';
309
        $class .= "\n" . ' * @method '.$entityName.' created($value) Query for chaining';
310
        $class .= "\n" . ' * @method '.$entityName.' modified($value) Query for chaining';
311
        $class .= "\n" . ' * @method '.$entityName.' published($value) Query for chaining';
312
        $class .= "\n" . ' */';
313
        $class .= "\n" . 'class ' . $entityName . ' extends EntityQuery';
314
        $class .= "\n" . '{';
315
316
        // Iterate additional fields
317
        $localizedFieldIDs = array();
318
        $notLocalizedFieldIDs = array();
319
        $allFieldIDs = array();
320
        $allFieldNames = array();
321
        $allFieldValueColumns = array();
322
        foreach ($navigationFields as $fieldID => $fieldRow) {
323
            $fieldName = $this->fieldName($fieldRow['Name']);
324
325
            // TODO: Add different method generation depending on their field type
326
            $class .= $this->generateFieldConditionMethod(
327
                $fieldName,
328
                $fieldRow[Field::F_PRIMARY],
329
                $fieldRow[Field::F_TYPE]
330
            );
331
332
            // Store field metadata
333
            $allFieldIDs[] = '"' . $fieldID . '" => "' . $fieldName . '"';
334
            $allFieldNames[] = '"' . $fieldName . '" => "' . $fieldID . '"';
335
            $allFieldValueColumns[] = '"' . $fieldID . '" => "' . Field::valueColumn($fieldRow[Field::F_TYPE]) . '"';
336
            if ($fieldRow[Field::F_LOCALIZED] == 1) {
337
                $localizedFieldIDs[] = '"' . $fieldID . '" => "' . $fieldName . '"';
338
            } else {
339
                $notLocalizedFieldIDs[] = '"' . $fieldID . '" => "' . $fieldName . '"';
340
            }
341
        }
342
343
        $class .= "\n\t";
344
        $class .= "\n\t" . '/** @var string Not transliterated entity name */';
345
        $class .= "\n\t" . 'protected static $identifier = "'.$this->fullEntityName($navigationName).'";';
346
        $class .= "\n\t" . '/** @var array Collection of navigation identifiers */';
347
        $class .= "\n\t" . 'protected static $navigationIDs = array(' . $navigationID . ');';
348
        $class .= "\n\t" . '/** @var array Collection of localized additional fields identifiers */';
349
        $class .= "\n\t" . 'protected static $localizedFieldIDs = array(' . "\n\t\t". implode(','."\n\t\t", $localizedFieldIDs) . "\n\t".');';
350
        $class .= "\n\t" . '/** @var array Collection of NOT localized additional fields identifiers */';
351
        $class .= "\n\t" . 'protected static $notLocalizedFieldIDs = array(' . "\n\t\t". implode(','."\n\t\t", $notLocalizedFieldIDs) . "\n\t".');';
352
        $class .= "\n\t" . '/** @var array Collection of all additional fields identifiers */';
353
        $class .= "\n\t" . 'protected static $fieldIDs = array(' . "\n\t\t". implode(','."\n\t\t", $allFieldIDs) . "\n\t".');';
354
        $class .= "\n\t" . '/** @var array Collection of additional fields value column names */';
355
        $class .= "\n\t" . 'protected static $fieldValueColumns = array(' . "\n\t\t". implode(','."\n\t\t", $allFieldValueColumns) . "\n\t".');';
356
        $class .= "\n\t" . '/** @var array Collection of additional field names */';
357
        $class .= "\n\t" . 'public static $fieldNames = array(' . "\n\t\t". implode(','."\n\t\t", $allFieldNames) . "\n\t".');';
358
        $class .= "\n" . '}';
359
360
        // Replace tabs with spaces
361
        return $class;
362
    }
363
364
    /** @return string Entity state hash */
365
    public function entityHash()
366
    {
367
        // Получим информацию о всех таблицах из БД
368
        return md5(serialize($this->database->fetch(
369
            'SELECT `TABLES`.`TABLE_NAME` as `TABLE_NAME`
370
              FROM `information_schema`.`TABLES` as `TABLES`
371
              WHERE `TABLES`.`TABLE_SCHEMA`="' . $this->database->database() . '";'
372
        )));
373
    }
374
375
    /** @return array Get collection of navigation objects */
376
    protected function entityNavigations($type = 0)
377
    {
378
        return $this->database->fetch('
379
        SELECT * FROM `structure`
380
        WHERE `Active` = "1" AND `Type` = "'.$type.'"'
381
        );
382
    }
383
384
    /** @return array Collection of navigation additional fields */
385
    protected function navigationFields($navigationID)
386
    {
387
        $return = array();
388
        // TODO: Optimize queries make one single query with only needed data
389
        foreach ($this->database->fetch('SELECT * FROM `structurefield` WHERE `StructureID` = "' . $navigationID . '" AND `Active` = "1"') as $fieldStructureRow) {
390
            foreach ($this->database->fetch('SELECT * FROM `field` WHERE `FieldID` = "' . $fieldStructureRow['FieldID'] . '"') as $fieldRow) {
391
                $return[$fieldRow['FieldID']] = $fieldRow;
392
            }
393
        }
394
395
        return $return;
396
    }
397
398
    /**
399
     * Generate entity classes.
400
     *
401
     * @param string $namespace Base namespace for generated classes
402
     * @return string Generated PHP code for entity classes
403
     */
404
    public function createEntityClasses($namespace = __NAMESPACE__)
405
    {
406
        $classes = "\n" . 'namespace ' . $namespace . ';';
407
        $classes .= "\n";
408
        $classes .= "\n" . 'use '.$namespace.'\Field;';
409
        $classes .= "\n" . 'use '.$namespace.'\query\EntityQuery;';
410
        $classes .= "\n" . 'use '.$namespace.'\FieldsTable;';
411
        $classes .= "\n" . 'use \samsonframework\orm\ArgumentInterface;';
412
        $classes .= "\n" . 'use \samsonframework\orm\QueryInterface;';
413
414
        // Iterate all structures
415
        foreach ($this->entityNavigations() as $structureRow) {
416
            $navigationFields = $this->navigationFields($structureRow['StructureID']);
417
            $entityName = $this->entityName($structureRow['Name']);
418
419
            $classes .= $this->createEntityClass(
420
                $structureRow['Name'],
421
                $entityName,
422
                $navigationFields
423
            );
424
425
            $classes .= $this->createQueryClass(
426
                $structureRow['StructureID'],
427
                $structureRow['Name'],
428
                $entityName.'Query',
429
                $navigationFields
430
            );
431
        }
432
433
        // Iterate table structures
434
        foreach ($this->entityNavigations(2) as $structureRow) {
435
            $navigationFields = $this->navigationFields($structureRow['StructureID']);
436
            $entityName = $this->entityName($structureRow['Name']);
437
438
            $classes .= $this->createTableClass(
439
                $structureRow['StructureID'],
440
                $structureRow['Name'],
441
                $entityName.'Table',
442
                $navigationFields
443
            );
444
445
        }
446
447
        // Make correct code formatting
448
        return str_replace("\t", '    ', $classes);
449
    }
450
451
    /**
452
     * Generator constructor.
453
     * @param DatabaseInterface $database Database instance
454
     */
455
    public function __construct(DatabaseInterface $database)
456
    {
457
        $this->database = $database;
458
    }
459
}
460