Issues (37)

src/Compiler.php (2 issues)

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_int;
24
use function memory_get_peak_usage;
25
use function microtime;
26
use function number_format;
27
use function printf;
28
use function realpath;
29
use function spl_autoload_functions;
30
use function spl_autoload_register;
31
use function spl_autoload_unregister;
32
use function strpos;
33
34
use const PHP_EOL;
35
36
final class Compiler
37
{
38
    /** @var ArrayObject<int, string> */
39
    private ArrayObject $classes;
40
    private Meta $appMeta;
41
    private CompileAutoload $dumpAutoload;
42
    private CompilePreload $compilePreload;
43
    private CompileObjectGraph $compilerObjectGraph;
44
45
    /**
46
     * @param string $appName application name "MyVendor|MyProject"
47
     * @param string $context application context "prod-app"
48
     * @param string $appDir  application path
49
     *
50
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
51
     */
52
    public function __construct(string $appName, private string $context, string $appDir, bool $prepend = true)
53
    {
54
        /** @var ArrayObject<int, string> $classes */
55
        $classes = new ArrayObject();
56
        $this->classes = $classes;
57
        $this->registerLoader($appDir, $prepend);
58
        $this->hookNullObjectClass($appDir);
59
        $this->appMeta = new Meta($appName, $context, $appDir);
60
        /** @psalm-suppress MixedAssignment (?) */
61
        $injector = Injector::getInstance($appName, $context, $appDir);
62
        /** @var ArrayObject<int, string> $overWritten */
63
        $overWritten = new ArrayObject();
64
        $filePutContents = new FilePutContents($overWritten);
65
        $fakeRun = new FakeRun($injector, $context, $this->appMeta);
66
        $this->dumpAutoload = new CompileAutoload($fakeRun, $filePutContents, $this->appMeta, $overWritten, $this->classes, $appDir, $context);
67
        $this->compilePreload = new CompilePreload($fakeRun, $this->dumpAutoload, $filePutContents, $classes, $context);
68
        $this->compilerObjectGraph = new CompileObjectGraph($filePutContents, $this->appMeta->logDir);
69
    }
70
71
    /**
72
     * Compile application
73
     *
74
     * @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...
75
     */
76
    public function compile(): int
77
    {
78
        $preload = ($this->compilePreload)($this->appMeta, $this->context);
79
        $module = (new Module())($this->appMeta, $this->context);
80
        $compiler = new \Ray\Compiler\Compiler();
81
        $scriptDir = realpath($this->appMeta->appDir) . '/var/di/' . $this->context;
82
        $compiler->compile($module, $scriptDir);
83
84
        // Compile class meta info (annotations and named parameters)
85
        $compiled = $this->compileClassMetaInfo();
86
87
        echo PHP_EOL;
88
        $dot = ($this->compilerObjectGraph)($module);
89
        $start = $_SERVER['REQUEST_TIME_FLOAT'] ?? 0;
90
        $time = number_format(microtime(true) - $start, 2);
91
        $memory = number_format(memory_get_peak_usage() / (1024 * 1024), 3);
92
        echo PHP_EOL;
93
        printf("Compilation took %f seconds and used %fMB of memory\n", $time, $memory);
94
        printf("Compiled: %d resource classes\n", $compiled);
95
        printf("Preload compile: %s\n", $this->dumpAutoload->getFileInfo($preload));
96
        printf("Object graph diagram: %s\n", realpath($dot));
97
98
        return 0;
99
    }
100
101
    public function dumpAutoload(): int
102
    {
103
        return ($this->dumpAutoload)();
104
    }
105
106
    private function compileClassMetaInfo(): int
107
    {
108
        $injector = Injector::getInstance($this->appMeta->name, $this->context, $this->appMeta->appDir);
109
        $reader = $injector->getInstance(Reader::class);
110
        assert($reader instanceof Reader);
111
        $namedParams = $injector->getInstance(NamedParameterInterface::class);
112
        assert($namedParams instanceof NamedParameterInterface);
113
114
        $compileClassMetaInfo = new CompileClassMetaInfo();
115
        $resources = $this->appMeta->getResourceListGenerator();
116
        $count = 0;
117
        foreach ($resources as $resource) {
118
            [$className] = $resource;
119
            $compileClassMetaInfo($reader, $namedParams, $className);
120
            $count++;
121
        }
122
123
        return $count;
124
    }
125
126
    /** @SuppressWarnings(PHPMD.BooleanArgumentFlag) */
127
    private function registerLoader(string $appDir, bool $prepend = true): void
128
    {
129
        $this->unregisterComposerLoader();
130
        $loaderFile = $appDir . '/vendor/autoload.php';
131
        if (! file_exists($loaderFile)) {
132
            throw new RuntimeException('no loader');
133
        }
134
135
        $loader = require $loaderFile;
136
        assert($loader instanceof ClassLoader);
137
        spl_autoload_register(
138
            /** @ class-string $class */
139
            function (string $class) use ($loader): void {
140
                $loader->loadClass($class);
141
                if (
142
                    $class === NullPage::class
143
                    || is_int(strpos($class, Compiler::class))
0 ignored issues
show
The condition is_int(strpos($class, BE...ckage\Compiler::class)) is always true.
Loading history...
144
                    || is_int(strpos($class, NullPage::class))
145
                ) {
146
                    return;
147
                }
148
149
                /** @psalm-suppress NullArgument */
150
                $this->classes[] = $class;
151
            },
152
            true,
153
            $prepend,
154
        );
155
    }
156
157
    private function hookNullObjectClass(string $appDir): void
158
    {
159
        $compileScript = realpath($appDir) . '/.compile.php';
160
        if (! file_exists($compileScript)) {
161
            // @codeCoverageIgnoreStart
162
            return;
163
            // @codeCoverageIgnoreEnd
164
        }
165
166
        require $compileScript;
167
    }
168
169
    private function unregisterComposerLoader(): void
170
    {
171
        $autoload = spl_autoload_functions();
172
        if (! isset($autoload[0])) {
173
            // @codeCoverageIgnoreStart
174
            return;
175
            // @codeCoverageIgnoreEnd
176
        }
177
178
        spl_autoload_unregister($autoload[0]);
179
    }
180
}
181