Completed
Push — 1.x ( fd0c91...cdd807 )
by Akihito
03:35
created

CompileAutoload   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 159
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 0
Metric Value
wmc 18
lcom 2
cbo 4
dl 0
loc 159
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 1
A getFileInfo() 0 8 2
A __invoke() 0 17 1
A getPaths() 0 20 4
A saveAutoloadFile() 0 22 2
A invokeTypicalRequest() 0 10 1
A isNotAutoloadble() 0 4 3
A isNotCompileFile() 0 4 2
A getRelativePath() 0 9 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Package\Compiler;
6
7
use ArrayObject;
8
use BEAR\AppMeta\Meta;
9
use BEAR\Package\Provide\Error\NullPage;
10
use BEAR\Resource\Uri;
11
use BEAR\Sunday\Extension\Application\AppInterface;
12
use Ray\Di\InjectorInterface;
13
use ReflectionClass;
14
15
use function assert;
16
use function class_exists;
17
use function file_exists;
18
use function in_array;
19
use function interface_exists;
20
use function is_float;
21
use function is_int;
22
use function memory_get_peak_usage;
23
use function microtime;
24
use function number_format;
25
use function preg_quote;
26
use function preg_replace;
27
use function printf;
28
use function property_exists;
29
use function realpath;
30
use function sprintf;
31
use function strpos;
32
use function trait_exists;
33
34
use const PHP_EOL;
35
36
class CompileAutoload
37
{
38
    /** @var string */
39
    private $appDir;
40
41
    /** @var string */
42
    private $context;
43
44
    /** @var Meta */
45
    private $appMeta;
46
47
    /** @var ArrayObject<int, string> */
48
    private $overwritten;
49
50
    /** @var ArrayObject<int, string> */
51
    private $classes;
52
53
    /** @var InjectorInterface */
54
    private $injector;
55
56
    /** @var FilePutContents */
57
    private $filePutContents;
58
59
    /**
60
     * @param ArrayObject<int, string> $overwritten
61
     * @param ArrayObject<int, string> $classes
62
     */
63
    public function __construct(
64
        InjectorInterface $injector,
65
        FilePutContents $filePutContents,
66
        Meta $appMeta,
67
        ArrayObject $overwritten,
68
        ArrayObject $classes,
69
        string $appDir,
70
        string $context
71
    ) {
72
        $this->appDir = $appDir;
73
        $this->context = $context;
74
        $this->appMeta = $appMeta;
75
        $this->overwritten = $overwritten;
76
        $this->classes = $classes;
77
        $this->injector = $injector;
78
        $this->filePutContents = $filePutContents;
79
    }
80
81
    public function getFileInfo(string $filename): string
82
    {
83
        if (in_array($filename, (array) $this->overwritten, true)) {
84
            return $filename . ' (overwritten)';
85
        }
86
87
        return $filename;
88
    }
89
90
    public function __invoke(): int
91
    {
92
        echo PHP_EOL;
93
        $this->invokeTypicalRequest();
94
        /** @var list<string> $classes */
0 ignored issues
show
Documentation introduced by
The doc-type list<string> could not be parsed: Expected "|" or "end of type", but got "<" at position 4. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
95
        $classes = (array) $this->classes;
96
        $paths = $this->getPaths($classes);
97
        $autolaod = $this->saveAutoloadFile($this->appMeta->appDir, $paths);
98
        $start = $_SERVER['REQUEST_TIME_FLOAT'];
99
        assert(is_float($start));
100
        $time = number_format(microtime(true) - $start, 2);
101
        $memory = number_format(memory_get_peak_usage() / (1024 * 1024), 3);
102
        printf("Compilation (2/2) took %f seconds and used %fMB of memory\n", $time, $memory);
103
        printf("autoload.php: %s\n", $this->getFileInfo($autolaod));
104
105
        return 0;
106
    }
107
108
    /**
109
     * @param array<string> $classes
110
     *
111
     * @return array<string>
112
     */
113
    public function getPaths(array $classes): array
114
    {
115
        $paths = [];
116
        foreach ($classes as $class) {
117
            // could be phpdoc tag by annotation loader
118
            if ($this->isNotAutoloadble($class)) {
119
                continue;
120
            }
121
122
            /** @var class-string $class */
0 ignored issues
show
Documentation introduced by
The doc-type class-string could not be parsed: Unknown type name "class-string" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
123
            $filePath = (string) (new ReflectionClass($class))->getFileName(); // @phpstan-ignore-line
124
            if (! $this->isNotCompileFile($filePath)) {
125
                continue; // @codeCoverageIgnore
126
            }
127
128
            $paths[] = $this->getRelativePath($this->appDir, $filePath);
129
        }
130
131
        return $paths;
132
    }
133
134
    /**
135
     * @param array<string> $paths
136
     */
137
    public function saveAutoloadFile(string $appDir, array $paths): string
138
    {
139
        $requiredFile = '';
140
        foreach ($paths as $path) {
141
            $requiredFile .= sprintf(
142
                "require %s';\n",
143
                $this->getRelativePath($appDir, $path)
144
            );
145
        }
146
147
        $autoloadFile = sprintf("<?php
148
149
// %s autoload
150
151
%s
152
require __DIR__ . '/vendor/autoload.php';
153
", $this->context, $requiredFile);
154
        $fileName = realpath($appDir) . '/autoload.php';
155
        ($this->filePutContents)($fileName, $autoloadFile);
156
157
        return $fileName;
158
    }
159
160
    /**
161
     * @psalm-suppress MixedFunctionCall
162
     * @psalm-suppress NoInterfaceProperties
163
     */
164
    public function invokeTypicalRequest(): void
165
    {
166
        $app = $this->injector->getInstance(AppInterface::class);
167
        assert($app instanceof AppInterface);
168
        assert(property_exists($app, 'resource'));
169
        $ro = new NullPage();
170
        $ro->uri = new Uri('app://self/');
171
        /** @psalm-suppress MixedMethodCall */
172
        $app->resource->get->object($ro)();
0 ignored issues
show
Bug introduced by
Accessing resource on the interface BEAR\Sunday\Extension\Application\AppInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
173
    }
174
175
    private function isNotAutoloadble(string $class): bool
176
    {
177
        return ! class_exists($class, false) && ! interface_exists($class, false) && ! trait_exists($class, false);
178
    }
179
180
    private function isNotCompileFile(string $filePath): bool
181
    {
182
        return file_exists($filePath) || is_int(strpos($filePath, 'phar'));
183
    }
184
185
    private function getRelativePath(string $rootDir, string $file): string
186
    {
187
        $dir = (string) realpath($rootDir);
188
        if (strpos($file, $dir) !== false) {
189
            return (string) preg_replace('#^' . preg_quote($dir, '#') . '#', "__DIR__ . '", $file);
190
        }
191
192
        return $file;
193
    }
194
}
195