Completed
Branch 2.x (867c01)
by Akihito
09:30
created

Compiler::compile()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

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