Completed
Pull Request — master (#29)
by
unknown
06:32
created

PhpBuilder::makeClass()   F

Complexity

Conditions 23
Paths 513

Size

Total Lines 145
Code Lines 92

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 78
CRAP Score 23.1925

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 23
eloc 92
c 3
b 0
f 0
nc 513
nop 2
dl 0
loc 145
ccs 78
cts 84
cp 0.9286
crap 23.1925
rs 0.6763

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\JsonSchema\SchemaContract;
11
use Swaggest\JsonSchema\SchemaExporter;
12
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...
13
use Swaggest\PhpCodeBuilder\PhpAnyType;
14
use Swaggest\PhpCodeBuilder\PhpClass;
15
use Swaggest\PhpCodeBuilder\PhpClassProperty;
16
use Swaggest\PhpCodeBuilder\PhpCode;
17
use Swaggest\PhpCodeBuilder\PhpConstant;
18
use Swaggest\PhpCodeBuilder\PhpDoc;
19
use Swaggest\PhpCodeBuilder\PhpFlags;
20
use Swaggest\PhpCodeBuilder\PhpFunction;
21
use Swaggest\PhpCodeBuilder\PhpNamedVar;
22
use Swaggest\PhpCodeBuilder\Property\AdditionalPropertiesGetter;
23
use Swaggest\PhpCodeBuilder\Property\AdditionalPropertySetter;
24
use Swaggest\PhpCodeBuilder\Property\Getter;
25
use Swaggest\PhpCodeBuilder\Property\PatternPropertiesGetter;
26
use Swaggest\PhpCodeBuilder\Property\PatternPropertySetter;
27
use Swaggest\PhpCodeBuilder\Property\Setter;
28
use Swaggest\PhpCodeBuilder\Types\TypeOf;
29
30
class PhpBuilder
31
{
32
    const IMPORT_METHOD_PHPDOC_ID = '::import';
33
34
    const SCHEMA = 'schema';
35
    const ORIGIN = 'origin';
36
    const PROPERTY_NAME = 'property_name';
37
    const IMPORT_TYPE = 'import_type';
38
39
    /** @var \SplObjectStorage */
40
    private $generatedClasses;
41
42
    public function __construct()
43 12
    {
44
        $this->generatedClasses = new \SplObjectStorage();
45 12
    }
46 12
47
    public $buildGetters = false;
48
    public $buildSetters = false;
49
    public $makeEnumConstants = false;
50
    public $skipSchemaDescriptions = false;
51
52
    /**
53
     * Use title/description where available instead of keyword in names
54
     * @var bool
55
     */
56
    public $namesFromDescriptions = false;
57
58
    /**
59
     * Squish multiple $ref, a PHP class for each $ref will be created if false
60
     * @var bool
61
     */
62
    public $minimizeRefs = true;
63
64
    /** @var PhpBuilderClassHook */
65
    public $classCreatedHook;
66
67
    /** @var PhpBuilderClassHook */
68
    public $classPreparedHook;
69
70
    /**
71 12
     * @param SchemaContract $schema
72
     * @param string $path
73 12
     * @return PhpAnyType
74 12
     * @throws \Swaggest\PhpCodeBuilder\JsonSchema\Exception
75
     * @throws Exception
76
     */
77
    public function getType($schema, $path = '#')
78
    {
79
        if (!$schema instanceof Schema) {
80
            throw new Exception('Could not find Schema instance in SchemaContract: ' . get_class($schema));
81
        }
82
        $typeBuilder = new TypeBuilder($schema, $path, $this);
83
        return $typeBuilder->build();
84
    }
85 11
86
87 11
    /**
88 10
     * @param Schema $schema
89
     * @param string $path
90 11
     * @return PhpClass
91
     * @throws Exception
92
     * @throws \Swaggest\PhpCodeBuilder\JsonSchema\Exception
93
     */
94
    public function getClass($schema, $path)
95
    {
96
        if ($this->generatedClasses->contains($schema)) {
97
            return $this->generatedClasses[$schema]->class;
98
        } else {
99
            return $this->makeClass($schema, $path)->class;
100
        }
101 11
    }
102
103 11
    /**
104
     * @param Schema $schema
105
     * @param string $path
106 11
     * @return GeneratedClass
107 11
     * @throws Exception
108
     * @throws \Swaggest\PhpCodeBuilder\JsonSchema\Exception
109 11
     */
110 11
    private function makeClass($schema, $path)
111 3
    {
112
        if (empty($path)) {
113
            throw new Exception('Empty path');
114 11
        }
115 11
        $generatedClass = new GeneratedClass();
116 4
        $generatedClass->schema = $schema;
117
118 11
        $class = new PhpClass();
119
        if ($fromRefs = $schema->getFromRefs()) {
120 11
            $path = $fromRefs[count($fromRefs) - 1];
121
        }
122 11
123 11
        $class->setName(PhpCode::makePhpClassName($path));
124
        if ($this->classCreatedHook !== null) {
125 11
            $this->classCreatedHook->process($class, $path, $schema);
126 11
        }
127
        $class->setExtends(Palette::classStructureClass());
128 11
129
        $setupProperties = new PhpFunction('setUpProperties');
130 11
        $setupProperties
131 11
            ->setVisibility(PhpFlags::VIS_PUBLIC)
132
            ->setIsStatic(true);
133 11
        $setupProperties
134 11
            ->addArgument(new PhpNamedVar('properties', Palette::propertiesOrStaticClass()))
135
            ->addArgument(new PhpNamedVar('ownerSchema', Palette::schemaClass()));
136 11
137 11
        $body = new PhpCode();
138
139
        $class->addMeta($schema, self::SCHEMA);
140
        $class->addMethod($setupProperties);
141 11
142 10
        $generatedClass->class = $class;
143 10
        $generatedClass->path = $path;
144 10
145
        $this->generatedClasses->attach($schema, $generatedClass);
146 10
        if (null !== $this->dynamicIterator) {
147 10
            $this->dynamicIterator->push($generatedClass);
148 10
        }
149 10
150
        if ($schema->properties) {
151 10
            $phpNames = array();
152 10
            /**
153
             * @var string $name
154
             * @var Schema $property
155 10
             */
156 4
            foreach ($schema->properties as $name => $property) {
157
                $propertyName = PhpCode::makePhpName($name);
158 10
159 10
                $i = 2;
160 10
                $basePropertyName = $propertyName;
161 10
                while (isset($phpNames[$propertyName])) {
162 2
                    $propertyName = $basePropertyName . $i;
163
                    $i++;
164 10
                }
165 10
                $phpNames[$propertyName] = true;
166
167
                $schemaBuilder = new SchemaBuilder($property, '$properties->' . $propertyName, $path . '->' . $name, $this);
168 10
                if ($this->skipSchemaDescriptions) {
169 5
                    $schemaBuilder->skipProperty(JsonSchema::names()->description);
170
                }
171 10
                if ($this->makeEnumConstants) {
172 10
                    $schemaBuilder->setSaveEnumConstInClass($class);
173
                }
174 10
                $propertyType = $this->getType($property, $path . '->' . $name);
175 3
                $phpProperty = new PhpClassProperty($propertyName, $propertyType);
176 3
                $phpProperty->addMeta($property, self::SCHEMA);
177
                $phpProperty->addMeta($name, self::PROPERTY_NAME);
178
179
                if (!is_null($property->default)) {
180
                    $phpProperty->setDefault($property->default);
181 11
                }
182 2
183 2
                if ($this->schemaIsNullable($property)) {
184
                    $phpProperty->setIsMagical(true);
185
                }
186 11
187 4
                if ($property->description) {
188 4
                    $phpProperty->setDescription($property->description);
189 4
                }
190
                $class->addProperty($phpProperty);
191 4
                if ($this->buildGetters) {
192 4
                    $class->addMethod(new Getter($phpProperty));
193
                }
194
                if ($this->buildSetters) {
195
                    $class->addMethod(new Setter($phpProperty, true));
196 11
                }
197 11
                $body->addSnippet(
198
                    $schemaBuilder->build()
199
                );
200 11
                if ($propertyName != $name) {
201 11
                    $body->addSnippet('$ownerSchema->addPropertyMapping(' . var_export($name, true) . ', self::names()->'
202
                        . $propertyName . ");\n");
203 11
                }
204
            }
205 11
        }
206 11
207 11
        if ($schema->additionalProperties instanceof Schema) {
208 7
            $class->addMethod(new AdditionalPropertiesGetter($this->getType($schema->additionalProperties)));
209 7
            $class->addMethod(new AdditionalPropertySetter($this->getType($schema->additionalProperties)));
210 7
        }
211 7
212
        if ($schema->patternProperties !== null) {
213 7
            foreach ($schema->patternProperties as $pattern => $patternProperty) {
214 7
                if ($patternProperty instanceof Schema) {
215
                    $const = new PhpConstant(PhpCode::makePhpConstantName($pattern . '_PROPERTY_PATTERN'), $pattern);
216
                    $class->addConstant($const);
217 7
218
                    $class->addMethod(new PatternPropertiesGetter($const, $this->getType($patternProperty)));
219
                    $class->addMethod(new PatternPropertySetter($const, $this->getType($patternProperty)));
220
                }
221 11
            }
222 2
        }
223
224
        $schemaBuilder = new SchemaBuilder($schema, '$ownerSchema', $path, $this, false);
225 11
        if ($this->skipSchemaDescriptions) {
226
            $schemaBuilder->skipProperty(JsonSchema::names()->description);
227
        }
228
        $schemaBuilder->setSkipProperties(true);
229
        $body->addSnippet($schemaBuilder->build());
230
231
        $setupProperties->setBody($body);
232
233
        $phpDoc = $class->getPhpDoc();
234 7
        $type = $this->getType($schema, $path);
235
        if (!$type instanceof PhpClass) {
0 ignored issues
show
introduced by
$type is never a sub-type of Swaggest\PhpCodeBuilder\PhpClass.
Loading history...
236 7
            $class->addMeta($type, self::IMPORT_TYPE);
237 7
            $phpDoc->add(
238 7
                PhpDoc::TAG_METHOD,
239
                new PlaceholderString(
240 7
                    'static :type import($data, :context $options = null)',
241 7
                    array(
242 7
                        ':type' => new TypeOf($type, true),
243
                        ':context' => new TypeOf(PhpClass::byFQN(Context::class))
244
                    )
245
                ),
246
                self::IMPORT_METHOD_PHPDOC_ID
247
            );
248
        }
249
250
        if ($this->classPreparedHook !== null) {
251
            $this->classPreparedHook->process($class, $path, $schema);
252
        }
253
254
        return $generatedClass;
255
    }
256
257
    /** @var DynamicIterator */
258
    private $dynamicIterator;
259
260
    /**
261
     * @return GeneratedClass[]|DynamicIterator
262
     */
263
    public function getGeneratedClasses()
264
    {
265
        $result = array();
266
        foreach ($this->generatedClasses as $schema) {
267
            $result[] = $this->generatedClasses[$schema];
268
        }
269
        $iterator = new DynamicIterator($result);
270
        $this->dynamicIterator = $iterator;
271
        return $iterator;
272
    }
273 7
274
    /**
275 7
     * @param AbstractTemplate $template
276 7
     * @return null|Schema
277
     */
278
    public static function getSchemaMeta(AbstractTemplate $template)
279 7
    {
280
        return $template->getMeta(self::SCHEMA);
281 7
    }
282
283
    /**
284 7
     * Returns true if null is allowed by schema.
285
     *
286 7
     * @param Schema $property
287 7
     * @return bool
288 7
     */
289
    private function schemaIsNullable($property)
290 7
    {
291 7
        if (!empty($property->enum) && !in_array(null, $property->enum)) {
292 7
            return false;
293 7
        }
294
295
        if ($property->const !== null) {
296
            return false;
297
        }
298
299
        if (!empty($property->anyOf)) {
300 7
            $nullable = false;
301
            foreach ($property->anyOf as $item) {
302 7
                if ($item instanceof Schema) {
303
                    if ($this->schemaIsNullable($item)) {
304
                        $nullable = true;
305 7
                        break;
306
                    }
307 7
                }
308 7
            }
309
            if (!$nullable) {
310
                return false;
311
            }
312
        }
313
314
        if (!empty($property->oneOf)) {
315
            $nullable = false;
316
            foreach ($property->oneOf as $item) {
317
                if ($item instanceof Schema) {
318
                    if ($this->schemaIsNullable($item)) {
319
                        $nullable = true;
320
                        break;
321
                    }
322
                }
323
            }
324
            if (!$nullable) {
325
                return false;
326
            }
327
        }
328
329
        if (!empty($property->allOf)) {
330
            foreach ($property->allOf as $item) {
331
                if ($item instanceof Schema) {
332
                    if (!$this->schemaIsNullable($item)) {
333
                        return false;
334
                    }
335
                }
336
            }
337
        }
338
339
        if (
340
            $property->type === null
341
            || $property->type === Schema::NULL
342
            || (is_array($property->type) && in_array(Schema::NULL, $property->type))
343
        ) {
344
            return true;
345
        }
346
347
        return false;
348
    }
349
}
350
351
352
class DynamicIterator implements \Iterator, \ArrayAccess
353
{
354
    private $rows;
355
    private $current;
356
    private $key;
357
    private $valid;
358
359
    public function push($item)
360
    {
361
        $this->rows[] = $item;
362
        return $this;
363
    }
364
365
    /**
366
     * DynamicIterator constructor.
367
     * @param array $rows
368
     */
369
    public function __construct($rows = array())
370
    {
371
        $this->rows = $rows;
372
    }
373
374
375
    public function current()
376
    {
377
        return $this->current;
378
    }
379
380
    public function next()
381
    {
382
        if (empty($this->rows)) {
383
            $this->valid = false;
384
            return;
385
        }
386
        $this->current = array_shift($this->rows);
387
        $this->valid = true;
388
        ++$this->key;
389
    }
390
391
    public function key()
392
    {
393
        return $this->key;
394
    }
395
396
    public function valid()
397
    {
398
        return $this->valid;
399
    }
400
401
    public function rewind()
402
    {
403
        $this->next();
404
    }
405
406
    public function offsetExists($offset)
407
    {
408
        return array_key_exists($offset, $this->rows);
409
    }
410
411
    public function offsetGet($offset)
412
    {
413
        return $this->rows[$offset];
414
    }
415
416
    public function offsetSet($offset, $value)
417
    {
418
        $this->rows[$offset] = $value;
419
    }
420
421
    public function offsetUnset($offset)
422
    {
423
        unset($this->rows[$offset]);
424
    }
425
426
427
}