Completed
Push — master ( 1f9443...d13f8a )
by Vitaly
02:39
created

Generator::createEntityClasses()   C

Complexity

Conditions 12
Paths 89

Size

Total Lines 138
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Importance

Changes 14
Bugs 4 Features 6
Metric Value
c 14
b 4
f 6
dl 0
loc 138
rs 5.034
cc 12
eloc 84
nc 89
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
//[PHPCOMPRESSOR(remove,start)]
3
/**
4
 * Created by PhpStorm.
5
 * User: VITALYIEGOROV
6
 * Date: 09.12.15
7
 * Time: 14:34
8
 */
9
namespace samsoncms\api;
10
11
use samson\activerecord\dbMySQLConnector;
12
use samsoncms\api\generator\exception\ParentEntityNotFound;
13
use samsoncms\api\generator\Metadata;
14
use samsoncms\api\query\Generic;
15
use samsonframework\orm\ArgumentInterface;
16
use samsonframework\orm\DatabaseInterface;
17
18
/**
19
 * Entity classes generator.
20
 * @package samsoncms\api
21
 */
22
class Generator
23
{
24
    /** @var DatabaseInterface */
25
    protected $database;
26
27
    /** @var \samsonphp\generator\Generator */
28
    protected $generator;
29
30
    /** @var Metadata[] Collection of entities metadata */
31
    protected $metadata;
32
33
    /**
34
     * Transliterate string to english.
35
     *
36
     * @param string $string Source string
37
     * @return string Transliterated string
38
     */
39
    protected function transliterated($string)
40
    {
41
        return str_replace(
42
            ' ',
43
            '',
44
            ucwords(iconv("UTF-8", "UTF-8//IGNORE", strtr($string, array(
45
                            "'" => "",
46
                            "`" => "",
47
                            "-" => " ",
48
                            "_" => " ",
49
                            "а" => "a", "А" => "a",
50
                            "б" => "b", "Б" => "b",
51
                            "в" => "v", "В" => "v",
52
                            "г" => "g", "Г" => "g",
53
                            "д" => "d", "Д" => "d",
54
                            "е" => "e", "Е" => "e",
55
                            "ж" => "zh", "Ж" => "zh",
56
                            "з" => "z", "З" => "z",
57
                            "и" => "i", "И" => "i",
58
                            "й" => "y", "Й" => "y",
59
                            "к" => "k", "К" => "k",
60
                            "л" => "l", "Л" => "l",
61
                            "м" => "m", "М" => "m",
62
                            "н" => "n", "Н" => "n",
63
                            "о" => "o", "О" => "o",
64
                            "п" => "p", "П" => "p",
65
                            "р" => "r", "Р" => "r",
66
                            "с" => "s", "С" => "s",
67
                            "т" => "t", "Т" => "t",
68
                            "у" => "u", "У" => "u",
69
                            "ф" => "f", "Ф" => "f",
70
                            "х" => "h", "Х" => "h",
71
                            "ц" => "c", "Ц" => "c",
72
                            "ч" => "ch", "Ч" => "ch",
73
                            "ш" => "sh", "Ш" => "sh",
74
                            "щ" => "sch", "Щ" => "sch",
75
                            "ъ" => "", "Ъ" => "",
76
                            "ы" => "y", "Ы" => "y",
77
                            "ь" => "", "Ь" => "",
78
                            "э" => "e", "Э" => "e",
79
                            "ю" => "yu", "Ю" => "yu",
80
                            "я" => "ya", "Я" => "ya",
81
                            "і" => "i", "І" => "i",
82
                            "ї" => "yi", "Ї" => "yi",
83
                            "є" => "e", "Є" => "e"
84
                        )
85
                    )
86
                )
87
            )
88
        );
89
    }
90
91
    /**
92
     * Get class constant name by its value.
93
     *
94
     * @param string $value Constant value
95
     * @param string $className Class name
96
     * @return string Full constant name
97
     */
98
    protected function constantNameByValue($value, $className = Field::ENTITY)
99
    {
100
        // Get array where class constants are values and their values are keys
101
        $nameByValue = array_flip((new \ReflectionClass($className))->getConstants());
102
103
        // Try to find constant by its value
104
        if (null !== $nameByValue[$value]) {
105
            // Return constant name
106
            return $nameByValue[$value];
107
        }
108
109
        return '';
110
    }
111
112
    /**
113
     * Get correct entity name.
114
     *
115
     * @param string $navigationName Original navigation entity name
116
     * @return string Correct PHP-supported entity name
117
     */
118
    protected function entityName($navigationName)
119
    {
120
        return ucfirst($this->getValidName($this->transliterated($navigationName)));
121
    }
122
	
123
    /**
124
     * Remove all wrong characters from entity name
125
     *
126
     * @param string $navigationName Original navigation entity name
127
     * @return string Correct PHP-supported entity name
128
     */
129
    protected function getValidName($navigationName)
130
    {
131
        return preg_replace('/(^\d*)|([^\w\d_])/', '', $navigationName);
132
    }
133
134
    /**
135
     * Get correct full entity name with name space.
136
     *
137
     * @param string $navigationName Original navigation entity name
138
     * @param string $namespace Namespace
139
     * @return string Correct PHP-supported entity name
140
     */
141
    protected function fullEntityName($navigationName, $namespace = __NAMESPACE__)
142
    {
143
        return '\\'.$namespace.'\\'.$this->entityName($navigationName);
144
    }
145
146
    /**
147
     * Get correct field name.
148
     *
149
     * @param string $fieldName Original field name
150
     * @return string Correct PHP-supported field name
151
     */
152
    protected function fieldName($fieldName)
153
    {
154
        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...
155
    }
156
157
    /**
158
     * Get additional field type in form of Field constant name
159
     * by database additional field type identifier.
160
     *
161
     * @param integer $fieldType Additional field type identifier
162
     * @return string Additional field type constant
163
     */
164
    protected function additionalFieldType($fieldType)
165
    {
166
        return 'Field::'.$this->constantNameByValue($fieldType);
167
    }
168
169
    /**
170
     * Generate Query::where() analog for specific field.
171
     *
172
     * @param string $fieldName Field name
173
     * @param string $fieldId Field primary identifier
174
     * @param string $fieldType Field PHP type
175
     * @return string Generated PHP method code
176
     */
177 View Code Duplication
    protected function generateFieldConditionMethod($fieldName, $fieldId, $fieldType)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
178
    {
179
        $code = "\n\t" . '/**';
180
        $code .= "\n\t" . ' * Add '.$fieldName.'(#' . $fieldId . ') field query condition.';
181
        $code .= "\n\t" . ' * @param '.Field::phpType($fieldType).' $value Field value';
182
        $code .= "\n\t" . ' * @return $this Chaining';
183
        $code .= "\n\t" . ' * @see Generic::where()';
184
        $code .= "\n\t" . ' */';
185
        $code .= "\n\t" . 'public function ' . $fieldName . '($value, $relation = ArgumentInterface::EQUAL)';
186
        $code .= "\n\t" . "{";
187
        $code .= "\n\t\t" . 'return $this->where("'.$fieldName.'", $value, $relation);';
188
189
        return $code . "\n\t" . "}"."\n";
190
    }
191
192
    /**
193
     * Generate Query::where() analog for specific field.
194
     *
195
     * @param string $fieldName Field name
196
     * @param string $fieldId Field primary identifier
197
     * @param string $fieldType Field PHP type
198
     * @return string Generated PHP method code
199
     */
200 View Code Duplication
    protected function generateLocalizedFieldConditionMethod($fieldName, $fieldId, $fieldType)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
201
    {
202
        $code = "\n\t" . '/**';
203
        $code .= "\n\t" . ' * Add '.$fieldName.'(#' . $fieldId . ') field query condition.';
204
        $code .= "\n\t" . ' * @param '.Field::phpType($fieldType).' $value Field value';
205
        $code .= "\n\t" . ' * @return $this Chaining';
206
        $code .= "\n\t" . ' * @see Generic::where()';
207
        $code .= "\n\t" . ' */';
208
        $code .= "\n\t" . 'public function ' . $fieldName . '($value)';
209
        $code .= "\n\t" . "{";
210
        $code .= "\n\t\t" . 'return $this->where("'.$fieldName.'", $value);';
211
212
        return $code . "\n\t" . "}"."\n";
213
    }
214
215
    /**
216
     * Create entity PHP class code.
217
     *
218
     * @param Metadata $metadata Entity metadata
219
     * @return string Generated entity query PHP class code
220
     */
221
    protected function createEntityClass(Metadata $metadata)
222
    {
223
        $this->generator
224
            ->multiComment(array('"'.$metadata->entityRealName.'" entity class'))
225
            ->defClass($metadata->entity, null !== $metadata->parent ? $metadata->parent->className : 'Entity')
226
            ->commentVar('string', '@deprecated Entity full class name, use ::class')
227
            ->defClassConst('ENTITY', $metadata->className)
228
            ->commentVar('string', 'Entity manager full class name')
229
            ->defClassConst('MANAGER', $metadata->className.'Query')
230
            ->commentVar('string', 'Entity database identifier')
231
            ->defClassConst('IDENTIFIER', $metadata->entityID)
232
            ->commentVar('string', 'Not transliterated entity name')
233
            ->defClassVar('$viewName', 'protected static', $metadata->entityRealName);
234
235
        foreach ($metadata->allFieldIDs as $fieldID => $fieldName) {
236
            $this->generator
237
                ->commentVar('string', $metadata->fieldDescriptions[$fieldID].' variable name')
238
                ->defClassConst('F_' . $fieldName, $fieldName)
239
                ->commentVar($metadata->allFieldTypes[$fieldID], $metadata->fieldDescriptions[$fieldID])
240
                ->defClassVar('$' . $fieldName, 'public');
241
        }
242
243
        return $this->generator
244
            ->defClassVar('$_sql_select', 'public static ', $metadata->arSelect)
0 ignored issues
show
Documentation introduced by
$metadata->arSelect is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
245
            ->defClassVar('$_attributes', 'public static ', $metadata->arAttributes)
0 ignored issues
show
Documentation introduced by
$metadata->arAttributes is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
246
            ->defClassVar('$_map', 'public static ', $metadata->arMap)
0 ignored issues
show
Documentation introduced by
$metadata->arMap is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
247
            ->defClassVar('$_sql_from', 'public static ', $metadata->arFrom)
0 ignored issues
show
Documentation introduced by
$metadata->arFrom is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
248
            ->defClassVar('$_own_group', 'public static ', $metadata->arGroup)
0 ignored issues
show
Documentation introduced by
$metadata->arGroup is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
249
            ->defClassVar('$_relation_alias', 'public static ', $metadata->arRelationAlias)
0 ignored issues
show
Documentation introduced by
$metadata->arRelationAlias is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
250
            ->defClassVar('$_relation_type', 'public static ', $metadata->arRelationType)
0 ignored issues
show
Documentation introduced by
$metadata->arRelationType is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
251
            ->defClassVar('$_relations', 'public static ', $metadata->arRelations)
0 ignored issues
show
Documentation introduced by
$metadata->arRelations is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
252
            ->endclass()
253
            ->flush();
254
    }
255
256
    /**
257
     * Generate FieldsTable::values() analog for specific field.
258
     *
259
     * @param string $fieldName Field name
260
     * @param string $fieldId Field primary identifier
261
     * @param string $fieldType Field PHP type
262
     * @return string Generated PHP method code
263
     */
264
    protected function generateTableFieldMethod($fieldName, $fieldId, $fieldType)
265
    {
266
        $code = "\n\t" . '/**';
267
        $code .= "\n\t" . ' * Get table column '.$fieldName.'(#' . $fieldId . ') values.';
268
        $code .= "\n\t" . ' * @return array Collection('.Field::phpType($fieldType).') of table column values';
269
        $code .= "\n\t" . ' */';
270
        $code .= "\n\t" . 'public function ' . $fieldName . '()';
271
        $code .= "\n\t" . "{";
272
        $code .= "\n\t\t" . 'return $this->values('.$fieldId.');';
273
274
        return $code . "\n\t" . "}"."\n";
275
    }
276
277
    /**
278
     * Create fields table row PHP class code.
279
     *
280
     * @param string $navigationName Original entity name
281
     * @param string $entityName PHP entity name
282
     * @param array $navigationFields Collection of entity additional fields
283
     * @return string Generated entity query PHP class code
284
     */
285
    protected function createTableRowClass($navigationName, $entityName, $navigationFields)
286
    {
287
        $class = "\n";
288
        $class .= "\n" . '/**';
289
        $class .= "\n" . ' * Class for getting "'.$navigationName.'" fields table rows';
290
        $class .= "\n" . ' */';
291
        $class .= "\n" . 'class ' . $entityName . ' extends Row';
292
        $class .= "\n" . '{';
293
294
        // Iterate additional fields
295
        $constants = '';
296
        $variables = '';
297
        foreach ($navigationFields as $fieldID => $fieldRow) {
298
            $fieldName = $this->fieldName($fieldRow['Name']);
299
300
            $constants .= "\n\t" . '/** ' . Field::phpType($fieldRow['Type']) . ' '.$fieldRow['Description'].' Field #' . $fieldID . ' variable name */';
301
            // Store original field name
302
            $constants .= "\n\t" . 'const F_' . strtoupper($fieldName) . ' = "'.$fieldName.'";';
303
304
            $variables .= "\n\t" . '/** ' . Field::phpType($fieldRow['Type']) . ' '.$fieldRow['Description'].' Field #' . $fieldID . ' row value */';
305
            $variables .= "\n\t" . 'public $' . $fieldName . ';';
306
        }
307
308
        $class .= $constants;
309
        $class .= "\n\t";
310
        $class .= $variables;
311
        $class .= "\n" . '}';
312
313
        return $class;
314
    }
315
316
    /**
317
     * Create fields table PHP class code.
318
     *
319
     * @param integer $navigationID     Entity navigation identifier
320
     * @param string  $navigationName   Original entity name
321
     * @param string  $entityName       PHP entity name
322
     * @param array   $navigationFields Collection of entity additional fields
323
     * @param string  $rowClassName Row class name
324
     *
325
     * @return string Generated entity query PHP class code
326
     * @throws exception\AdditionalFieldTypeNotFound
327
     */
328
    protected function createTableClass($navigationID, $navigationName, $entityName, $navigationFields, $rowClassName)
329
    {
330
        $this->generator
331
            ->multiComment(array('Class for getting "'.$navigationName.'" fields table'))
332
            ->defClass($entityName, 'FieldsTable');
333
334
        // Iterate additional fields
335
        $fields = array();
336
        foreach ($navigationFields as $fieldID => $fieldRow) {
337
            $fieldName = $this->fieldName($fieldRow['Name']);
338
339
            $this->generator
340
                ->text($this->generateTableFieldMethod(
341
                    $fieldName,
342
                    $fieldRow[Field::F_PRIMARY],
343
                    $fieldRow[Field::F_TYPE]
344
                ))
345
                ->commentVar(Field::phpType($fieldRow['Type']), $fieldRow['Description'] . ' Field #' . $fieldID . ' variable name')
346
                ->defClassConst('F_' . $fieldName, $fieldName);
347
348
            // Collection original to new one field names
349
            $fields[$fieldRow['Name']] = $fieldName;
350
        }
351
352
        // TODO: Add generator method generation logic
353
        $class = "\n\t".'/**';
354
        $class .= "\n\t".' * @param QueryInterface $query Database query instance';
355
        $class .= "\n\t".' * @param ViewInterface $renderer Rendering instance';
356
        $class .= "\n\t".' * @param integer $entityID Entity identifier to whom this table belongs';
357
        $class .= "\n\t".' * @param string $locale Localization identifier';
358
        $class .= "\n\t".' */';
359
        $class .= "\n\t".'public function __construct(QueryInterface $query, ViewInterface $renderer, $entityID, $locale = null)';
360
        $class .= "\n\t".'{';
361
        $class .= "\n\t\t".'parent::__construct($query, $renderer, static::$navigationIDs, $entityID, $locale);';
362
        $class .= "\n\t".'}'."\n";
363
364
        $this->generator->text($class);
365
366
        return $this->generator
367
            ->commentVar('array', 'Collection of real additional field names')
368
            ->defClassVar('$fieldsRealNames', 'public static', $fields)
0 ignored issues
show
Documentation introduced by
$fields is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
369
            ->commentVar('array', 'Collection of navigation identifiers')
370
            ->defClassVar('$navigationIDs', 'protected static', array($navigationID))
0 ignored issues
show
Documentation introduced by
array($navigationID) is of type array<integer,integer,{"0":"integer"}>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
371
            ->commentVar('string', 'Row class name')
372
            ->defClassVar('$identifier', 'protected', $this->fullEntityName($rowClassName))
373
            ->endClass()
374
            ->flush();
375
    }
376
377
    /**
378
     * Create entity query PHP class code.
379
     *
380
     * @param integer $navigationID Entity navigation identifier
381
     * @param string $navigationName Original entity name
382
     * @param string $entityName PHP entity name
383
     * @param array $navigationFields Collection of entity additional fields
384
     * @param string $parentClass Parent entity class name
385
     * @return string Generated entity query PHP class code
386
     */
387 View Code Duplication
    protected function createCollectionClass($navigationID, $navigationName, $entityName, $navigationFields, $parentClass = '\samsoncms\api\renderable\Collection')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
388
    {
389
        $this->generator
390
            ->multiComment(array('Class for getting "'.$navigationName.'" instances from database',))
391
            ->defClass($entityName, $parentClass)
392
        ;
393
394
        // Iterate additional fields
395
        $localizedFieldIDs = array();
396
        $notLocalizedFieldIDs = array();
397
        $allFieldIDs = array();
398
        $allFieldNames = array();
399
        $allFieldValueColumns = array();
400
        $realNames = array();
401
        foreach ($navigationFields as $fieldID => $fieldRow) {
402
            $fieldName = $this->fieldName($fieldRow['Name']);
403
404
            // TODO: Add different method generation depending on their field type
405
            $this->generator->text($this->generateFieldConditionMethod(
406
                $fieldName,
407
                $fieldRow[Field::F_PRIMARY],
408
                $fieldRow[Field::F_TYPE]
409
            ));
410
411
            // Store field metadata
412
            $realNames[$fieldRow['Name']] = $fieldName;
413
            $allFieldIDs[$fieldID] = $fieldName;
414
            $allFieldNames[$fieldName] = $fieldID;
415
            $allFieldValueColumns[$fieldID] = Field::valueColumn($fieldRow[Field::F_TYPE]);
416
            if ($fieldRow[Field::F_LOCALIZED] == 1) {
417
                $localizedFieldIDs[$fieldID] = $fieldName;
418
            } else {
419
                $notLocalizedFieldIDs[$fieldID] = $fieldName;
420
            }
421
        }
422
423
        // TODO: Add generator method generation logic
424
        $class = "\n\t".'/**';
425
        $class .= "\n\t".' * @param ViewInterface $renderer Rendering instance';
426
        $class .= "\n\t".' * @param QueryInterface $query Querying instance';
427
        $class .= "\n\t".' * @param string $locale Localization identifier';
428
        $class .= "\n\t".' */';
429
        $class .= "\n\t".'public function __construct(ViewInterface $renderer, QueryInterface $query = null, $locale = null)';
430
        $class .= "\n\t".'{';
431
        $class .= "\n\t\t".'parent::__construct($renderer, $query === null ? new dbQuery() : $query, $locale);';
432
        $class .= "\n\t".'}'."\n";
433
434
        return $this->generator
435
            ->commentVar('array', 'Collection of real additional field names')
436
            ->defClassVar('$fieldRealNames', 'public static', $realNames)
0 ignored issues
show
Documentation introduced by
$realNames is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
437
            ->commentVar('array', 'Collection of navigation identifiers')
438
            ->defClassVar('$navigationIDs', 'protected static', array($navigationID))
0 ignored issues
show
Documentation introduced by
array($navigationID) is of type array<integer,integer,{"0":"integer"}>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
439
            ->commentVar('string', 'Not transliterated entity name')
440
            ->defClassVar('$identifier', 'protected static', $this->fullEntityName($navigationName))
441
            ->commentVar('array', 'Collection of localized additional fields identifiers')
442
            ->defClassVar('$localizedFieldIDs', 'protected static', $localizedFieldIDs)
0 ignored issues
show
Documentation introduced by
$localizedFieldIDs is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
443
            ->commentVar('array', 'Collection of NOT localized additional fields identifiers')
444
            ->defClassVar('$notLocalizedFieldIDs', 'protected static', $notLocalizedFieldIDs)
0 ignored issues
show
Documentation introduced by
$notLocalizedFieldIDs is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
445
            ->commentVar('array', 'Collection of localized additional fields identifiers')
446
            ->defClassVar('$fieldIDs', 'protected static', $allFieldIDs)
0 ignored issues
show
Documentation introduced by
$allFieldIDs is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
447
            ->commentVar('array', 'Collection of additional fields value column names')
448
            ->defClassVar('$fieldValueColumns', 'protected static', $allFieldValueColumns)
0 ignored issues
show
Documentation introduced by
$allFieldValueColumns is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
449
            ->commentVar('array', 'Collection of additional field names')
450
            ->defClassVar('$fieldNames', 'public static', $allFieldNames)
0 ignored issues
show
Documentation introduced by
$allFieldNames is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
451
            ->text($class)
452
            ->endClass()
453
            ->flush()
454
            ;
455
    }
456
457
    /**
458
     * Create entity query PHP class code.
459
     *
460
     * @param integer $navigationID Entity navigation identifier
461
     * @param string $navigationName Original entity name
462
     * @param string $entityName PHP entity name
463
     * @param array $navigationFields Collection of entity additional fields
464
     * @param string $parentClass Parent entity class name
465
     * @return string Generated entity query PHP class code
466
     */
467 View Code Duplication
    protected function createQueryClass($navigationID, $navigationName, $entityName, $navigationFields, $parentClass = '\samsoncms\api\query\Entity')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
468
    {
469
        $this->generator
470
            ->multiComment(array('Class for getting "'.$navigationName.'" instances from database'))
471
            ->defClass($entityName, $parentClass)
472
        ;
473
474
        // Iterate additional fields
475
        $localizedFieldIDs = array();
476
        $notLocalizedFieldIDs = array();
477
        $allFieldIDs = array();
478
        $allFieldNames = array();
479
        $allFieldValueColumns = array();
480
        $realNames = array();
481
        foreach ($navigationFields as $fieldID => $fieldRow) {
482
            $fieldName = $this->fieldName($fieldRow['Name']);
483
484
            // TODO: Add different method generation depending on their field type
485
            $this->generator->text($this->generateFieldConditionMethod(
486
                $fieldName,
487
                $fieldRow[Field::F_PRIMARY],
488
                $fieldRow[Field::F_TYPE]
489
            ));
490
491
            // Store field metadata
492
            $realNames[$fieldRow['Name']] = $fieldName;
493
            $allFieldIDs[$fieldID] = $fieldName;
494
            $allFieldNames[$fieldName] = $fieldID;
495
            $allFieldValueColumns[$fieldID] = Field::valueColumn($fieldRow[Field::F_TYPE]);
496
            if ($fieldRow[Field::F_LOCALIZED] == 1) {
497
                $localizedFieldIDs[$fieldID] = $fieldName;
498
            } else {
499
                $notLocalizedFieldIDs[$fieldID] = $fieldName;
500
            }
501
        }
502
503
        // TODO: Add generator method generation logic
504
        $class = "\n\t".'/**';
505
        $class .= "\n\t".' * @param QueryInterface $query Rendering instance';
506
        $class .= "\n\t".' * @param string $locale Localization identifier';
507
        $class .= "\n\t".' */';
508
        $class .= "\n\t".'public function __construct(QueryInterface $query = null, $locale = null)';
509
        $class .= "\n\t".'{';
510
        $class .= "\n\t\t".'parent::__construct($query === null ? new dbQuery() : $query, $locale);';
511
        $class .= "\n\t".'}'."\n";
512
513
        return $this->generator
514
            ->commentVar('array', 'Collection of real additional field names')
515
            ->defClassVar('$fieldRealNames', 'public static', $realNames)
0 ignored issues
show
Documentation introduced by
$realNames is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
516
            ->commentVar('array', 'Collection of navigation identifiers')
517
            ->defClassVar('$navigationIDs', 'protected static', array($navigationID))
0 ignored issues
show
Documentation introduced by
array($navigationID) is of type array<integer,integer,{"0":"integer"}>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
518
            ->commentVar('string', 'Not transliterated entity name')
519
            ->defClassVar('$identifier', 'protected static', $this->fullEntityName($navigationName))
520
            ->commentVar('array', 'Collection of localized additional fields identifiers')
521
            ->defClassVar('$localizedFieldIDs', 'protected static', $localizedFieldIDs)
0 ignored issues
show
Documentation introduced by
$localizedFieldIDs is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
522
            ->commentVar('array', 'Collection of NOT localized additional fields identifiers')
523
            ->defClassVar('$notLocalizedFieldIDs', 'protected static', $notLocalizedFieldIDs)
0 ignored issues
show
Documentation introduced by
$notLocalizedFieldIDs is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
524
            ->commentVar('array', 'Collection of localized additional fields identifiers')
525
            ->defClassVar('$fieldIDs', 'protected static', $allFieldIDs)
0 ignored issues
show
Documentation introduced by
$allFieldIDs is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
526
            ->commentVar('array', 'Collection of additional fields value column names')
527
            ->defClassVar('$fieldValueColumns', 'protected static', $allFieldValueColumns)
0 ignored issues
show
Documentation introduced by
$allFieldValueColumns is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
528
            ->commentVar('array', 'Collection of additional field names')
529
            ->defClassVar('$fieldNames', 'public static', $allFieldNames)
0 ignored issues
show
Documentation introduced by
$allFieldNames is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
530
            ->text($class)
531
            ->endClass()
532
            ->flush()
533
        ;
534
    }
535
536
    /** @return string Entity state hash */
537
    public function entityHash()
538
    {
539
        // Получим информацию о всех таблицах из БД
540
        return md5(serialize($this->database->fetch(
541
            'SELECT `TABLES`.`TABLE_NAME` as `TABLE_NAME`
542
              FROM `information_schema`.`TABLES` as `TABLES`
543
              WHERE `TABLES`.`TABLE_SCHEMA`="' . $this->database->database() . '";'
544
        )));
545
    }
546
547
    /**
548
     * Find entity parent.
549
     *
550
     * @param $entityID
551
     *
552
     * @return null|int Parent entity identifier
553
     */
554
    public function entityParent($entityID)
555
    {
556
        $parentData = $this->database->fetch('
557
SELECT *
558
FROM structure_relation as sm
559
JOIN structure as s ON s.StructureID = sm.parent_id
560
WHERE sm.child_id = "' . $entityID . '"
561
AND s.StructureID != "' . $entityID . '"
562
');
563
564
        // Get parent entity identifier
565
        return count($parentData) ? $parentData[0]['StructureID'] : null;
566
    }
567
568
    /** @return array Get collection of navigation objects */
569
    protected function entityNavigations($type = 0)
570
    {
571
        return $this->database->fetch('
572
        SELECT * FROM `structure`
573
        WHERE `Active` = "1" AND `Type` = "'.$type.'"
574
        ORDER BY `ParentID` ASC
575
        ');
576
    }
577
578
    /** @return array Collection of navigation additional fields */
579
    protected function navigationFields($navigationID)
580
    {
581
        $return = array();
582
        // TODO: Optimize queries make one single query with only needed data
583
        foreach ($this->database->fetch('SELECT * FROM `structurefield` WHERE `StructureID` = "' . $navigationID . '" AND `Active` = "1"') as $fieldStructureRow) {
584
            foreach ($this->database->fetch('SELECT * FROM `field` WHERE `FieldID` = "' . $fieldStructureRow['FieldID'] . '"') as $fieldRow) {
585
                $return[$fieldRow['FieldID']] = $fieldRow;
586
            }
587
        }
588
589
        return $return;
590
    }
591
592
    /**
593
     * Generate entity classes.
594
     *
595
     * @param string $namespace Base namespace for generated classes
596
     * @return string Generated PHP code for entity classes
597
     */
598
    public function createEntityClasses($namespace = __NAMESPACE__)
599
    {
600
        $classes = "\n" . 'namespace ' . $namespace . ';';
601
        $classes .= "\n";
602
        $classes .= "\n" . 'use '.$namespace.'\renderable\FieldsTable;';
603
        $classes .= "\n" . 'use '.$namespace.'\field\Row;';
604
        $classes .= "\n" . 'use \samsonframework\core\ViewInterface;';
605
        $classes .= "\n" . 'use \samsonframework\orm\ArgumentInterface;';
606
        $classes .= "\n" . 'use \samsonframework\orm\QueryInterface;';
607
        $classes .= "\n" . 'use \samson\activerecord\dbQuery;';
608
        $classes .= "\n";
609
610
        // Iterate all structures, parents first
611
        foreach ($this->entityNavigations() as $structureRow) {
612
            // Fill in entity metadata
613
            $metadata = new Metadata();
614
            // Get CapsCase and transliterated entity name
615
            $metadata->entity = $this->entityName($structureRow['Name']);
616
            // Try to find entity parent identifier for building future relations
617
            $metadata->parentID = $this->entityParent($structureRow['StructureID']);
618
619
            // Set pointer to parent entity
620
            if (null !== $metadata->parentID) {
621
                if (array_key_exists($metadata->parentID, $this->metadata)) {
622
                    $metadata->parent = $this->metadata[$metadata->parentID];
623
                } else {
624
                    throw new ParentEntityNotFound($metadata->parentID);
625
                }
626
            }
627
628
            // Store entity original data
629
            $metadata->entityRealName = $structureRow['Name'];
630
            $metadata->entityID = $structureRow['StructureID'];
631
            $metadata->className = $this->fullEntityName($metadata->entity, __NAMESPACE__);
632
633
            // Get old AR collections of metadata
634
            $metadata->arSelect = \samson\activerecord\material::$_sql_select;
635
            $metadata->arAttributes = \samson\activerecord\material::$_attributes;
636
            $metadata->arMap = \samson\activerecord\material::$_map;
637
            $metadata->arFrom = \samson\activerecord\material::$_sql_from;
638
            $metadata->arGroup = \samson\activerecord\material::$_own_group;
639
            $metadata->arRelationAlias = \samson\activerecord\material::$_relation_alias;
640
            $metadata->arRelationType = \samson\activerecord\material::$_relation_type;
641
            $metadata->arRelations = \samson\activerecord\material::$_relations;
642
643
            // Add SamsonCMS material needed data
644
            $metadata->arSelect['this'] = ' STRAIGHT_JOIN ' . $metadata->arSelect['this'];
645
            $metadata->arFrom['this'] .= "\n" .
646
                'LEFT JOIN ' . dbMySQLConnector::$prefix . 'materialfield as _mf
647
                ON ' . dbMySQLConnector::$prefix . 'material.MaterialID = _mf.MaterialID';
648
            $metadata->arGroup[] = dbMySQLConnector::$prefix . 'material.MaterialID';
649
650
            // Iterate entity fields
651
            foreach ($this->navigationFields($structureRow['StructureID']) as $fieldID => $fieldRow) {
652
                // Get camelCase and transliterated field name
653
                $fieldName = $this->fieldName($fieldRow['Name']);
654
655
                // Store field metadata
656
                $metadata->realNames[$fieldRow['Name']] = $fieldName;
657
                $metadata->allFieldIDs[$fieldID] = $fieldName;
658
                $metadata->allFieldNames[$fieldName] = $fieldID;
659
                $metadata->allFieldValueColumns[$fieldID] = Field::valueColumn($fieldRow[Field::F_TYPE]);
660
                $metadata->allFieldTypes[$fieldID] = Field::phpType($fieldRow['Type']);
661
                $metadata->fieldDescriptions[$fieldID] = $fieldRow['Description'] . ', '.$fieldRow['Name'].'#' . $fieldID;
662
663
                // Fill localization fields collections
664
                if ($fieldRow[Field::F_LOCALIZED] === 1) {
665
                    $metadata->localizedFieldIDs[$fieldID] = $fieldName;
666
                } else {
667
                    $metadata->notLocalizedFieldIDs[$fieldID] = $fieldName;
668
                }
669
670
                // Set old AR collections of metadata
671
                $metadata->arAttributes[$fieldName] = $fieldName;
672
                $metadata->arMap[$fieldName] = dbMySQLConnector::$prefix . 'material.' . $fieldName;
673
674
                // Add additional field column to entity query
675
                $equal = '((_mf.FieldID = ' . $fieldID . ')&&(_mf.locale ' . ($fieldRow['local'] ? ' = "@locale"' : 'IS NULL') . '))';
676
                $metadata->arSelect['this'] .= "\n\t\t" . ',MAX(IF(' . $equal . ', _mf.`' . Field::valueColumn($fieldRow['Type']) . '`, NULL)) as `' . $fieldName . '`';
677
            }
678
679
            // Store metadata by entity identifier
680
            $this->metadata[$structureRow['StructureID']] = $metadata;
681
        }
682
683
        // Iterate all entities metadata
684
        foreach ($this->metadata as $metadata) {
685
            $classes .= $this->createEntityClass($metadata);
686
        }
687
688
        // Iterate all structures
689
        foreach ($this->entityNavigations() as $structureRow) {
690
            $navigationFields = $this->navigationFields($structureRow['StructureID']);
691
            $entityName = $this->entityName($structureRow['Name']);
692
693
            $classes .= $this->createQueryClass(
694
                $structureRow['StructureID'],
695
                $structureRow['Name'],
696
                $entityName.'Query',
697
                $navigationFields,
698
                isset($parentEntity) ? $parentEntity.'Query' : '\samsoncms\api\query\Entity'
699
            );
700
701
            $classes .= $this->createCollectionClass(
702
                $structureRow['StructureID'],
703
                $structureRow['Name'],
704
                $entityName.'Collection',
705
                $navigationFields,
706
                isset($parentEntity) ? $parentEntity.'Collection' : '\samsoncms\api\renderable\Collection'
707
            );
708
        }
709
710
        // Iterate table structures
711
        foreach ($this->entityNavigations(2) as $structureRow) {
712
            $navigationFields = $this->navigationFields($structureRow['StructureID']);
713
            $entityName = $this->entityName($structureRow['Name']);
714
715
            $rowClassName = $entityName.'TableRow';
716
717
            $classes .= $this->createTableRowClass(
718
                $structureRow['Name'],
719
                $rowClassName,
720
                $navigationFields
721
            );
722
723
            $classes .= $this->createTableClass(
724
                $structureRow['StructureID'],
725
                $structureRow['Name'],
726
                $entityName.'Table',
727
                $navigationFields,
728
                $rowClassName
729
            );
730
731
        }
732
733
        // Make correct code formatting
734
        return str_replace("\t", '    ', $classes);
735
    }
736
737
    /**
738
     * Generator constructor.
739
     * @param DatabaseInterface $database Database instance
740
     */
741
    public function __construct(DatabaseInterface $database)
742
    {
743
        $this->generator = new \samsonphp\generator\Generator();
744
        $this->database = $database;
745
    }
746
}
747
748
//[PHPCOMPRESSOR(remove,end)]
749