Passed
Pull Request — 1.x (#446)
by Akihito
03:33 queued 01:45
created

Compiler::compileClassMetaInfo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 13
c 2
b 0
f 0
dl 0
loc 18
ccs 12
cts 12
cp 1
rs 9.8333
cc 2
nc 2
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Package;
6
7
use ArrayObject;
8
use BEAR\AppMeta\Meta;
9
use BEAR\Package\Compiler\CompileAutoload;
10
use BEAR\Package\Compiler\CompileClassMetaInfo;
11
use BEAR\Package\Compiler\CompileObjectGraph;
12
use BEAR\Package\Compiler\CompilePreload;
13
use BEAR\Package\Compiler\FakeRun;
14
use BEAR\Package\Compiler\FilePutContents;
15
use BEAR\Package\Provide\Error\NullPage;
16
use BEAR\Resource\NamedParameterInterface;
17
use Composer\Autoload\ClassLoader;
18
use Doctrine\Common\Annotations\Reader;
19
use RuntimeException;
20
21
use function assert;
22
use function file_exists;
23
use function is_dir;
24
use function is_int;
25
use function is_string;
26
use function memory_get_peak_usage;
27
use function microtime;
28
use function mkdir;
29
use function number_format;
30
use function printf;
31
use function realpath;
32
use function sprintf;
33 1
use function spl_autoload_functions;
34
use function spl_autoload_register;
35 1
use function spl_autoload_unregister;
36 1
use function strpos;
37
38 1
use const PHP_EOL;
39
40
final class Compiler
41 1
{
42
    /** @var ArrayObject<int, string> */
43 1
    private ArrayObject $classes;
44 1
    private Meta $appMeta;
45 1
    private CompileAutoload $dumpAutoload;
46 1
    private CompilePreload $compilePreload;
47 1
    private CompileObjectGraph $compilerObjectGraph;
48 1
49
    /**
50 1
     * @param string $appName application name "MyVendor|MyProject"
51
     * @param string $context application context "prod-app"
52
     * @param string $appDir  application path
53
     *
54 1
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
55
     */
56
    public function __construct(string $appName, private string $context, string $appDir, bool $prepend = true)
57 1
    {
58 1
        /** @var ArrayObject<int, string> $classes */
59
        $classes = new ArrayObject();
60 1
        $this->classes = $classes;
61 1
        $this->registerLoader($appDir, $prepend);
62
        $this->hookNullObjectClass($appDir);
63 1
        $this->appMeta = new Meta($appName, $context, $appDir);
64
        /** @psalm-suppress MixedAssignment (?) */
65
        $injector = Injector::getInstance($appName, $context, $appDir);
66 1
        /** @var ArrayObject<int, string> $overWritten */
67
        $overWritten = new ArrayObject();
68 1
        $filePutContents = new FilePutContents($overWritten);
69 1
        $fakeRun = new FakeRun($injector, $context, $this->appMeta);
70 1
        $this->dumpAutoload = new CompileAutoload($fakeRun, $filePutContents, $this->appMeta, $overWritten, $this->classes, $appDir, $context);
71
        $this->compilePreload = new CompilePreload($fakeRun, $this->dumpAutoload, $filePutContents, $classes, $context);
72
        $this->compilerObjectGraph = new CompileObjectGraph($filePutContents, $this->appMeta->logDir);
73
    }
74
75
    /**
76
     * Compile application
77
     *
78
     * @return 0|1 exit code
0 ignored issues
show
Documentation Bug introduced by
The doc comment 0|1 at position 0 could not be parsed: Unknown type name '0' at position 0 in 0|1.
Loading history...
79
     */
80
    public function compile(): int
81
    {
82
        $preload = ($this->compilePreload)($this->appMeta, $this->context);
83
        $module = (new Module())($this->appMeta, $this->context);
84
        $compiler = new \Ray\Compiler\Compiler();
85
86
        // Validate appDir path
87
        $appDirRealPath = realpath($this->appMeta->appDir);
88
        if (! is_string($appDirRealPath)) {
0 ignored issues
show
introduced by
The condition is_string($appDirRealPath) is always true.
Loading history...
89
            throw new RuntimeException(sprintf(
90
                'Invalid application directory: %s',
91
                $this->appMeta->appDir
92
            ));
93
        }
94
95
        // Build and ensure scriptDir exists
96
        // Note: BEAR.Package uses var/tmp/{context}/di/ as the script directory
97
        $scriptDir = $this->appMeta->tmpDir . '/di';
98
        if (! is_dir($scriptDir) && ! mkdir($scriptDir, 0755, true) && ! is_dir($scriptDir)) {
99
            throw new RuntimeException(sprintf(
100
                'Failed to create script directory: %s',
101
                $scriptDir
102
            ));
103
        }
104
105
        $compiler->compile($module, $scriptDir);
106
107
        // Compile class meta info (annotations and named parameters)
108
        $compiled = $this->compileClassMetaInfo();
109
110
        echo PHP_EOL;
111
        $dot = ($this->compilerObjectGraph)($module);
112
        $start = $_SERVER['REQUEST_TIME_FLOAT'] ?? 0;
113
        $time = number_format(microtime(true) - $start, 2);
114
        $memory = number_format(memory_get_peak_usage() / (1024 * 1024), 3);
115
        echo PHP_EOL;
116
        printf("Compilation took %f seconds and used %fMB of memory\n", $time, $memory);
117
        printf("Compiled: %d resource classes\n", $compiled);
118
        printf("Preload compile: %s\n", $this->dumpAutoload->getFileInfo($preload));
119
        printf("Object graph diagram: %s\n", realpath($dot));
120
121 1
        return 0;
122
    }
123
124 1
    public function dumpAutoload(): int
125
    {
126
        return ($this->dumpAutoload)();
127
    }
128
129
    private function compileClassMetaInfo(): int
130 1
    {
131 1
        $injector = Injector::getInstance($this->appMeta->name, $this->context, $this->appMeta->appDir);
132 1
        $reader = $injector->getInstance(Reader::class);
133 1
        assert($reader instanceof Reader);
134 1
        $namedParams = $injector->getInstance(NamedParameterInterface::class);
135 1
        assert($namedParams instanceof NamedParameterInterface);
136 1
137
        $compileClassMetaInfo = new CompileClassMetaInfo();
138 1
        $resources = $this->appMeta->getResourceListGenerator();
139
        $count = 0;
140 1
        foreach ($resources as $resource) {
141
            [$className] = $resource;
142 1
            $compileClassMetaInfo($reader, $namedParams, $className);
143
            $count++;
144 1
        }
145
146 1
        return $count;
147
    }
148
149 1
    /** @SuppressWarnings(PHPMD.BooleanArgumentFlag) */
150
    private function registerLoader(string $appDir, bool $prepend = true): void
151
    {
152 1
        $this->unregisterComposerLoader();
153 1
        $loaderFile = $appDir . '/vendor/autoload.php';
154
        if (! file_exists($loaderFile)) {
155
            throw new RuntimeException('no loader');
156 1
        }
157 1
158 1
        $loader = require $loaderFile;
159
        assert($loader instanceof ClassLoader);
160 1
        spl_autoload_register(
161
            /** @ class-string $class */
162 1
            function (string $class) use ($loader): void {
163
                $loader->loadClass($class);
164 1
                if (
165
                    $class === NullPage::class
166 1
                    || is_int(strpos($class, Compiler::class))
0 ignored issues
show
introduced by
The condition is_int(strpos($class, BE...ckage\Compiler::class)) is always true.
Loading history...
167 1
                    || is_int(strpos($class, NullPage::class))
168 1
                ) {
169
                    return;
170 1
                }
171 1
172
                /** @psalm-suppress NullArgument */
173
                $this->classes[] = $class;
174
            },
175
            true,
176
            $prepend,
177
        );
178
    }
179
180
    private function hookNullObjectClass(string $appDir): void
181
    {
182
        $compileScript = realpath($appDir) . '/.compile.php';
183
        if (! file_exists($compileScript)) {
184
            // @codeCoverageIgnoreStart
185
            return;
186
            // @codeCoverageIgnoreEnd
187
        }
188
189
        require $compileScript;
190
    }
191
192
    private function unregisterComposerLoader(): void
193
    {
194
        $autoload = spl_autoload_functions();
195
        if (! isset($autoload[0])) {
196
            // @codeCoverageIgnoreStart
197
            return;
198
            // @codeCoverageIgnoreEnd
199
        }
200
201
        spl_autoload_unregister($autoload[0]);
202
    }
203
}
204