BuilderClassFactory::addGetObjectFqcnMethod()   B
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 12

Duplication

Lines 24
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 24
loc 24
ccs 0
cts 20
cp 0
rs 8.9713
c 2
b 0
f 0
cc 3
eloc 12
nc 3
nop 0
crap 12
1
<?php
2
3
namespace RunOpenCode\AbstractBuilder\Generator;
4
5
use PhpParser\Comment;
6
use PhpParser\Node\Param;
7
use PhpParser\Node\Stmt\ClassMethod;
8
use RunOpenCode\AbstractBuilder\Ast\BuilderFactory;
9
use RunOpenCode\AbstractBuilder\Ast\Metadata\ClassMetadata;
10
use RunOpenCode\AbstractBuilder\Ast\Metadata\FileMetadata;
11
use RunOpenCode\AbstractBuilder\Ast\Metadata\ParameterMetadata;
12
use RunOpenCode\AbstractBuilder\Ast\MetadataLoader;
13
use RunOpenCode\AbstractBuilder\Ast\Parser;
14
use RunOpenCode\AbstractBuilder\Exception\LogicException;
15
use RunOpenCode\AbstractBuilder\ReflectiveAbstractBuilder;
16
use RunOpenCode\AbstractBuilder\Utils\ClassUtils;
17
18
/**
19
 * Class BuilderClassFactory
20
 *
21
 * @package RunOpenCode\AbstractBuilder\Generator
22
 */
