Completed
Pull Request — master (#3)
by Valentin
01:55
created

Printer   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 166
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 30

Importance

Changes 0
Metric Value
wmc 12
lcom 1
cbo 30
dl 0
loc 166
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 22 1
A make() 0 48 4
A initMethodName() 0 4 1
A resolverPropertyName() 0 4 1
A unsetPropertiesConstName() 0 4 1
A useStmts() 0 9 2
A propertyType() 0 4 1
A getNodesFromStub() 0 4 1
1
<?php
2
declare(strict_types=1);
3
4
namespace Cycle\ORM\Promise;
5
6
use Cycle\ORM\ORMInterface;
7
use Cycle\ORM\Promise\Declaration;
8
use PhpParser\Lexer;
9
use PhpParser\Node;
10
use PhpParser\Parser;
11
use PhpParser\PrettyPrinter\Standard;
12
use PhpParser\PrettyPrinterAbstract;
13
14
class Printer
15
{
16
    private const RESOLVER_PROPERTY      = '__resolver';
17
    private const UNSET_PROPERTIES_CONST = 'UNSET_PROPERTIES';
18
    private const RESOLVE_METHOD         = '__resolve';
19
    private const INIT_METHOD            = '__init';
20
21
    private const DEPENDENCIES = [
22
        'orm'   => ORMInterface::class,
23
        'role'  => 'string',
24
        'scope' => 'array'
25
    ];
26
27
    private const USE_STMTS = [
28
        PromiseInterface::class,
29
        PromiseResolver::class,
30
        PromiseException::class,
31
        ORMInterface::class
32
    ];
33
34
    private const PROMISE_METHODS = [
35
        '__loaded'  => 'bool',
36
        '__role'    => 'string',
37
        '__scope'   => 'array',
38
        '__resolve' => null,
39
    ];
40
41
    /** @var ConflictResolver */
42
    private $resolver;
43
44
    /** @var Traverser */
45
    private $traverser;
46
47
    /** @var Declaration\Extractor */
48
    private $extractor;
49
50
    /** @var Lexer */
51
    private $lexer;
52
53
    /** @var Parser */
54
    private $parser;
55
56
    /** @var PrettyPrinterAbstract */
57
    private $printer;
58
59
    /** @var Stubs */
60
    private $stubs;
61
62
    public function __construct(ConflictResolver $resolver, Traverser $traverser, Declaration\Extractor $extractor, Stubs $stubs)
63
    {
64
        $this->resolver = $resolver;
65
        $this->traverser = $traverser;
66
        $this->extractor = $extractor;
67
68
        $lexer = new Lexer\Emulative([
69
            'usedAttributes' => [
70
                'comments',
71
                'startLine',
72
                'endLine',
73
                'startTokenPos',
74
                'endTokenPos',
75
            ],
76
        ]);
77
78
        $this->lexer = $lexer;
79
        $this->parser = new Parser\Php7($this->lexer);
80
81
        $this->printer = new Standard();
82
        $this->stubs = $stubs;
83
    }
84
85
    /**
86
     * @param \ReflectionClass                 $reflection
87
     * @param Declaration\DeclarationInterface $class
88
     * @param Declaration\DeclarationInterface $parent
89
     *
90
     * @return string
91
     * @throws \Cycle\ORM\Promise\ProxyFactoryException
92
     */
93
    public function make(\ReflectionClass $reflection, Declaration\DeclarationInterface $class, Declaration\DeclarationInterface $parent): string
94
    {
95
        $structure = $this->extractor->extract($reflection);
96
        foreach ($structure->methodNames() as $name) {
97
            if (array_key_exists($name, self::PROMISE_METHODS)) {
98
                throw new ProxyFactoryException("Promise method `$name` already defined.");
99
            }
100
        }
101
102
        $property = $this->resolverPropertyName($structure);
103
        $unsetPropertiesConst = $this->unsetPropertiesConstName($structure);
104
105
        $visitors = [
106
            new Visitor\AddUseStmts($this->useStmts($class, $parent)),
107
            new Visitor\UpdateNamespace($class->getNamespaceName()),
108
            new Visitor\DeclareClass($class->getShortName(), $parent->getShortName(), Utils::shortName(PromiseInterface::class)),
109
            new Visitor\AddUnsetPropertiesConst($unsetPropertiesConst, $structure->properties),
110
            new Visitor\AddResolverProperty($property, $this->propertyType(), $parent->getShortName()),
111
            new Visitor\AddInitMethod(
112
                $property,
113
                $this->propertyType(),
114
                self::DEPENDENCIES,
115
                $this->unsetPropertiesConstName($structure),
116
                $this->initMethodName($structure)
117
            ),
118
            new Visitor\AddMagicCloneMethod($property, $structure->hasClone),
119
            new Visitor\AddMagicGetMethod($property, self::RESOLVE_METHOD),
120
            new Visitor\AddMagicSetMethod($property, self::RESOLVE_METHOD),
121
            new Visitor\AddMagicIssetMethod($property, self::RESOLVE_METHOD, $unsetPropertiesConst),
122
            new Visitor\AddMagicUnset($property, self::RESOLVE_METHOD, $unsetPropertiesConst),
123
            new Visitor\AddMagicDebugInfoMethod($property, self::RESOLVE_METHOD, $structure->properties),
124
            new Visitor\UpdatePromiseMethods($property),
125
            new Visitor\AddProxiedMethods($property, $structure->methods, self::RESOLVE_METHOD),
126
        ];
127
128
        foreach (self::PROMISE_METHODS as $method => $returnType) {
129
            $visitors[] = new Visitor\AddPromiseMethod($property, $method, $returnType);
130
        }
131
132
        $nodes = $this->getNodesFromStub();
133
        $output = $this->traverser->traverseClonedNodes($nodes, ...$visitors);
134
135
        return $this->printer->printFormatPreserving(
136
            $output,
137
            $nodes,
138
            $this->lexer->getTokens()
139
        );
140
    }
141
142
    public function initMethodName(Declaration\Structure $structure): string
143
    {
144
        return $this->resolver->resolve($structure->methodNames(), self::INIT_METHOD)->fullName();
145
    }
146
147
    private function resolverPropertyName(Declaration\Structure $structure): string
148
    {
149
        return $this->resolver->resolve($structure->properties, self::RESOLVER_PROPERTY)->fullName();
150
    }
151
152
    private function unsetPropertiesConstName(Declaration\Structure $structure): string
153
    {
154
        return $this->resolver->resolve($structure->constants, self::UNSET_PROPERTIES_CONST)->fullName('_');
155
    }
156
157
    private function useStmts(Declaration\DeclarationInterface $class, Declaration\DeclarationInterface $parent): array
158
    {
159
        $useStmts = self::USE_STMTS;
160
        if ($class->getNamespaceName() !== $parent->getNamespaceName()) {
161
            $useStmts[] = $parent->getFullName();
162
        }
163
164
        return $useStmts;
165
    }
166
167
    private function propertyType(): string
168
    {
169
        return Utils::shortName(PromiseResolver::class);
170
    }
171
172
    /**
173
     * @return Node\Stmt[]
174
     */
175
    private function getNodesFromStub(): array
176
    {
177
        return $this->parser->parse($this->stubs->getContent()) ?? [];
178
    }
179
}