Completed
Push — 2.x ( eb2bc2...ce9b15 )
by Akihito
03:04
created

Compiler::getNewClassName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
crap 1
1
<?php
2
/**
3
 * This file is part of the Ray.Aop package
4
 *
5
 * @license http://opensource.org/licenses/MIT MIT
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
        );
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 UntrustedInclusionInspection */
69
            /** @noinspection PhpIncludeInspection */
70 1
            include $file;
71
72 1
            return $newClass;
73
        }
74 8
        $this->includeGeneratedCode($newClass, new ReflectionClass($class), $file, $bind);
75
76 8
        return $newClass;
77
    }
78
79
    /**
80
     * @param string        $class
81
     * @param BindInterface $bind
82
     *
83
     * @return bool
84
     */
85 17
    private function hasNoBinding($class, BindInterface $bind)
86
    {
87 17
        $hasMethod = $this->hasBoundMethod($class, $bind);
88
89 17
        return ! $bind->getBindings() && ! $hasMethod;
90
    }
91
92
    /**
93
     * @param string        $class
94
     * @param BindInterface $bind
95
     *
96
     * @return string
97
     */
98 16
    private function getNewClassName($class, BindInterface $bind)
99
    {
100 16
        $fileTime = filemtime((new \ReflectionClass($class))->getFileName());
101 16
        $newClass = sprintf('%s_%s', str_replace('\\', '_', $class), $bind->toString($fileTime));
102
103 16
        return $newClass;
104
    }
105
106
    /**
107
     * @param string        $class
108
     * @param BindInterface $bind
109
     *
110
     * @return bool
111
     */
112 17
    private function hasBoundMethod($class, BindInterface $bind)
113
    {
114 17
        $bindingMethods = array_keys($bind->getBindings());
115 17
        $hasMethod = false;
116 17
        foreach ($bindingMethods as $bindingMethod) {
117 16
            if (method_exists($class, $bindingMethod)) {
118 15
                $hasMethod = true;
119
            }
120
        }
121
122 17
        return $hasMethod;
123
    }
124
125
    /**
126
     * @param string           $newClass
127
     * @param \ReflectionClass $sourceClass
128
     * @param string           $file
129
     * @param BindInterface    $bind
130
     */
131 8
    private function includeGeneratedCode($newClass, \ReflectionClass $sourceClass, $file, BindInterface $bind)
132
    {
133 8
        $code = $this->codeGen->generate($newClass, $sourceClass, $bind);
134 8
        file_put_contents($file, '<?php ' . PHP_EOL . $code);
135
        /** @noinspection PhpIncludeInspection */
136 8
        require $file;
137 8
    }
138
}
139