Compiler   A
last analyzed

Complexity

Total Complexity 15

Size/Duplication

Total Lines 118
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 13
Bugs 1 Features 1
Metric Value
wmc 15
eloc 42
c 13
b 1
f 1
dl 0
loc 118
ccs 49
cts 49
cp 1
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A hasBoundMethod() 0 11 3
A __construct() 0 15 2
A requireFile() 0 7 1
A __wakeup() 0 3 1
A __sleep() 0 3 1
A compile() 0 14 3
A hasNoBinding() 0 5 2
A newInstance() 0 10 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Aop;
6
7
use Doctrine\Common\Annotations\AnnotationException;
8
use PhpParser\BuilderFactory;
9
use Ray\Aop\Exception\NotWritableException;
10
11
use function array_keys;
12
use function assert;
13
use function class_exists;
14
use function file_exists;
15
use function is_writable;
16
use function method_exists;
17
18
final class Compiler implements CompilerInterface
19
{
20
    /** @var string */
21
    public $classDir;
22
23
    /** @var CodeGenInterface */
24
    private $codeGen;
25
26
    /** @var AopClassName */
27 24
    private $aopClassName;
28
29 24
    /**
30 1
     * @throws AnnotationException
31
     */
32 24
    public function __construct(string $classDir)
33 24
    {
34 24
        if (! is_writable($classDir)) {
35 24
            throw new NotWritableException($classDir);
36 24
        }
37
38 24
        $this->classDir = $classDir;
39
        $this->aopClassName = new AopClassName($classDir);
40 1
        $parser = (new ParserFactory())->newInstance();
41
        $factory = new BuilderFactory();
42 1
        $aopClassName = new AopClassName($classDir);
43
        $this->codeGen = new CodeGen(
44
            $factory,
45 1
            new VisitorFactory($parser),
46
            new AopClass($parser, $factory, $aopClassName)
47 1
        );
48 1
    }
49
50
    /**
51
     * @return list<string>
0 ignored issues
show
Bug introduced by
The type Ray\Aop\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
52
     */
53 10
    public function __sleep()
54
    {
55 10
        return ['classDir'];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('classDir') returns the type array<integer,string> which is incompatible with the documented return type Ray\Aop\list.
Loading history...
56 10
    }
57 10
58
    /**
59 10
     * @throws AnnotationException
60
     */
61
    public function __wakeup()
62
    {
63
        $this->__construct($this->classDir);
64
    }
65 18
66
    /**
67 18
     * {@inheritdoc}
68 1
     */
69
    public function newInstance(string $class, array $args, BindInterface $bind)
70 17
    {
71 17
        $compiledClass = $this->compile($class, $bind);
72 7
        assert(class_exists($compiledClass));
73
        $instance = (new ReflectionClass($compiledClass))->newInstanceArgs($args);
74 10
        if (isset($instance->bindings)) {
75 10
            $instance->bindings = $bind->getBindings();
76
        }
77
78 1
        return $instance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $instance also could return the type object which is incompatible with the return type mandated by Ray\Aop\CompilerInterface::newInstance() of object.
Loading history...
79
    }
80 1
81
    /**
82 9
     * {@inheritdoc}
83
     */
84 9
    public function compile(string $class, BindInterface $bind): string
85
    {
86
        if ($this->hasNoBinding($class, $bind)) {
87 18
            return $class;
88
        }
89 18
90
        $aopClassName = ($this->aopClassName)($class, $bind->toString(''));
91 18
        if (class_exists($aopClassName, false)) {
92
            return $aopClassName;
93
        }
94 17
95
        $this->requireFile($aopClassName, new ReflectionClass($class), $bind);
96 17
97
        return $aopClassName;
98 17
    }
99
100
    /**
101 18
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
102
     */
103 18
    private function hasNoBinding(string $class, BindInterface $bind): bool
104 18
    {
105 18
        $hasMethod = $this->hasBoundMethod($class, $bind);
106 17
107 17
        return ! $bind->getBindings() && ! $hasMethod;
108
    }
109
110
    /**
111 18
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
112
     */
113
    private function hasBoundMethod(string $class, BindInterface $bind): bool
114 9
    {
115
        $bindingMethods = array_keys($bind->getBindings());
116 9
        $hasMethod = false;
117 9
        foreach ($bindingMethods as $bindingMethod) {
118
            if (method_exists($class, $bindingMethod)) {
119 9
                $hasMethod = true;
120 9
            }
121
        }
122
123
        return $hasMethod;
124
    }
125
126
    /**
127
     * @param \ReflectionClass<object> $sourceClass
128
     */
129
    private function requireFile(string $aopClassName, \ReflectionClass $sourceClass, BindInterface $bind): void
130
    {
131
        $code = $this->codeGen->generate($sourceClass, $bind);
132
        $file = $code->save($this->classDir, $aopClassName);
133
        assert(file_exists($file));
134
        require_once $file;
135
        class_exists($aopClassName); // ensue class is created
136
    }
137
}
138