Completed
Push — master ( 0e6e64...9192b1 )
by Viacheslav
12s
created

PhpBuilder::makeClass()   F

Complexity

Conditions 20
Paths 513

Size

Total Lines 125
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 76
CRAP Score 20.0939

Importance

Changes 0
Metric Value
cc 20
eloc 84
nc 513
nop 2
dl 0
loc 125
ccs 76
cts 81
cp 0.9383
crap 20.0939
rs 0.6763
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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
namespace Swaggest\PhpCodeBuilder\JsonSchema;
4
5
use Swaggest\CodeBuilder\AbstractTemplate;
6
use Swaggest\CodeBuilder\PlaceholderString;
7
use Swaggest\JsonSchema\Context;
8
use Swaggest\JsonSchema\JsonSchema;
9
use Swaggest\JsonSchema\Schema;
10
use Swaggest\PhpCodeBuilder\Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Swaggest\PhpCodeBuilder\JsonSchema\Exception. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
11
use Swaggest\PhpCodeBuilder\PhpAnyType;
12
use Swaggest\PhpCodeBuilder\PhpClass;
13
use Swaggest\PhpCodeBuilder\PhpClassProperty;
14
use Swaggest\PhpCodeBuilder\PhpCode;
15
use Swaggest\PhpCodeBuilder\PhpConstant;
16
use Swaggest\PhpCodeBuilder\PhpDoc;
17
use Swaggest\PhpCodeBuilder\PhpFlags;
18
use Swaggest\PhpCodeBuilder\PhpFunction;
19
use Swaggest\PhpCodeBuilder\PhpNamedVar;
20
use Swaggest\PhpCodeBuilder\Property\AdditionalPropertiesGetter;
21
use Swaggest\PhpCodeBuilder\Property\AdditionalPropertySetter;
22
use Swaggest\PhpCodeBuilder\Property\Getter;
23
use Swaggest\PhpCodeBuilder\Property\PatternPropertiesGetter;
24
use Swaggest\PhpCodeBuilder\Property\PatternPropertySetter;
25
use Swaggest\PhpCodeBuilder\Property\Setter;
26
use Swaggest\PhpCodeBuilder\Types\TypeOf;
27
28
/**
29
 * @todo properly process $ref, $schema property names
30
 */