23
class BuilderClassFactory
24
{
25
    /**
26
     * @var BuilderFactory
27
     */
28
    private $factory;
29
30
    /**
31
     * @var \PhpParser\Parser
32
     */
33
    private $parser;
34
35
    /**
36
     * @var ClassMetadata
37
     */
38
    private $subject;
39
40
    /**
41
     * @var ClassMetadata
42
     */
43
    private $builder = null;
44
45
    /**
46
     * @var bool
47
     */
48
    private $withReturnTypeDeclaration;
49
50
    public function __construct(ClassMetadata $subject, ClassMetadata $builder = null, $withReturnTypeDeclaration = false)
51
    {
52
        $this->factory = BuilderFactory::getInstance();
53
        $this->parser = Parser::getInstance();
54
        $this->builder = $builder;
55
        $this->subject = $subject;
56
        $this->withReturnTypeDeclaration = $withReturnTypeDeclaration;
57
    }
58
59
    public function addBuildMethod()
60
    {
61
        if ($this->builder->hasPublicMethod('build', false)) {
62
            throw new LogicException(sprintf('Method "build()" is already defined in "%s".', $this->builder->getName()));
63
        }
64
65
        $method = $this->factory->method('build')
66
            ->makePublic()
67
            ->addStmts($this->parser->parse('<?php return parent::build();'))
0 ignored issues
show
Bug introduced by
It seems like $this->parser->parse('<?...turn parent::build();') targeting PhpParser\Parser::parse() can also be of type null; however, PhpParser\Builder\Declaration::addStmts() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
68
            ->setDocComment(sprintf(
69
                '
70
/**
71
 * Builds new instance of %s from provided arguments. 
72
 *
73
 * @return %s
74
 */', $this->subject->getFqcn(), $this->subject->getFqcn()));
75
76
        if ($this->withReturnTypeDeclaration) {
77
            $method->setReturnType($this->subject->getFqcn());
78
        }
79
80
        $this->appendMethod($method->getNode());
81
82
        return $this;
83
    }
84
85
    public function addCreateBuilderMethod()
86
    {
87
        if ($this->builder->hasPublicMethod('createBuilder', false)) {
88
            throw new LogicException(sprintf('Method "createBuilder()" is already defined in "%s".', $this->builder->getFqcn()));
89
        }
90
91
        if ($this->builder->isAbstract()) {
92
            throw new LogicException(sprintf('Method "createBuilder()" can not be generated for class "%s" since it\'s abstract.', $this->builder->getFqcn()));
93
        }
94
95
        $method = $this->factory->method('createBuilder')
96
            ->makePublic()
97
            ->makeStatic()
98
            ->addStmts($this->parser->parse('<?php return parent::createBuilder();'))
0 ignored issues
show
Bug introduced by
It seems like $this->parser->parse('<?...ent::createBuilder();') targeting PhpParser\Parser::parse() can also be of type null; however, PhpParser\Builder\Declaration::addStmts() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
99
            ->setDocComment(sprintf(
100
                '
101
/**
102
 * Produces new builder.
103
 *
104
 * @return %s
105
 *
106
 * @throws \RunOpenCode\AbstractBuilder\Exception\RuntimeException
107
 */', $this->builder->getFqcn()));
108
109
        if ($this->withReturnTypeDeclaration) {
110
            $method->setReturnType($this->builder->getFqcn());
111
        }
112
113
        $this->appendMethod($method->getNode());
114
115
        return $this;
116
    }
117
118 View Code Duplication
    public function addGetObjectFqcnMethod()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
119
    {
120
        if ($this->builder->hasPublicMethod('getObjectFqcn', false)) {
121
            throw new LogicException(sprintf('Method "getObjectFqcn()" is already defined in "%s".', $this->builder->getFqcn()));
122
        }
123
124
        $method = $this->factory->method('getObjectFqcn')
125
            ->makeProtected()
126
            ->addStmts($this->parser->parse(sprintf('<?php return %s::class;', $this->subject->getFqcn())))
0 ignored issues
show
Bug introduced by
It seems like $this->parser->parse(spr...s->subject->getFqcn())) targeting PhpParser\Parser::parse() can also be of type null; however, PhpParser\Builder\Declaration::addStmts() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
127
            ->setDocComment(
128
                '
129
/**
130
 * {@inheritdoc}
131
 */'
132
            );
133
134
        if ($this->withReturnTypeDeclaration) {
135
            $method->setReturnType('string');
136
        }
137
138
        $this->appendMethod($method->getNode());
139
140
        return $this;
141
    }
142
143 View Code Duplication
    public function addConfigureParametersMethod()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
144
    {
145
        if ($this->builder->hasPublicMethod('configureParameters', false)) {
146
            throw new LogicException(sprintf('Method "configureParameters()" is already defined in "%s".', $this->builder->getFqcn()));
147
        }
148
149
        $method = $this->factory->method('configureParameters')
150
            ->makeProtected()
151
            ->addStmts($this->parser->parse("<?php \$defaults = parent::configureParameters();\n// Modify default values here\nreturn \$defaults;"))
0 ignored issues
show
Bug introduced by
It seems like $this->parser->parse('<?...ere return $defaults;') targeting PhpParser\Parser::parse() can also be of type null; however, PhpParser\Builder\Declaration::addStmts() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
152
            ->setDocComment(
153
                '
154
/**
155
 * You can override default building parameter values here 
156
 *
157
 * {@inheritdoc}
158
 */'
159
            );
160
161
        if ($this->withReturnTypeDeclaration) {
162
            $method->setReturnType('array');
163
        }
164
165
        $this->appendMethod($method->getNode());
166
167
        return $this;
168
    }
169
170
    public function addGetter($name, ParameterMetadata $parameter)
171
    {
172
        $method = $this->factory->method($name)
173
            ->makePublic()
174
            ->addStmts($this->parser->parse(sprintf('<?php return $this->__doGet(\'%s\');', $parameter->getName())))
0 ignored issues
show
Bug introduced by
It seems like $this->parser->parse(spr...$parameter->getName())) targeting PhpParser\Parser::parse() can also be of type null; however, PhpParser\Builder\Declaration::addStmts() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
175
            ->setDocComment(sprintf(
176
                '
177
/**
178
 * Get value for constructor parameter %s 
179
 *
180
 * @return %s
181
 */', $parameter->getName(), $parameter->getType() ?: 'mixed'));
182
183
        if ($this->withReturnTypeDeclaration && $parameter->getAst()->type) {
184
            $method->setReturnType($parameter->getAst()->type);
185
        }
186
187
        $this->builder->getAst()->stmts[] = $method->getNode();
188
189
        return $this;
190
    }
191
192
    public function addSetter($name, ParameterMetadata $parameter)
193
    {
194
        $ast = $parameter->getAst();
195
196
        $method = $this->factory->method($name)
197
            ->makePublic()
198
            ->addParam(new Param(
199
                $ast->name,
200
                null,
201
                $ast->type,
202
                $ast->byRef,
203
                $ast->variadic,
204
                $ast->getAttributes()
205
            ))
206
            ->addStmts($this->parser->parse(sprintf('<?php return $this->__doSet(\'%s\', $%s);', $parameter->getName(), $parameter->getName())))
0 ignored issues
show
Bug introduced by
It seems like $this->parser->parse(spr...$parameter->getName())) targeting PhpParser\Parser::parse() can also be of type null; however, PhpParser\Builder\Declaration::addStmts() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
207
            ->setDocComment(sprintf(
208
                '
209
/**
210
 * Set value for constructor parameter %s 
211
 *
212
 * @return %s
213
 */', $parameter->getName(), $this->builder->getFqcn()));
214
215
        if ($this->withReturnTypeDeclaration) {
216
            $method->setReturnType($this->builder->getFqcn());
217
        }
218
219
        $this->appendMethod($method->getNode());
220
221
        return $this;
222
    }
223
224
    /**
225
     * Initializes new builder class
226
     *
227
     * @param string $filename
228
     * @param string $class
229
     *
230
     * @return FileMetadata
231
     */
232
    public function initialize($filename, $class)
233
    {
234
        $namespace = $this->factory->namespace($ns = ClassUtils::getNamespace($class));
235
236
        $builderAstFactory = $this->factory->class(ClassUtils::getShortName($class))
237
            ->extend('\\'.ReflectiveAbstractBuilder::class)
238
            ->setDocComment(sprintf(
239
                '
240
/**
241
 * Class %s
242
 *
243
 * This class is implementation of builder pattern 
244
 * for class %s. 
245
 *
246
 * @package %s
247
 *
248
 * @see \%s
249
 * @see https://en.wikipedia.org/wiki/Builder_pattern
250
 */', $class, $this->subject->getFqcn(), $ns, ReflectiveAbstractBuilder::class));
251
252
253
        if ($this->subject->isFinal()) {
254
            $builderAstFactory->makeFinal();
255
        }
256
257
        if ($this->subject->isAbstract()) {
258
            $builderAstFactory->makeAbstract();
259
        }
260
261
        $this->builder = new ClassMetadata(
262
            $class,
263
            MetadataLoader::create()->load(ReflectiveAbstractBuilder::class)->getClass(ReflectiveAbstractBuilder::class),
264
            [],
265
            $this->subject->isFinal(),
266
            $this->subject->isAbstract(),
267
            [],
268
            $builderAstFactory->getNode()
269
        );
270
271
        $namespace->addStmt($this->builder->getAst());
272
273
        $this
274
            ->addBuildMethod()
275
            ->addGetObjectFqcnMethod()
276
            ->addConfigureParametersMethod();
277
278
        if (!$this->builder->isAbstract()) {
279
            $this->addCreateBuilderMethod();
280
        }
281
282
        return new FileMetadata($filename, [], [$this->builder], [], [$namespace->getNode()]);
283
    }
284
285
    /**
286
     * Append method to class AST, order by visibility.
287
     *
288
     * @param ClassMethod $method
289
     */
290
    private function appendMethod(ClassMethod $method)
291
    {
292
        $this->builder->getAst()->stmts[] = $method;
293
294
        // TODO Sort according to visibility and other parameters.
295
    }
296
}
297
298