Completed
Push — compiler ( cf031a...896b99 )
by Akihito
01:38
created

Compiler::compile()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.7458

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 10
cts 17
cp 0.5881
rs 9.2248
c 0
b 0
f 0
cc 5
nc 8
nop 0
crap 6.7458
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\CompileDependencies;
12
use BEAR\Package\Compiler\CompileDiScripts;
13
use BEAR\Package\Compiler\CompileObjectGraph;
14
use BEAR\Package\Compiler\CompilePreload;
15
use BEAR\Package\Compiler\FakeRun;
16
use BEAR\Package\Compiler\FilePutContents;
17
use BEAR\Package\Compiler\NewInstance;
18
use BEAR\Package\Provide\Error\NullPage;
19
use Composer\Autoload\ClassLoader;
20
use Ray\Di\InjectorInterface;
21
use RuntimeException;
22
23
use function assert;
24
use function count;
25
use function file_exists;
26
use function is_float;
27
use function is_int;
28
use function memory_get_peak_usage;
29
use function microtime;
30
use function number_format;
31
use function printf;
32
use function realpath;
33
use function spl_autoload_functions;
34
use function spl_autoload_register;
35 1
use function spl_autoload_unregister;
36
use function strpos;
37 1
38 1
use const PHP_EOL;
39
40 1
final class Compiler
41
{
42
    /** @var ArrayObject<int, string> */
43 1
    private $classes;
44
45 1
    /** @var InjectorInterface */
46 1
    private $injector;
47 1
48 1
    /** @var string */
49 1
    private $context;
50
51 1
    /** @var Meta */
52
    private $appMeta;
53
54
    /** @var CompileDiScripts */
55 1
    private $compilerDiScripts;
56
57
    /** @var NewInstance */
58 1
    private $newInstance;
59 1
60
    /** @var CompileAutoload */
61 1
    private $dumpAutoload;
62 1
63
    /** @var CompilePreload */
64 1
    private $compilePreload;
65
66
    /** @var CompileObjectGraph */
67 1
    private $compilerObjectGraph;
68
69 1
    /** @var CompileDependencies */
70 1
    private $compileDependencies;
71 1
72
    /**
73
     * @param string $appName application name "MyVendor|MyProject"
74
     * @param string $context application context "prod-app"
75
     * @param string $appDir  application path
76
     */
77
    public function __construct(string $appName, string $context, string $appDir)
78
    {
79
        /** @var ArrayObject<int, string> $classes */
80
        $classes = new ArrayObject();
81
        $this->classes = $classes;
82
        $this->registerLoader($appDir);
83
        $this->hookNullObjectClass($appDir);
84
        $this->context = $context;
85
        $this->appMeta = new Meta($appName, $context, $appDir);
86
        /** @psalm-suppress MixedAssignment (?) */
87
        $this->injector = Injector::getInstance($appName, $context, $appDir);
88
        $this->compilerDiScripts = new CompileDiScripts(new CompileClassMetaInfo(), $this->injector);
89
        $this->newInstance = new NewInstance($this->injector);
90
        /** @var ArrayObject<int, string> $overWritten */
91
        $overWritten = new ArrayObject();
92
        /** @var ArrayObject<int, string> $classes */
93
        $filePutContents = new FilePutContents($overWritten);
94
        $fakeRun = new FakeRun($this->injector, $context, $this->appMeta);
95
        $this->dumpAutoload = new CompileAutoload($fakeRun, $filePutContents, $this->appMeta, $overWritten, $this->classes, $appDir, $context);
96
        $this->compilePreload = new CompilePreload($fakeRun, $this->newInstance, $this->dumpAutoload, $filePutContents, $classes, $context);
97
        $this->compilerObjectGraph = new CompileObjectGraph($filePutContents, $appDir);
98
        $this->compileDependencies = new CompileDependencies($this->newInstance);
99
    }
100
101
    /**
102
     * Compile application
103
     *
104
     * @return 0|1 exit code
0 ignored issues
show
Documentation introduced by
The doc-type 0|1 could not be parsed: Unknown type name "0" 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...
105
     */
106
    public function compile(): int
107
    {
108
        $preload = ($this->compilePreload)($this->appMeta, $this->context);
109
        $module = (new Module())($this->appMeta, $this->context);
110
        ($this->compileDependencies)($module);
111
        echo PHP_EOL;
112
        ($this->compilerDiScripts)($this->appMeta);
113
        $failed = $this->newInstance->getFailed();
114
        $dot = $failed ? '' : ($this->compilerObjectGraph)($module);
115
        $start = $_SERVER['REQUEST_TIME_FLOAT'];
116
        assert(is_float($start));
117 1
        $time = number_format(microtime(true) - $start, 2);
118
        $memory = number_format(memory_get_peak_usage() / (1024 * 1024), 3);
119 1
        echo PHP_EOL;
120 1
        printf("Compilation (1/2) took %f seconds and used %fMB of memory\n", $time, $memory);
121 1
        printf("Success: %d Failed: %d\n", $this->newInstance->getCompiled(), count($this->newInstance->getFailed()));
122 1
        printf("preload.php: %s\n", $this->dumpAutoload->getFileInfo($preload));
123 1
        printf("module.dot: %s\n", $dot ? $this->dumpAutoload->getFileInfo($dot) : 'n/a');
124 1
        foreach ($this->newInstance->getFailed() as $depedencyIndex => $error) {
125 1
            printf("UNBOUND: %s for %s \n", $error, $depedencyIndex);
126 1
        }
127
128 1
        return $failed ? 1 : 0;
129
    }
130 1
131
    public function dumpAutoload(): int
132 1
    {
133
        return ($this->dumpAutoload)();
134 1
    }
135
136 1
    private function registerLoader(string $appDir): void
137
    {
138
        $this->unregisterComposerLoader();
139 1
        $loaderFile = $appDir . '/vendor/autoload.php';
140
        if (! file_exists($loaderFile)) {
141
            throw new RuntimeException('no loader');
142 1
        }
143 1
144
        $loader = require $loaderFile;
145
        assert($loader instanceof ClassLoader);
146 1
        spl_autoload_register(
147 1
            /** @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...
148 1
            function (string $class) use ($loader): void {
149
                $loader->loadClass($class);
150 1
                if (
151
                    $class !== NullPage::class
152 1
                    && ! is_int(strpos($class, 'BEAR\Package\Compiler'))
153
                    && ! is_int(strpos($class, NullPage::class))
154 1
                ) {
155
                    /** @psalm-suppress NullArgument */
156 1
                    $this->classes[] = $class;
157 1
                }
158 1
            }
159
        );
160 1
    }
161 1
162
    private function hookNullObjectClass(string $appDir): void
163
    {
164
        $compileScript = realpath($appDir) . '/.compile.php';
165
        if (file_exists($compileScript)) {
166
            require $compileScript;
167
        }
168
    }
169
170
    private function unregisterComposerLoader(): void
171
    {
172
        $autoload = spl_autoload_functions();
173
        if (isset($autoload[0])) {
174
            spl_autoload_unregister($autoload[0]);
175
        }
176
    }
177
}
178