31
class PhpBuilder
32
{
33
    const IMPORT_METHOD_PHPDOC_ID = '::import';
34
35
    const SCHEMA = 'schema';
36
    const ORIGIN = 'origin';
37
    const PROPERTY_NAME = 'property_name';
38
39
    /** @var \SplObjectStorage|GeneratedClass[] */
40
    private $generatedClasses;
41
    private $untitledIndex = 0;
0 ignored issues
show
introduced by
The private property $untitledIndex is not used, and could be removed.
Loading history...
42
43 11
    public function __construct()
44
    {
45 11
        $this->generatedClasses = new \SplObjectStorage();
46 11
    }
47
48
    public $buildGetters = false;
49
    public $buildSetters = false;
50
    public $makeEnumConstants = false;
51
    public $skipSchemaDescriptions = false;
52
53
    /**
54
     * Squish multiple $ref, a PHP class for each $ref will be created if false
55
     * @var bool
56
     */
57
    public $minimizeRefs = true;
58
59
    /** @var PhpBuilderClassHook */
60
    public $classCreatedHook;
61
62
    /** @var PhpBuilderClassHook */
63
    public $classPreparedHook;
64
65
    /**
66
     * @param Schema $schema
67
     * @param string $path
68
     * @return PhpAnyType
69
     * @throws \Swaggest\PhpCodeBuilder\JsonSchema\Exception
70
     */
71 11
    public function getType($schema, $path = '#')
72
    {
73 11
        $typeBuilder = new TypeBuilder($schema, $path, $this);
74 11
        return $typeBuilder->build();
75
    }
76
77
78
    /**
79
     * @param Schema $schema
80
     * @param string $path
81
     * @return PhpClass
82
     * @throws Exception
83
     * @throws \Swaggest\PhpCodeBuilder\JsonSchema\Exception
84
     */
85 10
    public function getClass($schema, $path)
86
    {
87 10
        if ($this->generatedClasses->contains($schema)) {
88 9
            return $this->generatedClasses[$schema]->class;
89
        } else {
90 10
            return $this->makeClass($schema, $path)->class;
91
        }
92
    }
93
94
    /**
95
     * @param Schema $schema
96
     * @param string $path
97
     * @return GeneratedClass
98
     * @throws Exception
99
     * @throws \Swaggest\PhpCodeBuilder\JsonSchema\Exception
100
     */
101 10
    private function makeClass($schema, $path)
102
    {
103 10
        if (empty($path)) {
104
            throw new Exception('Empty path');
105
        }
106 10
        $generatedClass = new GeneratedClass();
107 10
        $generatedClass->schema = $schema;
108
109 10
        $class = new PhpClass();
110 10
        if ($fromRefs = $schema->getFromRefs()) {
111 3
            $path = $fromRefs[count($fromRefs) - 1];
0 ignored issues
show
Bug introduced by
$fromRefs of type string is incompatible with the type Countable|array expected by parameter $var of count(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
            $path = $fromRefs[count(/** @scrutinizer ignore-type */ $fromRefs) - 1];
Loading history...
112
        }
113
114 10
        $class->setName(PhpCode::makePhpClassName($path));
115 10
        if ($this->classCreatedHook !== null) {
116 3
            $this->classCreatedHook->process($class, $path, $schema);
117
        }
118 10
        $class->setExtends(Palette::classStructureClass());
119
120 10
        $setupProperties = new PhpFunction('setUpProperties');
121
        $setupProperties
122 10
            ->setVisibility(PhpFlags::VIS_PUBLIC)
123 10
            ->setIsStatic(true);
124
        $setupProperties
125 10
            ->addArgument(new PhpNamedVar('properties', Palette::propertiesOrStaticClass()))
126 10
            ->addArgument(new PhpNamedVar('ownerSchema', Palette::schemaClass()));
127
128 10
        $body = new PhpCode();
129
130 10
        $class->addMeta($schema, self::SCHEMA);
131 10
        $class->addMethod($setupProperties);
132
133 10
        $generatedClass->class = $class;
134 10
        $generatedClass->path = $path;
135
136 10
        $this->generatedClasses->attach($schema, $generatedClass);
137 10
        if (null !== $this->dynamicIterator) {
138
            $this->dynamicIterator->push($generatedClass);
139
        }
140
141 10
        if ($schema->properties) {
142 9
            $phpNames = array();
143 9
            foreach ($schema->properties as $name => $property) {
144 9
                $i = '';
145
                do {
146 9
                    $propertyName = PhpCode::makePhpName($name . $i);
147 9
                    $i .= 'a';
148 9
                } while (isset($phpNames[$propertyName]));
149 9
                $phpNames[$propertyName] = true;
150
151 9
                $schemaBuilder = new SchemaBuilder($property, '$properties->' . $propertyName, $path . '->' . $name, $this);
152 9
                if ($this->skipSchemaDescriptions) {
153
                    $schemaBuilder->skipProperty(JsonSchema::names()->description);
154
                }
155 9
                if ($this->makeEnumConstants) {
156 3
                    $schemaBuilder->setSaveEnumConstInClass($class);
157
                }
158 9
                $phpProperty = new PhpClassProperty($propertyName, $this->getType($property, $path . '->' . $name));
159 9
                $phpProperty->addMeta($property, self::SCHEMA);
160 9
                $phpProperty->addMeta($name, self::PROPERTY_NAME);
161 9
                if ($property->description) {
162 2
                    $phpProperty->setDescription($property->description);
163
                }
164 9
                $class->addProperty($phpProperty);
165 9
                if ($this->buildGetters) {
166
                    $class->addMethod(new Getter($phpProperty));
167
                }
168 9
                if ($this->buildSetters) {
169 5
                    $class->addMethod(new Setter($phpProperty, true));
170
                }
171 9
                $body->addSnippet(
172 9
                    $schemaBuilder->build()
173
                );
174 9
                if ($propertyName != $name) {
175 3
                    $body->addSnippet('$ownerSchema->addPropertyMapping(' . var_export($name, 1) . ', self::names()->'
0 ignored issues
show
Bug introduced by
Are you sure the usage of var_export($name, 1) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
176 9
                        . $propertyName . ");\n");
177
                }
178
            }
179
        }
180
181 10
        if ($schema->additionalProperties instanceof Schema) {
182 2
            $class->addMethod(new AdditionalPropertiesGetter($this->getType($schema->additionalProperties)));
183 2
            $class->addMethod(new AdditionalPropertySetter($this->getType($schema->additionalProperties)));
184
        }
185
186 10
        if ($schema->patternProperties) {
187 4
            foreach ($schema->patternProperties as $pattern => $patternProperty) {
188 4
                $const = new PhpConstant(PhpCode::makePhpConstantName($pattern . '_PROPERTY_PATTERN'), $pattern);
189 4
                $class->addConstant($const);
190
191 4
                $class->addMethod(new PatternPropertiesGetter($const, $this->getType($patternProperty)));
192 4
                $class->addMethod(new PatternPropertySetter($const, $this->getType($patternProperty)));
193
            }
194
        }
195
196 10
        $schemaBuilder = new SchemaBuilder($schema, '$ownerSchema', $path, $this, false);
197 10
        if ($this->skipSchemaDescriptions) {
198
            $schemaBuilder->skipProperty(JsonSchema::names()->description);
199
        }
200 10
        $schemaBuilder->setSkipProperties(true);
201 10
        $body->addSnippet($schemaBuilder->build());
202
203 10
        $setupProperties->setBody($body);
204
205 10
        $phpDoc = $class->getPhpDoc();
206 10
        $type = $this->getType($schema, $path);
207 10
        if (!$type instanceof PhpClass) {
0 ignored issues
show
introduced by
$type is never a sub-type of Swaggest\PhpCodeBuilder\PhpClass.
Loading history...
208 7
            $phpDoc->add(
209 7
                PhpDoc::TAG_METHOD,
210 7
                new PlaceholderString(
211 7
                    'static :type import($data, :context $options = null)',
212
                    array(
213 7
                        ':type' => new TypeOf($type, true),
214 7
                        ':context' => new TypeOf(PhpClass::byFQN(Context::class))
215
                    )
216
                ),
217 7
                self::IMPORT_METHOD_PHPDOC_ID
218
            );
219
        }
220
221 10
        if ($this->classPreparedHook !== null) {
222 2
            $this->classPreparedHook->process($class, $path, $schema);
223
        }
224
225 10
        return $generatedClass;
226
    }
227
228
    /** @var DynamicIterator */
229
    private $dynamicIterator;
230
231
    /**
232
     * @return GeneratedClass[]|DynamicIterator
233
     */
234 7
    public function getGeneratedClasses()
235
    {
236 7
        $result = array();
237 7
        foreach ($this->generatedClasses as $schema) {
238 7
            $result[] = $this->generatedClasses[$schema];
239
        }
240 7
        $iterator = new DynamicIterator($result);
241 7
        $this->dynamicIterator = $iterator;
242 7
        return $iterator;
243
    }
244
245
    /**
246
     * @param AbstractTemplate $template
247
     * @return null|Schema
248
     */
249
    public static function getSchemaMeta(AbstractTemplate $template)
250
    {
251
        return $template->getMeta(self::SCHEMA);
252
    }
253
}
254
255
256
class DynamicIterator implements \Iterator, \ArrayAccess
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
257
{
258
    private $rows;
259
    private $current;
260
    private $key;
261
    private $valid;
262
263
    public function push($item)
264
    {
265
        $this->rows[] = $item;
266
        return $this;
267
    }
268
269
    /**
270
     * DynamicIterator constructor.
271
     * @param array $rows
272
     */
273 7
    public function __construct($rows = array())
274
    {
275 7
        $this->rows = $rows;
276 7
    }
277
278
279 7
    public function current()
280
    {
281 7
        return $this->current;
282
    }
283
284 7
    public function next()
285
    {
286 7
        if (empty($this->rows)) {
287 7
            $this->valid = false;
288 7
            return;
289
        }
290 7
        $this->current = array_shift($this->rows);
291 7
        $this->valid = true;
292 7
        ++$this->key;
293 7
    }
294
295
    public function key()
296
    {
297
        return $this->key;
298
    }
299
300 7
    public function valid()
301
    {
302 7
        return $this->valid;
303
    }
304
305 7
    public function rewind()
306
    {
307 7
        $this->next();
308 7
    }
309
310
    public function offsetExists($offset)
311
    {
312
        return array_key_exists($offset, $this->rows);
313
    }
314
315
    public function offsetGet($offset)
316
    {
317
        return $this->rows[$offset];
318
    }
319
320
    public function offsetSet($offset, $value)
321
    {
322
        $this->rows[$offset] = $value;
323
    }
324
325
    public function offsetUnset($offset)
326
    {
327
        unset($this->rows[$offset]);
328
    }
329
330
331
}