Completed
Push — 2.x ( f14446...eb6a38 )
by Akihito
02:29
created

Compiler::newInstance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 8
ccs 0
cts 5
cp 0
rs 9.4286
cc 1
eloc 5
nc 1
nop 3
crap 2
1
<?php
2
/**
3
 * This file is part of the Ray.Aop package
4
 *
5
 * @license http://opensource.org/licenses/bsd-license.php BSD
6
 */
7
namespace Ray\Aop;
8
9
use PhpParser\BuilderFactory;
10
use PhpParser\Lexer;
11
use PhpParser\Parser;
12
use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter;
13
use Ray\Aop\Exception\NotWritableException;
14
use ReflectionClass;
15
16
final class Compiler implements CompilerInterface
17
{
18
    /**
19
     * @var string
20
     */
21
    public $classDir;
22
23
    /**
24
     * @var CodeGenInterface
25
     */
26
    private $codeGen;
27
28
    /**
29
     * @param string           $classDir
30
     * @param CodeGenInterface $codeGen
31
     */
32 16
    public function __construct($classDir, CodeGenInterface $codeGen = null)
33
    {
34 16
        if (! is_writable($classDir)) {
35 16
            throw new NotWritableException($classDir);
36
        }
37
        $this->classDir = $classDir;
38
        $this->codeGen = $codeGen ?: new CodeGen(
39
            new Parser(new Lexer()),
40
            new BuilderFactory(),
41
            new StandardPrettyPrinter()
42
        );
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function newInstance($class, array $args, BindInterface $bind)
49
    {
50
        $compiledClass = $this->compile($class, $bind);
51
        $instance = (new ReflectionClass($compiledClass))->newInstanceArgs($args);
52
        $instance->bindings = $bind->getBindings();
53
54
        return $instance;
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60
    public function compile($class, BindInterface $bind)
61
    {
62
        if ($this->hasNoBinding($class, $bind)) {
63
            return $class;
64
        }
65
        $newClass = $this->getNewClassName($class, $bind);
66
        if (class_exists($newClass)) {
67
            return $newClass;
68
        }
69
        $file = "{$this->classDir}/{$newClass}.php";
70
        if (file_exists($file)) {
71
            /** @noinspection PhpIncludeInspection */
72
            include $file;
73
74
            return $newClass;
75
        }
76
        $this->includeGeneratedCode($newClass, new ReflectionClass($class), $file, $bind);
77
78
        return $newClass;
79
    }
80
81
    /**
82
     * @param string        $class
83
     * @param BindInterface $bind
84
     *
85
     * @return bool
86
     */
87
    private function hasNoBinding($class, BindInterface $bind)
88
    {
89
        $hasMethod = $this->hasBoundMethod($class, $bind);
90
91
        return ! $bind->getBindings() && ! $hasMethod;
92
    }
93
94
    /**
95
     * @param string        $class
96
     * @param BindInterface $bind
97
     *
98
     * @return string
99
     */
100
    private function getNewClassName($class, BindInterface $bind)
101
    {
102
        $fileTime = filemtime((new \ReflectionClass($class))->getFileName());
103
        $newClass = sprintf("%s_%s", str_replace('\\', '_', $class), $bind->toString($fileTime));
104
105
        return $newClass;
106
    }
107
108
    /**
109
     * @param string        $class
110
     * @param BindInterface $bind
111
     *
112
     * @return bool
113
     */
114
    private function hasBoundMethod($class, BindInterface $bind)
115
    {
116
        $bindingMethods = array_keys($bind->getBindings());
117
        $hasMethod = false;
118
        foreach ($bindingMethods as $bindingMethod) {
119
            if (method_exists($class, $bindingMethod)) {
120
                $hasMethod = true;
121
            }
122
        }
123
124
        return $hasMethod;
125
    }
126
127
    /**
128
     * @param string          $newClass
129
     * @param ReflectionClass $sourceClass
130
     * @param string          $file
131
     */
132
    private function includeGeneratedCode($newClass, \ReflectionClass $sourceClass, $file, BindInterface $bind)
133
    {
134
        $code = $this->codeGen->generate($newClass, $sourceClass, $bind);
135
        file_put_contents($file, '<?php ' . PHP_EOL . $code);
136
        /** @noinspection PhpIncludeInspection */
137
        require $file;
138
    }
139
}
140