Completed
Push — namespace ( 973949 )
by Akihito
02:15
created

Compiler::getBaseName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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