Compiler::compile()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 27
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 21
dl 0
loc 27
rs 9.584
c 7
b 0
f 0
cc 1
nc 1
nop 0
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
/**
37
 * @psalm-import-type AppName from Types
38
 * @psalm-import-type Context from Types
39
 * @psalm-import-type AppDir from Types
40
 * @psalm-import-type ClassList from Types
41
 * @psalm-import-type OverwrittenFiles from Types
42
 */
43
44
final class Compiler
45
{
46
    /** @var ArrayObject<int, string> */
47
    private ArrayObject $classes;
48
    private Meta $appMeta;
49
    private CompileAutoload $dumpAutoload;
50
    private CompilePreload $compilePreload;
51
    private CompileObjectGraph $compilerObjectGraph;
52
53
    /**
54
     * @param AppName $appName application name "MyVendor|MyProject"
0 ignored issues
show
Bug introduced by
The type BEAR\Package\AppName was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
55
     * @param Context $context application context "prod-app"
56
     * @param AppDir  $appDir  application path
0 ignored issues
show
Bug introduced by
The type BEAR\Package\AppDir was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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