Completed
Push — fix-compile ( af21ca...556ec5 )
by Akihito
06:25
created

Compiler::compileLoader()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 40.3921

Importance

Changes 0
Metric Value
dl 0
loc 36
ccs 3
cts 25
cp 0.12
rs 8.4106
c 0
b 0
f 0
cc 7
nc 8
nop 3
crap 40.3921
1
<?php
2
/**
3
 * This file is part of the BEAR.Package package.
4
 *
5
 * @license http://opensource.org/licenses/MIT MIT
6
 */
7
namespace BEAR\Package;
8
9
use BEAR\AppMeta\AbstractAppMeta;
10
use BEAR\AppMeta\AppMeta;
11
use BEAR\Package\Provide\Error\NullPage;
12
use BEAR\Resource\Exception\ParameterException;
13
use BEAR\Resource\NamedParameterInterface;
14
use BEAR\Resource\Uri;
15
use Doctrine\Common\Annotations\AnnotationReader;
16
use Doctrine\Common\Annotations\Reader;
17
use Doctrine\Common\Cache\Cache;
18
use Ray\Di\AbstractModule;
19
use Ray\Di\Bind;
20
use Ray\Di\InjectorInterface;
21
22
final class Compiler
23
{
24
    private $classes = [];
25
26
    private $files = [];
27
28
    /**
29
     * Compile application
30
     *
31
     * @param string $appName application name "MyVendor|MyProject"
32
     * @param string $context application context "prod-app"
33
     * @param string $appDir  application path
34
     */
35 1
    public function __invoke(string $appName, string $context, string $appDir) : string
36
    {
37 1
        $loader = $this->compileLoader($appName, $context, $appDir);
38 1
        $log = $this->compileDiScripts($appName, $context, $appDir);
39
        $msg = sprintf("Compile Log: %s\nautload.php: %s", $log, $loader);
40 1
41
        return $msg;
42
    }
43 1
44
    public function compileDiScripts(string $appName, string $context, string $appDir) : string
45 1
    {
46 1
        $appMeta = new AppMeta($appName, $context, $appDir);
47 1
        (new Unlink)->force($appMeta->tmpDir);
48 1
        $injector = new AppInjector($appName, $context);
49 1
        $cache = $injector->getInstance(Cache::class);
50
        $reader = $injector->getInstance(AnnotationReader::class);
51 1
        /* @var $reader \Doctrine\Common\Annotations\Reader */
52
        $namedParams = $injector->getInstance(NamedParameterInterface::class);
53
        /* @var $namedParams NamedParameterInterface */
54
55 1
        // create DI factory class and AOP compiled class for all resources and save $app cache.
56
        (new Bootstrap)->newApp($appMeta, $context, $cache);
57
58 1
        // check resource injection and create annotation cache
59 1
        foreach ($appMeta->getResourceListGenerator() as list($className)) {
60
            $this->scanClass($injector, $reader, $namedParams, $className);
61 1
        }
62 1
        $logFile = realpath($appMeta->logDir . '/compile.log');
63
        $this->saveCompileLog($appMeta, $context, $logFile);
64 1
65
        return $logFile;
66
    }
67 1
68
    private function compileLoader(string $appName, string $context, string $appDir) : string
69 1
    {
70 1
        $loaderFile = $appDir . '/vendor/autoload.php';
71 1
        if (! file_exists($loaderFile)) {
72
            return '';
73
        }
74
        $loaderFile = require $loaderFile;
75
        spl_autoload_register(
76
            function ($class) use ($loaderFile) {
77
                $loaderFile->loadClass($class);
78
                if ($class !== NullPage::class) {
79
                    $this->classes[] = $class;
80
                }
81
            },
82
            false,
83
            true
84
        );
85
86
        $this->invokeTypicalReuqest($appName, $context);
87
        $fies = '<?php' . PHP_EOL;
88
        foreach ($this->classes as $class) {
89
            $isAutoloadFailed = ! class_exists($class, false) && ! interface_exists($class, false) && ! trait_exists($class, false); // could be phpdoc tag by anotation loader
90
            if ($isAutoloadFailed) {
91
                continue;
92
            }
93
            $fies .= sprintf(
94
                "require %s';\n",
95
                $this->getRelaticePath($appDir, (new \ReflectionClass($class))->getFileName())
96
            );
97
        }
98
        $fies .= "require __DIR__ . '/vendor/autoload.php';" . PHP_EOL . PHP_EOL;
99
        $loaderFile = realpath($appDir . '/autoload.php');
100
        file_put_contents($loaderFile, $fies);
101
102
        return $loaderFile;
103
    }
104
105
    private function getRelaticePath(string $rootDir, string $file)
106
    {
107
        if (strpos($file, $rootDir) !== false) {
108
            return str_replace("{$rootDir}", "__DIR__ . '", $file);
109
        }
110
111
        return "'" . $file;
112
    }
113
114
    private function invokeTypicalReuqest(string $appName, string $context)
115
    {
116
        $app = (new Bootstrap)->getApp($appName, $context);
117 1
        $ro = new NullPage;
118
        $ro->uri = new Uri('app://self/');
119 1
        $app->resource->get->object($ro)();
0 ignored issues
show
Bug introduced by
Accessing get on the interface BEAR\Resource\ResourceInterface 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...
120 1
    }
121 1
122 1
    private function scanClass(InjectorInterface $injector, Reader $reader, NamedParameterInterface $namedParams, string $className)
123 1
    {
124 1
        try {
125 1
            $instance = $injector->getInstance($className);
126 1
        } catch (\Exception $e) {
127
            error_log(sprintf('Failed to instantiate [%s]: %s(%s)', $className, get_class($e), $e->getMessage()));
128 1
129
            return;
130 1
        }
131
        $class = new \ReflectionClass($className);
132 1
        $reader->getClassAnnotations($class);
133
        $methods = $class->getMethods();
134 1
        foreach ($methods as $method) {
135
            $methodName = $method->getName();
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
136 1
            if ($this->isMagicMethod($methodName)) {
137
                continue;
138
            }
139 1
            $this->saveNamedParam($namedParams, $instance, $methodName);
140
            // method annotation
141
            $reader->getMethodAnnotations($method);
142 1
        }
143 1
    }
144
145
    private function isMagicMethod($method) : bool
146 1
    {
147 1
        return \in_array($method, ['__sleep', '__wakeup', 'offsetGet', 'offsetSet', 'offsetExists', 'offsetUnset', 'count', 'ksort', 'asort', 'jsonSerialize'], true);
148 1
    }
149
150 1
    private function saveNamedParam(NamedParameterInterface $namedParameter, $instance, string $method)
151
    {
152 1
        // named parameter
153
        if (! \in_array($method, ['onGet', 'onPost', 'onPut', 'onPatch', 'onDelete', 'onHead'], true)) {
154 1
            return;
155
        }
156 1
        try {
157 1
            $namedParameter->getParameters([$instance, $method], []);
158 1
        } catch (ParameterException $e) {
159
            return;
160 1
        }
161 1
    }
162
163
    private function saveCompileLog(AbstractAppMeta $appMeta, string $context, string $logFile)
164
    {
165
        $module = (new Module)($appMeta, $context);
166
        /** @var AbstractModule $module */
167
        $container = $module->getContainer();
168
        foreach ($appMeta->getResourceListGenerator() as list($class)) {
169
            new Bind($container, $class);
170
        }
171
        file_put_contents($logFile, (string) $module);
172
    }
173
}
174