Completed
Push — master ( 1a6ac1...4cd8fe )
by Vitaly
02:40
created

Generator::createEntityClass()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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