Completed
Push — master ( bc7d7f...7a9596 )
by Théo
02:46
created

StubGenerator::generate()   C

Complexity

Conditions 10
Paths 60

Size

Total Lines 49
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 49
c 0
b 0
f 0
rs 5.5471
cc 10
eloc 25
nc 60
nop 0

How to fix   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
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box;
16
17
use Herrera\Annotations\Tokenizer;
18
use KevinGH\Box\Compactor\Php;
19
use KevinGH\Box\Exception\InvalidArgumentException;
20
21
/**
22
 * Generates a new PHP bootstrap loader stub for a Phar.
23
 *
24
 * @author Kevin Herrera <[email protected]>
25
 */
26
class StubGenerator
27
{
28
    /**
29
     * The list of server variables that are allowed to be modified.
30
     *
31
     * @var array
32
     */
33
    private static $allowedMung = [
34
        'PHP_SELF',
35
        'REQUEST_URI',
36
        'SCRIPT_FILENAME',
37
        'SCRIPT_NAME',
38
    ];
39
40
    /**
41
     * The alias to be used in "phar://" URLs.
42
     *
43
     * @var string
44
     */
45
    private $alias = '';
46
47
    /**
48
     * The top header comment banner text.
49
     *
50
     * @var string
51
     */
52
    private $banner = 'Generated by Box.
53
54
@link https://github.com/herrera-io/php-box/';
55
56
    /**
57
     * Embed the Extract class in the stub?
58
     *
59
     * @var bool
60
     */
61
    private $extract = false;
62
63
    /**
64
     * The processed extract code.
65
     *
66
     * @var array
67
     */
68
    private $extractCode = [];
69
70
    /**
71
     * Force the use of the Extract class?
72
     *
73
     * @var bool
74
     */
75
    private $extractForce = false;
76
77
    /**
78
     * The location within the Phar of index script.
79
     *
80
     * @var string
81
     */
82
    private $index;
83
84
    /**
85
     * Use the Phar::interceptFileFuncs() method?
86
     *
87
     * @var bool
88
     */
89
    private $intercept = false;
90
91
    /**
92
     * The map for file extensions and their mimetypes.
93
     *
94
     * @var array
95
     */
96
    private $mimetypes = [];
97
98
    /**
99
     * The list of server variables to modify.
100
     *
101
     * @var array
102
     */
103
    private $mung = [];
104
105
    /**
106
     * The location of the script to run when a file is not found.
107
     *
108
     * @var string
109
     */
110
    private $notFound;
111
112
    /**
113
     * The rewrite function.
114
     *
115
     * @var string
116
     */
117
    private $rewrite;
118
119
    /**
120
     * The shebang line.
121
     *
122
     * @var string
123
     */
124
    private $shebang = '#!/usr/bin/env php';
125
126
    /**
127
     * Use Phar::webPhar() instead of Phar::mapPhar()?
128
     *
129
     * @var bool
130
     */
131
    private $web = false;
132
133
    /**
134
     * Sets the alias to be used in "phar://" URLs.
135
     *
136
     * @param string $alias the alias
137
     *
138
     * @return StubGenerator the stub generator
139
     */
140
    public function alias($alias)
141
    {
142
        $this->alias = $alias;
143
144
        return $this;
145
    }
146
147
    /**
148
     * Sets the top header comment banner text.
149
     *
150
     * @param string $banner the banner text
151
     *
152
     * @return StubGenerator the stub generator
153
     */
154
    public function banner($banner)
155
    {
156
        $this->banner = $banner;
157
158
        return $this;
159
    }
160
161
    /**
162
     * Creates a new instance of the stub generator.
163
     *
164
     * @return StubGenerator the stub generator
165
     */
166
    public static function create()
167
    {
168
        return new static();
169
    }
170
171
    /**
172
     * Embed the Extract class in the stub?
173
     *
174
     * @param bool $extract Embed the class?
175
     * @param bool $force   Force the use of the class?
176
     *
177
     * @return StubGenerator the stub generator
178
     */
179
    public function extract($extract, $force = false)
180
    {
181
        $this->extract = $extract;
182
        $this->extractForce = $force;
183
184
        if ($extract) {
185
            $this->extractCode = [
186
                'constants' => [],
187
                'class' => [],
188
            ];
189
190
            $compactor = new Php(new Tokenizer());
191
            $code = file_get_contents(__DIR__.'/Extract.php');
192
            $code = $compactor->compact($code);
0 ignored issues
show
Bug introduced by
It seems like $code can also be of type false; however, parameter $contents of KevinGH\Box\Compactor\Php::compact() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

192
            $code = $compactor->compact(/** @scrutinizer ignore-type */ $code);
Loading history...
193
            $code = preg_replace('/\n+/', "\n", $code);
194
            $code = explode("\n", $code);
195
            $code = array_slice($code, 2);
196
197
            foreach ($code as $i => $line) {
198
                if ((0 === strpos($line, 'use'))
199
                    && (false === strpos($line, '\\'))
200
                ) {
201
                    unset($code[$i]);
202
                } elseif (0 === strpos($line, 'define')) {
203
                    $this->extractCode['constants'][] = $line;
204
                } else {
205
                    $this->extractCode['class'][] = $line;
206
                }
207
            }
208
        }
209
210
        return $this;
211
    }
212
213
    /**
214
     * Sets location within the Phar of index script.
215
     *
216
     * @param string $index the index file
217
     *
218
     * @return StubGenerator the stub generator
219
     */
220
    public function index($index)
221
    {
222
        $this->index = $index;
223
224
        return $this;
225
    }
226
227
    /**
228
     * Use the Phar::interceptFileFuncs() method in the stub?
229
     *
230
     * @param bool $intercept Use interceptFileFuncs()?
231
     *
232
     * @return StubGenerator the stub generator
233
     */
234
    public function intercept($intercept)
235
    {
236
        $this->intercept = $intercept;
237
238
        return $this;
239
    }
240
241
    /**
242
     * Generates the stub.
243
     *
244
     * @return string the stub
245
     */
246
    public function generate()
247
    {
248
        $stub = [];
249
250
        if ('' !== $this->shebang) {
251
            $stub[] = $this->shebang;
252
        }
253
254
        $stub[] = '<?php';
255
256
        if (null !== $this->banner) {
257
            $stub[] = $this->getBanner();
258
        }
259
260
        if ($this->extract) {
261
            $stub[] = implode("\n", $this->extractCode['constants']);
262
263
            if ($this->extractForce) {
264
                $stub = array_merge($stub, $this->getExtractSections());
265
            }
266
        }
267
268
        $stub = array_merge($stub, $this->getPharSections());
269
270
        if ($this->extract) {
271
            if ($this->extractForce) {
272
                if ($this->index && !$this->web) {
273
                    $stub[] = "require \"\$dir/{$this->index}\";";
274
                }
275
            } else {
276
                end($stub);
277
278
                $stub[key($stub)] .= ' else {';
279
280
                $stub = array_merge($stub, $this->getExtractSections());
281
282
                if ($this->index) {
283
                    $stub[] = "require \"\$dir/{$this->index}\";";
284
                }
285
286
                $stub[] = '}';
287
            }
288
289
            $stub[] = implode("\n", $this->extractCode['class']);
290
        }
291
292
        $stub[] = '__HALT_COMPILER();';
293
294
        return implode("\n", $stub);
295
    }
296
297
    /**
298
     * Sets the map for file extensions and their mimetypes.
299
     *
300
     * @param array $mimetypes the map
301
     *
302
     * @return StubGenerator the stub generator
303
     */
304
    public function mimetypes(array $mimetypes)
305
    {
306
        $this->mimetypes = $mimetypes;
307
308
        return $this;
309
    }
310
311
    /**
312
     * Sets the list of server variables to modify.
313
     *
314
     * @param array $list the list
315
     *
316
     * @throws Exception\Exception
317
     * @throws InvalidArgumentException if the list contains an invalid value
318
     *
319
     * @return StubGenerator the stub generator
320
     */
321
    public function mung(array $list)
322
    {
323
        foreach ($list as $value) {
324
            if (false === in_array($value, self::$allowedMung, true)) {
325
                throw InvalidArgumentException::create(
326
                    'The $_SERVER variable "%s" is not allowed.',
327
                    $value
328
                );
329
            }
330
        }
331
332
        $this->mung = $list;
333
334
        return $this;
335
    }
336
337
    /**
338
     * Sets the location of the script to run when a file is not found.
339
     *
340
     * @param string $script the script
341
     *
342
     * @return StubGenerator the stub generator
343
     */
344
    public function notFound($script)
345
    {
346
        $this->notFound = $script;
347
348
        return $this;
349
    }
350
351
    /**
352
     * Sets the rewrite function.
353
     *
354
     * @param string $function the function
355
     *
356
     * @return StubGenerator the stub generator
357
     */
358
    public function rewrite($function)
359
    {
360
        $this->rewrite = $function;
361
362
        return $this;
363
    }
364
365
    /**
366
     * Sets the shebang line.
367
     *
368
     * @param string $shebang the shebang line
369
     *
370
     * @return StubGenerator the stub generator
371
     */
372
    public function shebang($shebang)
373
    {
374
        $this->shebang = $shebang;
375
376
        return $this;
377
    }
378
379
    /**
380
     * Use Phar::webPhar() instead of Phar::mapPhar()?
381
     *
382
     * @param bool $web Use Phar::webPhar()?
383
     *
384
     * @return StubGenerator the stub generator
385
     */
386
    public function web($web)
387
    {
388
        $this->web = $web;
389
390
        return $this;
391
    }
392
393
    /**
394
     * Escapes an argument so it can be written as a string in a call.
395
     *
396
     * @param string $arg   the argument
397
     * @param string $quote the quote
398
     *
399
     * @return string the escaped argument
400
     */
401
    private function arg($arg, $quote = "'")
402
    {
403
        return $quote.addcslashes($arg, $quote).$quote;
404
    }
405
406
    /**
407
     * Returns the alias map.
408
     *
409
     * @return string the alias map
410
     */
411
    private function getAlias()
412
    {
413
        $stub = '';
414
        $prefix = '';
415
416
        if ($this->extractForce) {
417
            $prefix = '$dir/';
418
        }
419
420
        if ($this->web) {
421
            $stub .= 'Phar::webPhar('.$this->arg($this->alias);
422
423
            if ($this->index) {
424
                $stub .= ', '.$this->arg($prefix.$this->index, '"');
425
426
                if ($this->notFound) {
427
                    $stub .= ', '.$this->arg($prefix.$this->notFound, '"');
428
429
                    if ($this->mimetypes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->mimetypes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
430
                        $stub .= ', '.var_export(
431
                            $this->mimetypes,
432
                            true
433
                        );
434
435
                        if ($this->rewrite) {
436
                            $stub .= ', '.$this->arg($this->rewrite);
437
                        }
438
                    }
439
                }
440
            }
441
442
            $stub .= ');';
443
        } else {
444
            $stub .= 'Phar::mapPhar('.$this->arg($this->alias).');';
445
        }
446
447
        return $stub;
448
    }
449
450
    /**
451
     * Returns the banner after it has been processed.
452
     *
453
     * @return string the processed banner
454
     */
455
    private function getBanner()
456
    {
457
        $banner = "/**\n * ";
458
        $banner .= str_replace(
459
            " \n",
460
            "\n",
461
            str_replace("\n", "\n * ", $this->banner)
462
        );
463
464
        $banner .= "\n */";
465
466
        return $banner;
467
    }
468
469
    /**
470
     * Returns the self extracting sections of the stub.
471
     *
472
     * @return array the stub sections
473
     */
474
    private function getExtractSections()
475
    {
476
        return [
477
            '$extract = new Extract(__FILE__, Extract::findStubLength(__FILE__));',
478
            '$dir = $extract->go();',
479
            'set_include_path($dir . PATH_SEPARATOR . get_include_path());',
480
        ];
481
    }
482
483
    /**
484
     * Returns the sections of the stub that use the Phar class.
485
     *
486
     * @return array the stub sections
487
     */
488
    private function getPharSections()
489
    {
490
        $stub = [
491
            'if (class_exists(\'Phar\')) {',
492
            $this->getAlias(),
493
        ];
494
495
        if ($this->intercept) {
496
            $stub[] = 'Phar::interceptFileFuncs();';
497
        }
498
499
        if ($this->mung) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->mung of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
500
            $stub[] = 'Phar::mungServer('.var_export($this->mung, true).');';
501
        }
502
503
        if ($this->index && !$this->web && !$this->extractForce) {
504
            $stub[] = "require 'phar://' . __FILE__ . '/{$this->index}';";
505
        }
506
507
        $stub[] = '}';
508
509
        return $stub;
510
    }
511
}
512