Completed
Pull Request — master (#1)
by Viacheslav
02:50
created

PhpBuilder::makeClass()   D

Complexity

Conditions 17
Paths 129

Size

Total Lines 110
Code Lines 75

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 67
CRAP Score 17.0965

Importance

Changes 0
Metric Value
cc 17
eloc 75
nc 129
nop 2
dl 0
loc 110
ccs 67
cts 72
cp 0.9306
crap 17.0965
rs 4.5364
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\PhpDoc;
15
use Swaggest\PhpCodeBuilder\PhpFlags;
16
use Swaggest\PhpCodeBuilder\PhpFunction;
17
use Swaggest\PhpCodeBuilder\PhpNamedVar;
18
use Swaggest\PhpCodeBuilder\PhpCode;
19
use Swaggest\PhpCodeBuilder\Property\Getter;
20
use Swaggest\PhpCodeBuilder\Property\Setter;
21
use Swaggest\PhpCodeBuilder\Types\TypeOf;
22
23
/**
24
 * @todo properly process $ref, $schema property names
25
 */
26
class PhpBuilder
27
{
28
    const IMPORT_METHOD_PHPDOC_ID = '::import';
29
30
    const SCHEMA = 'schema';
31
    const ORIGIN = 'origin';
32
    const PROPERTY_NAME = 'property_name';
33
34
    /** @var \SplObjectStorage|GeneratedClass[] */
35
    private $generatedClasses;
36
    private $untitledIndex = 0;
0 ignored issues
show
introduced by
The private property $untitledIndex is not used, and could be removed.
Loading history...
37
38 5
    public function __construct()
39
    {
40 5
        $this->generatedClasses = new \SplObjectStorage();
41 5
    }
42
43
    public $buildGetters = false;
44
    public $buildSetters = false;
45
    public $makeEnumConstants = false;
46
    public $skipSchemaDescriptions = false;
47
48
    /**
49
     * Squish multiple $ref, a PHP class for each $ref will be created if false
50
     * @var bool
51
     */
52
    public $minimizeRefs = true;
53
54
    /** @var PhpBuilderClassHook */
55
    public $classCreatedHook;
56
57
    /** @var PhpBuilderClassHook */
58
    public $classPreparedHook;
59
60
    /**
61
     * @param Schema $schema
62
     * @param string $path
63
     * @return PhpAnyType
64
     * @throws \Swaggest\PhpCodeBuilder\JsonSchema\Exception
65
     */
66 5
    public function getType($schema, $path = '#')
67
    {
68 5
        $typeBuilder = new TypeBuilder($schema, $path, $this);
69 5
        return $typeBuilder->build();
70
    }
71
72
73
    /**
74
     * @param Schema $schema
75
     * @param string $path
76
     * @return PhpClass
77
     * @throws Exception
78
     * @throws \Swaggest\PhpCodeBuilder\JsonSchema\Exception
79
     */
80 4
    public function getClass($schema, $path)
81
    {
82 4
        if ($this->generatedClasses->contains($schema)) {
83 4
            return $this->generatedClasses[$schema]->class;
84
        } else {
85 4
            return $this->makeClass($schema, $path)->class;
86
        }
87
    }
88
89
    /**
90
     * @param Schema $schema
91
     * @param string $path
92
     * @return GeneratedClass
93
     * @throws Exception
94
     * @throws \Swaggest\PhpCodeBuilder\JsonSchema\Exception
95
     */
96 4
    private function makeClass($schema, $path)
97
    {
98 4
        if (empty($path)) {
99
            throw new Exception('Empty path');
100
        }
101 4
        $generatedClass = new GeneratedClass();
102 4
        $generatedClass->schema = $schema;
103
104 4
        $class = new PhpClass();
105 4
        if ($fromRefs = $schema->getFromRefs()) {
106 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

106
            $path = $fromRefs[count(/** @scrutinizer ignore-type */ $fromRefs) - 1];
Loading history...
107
        }
108
109 4
        $class->setName(PhpCode::makePhpClassName($path));
110 4
        if ($this->classCreatedHook !== null) {
111 2
            $this->classCreatedHook->process($class, $path, $schema);
112
        }
113 4
        $class->setExtends(Palette::classStructureClass());
114
115 4
        $setupProperties = new PhpFunction('setUpProperties');
116
        $setupProperties
117 4
            ->setVisibility(PhpFlags::VIS_PUBLIC)
118 4
            ->setIsStatic(true);
119
        $setupProperties
120 4
            ->addArgument(new PhpNamedVar('properties', Palette::propertiesOrStaticClass()))
121 4
            ->addArgument(new PhpNamedVar('ownerSchema', Palette::schemaClass()));
122
123 4
        $body = new PhpCode();
124
125 4
        $class->addMeta($schema, self::SCHEMA);
126 4
        $class->addMethod($setupProperties);
127
128 4
        $generatedClass->class = $class;
129 4
        $generatedClass->path = $path;
130
131 4
        $this->generatedClasses->attach($schema, $generatedClass);
132 4
        if (null !== $this->dynamicIterator) {
133
            $this->dynamicIterator->push($generatedClass);
134
        }
135
136 4
        if ($schema->properties) {
137 4
            $phpNames = array();
138 4
            foreach ($schema->properties as $name => $property) {
139 4
                $i = '';
140
                do {
141 4
                    $propertyName = PhpCode::makePhpName($name . $i);
142 4
                    $i .= 'a';
143 4
                } while (isset($phpNames[$propertyName]));
144 4
                $phpNames[$propertyName] = true;
145
146 4
                $schemaBuilder = new SchemaBuilder($property, '$properties->' . $propertyName, $path . '->' . $name, $this);
147 4
                if ($this->skipSchemaDescriptions) {
148
                    $schemaBuilder->skipProperty(JsonSchema::names()->description);
149
                }
150 4
                if ($this->makeEnumConstants) {
151 2
                    $schemaBuilder->setSaveEnumConstInClass($class);
152
                }
153 4
                $phpProperty = new PhpClassProperty($propertyName, $this->getType($property, $path . '->' . $name));
154 4
                $phpProperty->addMeta($property, self::SCHEMA);
155 4
                $phpProperty->addMeta($name, self::PROPERTY_NAME);
156 4
                if ($property->description) {
157 2
                    $phpProperty->setDescription($property->description);
158
                }
159 4
                $class->addProperty($phpProperty);
160 4
                if ($this->buildGetters) {
161
                    $class->addMethod(new Getter($phpProperty));
162
                }
163 4
                if ($this->buildSetters) {
164 4
                    $class->addMethod(new Setter($phpProperty, true));
165
                }
166 4
                $body->addSnippet(
167 4
                    $schemaBuilder->build()
168
                );
169 4
                if ($propertyName != $name) {
170 2
                    $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...
171 4
                        . $propertyName . ");\n");
172
                }
173
            }
174
        }
175
176 4
        $schemaBuilder = new SchemaBuilder($schema, '$ownerSchema', $path, $this, false);
177 4
        if ($this->skipSchemaDescriptions) {
178
            $schemaBuilder->skipProperty(JsonSchema::names()->description);
179
        }
180 4
        $schemaBuilder->setSkipProperties(true);
181 4
        $body->addSnippet($schemaBuilder->build());
182
183 4
        $setupProperties->setBody($body);
184
185 4
        $phpDoc = $class->getPhpDoc();
186 4
        $type = $this->getType($schema, $path);
187 4
        if (!$type instanceof PhpClass) {
188 2
            $phpDoc->add(
189 2
                PhpDoc::TAG_METHOD,
190 2
                new PlaceholderString(
191 2
                    'static :type import($data, :context $options=null)',
192
                    array(
193 2
                        ':type' => new TypeOf($type, true),
194 2
                        ':context' => new TypeOf(PhpClass::byFQN(Context::class))
195
                    )
196
                ),
197 2
                self::IMPORT_METHOD_PHPDOC_ID
198
            );
199
        }
200
201 4
        if ($this->classPreparedHook !== null) {
202 2
            $this->classPreparedHook->process($class, $path, $schema);
203
        }
204
205 4
        return $generatedClass;
206
    }
207
208
    /** @var DynamicIterator */
209
    private $dynamicIterator;
210
211
    /**
212
     * @return GeneratedClass[]|DynamicIterator
213
     */
214 2
    public function getGeneratedClasses()
215
    {
216 2
        $result = array();
217 2
        foreach ($this->generatedClasses as $schema) {
218 2
            $result[] = $this->generatedClasses[$schema];
219
        }
220 2
        $iterator = new DynamicIterator($result);
221 2
        $this->dynamicIterator = $iterator;
222 2
        return $iterator;
223
    }
224
225
    /**
226
     * @param AbstractTemplate $template
227
     * @return null|Schema
228
     */
229
    public static function getSchemaMeta(AbstractTemplate $template)
230
    {
231
        return $template->getMeta(self::SCHEMA);
232
    }
233
}
234
235
236
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...
237
{
238
    private $rows;
239
    private $current;
240
    private $key;
241
    private $valid;
242
243
    public function push($item)
244
    {
245
        $this->rows[] = $item;
246
        return $this;
247
    }
248
249
    /**
250
     * DynamicIterator constructor.
251
     * @param array $rows
252
     */
253 2
    public function __construct($rows = array())
254
    {
255 2
        $this->rows = $rows;
256 2
    }
257
258
259 2
    public function current()
260
    {
261 2
        return $this->current;
262
    }
263
264 2
    public function next()
265
    {
266 2
        if (empty($this->rows)) {
267 2
            $this->valid = false;
268 2
            return;
269
        }
270 2
        $this->current = array_shift($this->rows);
271 2
        $this->valid = true;
272 2
        ++$this->key;
273 2
    }
274
275
    public function key()
276
    {
277
        return $this->key;
278
    }
279
280 2
    public function valid()
281
    {
282 2
        return $this->valid;
283
    }
284
285 2
    public function rewind()
286
    {
287 2
        $this->next();
288 2
    }
289
290
    public function offsetExists($offset)
291
    {
292
        return array_key_exists($offset, $this->rows);
293
    }
294
295
    public function offsetGet($offset)
296
    {
297
        return $this->rows[$offset];
298
    }
299
300
    public function offsetSet($offset, $value)
301
    {
302
        $this->rows[$offset] = $value;
303
    }
304
305
    public function offsetUnset($offset)
306
    {
307
        unset($this->rows[$offset]);
308
    }
309
310
311
}