Completed
Pull Request — 1.x (#302)
by kazusuke
02:38
created

Compiler   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 157
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 14

Test Coverage

Coverage 58.14%

Importance

Changes 0
Metric Value
wmc 24
lcom 2
cbo 14
dl 0
loc 157
ccs 50
cts 86
cp 0.5814
rs 10
c 0
b 0
f 0

9 Methods

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