Completed
Push — fix-compile ( d74749 )
by Akihito
04:38
created

Compiler::getRelativePath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 5
cp 0
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 6
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->getRelativePath($appDir, (new \ReflectionClass($class))->getFileName())
96
            );
97
        }
98
        $fies .= "require __DIR__ . '/vendor/autoload.php';" . PHP_EOL;
99
        $loaderFile = realpath($appDir . '/autoload.php');
100
        file_put_contents($loaderFile, $fies);
101
102
        return $loaderFile;
103
    }
104
105
    private function getRelativePath(string $rootDir, string $file)
106
    {
107
        $dir = realpath($rootDir);
108
        if (strpos($file, $dir) !== false) {
109
            return str_replace("{$dir}", "__DIR__ . '", $file);
110
        }
111
112
        return "'" . $file;
113
    }
114
115
    private function invokeTypicalReuqest(string $appName, string $context)
116
    {
117
        $app = (new Bootstrap)->getApp($appName, $context);
118
        $ro = new NullPage;
119
        $ro->uri = new Uri('app://self/');
120
        $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...
121
    }
122
123 1
    private function scanClass(InjectorInterface $injector, Reader $reader, NamedParameterInterface $namedParams, string $className)
124
    {
125
        try {
126 1
            $instance = $injector->getInstance($className);
127
        } catch (\Exception $e) {
128
            error_log(sprintf('Failed to instantiate [%s]: %s(%s)', $className, get_class($e), $e->getMessage()));
129
130
            return;
131
        }
132 1
        $class = new \ReflectionClass($className);
133 1
        $reader->getClassAnnotations($class);
134 1
        $methods = $class->getMethods();
135 1
        foreach ($methods as $method) {
136 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...
137 1
            if ($this->isMagicMethod($methodName)) {
138 1
                continue;
139
            }
140 1
            $this->saveNamedParam($namedParams, $instance, $methodName);
141
            // method annotation
142 1
            $reader->getMethodAnnotations($method);
143
        }
144 1
    }
145
146 1
    private function isMagicMethod($method) : bool
147
    {
148 1
        return \in_array($method, ['__sleep', '__wakeup', 'offsetGet', 'offsetSet', 'offsetExists', 'offsetUnset', 'count', 'ksort', 'asort', 'jsonSerialize'], true);
149
    }
150
151 1
    private function saveNamedParam(NamedParameterInterface $namedParameter, $instance, string $method)
152
    {
153
        // named parameter
154 1
        if (! \in_array($method, ['onGet', 'onPost', 'onPut', 'onPatch', 'onDelete', 'onHead'], true)) {
155 1
            return;
156
        }
157
        try {
158 1
            $namedParameter->getParameters([$instance, $method], []);
159 1
        } catch (ParameterException $e) {
160 1
            return;
161
        }
162 1
    }
163
164 1
    private function saveCompileLog(AbstractAppMeta $appMeta, string $context, string $logFile)
165
    {
166 1
        $module = (new Module)($appMeta, $context);
167
        /** @var AbstractModule $module */
168 1
        $container = $module->getContainer();
169 1
        foreach ($appMeta->getResourceListGenerator() as list($class)) {
170 1
            new Bind($container, $class);
171
        }
172 1
        file_put_contents($logFile, (string) $module);
173 1
    }
174
}
175