Passed
Push — master ( 69c271...cb615c )
by Valentin
07:24
created

Printer::publicPropertiesConstName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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