Completed
Push — master ( 82460f...99f20e )
by Vitaly
03:07
created

Generator::arrayValue()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 20
rs 9.4285
cc 3
eloc 11
nc 2
nop 1
1
<?php declare(strict_types=1);
2
//[PHPCOMPRESSOR(remove,start)]
3
namespace samsonphp\generator;
4
5
class Generator
6
{
7
    /** Single quote for string value **/
8
    const QUOTE_SINGLE = "'";
9
10
    /** Double quote for string value **/
11
    const QUOTE_DOUBLE = '"';
12
13
    /** No quote for string heredoc value **/
14
    const QUOTE_NO = '';
15
16
17
    /** @var string Generated code */
18
    public $code = '';
19
20
    /** @var integer Level of code line tabbing for new lines */
21
    public $tabs = 0;
22
23
    /** @var string Current class name */
24
    public $class;
25
26
    /** @var int Current conditions nesting level */
27
    public $ifConditionLevel = 0;
28
29
    /**
30
     * Add simple text to current code position
31
     * @param string $text Text to add
32
     * @return self
33
     */
34
    public function text($text = '')
35
    {
36
        $this->code .= $text;
37
38
        return $this;
39
    }
40
41
    /**
42
     * Add current tabbing level to current line.
43
     *
44
     * @param string $endText Text to add after tabs
45
     * @param integer $tabs Amount of tabs to add
46
     * @param string $startText Text to add before tabs
47
     * @return Generator Chaining
48
     */
49
    public function tabs($endText = '', $tabs = null, $startText = '')
50
    {
51
        // Generate tabs array
52
        $tabs = isset($tabs) && $tabs > 0 ? array_fill(0, $tabs, "\t") : array();
53
54
        // Add necessary amount of tabs to line and append text
55
        $this->text($startText.implode('', $tabs) . $endText);
56
57
        return $this;
58
    }
59
    
60
    /**
61
     * Increase current code indentation.
62
     *
63
     * @param int $amount Indentation amount
64
     *
65
     * @return $this Chaining
66
     */
67
    public function increaseIndentation($amount = 1)
68
    {
69
        $this->tabs += $amount;
70
71
        return $this;
72
    }
73
74
    /**
75
     * Reduce current code indentation.
76
     *
77
     * @param int $amount Indentation amount
78
     *
79
     * @return $this Chaining
80
     */
81
    public function decreaseIndentation($amount = 1)
82
    {
83
        $this->tabs = $this->tabs > $amount ? $this->tabs - $amount : 0;
84
85
        return $this;
86
    }
87
88
    /**
89
     * Add new line to code.
90
     *
91
     * @param string $text Code to add to new line
92
     * @param integer $tabs Tabs count
93
     * @return self
94
     */
95
    public function newLine($text = '', $tabs = null)
96
    {
97
        // If no tabs count is specified set default tabs
98
        if (!isset($tabs)) {
99
            $tabs = $this->tabs;
100
        }
101
102
        return $this->tabs($text, $tabs, "\n");
103
    }
104
105
    /**
106
     * Add single line comment to code
107
     * @param string $text Comment text
108
     * @return self Chaining
109
     */
110
    public function comment($text = '')
111
    {
112
        return isset($text{0}) ? $this->newLine("// " . $text) : $this;
113
    }
114
115
    /**
116
     * Add multi-line comment. If array with one line is passed
117
     * we create special syntax comment in one line, usually
118
     * used for class variable definition in more compact form.
119
     *
120
     * @param array $lines Array of comments lines
121
     * @return self Chaining
122
     */
123
    public function multiComment(array $lines = array())
124
    {
125
        // If array is not empty
126
        if (sizeof($lines)) {
127
            $this->newLine("/**");
128
129
            // Multi-comment with single line
130
            if (sizeof($lines) === 1) {
131
                $this->text(' '.$lines[0].' */');
132
            } else { // Iterate comments lines and if comment line is not empty
133
                foreach ($lines as $line) {
134
                    if (isset($line{0})) {
135
                        $this->newLine(" * " . $line);
136
                    }
137
                }
138
139
                return $this->newLine(" */");
140
            }
141
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
142
        }
143
144
        return $this;
145
    }
146
147
    /**
148
     * Add one line variable definition comment.
149
     *
150
     * @param string $type Variable typeHint
151
     * @param string $description Variable description
152
     * @param string $name Variable name
153
     * @return self Chaining
154
     */
155
    public function commentVar($type, $description, $name = '')
156
    {
157
        return $this->multiComment(array(
158
            '@var ' . trim($type) . (isset($name) ? trim($name) . ' ' : ' ') . trim($description)
159
        ));
160
    }
161
162
    /**
163
     * Add string value definition.
164
     *
165
     * @param string $value String value to add
166
     * @param string $tabs Tabs count
167
     * @param string $quote Type of quote
168
     * @return self Chaining
169
     */
170
    public function stringValue($value, $tabs = null, $quote = self::QUOTE_SINGLE)
171
    {
172
        return $this->tabs($quote . $value . $quote, $tabs);
173
    }
174
175
    /**
176
     * Generate correct value.
177
     *
178
     * Metho handles arrays, numerics, strings and constants.
179
     *
180
     * @param mixed $value Value to put in generated code
181
     *
182
     * @return $this
183
     */
184
    protected function defineValue($value) 
185
    {
186
        // If item value is array - recursion
187
        if (is_array($value)) {
188
            $this->arrayValue($value);
189
        } elseif (is_numeric($value) || is_float($value)) {
190
            $this->text($value);
191
        } else {
192
            try { // Try to evaluate
193
                eval('$value = '.$value.';');
194
                $this->text($value);
0 ignored issues
show
Bug introduced by
It seems like $value defined by parameter $value on line 184 can also be of type boolean or null or object; however, samsonphp\generator\Generator::text() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
195
            } catch (\Throwable $e) { // Consider it as a string
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
196
                $this->stringValue($value);
0 ignored issues
show
Bug introduced by
It seems like $value defined by parameter $value on line 184 can also be of type boolean or null or object; however, samsonphp\generator\Generator::stringValue() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
197
            }
198
        }
199
        
200
        return $this;
201
    }
202
203
    /**
204
     * Add array values definition.
205
     *
206
     * @param array $items Array key-value pairs collection
207
     * @return self Chaining
208
     */
209
    public function arrayValue(array $items = array())
210
    {
211
        if (sizeof($items)) {
212
            $this->text('[');
213
            $this->tabs++;
214
215
            // Iterate array items
216
            foreach ($items as $key => $value) {
217
                // Start array key definition
218
                $this->newLine()->defineValue($key)->text(' => ')->defineValue($value)->text(',');
219
            }
220
221
            $this->tabs--;
222
            $this->newLine(']');
223
        } else {
224
            $this->text('[]');
225
        }
226
227
        return $this;
228
    }
229
230
    /**
231
     * Add variable definition with array merging.
232
     *
233
     * @param string $name Variable name
234
     * @param array $value Array of key-value items for merging it to other array
235
     * @param string $arrayName Name of array to merge to, if no is specified - $name is used
236
     * @return self Chaining
237
     */
238
    public function defArrayMerge($name, array $value, $arrayName = null)
239
    {
240
        // If no other array is specified - set it to current
241
        if (!isset($arrayName)) {
242
            $arrayName = $name;
243
        }
244
245
        return $this->defvar($name, $value, ' = array_merge( ' . $arrayName . ', ', '')->text(');');
246
    }
247
248
    /**
249
     * Add variable definition.
250
     *
251
     * @param string $name Variable name
252
     * @param mixed $value Variable default value
253
     * @param string $after String to insert after variable definition
254
     * @param string $end Closing part of variable definition
255
     * @param string $quote Type of quote
256
     * @return Generator Chaining
257
     */
258
    public function defVar($name, $value = null, $after = ' = ', $end = ';', $quote = self::QUOTE_SINGLE)
259
    {
260
        // Output variable definition
261
        $this->newLine($name);
262
263
        // Get variable typeHint
264
        switch (gettype($value)) {
265
            case 'integer':
266
            case 'boolean':
267
            case 'double':
268
                $this->text($after)->text($value)->text($end);
0 ignored issues
show
Documentation introduced by
$value is of type integer|boolean|double, but the function expects a string.

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...
269
                break;
270
            case 'string':
271
                if (strpos($value, 'EOT') !== false) {
272
                    $this->text($after)->stringValue($value, 0, self::QUOTE_NO)->text($end);
273
                } else {
274
                    $this->text($after)->stringValue($value, 0, $quote)->text($end);
275
                }
276
                break;
277
            case 'array':
278
                $this->text($after)->arrayValue($value)->text($end);
279
                break;
280
            case 'NULL':
281
            case 'object':
282
            case 'resource':
283
            default:
284
                $this->text(';');
285
        }
286
287
        return $this;
288
    }
289
290
    /**
291
     * Add trait definition.
292
     *
293
     * @param string $name Trait name
294
     * @return self Chaining
295
     */
296
    public function defTrait($name)
297
    {
298
        // If we define another class, and we were in other class context
299
        if (isset($this->class) && ($name !== $this->class)) {
300
            // Close old class context
301
            $this->endClass();
302
        }
303
304
        // Save new class name
305
        $this->class = $name;
306
307
        // Class definition start
308
        $this->newLine('trait ' . $name);
309
310
        $this->newLine('{');
311
312
        $this->tabs++;
313
314
        return $this;
315
    }
316
317
    /**
318
     * Add class definition.
319
     *
320
     * @param string $name Class name
321
     * @param string $extends Parent class name
322
     * @param array $implements Interfaces names collection
323
     * @return self Chaining
324
     */
325
    public function defClass($name, $extends = null, array $implements = array())
326
    {
327
        // If we define another class, and we were in other class context
328
        if (isset($this->class) && ($name !== $this->class)) {
329
            // Close old class context
330
            $this->endClass();
331
        }
332
333
        // Save new class name
334
        $this->class = $name;
335
336
        // Class definition start
337
        $this->newLine('class ' . $name);
338
339
        // Parent class definition
340
        if (isset($extends)) {
341
            $this->text(' extends ' . $extends);
342
        }
343
344
        // Interfaces
345
        if (sizeof($implements)) {
346
            $this->text(' implements ' . implode(',', $implements));
347
        }
348
349
        $this->newLine('{');
350
351
        $this->tabs++;
352
353
        return $this;
354
    }
355
356
    /**
357
     * Close current class context.
358
     *
359
     * @return self Chaining
360
     */
361
    public function endClass()
362
    {
363
        $this->tabs > 0 ? $this->tabs-- : null;
364
365
        // Close class definition
366
        return $this->newLine('}')
367
            // Add one empty line after class definition
368
        ->newLine('');
369
    }
370
371
    /**
372
     * Define if statement condition.
373
     *
374
     * @param string $condition Condition statement
375
     * @return self Chaining
376
     */
377
    public function defIfCondition($condition)
378
    {
379
        $this->ifConditionLevel++;
380
381
        // Class definition start
382
        $this->newLine('if (' . $condition . ') {');
383
        $this->tabs++;
384
        return $this;
385
    }
386
387
    /**
388
     * Define elseif statement condition.
389
     *
390
     * @param string $condition Condition statement
391
     * @return self Chaining
392
     */
393
    public function defElseIfCondition($condition)
394
    {
395
        $this->tabs--;
396
        // Class definition start
397
        $this->newLine('} elseif (' . $condition . ') {');
398
        $this->tabs++;
399
        return $this;
400
    }
401
402
    /**
403
     * Define else statement.
404
     *
405
     * @return self Chaining
406
     */
407
    public function defElseCondition()
408
    {
409
        $this->tabs--;
410
        // Class definition start
411
        $this->newLine('} else {');
412
        $this->tabs++;
413
        return $this;
414
    }
415
416
    /**
417
     * Close if condition statement.
418
     *
419
     * @return self Chaining
420
     */
421
    public function endIfCondition()
422
    {
423
        if ($this->ifConditionLevel--) {
424
            $this->tabs--;
425
426
            // Close class definition
427
            return $this->newLine('}');
428
        }
429
430
        return $this;
431
    }
432
433
    /**
434
     * Add class variable definition.
435
     *
436
     * @param string $name Variable name
437
     * @param string $visibility Variable accessibility level
438
     * @param mixed $value Variable default value
439
     * @return self Chaining
440
     */
441
    public function defClassVar($name, $visibility = 'public', $value = null)
442
    {
443
        if (isset($comment) && isset($comment{0})) {
0 ignored issues
show
Bug introduced by
The variable $comment seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
444
            $this->multiComment(array($comment));
445
        }
446
447
        return $this->defvar($visibility . ' ' . $name, $value)->newLine();
448
    }
449
450
    /**
451
     * Add class constant definition.
452
     *
453
     * @param string $name Constant name
454
     * @param string $value Variable default value
455
     * @return self Chaining
456
     */
457
    public function defClassConst($name, $value)
458
    {
459
        return $this->defClassVar(strtoupper($name), 'const', $value);
460
    }
461
462
    /**
463
     * Write file to disk
464
     * @param string $name Path to file
465
     * @param string $format Output file format
466
     */
467
    public function write($name, $format = 'php')
468
    {
469
        $code = $this->flush();
470
471
        if ($format === 'php') {
472
            $code = '<?php ' . $code;
473
        }
474
475
        file_put_contents($name, $code, 0775);
476
    }
477
478
    /**
479
     * Flush internal data and return it.
480
     *
481
     * @return string Current generated code
482
     */
483
    public function flush()
484
    {
485
        // We should use 4 spaces instead of tabs
486
        $code = str_replace("\t", '    ', $this->code);
487
488
        $this->tabs = 0;
489
        $this->code = '';
490
        $this->class = null;
491
492
        return $code;
493
    }
494
495
    /**
496
     * Add class function definition.
497
     *
498
     * @param string $name       Class function name
499
     * @param string $visibility Class function visibility
500
     * @param array  $parameters Class function arguments
501
     * @param array  $comments   Class function multi-line comments
502
     * @param null   $returnType Class function return type PHP7
503
     *
504
     * @return $this
505
     *
506
     */
507
    public function defClassFunction(string $name, string $visibility = 'public', array $parameters = [], array $comments = [], $returnType = null)
508
    {
509
        if ($this->class === null) {
510
            throw new \InvalidArgumentException('Cannot create class function '.$name.' with out class creation');
511
        }
512
513
        $this->defFunction($name, $parameters, $visibility.' ', $comments, $returnType);
514
515
        return $this;
516
    }
517
518
    /**
519
     * @see self::defClassFunction with public visibility
520
     *
521
     * @return $this
522
     */
523
    public function defPublicClassFunction(string $name, array $parameters = [], array $comments = [], $returnType = null)
524
    {
525
        return $this->defClassFunction($name, 'public', $parameters, $comments, $returnType);
526
    }
527
528
    /**
529
     * @see self::defClassFunction with private visibility
530
     *
531
     * @return $this
532
     */
533
    public function defPrivateClassFunction(string $name, array $parameters = [], array $comments = [], $returnType = null)
534
    {
535
        return $this->defClassFunction($name, 'private', $parameters, $comments, $returnType);
536
    }
537
538
    /**
539
     * @see self::defClassFunction with protected visibility
540
     *
541
     * @return $this
542
     */
543
    public function defProtectedClassFunction(string $name, array $parameters = [], array $comments = [], $returnType = null)
544
    {
545
        return $this->defClassFunction($name, 'protected', $parameters, $comments, $returnType);
546
    }
547
548
549
    /**
550
     * Close class function definition.
551
     *
552
     * @return $this Chaining
553
     */
554
    public function endClassFunction()
555
    {
556
        $this->endFunction();
557
558
        return $this;
559
    }
560
561
    /**
562
     * Add function definition.
563
     *
564
     * @param string $name       Function name
565
     * @param array  $parameters Collection of parameters $typeHint => $paramName
566
     * @param string $prefix     Function prefix
567
     * @param array  $comments   Function multi-line comments
568
     * @param string $returnType Function return type PHP7
569
     *
570
     * @return Generator Chaining
571
     */
572
    public function defFunction(string $name, array $parameters = [], string $prefix = '', array $comments = [], string $returnType = null)
573
    {
574
        // Convert parameters to string
575
        $parameterList = array();
576
        foreach ($parameters as $type => $parameter) {
577
            $parameterList[] = (is_string($type) ? $type.' ' : '') . $parameter;
578
        }
579
        $parameterList = sizeof($parameterList) ? implode(', ', $parameterList) : '';
580
581
        $this
582
            ->newLine('')
583
            ->multiComment($comments)
584
            ->newLine($prefix.'function ' . $name . '('.$parameterList.')' . ($returnType  !== null ? ' : ' . $returnType : ''))
585
            ->newLine('{')
586
            ->tabs('');
587
588
        $this->tabs++;
589
590
        return $this;
591
    }
592
593
    /**
594
     * Close current function context.
595
     *
596
     * @return self Chaining
597
     */
598
    public function endFunction()
599
    {
600
        $this->tabs--;
601
602
        return $this->newLine('}');
603
    }
604
605
    /**
606
     * Constructor
607
     * @param string $namespace Code namespace
608
     */
609
    public function __construct($namespace = null)
610
    {
611
        // If namespace is defined - set it
612
        if (isset($namespace)) {
613
            $this->defNamespace($namespace);
614
        }
615
    }
616
617
    /**
618
     * Add namespace declaration.
619
     *
620
     * @param string $name Namespace name
621
     *
622
     * @return $this Chaining
623
     */
624
    public function defNamespace($name)
625
    {
626
        if ($name !== '' && $name !== null) {
627
            $this->newLine('namespace ' . $name . ';')->newLine();
628
        }
629
630
        return $this;
631
    }
632
}
633
//[PHPCOMPRESSOR(remove,end)]
634