Completed
Push — fix-compile ( 556ec5...72f72d )
by Akihito
04:02
created

Compiler::compileLoader()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 36.6903

Importance

Changes 0
Metric Value
dl 0
loc 36
ccs 4
cts 26
cp 0.1538
rs 8.4106
c 0
b 0
f 0
cc 7
nc 8
nop 3
crap 36.6903
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 1
        $msg = sprintf("Compile Log: %s\nautload.php: %s", $log, $loader);
40
41 1
        return $msg;
42
    }
43
44 1
    public function compileDiScripts(string $appName, string $context, string $appDir) : string
45
    {
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 1
        $reader = $injector->getInstance(AnnotationReader::class);
51
        /* @var $reader \Doctrine\Common\Annotations\Reader */
52 1
        $namedParams = $injector->getInstance(NamedParameterInterface::class);
53
        /* @var $namedParams NamedParameterInterface */
54
55
        // create DI factory class and AOP compiled class for all resources and save $app cache.
56 1
        (new Bootstrap)->newApp($appMeta, $context, $cache);
57
58
        // check resource injection and create annotation cache
59 1
        foreach ($appMeta->getResourceListGenerator() as list($className)) {
60 1
            $this->scanClass($injector, $reader, $namedParams, $className);
61
        }
62 1
        $logFile = realpath($appMeta->logDir) . '/compile.log';
63 1
        $this->saveCompileLog($appMeta, $context, $logFile);
64
65 1
        return $logFile;
66
    }
67
68 1
    private function compileLoader(string $appName, string $context, string $appDir) : string
69
    {
70 1
        $loaderFile = $appDir . '/vendor/autoload.php';
71 1
        if (! file_exists($loaderFile)) {
72 1
            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
        $ro = new NullPage;
118
        $ro->uri = new Uri('app://self/');
119
        $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
    }
121
122 1
    private function scanClass(InjectorInterface $injector, Reader $reader, NamedParameterInterface $namedParams, string $className)
123
    {
124
        try {
125 1
            $instance = $injector->getInstance($className);
126
        } catch (\Exception $e) {
127
            error_log(sprintf('Failed to instantiate [%s]: %s(%s)', $className, get_class($e), $e->getMessage()));
128
129
            return;
130
        }
131 1
        $class = new \ReflectionClass($className);
132 1
        $reader->getClassAnnotations($class);
133 1
        $methods = $class->getMethods();
134 1
        foreach ($methods as $method) {
135 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...
136 1
            if ($this->isMagicMethod($methodName)) {
137 1
                continue;
138
            }
139 1
            $this->saveNamedParam($namedParams, $instance, $methodName);
140
            // method annotation
141 1
            $reader->getMethodAnnotations($method);
142
        }
143 1
    }
144
145 1
    private function isMagicMethod($method) : bool
146
    {
147 1
        return \in_array($method, ['__sleep', '__wakeup', 'offsetGet', 'offsetSet', 'offsetExists', 'offsetUnset', 'count', 'ksort', 'asort', 'jsonSerialize'], true);
148
    }
149
150 1
    private function saveNamedParam(NamedParameterInterface $namedParameter, $instance, string $method)
151
    {
152
        // named parameter
153 1
        if (! \in_array($method, ['onGet', 'onPost', 'onPut', 'onPatch', 'onDelete', 'onHead'], true)) {
154 1
            return;
155
        }
156
        try {
157 1
            $namedParameter->getParameters([$instance, $method], []);
158 1
        } catch (ParameterException $e) {
159 1
            return;
160
        }
161 1
    }
162
163 1
    private function saveCompileLog(AbstractAppMeta $appMeta, string $context, string $logFile)
164
    {
165 1
        $module = (new Module)($appMeta, $context);
166
        /** @var AbstractModule $module */
167 1
        $container = $module->getContainer();
168 1
        foreach ($appMeta->getResourceListGenerator() as list($class)) {
169 1
            new Bind($container, $class);
170
        }
171 1
        file_put_contents($logFile, (string) $module);
172 1
    }
173
}
174