Printer::make()   B
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 87

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 5
nop 3
dl 0
loc 87
rs 8.2836
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
use ReflectionClass;
23
use ReflectionException;
24
25
final class Printer
26
{
27
    public const RESOLVER_PROPERTY       = 'resolver';
28
    public const UNSET_PROPERTIES_CONST  = 'UNSET_PROPERTIES';
29
    public const PUBLIC_PROPERTIES_CONST = 'PUBLIC_PROPERTIES';
30
    public const INIT_METHOD             = '__init';
31
32
    private const LOADED_METHOD  = '__loaded';
33
    private const ROLE_METHOD    = '__role';
34
    private const SCOPE_METHOD   = '__scope';
35
    private const RESOLVE_METHOD = '__resolve';
36
37
    private const DEPENDENCIES = [
38
        'orm'   => ORMInterface::class,
39
        'role'  => 'string',
40
        'scope' => 'array'
41
    ];
42
43
    private const USE_STMTS = [
44
        PromiseInterface::class,
45
        Resolver::class,
46
        ProxyFactoryException::class,
47
        ORMInterface::class
48
    ];
49
50
    private const PROMISE_METHODS = [
51
        self::LOADED_METHOD  => 'bool',
52
        self::ROLE_METHOD    => 'string',
53
        self::SCOPE_METHOD   => 'array',
54
        self::RESOLVE_METHOD => null,
55
    ];
56
57
    /** @var ConflictResolver */
58
    private $resolver;
59
60
    /** @var Traverser */
61
    private $traverser;
62
63
    /** @var Declaration\Extractor */
64
    private $extractor;
65
66
    /** @var Lexer */
67
    private $lexer;
68
69
    /** @var Parser */
70
    private $parser;
71
72
    /** @var PrettyPrinterAbstract */
73
    private $printer;
74
75
    /**
76
     * @param Declaration\Extractor $extractor
77
     * @param ConflictResolver      $resolver
78
     * @param Traverser             $traverser
79
     */
80
    public function __construct(
81
        Declaration\Extractor $extractor,
82
        Traverser $traverser,
83
        ConflictResolver $resolver
84
    ) {
85
        $this->resolver = $resolver;
86
        $this->traverser = $traverser;
87
        $this->extractor = $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
    }
104
105
    /**
106
     * @param ReflectionClass                  $reflection
107
     * @param Declaration\DeclarationInterface $class
108
     * @param Declaration\DeclarationInterface $parent
109
     * @return string
110
     * @throws ProxyFactoryException
111
     * @throws ReflectionException
112
     */
113
    public function make(
114
        ReflectionClass $reflection,
115
        Declaration\DeclarationInterface $class,
116
        Declaration\DeclarationInterface $parent
117
    ): string {
118
        $structure = $this->extractor->extract($reflection);
119
        foreach ($structure->methodNames() as $name) {
120
            if (array_key_exists($name, self::PROMISE_METHODS)) {
121
                throw new ProxyFactoryException("Promise method `$name` already defined.");
122
            }
123
        }
124
125
        $resolverProperty = $this->resolverPropertyName($structure);
126
        $publicPropertiesConst = $this->publicPropertiesConstName($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(
133
                $class->getShortName(),
134
                $parent->getShortName(),
135
                shortName(PromiseInterface::class)
136
            ),
137
            new Visitor\AddPropertiesConst($unsetPropertiesConst, $structure->toBeUnsetProperties()),
138
            new Visitor\AddPropertiesConst($publicPropertiesConst, $structure->publicProperties()),
139
            new Visitor\AddResolverProperty($resolverProperty, $this->propertyType(), $parent->getShortName()),
140
            new Visitor\AddInitMethod(
141
                $resolverProperty,
142
                $this->propertyType(),
143
                self::DEPENDENCIES,
144
                $unsetPropertiesConst,
145
                $this->initMethodName($structure)
146
            ),
147
            new Visitor\AddMagicCloneMethod($resolverProperty, $structure->hasClone),
148
            new Visitor\AddMagicGetMethod(
149
                $parent->getFullName(),
150
                $resolverProperty,
151
                self::RESOLVE_METHOD
152
            ),
153
            new Visitor\AddMagicSetMethod(
154
                $parent->getFullName(),
155
                $resolverProperty,
156
                self::RESOLVE_METHOD
157
            ),
158
            new Visitor\AddMagicIssetMethod(
159
                $parent->getFullName(),
160
                $resolverProperty,
161
                self::RESOLVE_METHOD,
162
                $publicPropertiesConst
163
            ),
164
            new Visitor\AddMagicUnset(
165
                $parent->getFullName(),
166
                $resolverProperty,
167
                self::RESOLVE_METHOD,
168
                $publicPropertiesConst
169
            ),
170
            new Visitor\AddMagicDebugInfoMethod(
171
                $resolverProperty,
172
                self::RESOLVE_METHOD,
173
                self::LOADED_METHOD,
174
                self::ROLE_METHOD,
175
                self::SCOPE_METHOD,
176
                $structure->properties()
177
            ),
178
            new Visitor\UpdatePromiseMethods($resolverProperty),
179
            new Visitor\AddProxiedMethods(
180
                $parent->getFullName(),
181
                $resolverProperty,
182
                $structure->methods,
183
                self::RESOLVE_METHOD
184
            ),
185
        ];
186
187
        foreach (self::PROMISE_METHODS as $method => $returnType) {
188
            $visitors[] = new Visitor\AddPromiseMethod($resolverProperty, $method, $returnType);
189
        }
190
191
        $nodes = $this->getNodesFromStub();
192
        $output = $this->traverser->traverseClonedNodes($nodes, ...$visitors);
193
194
        return $this->printer->printFormatPreserving(
195
            $output,
196
            $nodes,
197
            $this->lexer->getTokens()
198
        );
199
    }
200
201
    /**
202
     * @param Declaration\Structure $structure
203
     * @return string
204
     */
205
    public function initMethodName(Declaration\Structure $structure): string
206
    {
207
        return $this->resolver->resolve($structure->methodNames(), self::INIT_METHOD)->fullName();
208
    }
209
210
    /**
211
     * @param Declaration\Structure $structure
212
     * @return string
213
     */
214
    private function resolverPropertyName(Declaration\Structure $structure): string
215
    {
216
        return $this->resolver->resolve($structure->properties(), self::RESOLVER_PROPERTY)->fullName();
217
    }
218
219
    /**
220
     * @param Declaration\Structure $structure
221
     * @return string
222
     */
223
    private function unsetPropertiesConstName(Declaration\Structure $structure): string
224
    {
225
        return $this->resolver->resolve($structure->constants, self::UNSET_PROPERTIES_CONST)->fullName('_');
226
    }
227
228
    /**
229
     * @param Declaration\Structure $structure
230
     * @return string
231
     */
232
    private function publicPropertiesConstName(Declaration\Structure $structure): string
233
    {
234
        return $this->resolver->resolve($structure->constants, self::PUBLIC_PROPERTIES_CONST)->fullName('_');
235
    }
236
237
    /**
238
     * @param Declaration\DeclarationInterface $class
239
     * @param Declaration\DeclarationInterface $parent
240
     * @return array
241
     */
242
    private function useStmts(Declaration\DeclarationInterface $class, Declaration\DeclarationInterface $parent): array
243
    {
244
        $useStmts = self::USE_STMTS;
245
        if ($class->getNamespaceName() !== $parent->getNamespaceName()) {
246
            $useStmts[] = $parent->getFullName();
247
        }
248
249
        return $useStmts;
250
    }
251
252
    /**
253
     * @return string
254
     */
255
    private function propertyType(): string
256
    {
257
        return shortName(Resolver::class);
258
    }
259
260
    /**
261
     * @return Node\Stmt[]
262
     */
263
    private function getNodesFromStub(): array
264
    {
265
        return $this->parser->parse(getStubContent()) ?? [];
266
    }
267
}
268