Completed
Push — master ( f04f52...152406 )
by Anton
03:17
created

Printer::useStmts()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license MIT
6
 * @author  Valentin V (Vvval)
7
 */
8
declare(strict_types=1);
9
10
namespace Cycle\ORM\Promise;
11
12
use Cycle\ORM\ORMInterface;
13
use Cycle\ORM\Promise\Declaration;
14
use Cycle\ORM\Promise\Exception\ProxyFactoryException;
15
use PhpParser\Lexer;
16
use PhpParser\Node;
17
use PhpParser\Parser;
18
use PhpParser\PrettyPrinter\Standard;
19
use PhpParser\PrettyPrinterAbstract;
20
21
final class Printer
22
{
23
    private const RESOLVER_PROPERTY      = '__resolver';
24
    private const UNSET_PROPERTIES_CONST = 'UNSET_PROPERTIES';
25
    private const INIT_METHOD            = '__init';
26
27
    private const LOADED_METHOD  = '__loaded';
28
    private const ROLE_METHOD    = '__role';
29
    private const SCOPE_METHOD   = '__scope';
30
    private const RESOLVE_METHOD = '__resolve';
31
32
    private const DEPENDENCIES = [
33
        'orm'   => ORMInterface::class,
34
        'role'  => 'string',
35
        'scope' => 'array'
36
    ];
37
38
    private const USE_STMTS = [
39
        PromiseInterface::class,
40
        Resolver::class,
41
        ProxyFactoryException::class,
42
        ORMInterface::class
43
    ];
44
45
    private const PROMISE_METHODS = [
46
        self::LOADED_METHOD  => 'bool',
47
        self::ROLE_METHOD    => 'string',
48
        self::SCOPE_METHOD   => 'array',
49
        self::RESOLVE_METHOD => null,
50
    ];
51
52
    /** @var ConflictResolver */
53
    private $resolver;
54
55
    /** @var Traverser */
56
    private $traverser;
57
58
    /** @var Declaration\Extractor */
59
    private $extractor;
60
61
    /** @var Lexer */
62
    private $lexer;
63
64
    /** @var Parser */
65
    private $parser;
66
67
    /** @var PrettyPrinterAbstract */
68
    private $printer;
69
70
    /** @var Stubs */
71
    private $stubs;
72
73
    /**
74
     * @param ConflictResolver      $resolver
75
     * @param Traverser             $traverser
76
     * @param Declaration\Extractor $extractor
77
     * @param Stubs                 $stubs
78
     */
79
    public function __construct(
80
        ConflictResolver $resolver = null,
81
        Traverser $traverser = null,
82
        Declaration\Extractor $extractor = null,
83
        Stubs $stubs = null
84
    ) {
85
        $this->resolver = $resolver ?? new ConflictResolver();
86
        $this->traverser = $traverser ?? new Traverser();
87
        $this->extractor = $extractor ?? new Declaration\Extractor();
88
89
        $lexer = new Lexer\Emulative([
90
            'usedAttributes' => [
91
                'comments',
92
                'startLine',
93
                'endLine',
94
                'startTokenPos',
95
                'endTokenPos',
96
            ],
97
        ]);
98
99
        $this->lexer = $lexer;
100
        $this->parser = new Parser\Php7($this->lexer);
101
102
        $this->printer = new Standard();
103
        $this->stubs = $stubs ?? new Stubs();
104
    }
105
106
    /**
107
     * @param \ReflectionClass                 $reflection
108
     * @param Declaration\DeclarationInterface $class
109
     * @param Declaration\DeclarationInterface $parent
110
     * @return string
111
     *
112
     * @throws ProxyFactoryException
113
     */
114
    public function make(
115
        \ReflectionClass $reflection,
116
        Declaration\DeclarationInterface $class,
117
        Declaration\DeclarationInterface $parent
118
    ): string {
119
        $structure = $this->extractor->extract($reflection);
120
        foreach ($structure->methodNames() as $name) {
121
            if (array_key_exists($name, self::PROMISE_METHODS)) {
122
                throw new ProxyFactoryException("Promise method `$name` already defined.");
123
            }
124
        }
125
126
        $property = $this->resolverPropertyName($structure);
127
        $unsetPropertiesConst = $this->unsetPropertiesConstName($structure);
128
129
        $visitors = [
130
            new Visitor\AddUseStmts($this->useStmts($class, $parent)),
131
            new Visitor\UpdateNamespace($class->getNamespaceName()),
132
            new Visitor\DeclareClass($class->getShortName(), $parent->getShortName(),
133
                Utils::shortName(PromiseInterface::class)),
134
            new Visitor\AddUnsetPropertiesConst($unsetPropertiesConst, $structure->properties),
135
            new Visitor\AddResolverProperty($property, $this->propertyType(), $parent->getShortName()),
136
            new Visitor\AddInitMethod(
137
                $property,
138
                $this->propertyType(),
139
                self::DEPENDENCIES,
140
                $this->unsetPropertiesConstName($structure),
141
                $this->initMethodName($structure)
142
            ),
143
            new Visitor\AddMagicCloneMethod($property, $structure->hasClone),
144
            new Visitor\AddMagicGetMethod($property, self::RESOLVE_METHOD),
145
            new Visitor\AddMagicSetMethod($property, self::RESOLVE_METHOD),
146
            new Visitor\AddMagicIssetMethod($property, self::RESOLVE_METHOD, $unsetPropertiesConst),
147
            new Visitor\AddMagicUnset($property, self::RESOLVE_METHOD, $unsetPropertiesConst),
148
            new Visitor\AddMagicDebugInfoMethod(
149
                $property,
150
                self::RESOLVE_METHOD,
151
                self::LOADED_METHOD,
152
                self::ROLE_METHOD,
153
                self::SCOPE_METHOD,
154
                $structure->properties
155
            ),
156
            new Visitor\UpdatePromiseMethods($property),
157
            new Visitor\AddProxiedMethods($property, $structure->methods, self::RESOLVE_METHOD),
158
        ];
159
160
        foreach (self::PROMISE_METHODS as $method => $returnType) {
161
            $visitors[] = new Visitor\AddPromiseMethod($property, $method, $returnType);
162
        }
163
164
        $nodes = $this->getNodesFromStub();
165
        $output = $this->traverser->traverseClonedNodes($nodes, ...$visitors);
166
167
        return $this->printer->printFormatPreserving(
168
            $output,
169
            $nodes,
170
            $this->lexer->getTokens()
171
        );
172
    }
173
174
    /**
175
     * @param Declaration\Structure $structure
176
     * @return string
177
     */
178
    public function initMethodName(Declaration\Structure $structure): string
179
    {
180
        return $this->resolver->resolve($structure->methodNames(), self::INIT_METHOD)->fullName();
181
    }
182
183
    /**
184
     * @param Declaration\Structure $structure
185
     * @return string
186
     */
187
    private function resolverPropertyName(Declaration\Structure $structure): string
188
    {
189
        return $this->resolver->resolve($structure->properties, self::RESOLVER_PROPERTY)->fullName();
190
    }
191
192
    /**
193
     * @param Declaration\Structure $structure
194
     * @return string
195
     */
196
    private function unsetPropertiesConstName(Declaration\Structure $structure): string
197
    {
198
        return $this->resolver->resolve($structure->constants, self::UNSET_PROPERTIES_CONST)->fullName('_');
199
    }
200
201
    /**
202
     * @param Declaration\DeclarationInterface $class
203
     * @param Declaration\DeclarationInterface $parent
204
     * @return array
205
     */
206
    private function useStmts(Declaration\DeclarationInterface $class, Declaration\DeclarationInterface $parent): array
207
    {
208
        $useStmts = self::USE_STMTS;
209
        if ($class->getNamespaceName() !== $parent->getNamespaceName()) {
210
            $useStmts[] = $parent->getFullName();
211
        }
212
213
        return $useStmts;
214
    }
215
216
    /**
217
     * @return string
218
     */
219
    private function propertyType(): string
220
    {
221
        return Utils::shortName(Resolver::class);
222
    }
223
224
    /**
225
     * @return Node\Stmt[]
226
     */
227
    private function getNodesFromStub(): array
228
    {
229
        return $this->parser->parse($this->stubs->getContent()) ?? [];
230
    }
231
}