Completed
Pull Request — develop (#552)
by
unknown
28:27 queued 23:25
created

JsonDefinition::hasFixtures()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
namespace Graviton\GeneratorBundle\Definition;
3
4
use Graviton\GeneratorBundle\Definition\Schema\Constraint;
5
use Graviton\GeneratorBundle\Definition\Schema\Service;
6
use Graviton\SchemaBundle\Constraint\VersionFieldConstraint;
7
8
/**
9
 * This class represents the json file that defines the structure
10
 * of a mongo collection that exists and serves as a base to generate
11
 * a bundle.
12
 *
13
 * @todo     if this json format serves in more places; move this class
14
 * @todo     validate json
15
 *
16
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
17
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
18
 * @link     http://swisscom.ch
19
 */
20
class JsonDefinition
21
{
22
    /**
23
     * Schema
24
     *
25
     * @var Schema\Definition
26
     */
27
    private $def;
28
29
    /**
30
     * Composed namespace of this definition, must be explicitly set
31
     *
32
     * @var string
33
     */
34
    private $namespace;
35
36
    /**
37
     * Constructor
38
     *
39
     * @param Schema\Definition $definition
40
     */
41 64
    public function __construct(Schema\Definition $definition)
42
    {
43 64
        $this->def = $definition;
44 64
    }
45
46
    /**
47
     * @return Schema\Definition
48
     */
49
    public function getDef()
50
    {
51
        return $this->def;
52
    }
53
54
    /**
55
     * Returns this loads ID
56
     *
57
     * @return string ID
58
     */
59 24
    public function getId()
60
    {
61 24
        if ($this->def->getId() === null) {
62 2
            throw new \RuntimeException('No id found for document');
63
        }
64
65 22
        return $this->def->getId();
66
    }
67
68
    /**
69
     * Returns the description
70
     *
71
     * @return string Description
72
     */
73 2
    public function getDescription()
74
    {
75 2
        return $this->def->getDescription();
76
    }
77
78
    /**
79
     * Returns the title
80
     *
81
     * @return string Title
82
     */
83
    public function getTitle()
84
    {
85
        return $this->def->getTitle();
86
    }
87
88
    /**
89
     * Returns whether this definition requires the generation
90
     * of a controller. normally yes, but sometimes not ;-)
91
     *
92
     * @return bool true if yes, false if no
93
     */
94 4
    public function hasController()
95
    {
96 4
        return $this->def->getService() !== null &&
97 4
            $this->def->getService()->getRouterBase() !== null;
98
    }
99
100
    /**
101
     * This is a method that allows us to distinguish between a full json spec
102
     * and a hash defined in a full spec which was divided into a seperate Document (thus, a SubDocument).
103
     * To be aware what it is mainly serves for the generator to generate them as embedded documents,
104
     * as subdocuments are always embedded.
105
     *
106
     * @return bool true if yes, false if not
107
     */
108 6
    public function isSubDocument()
109
    {
110 6
        return $this->def->getIsSubDocument();
111
    }
112
113
    /**
114
     * Gets the namespace
115
     *
116
     * @return string namespace
117
     */
118 14
    public function getNamespace()
119
    {
120 14
        return $this->namespace;
121
    }
122
123
    /**
124
     * Sets the namespace
125
     *
126
     * @param string $namespace namespace
127
     *
128
     * @return void
129
     */
130 10
    public function setNamespace($namespace)
131
    {
132
        // normalize namespace
133 10
        $namespace = str_replace('/', '\\', $namespace);
134
135 10
        if (substr($namespace, -1) == '\\') {
136 2
            $namespace = substr($namespace, 0, -1);
137 1
        }
138
139 10
        $this->namespace = $namespace;
140 10
    }
141
142
    /**
143
     * Returns whether this service is read-only
144
     *
145
     * @return bool true if yes, false if not
146
     */
147 4
    public function isReadOnlyService()
148
    {
149 4
        if ($this->def->getService() === null) {
150
            return false;
151
        }
152
153 4
        return $this->def->getService()->getReadOnly();
154
    }
155
156
    /**
157
     * Returns whether this service is versioning
158
     *
159
     * @return bool true if yes, false if not
160
     */
161
    public function isVersionedService()
162
    {
163
        if ($this->def->getService() === null || !$this->def->getService()->getVersioning()) {
164
            return false;
165
        }
166
167
        return $this->def->getService()->getVersioning();
168
    }
169
170
    /**
171
     * Returns whether this service has fixtures
172
     *
173
     * @return bool true if yes, false if not
174
     */
175 4
    public function hasFixtures()
176
    {
177 4
        return count($this->getFixtures()) > 0;
178
    }
179
180
    /**
181
     * Returns the fixtures or empty array if none
182
     *
183
     * @return array fixtures
184
     */
185 4
    public function getFixtures()
186
    {
187 4
        if ($this->def->getService() === null) {
188
            return [];
189
        }
190
191 4
        return $this->def->getService()->getFixtures();
192
    }
193
194
    /**
195
     * Returns the order number at which order this fixture should be loaded.
196
     * this is needed if we have relations/references between the fixtures..
197
     *
198
     * @return int order
199
     */
200 4
    public function getFixtureOrder()
201
    {
202 4
        if ($this->def->getService() === null ||
203 4
            $this->def->getService()->getFixtureOrder() === null) {
204 2
            return 100;
205
        }
206
207 2
        return $this->def->getService()->getFixtureOrder();
208
    }
209
210
    /**
211
     * Returns a router base path. false if default should be used.
212
     *
213
     * @return string router base, i.e. /bundle/name/
0 ignored issues
show
Documentation introduced by
Should the return type not be false|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
214
     */
215 6
    public function getRouterBase()
216
    {
217 6
        if ($this->def->getService() === null ||
218 6
            $this->def->getService()->getRouterBase() === null) {
219 2
            return false;
220
        }
221
222 4
        $routerBase = $this->def->getService()->getRouterBase();
223 4
        if (substr($routerBase, 0, 1) !== '/') {
224 2
            $routerBase = '/' . $routerBase;
225 1
        }
226 4
        if (substr($routerBase, -1) === '/') {
227 2
            $routerBase = substr($routerBase, 0, -1);
228 1
        }
229
230 4
        return $routerBase;
231
    }
232
233
    /**
234
     * Returns the Controller classname this services' controller shout inherit.
235
     * Defaults to the RestController of the RestBundle of course.
236
     *
237
     * @return string base controller
238
     */
239 4
    public function getBaseController()
240
    {
241 4
        if ($this->def->getService() === null ||
242 4
            $this->def->getService()->getBaseController() === null) {
243 2
            return 'RestController';
244
        }
245
246 2
        return $this->def->getService()->getBaseController();
247
    }
248
249
    /**
250
     * Returns the parent service to use when adding the service xml
251
     *
252
     * Defaults to graviton.rest.controller
253
     *
254
     * @return string base controller
255
     */
256
    public function getParentService()
257
    {
258
        if ($this->def->getService() === null ||
259
            $this->def->getService()->getParent() === null) {
260
            return 'graviton.rest.controller';
261
        }
262
263
        return $this->def->getService()->getParent();
264
    }
265
266
    /**
267
     * Returns a specific field or null
268
     *
269
     * @param string $name Field name
270
     *
271
     * @return DefinitionElementInterface The field
0 ignored issues
show
Documentation introduced by
Should the return type not be DefinitionElementInterface|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
272
     */
273 30
    public function getField($name)
274
    {
275 30
        $fields = $this->getFields();
276 30
        return isset($fields[$name]) ? $fields[$name] : null;
277
    }
278
279
    /**
280
     * Returns the field definition
281
     *
282
     * @return DefinitionElementInterface[] Fields
283
     */
284 32
    public function getFields()
285
    {
286 32
        $hierarchy = [];
287 32
        foreach ($this->def->getTarget()->getFields() as $field) {
288 30
            $hierarchy = array_merge_recursive(
289 15
                $hierarchy,
290 30
                $this->createFieldHierarchyRecursive($field, $field->getName())
291 15
            );
292 16
        }
293
294 32
        $fields = [];
295 32
        foreach ($hierarchy as $name => $definition) {
296 30
            $fields[$name] = $this->processFieldHierarchyRecursive($name, $definition);
297 16
        }
298
299
        // Versioning field, for version control.
300 32
        if ($this->def->getService() && $this->def->getService()->getVersioning()) {
301
            $definition = new Schema\Field();
302
            $constraint = new Constraint();
303
            $constraint->setName('versioning');
304
            $definition->setName(VersionFieldConstraint::FIELD_NAME)->setTitle('Version')->setType('int')
305
                ->setConstraints([$constraint])->setDescription('Version control, auto-increment on update');
306
            $fields['version'] = $this->processSimpleField('version',  $definition);
0 ignored issues
show
Coding Style introduced by
Expected 1 space instead of 2 after comma in function call.
Loading history...
307
        }
308
309 32
        return $fields;
310
    }
311
312
    /**
313
     * @param Schema\Field $definition Raw field definition
314
     * @param string       $path       Relative field path
315
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array<strin...a\Field>|Schema\Field>>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
316
     * @throws \InvalidArgumentException
317
     */
318 30
    private function createFieldHierarchyRecursive(Schema\Field $definition, $path)
319
    {
320 30
        if (!preg_match('/^(?P<name>[^\.]+)(\.(?P<sub>.+))?$/', $path, $matches)) {
321
            throw new \InvalidArgumentException(sprintf('Invalid field name "%s" definition', $definition->getName()));
322
        }
323
324 30
        $name = ctype_digit($matches['name']) ? "\x00array" : $matches['name'];
325 30
        if (isset($matches['sub'])) {
326 30
            $definition = $this->createFieldHierarchyRecursive($definition, $matches['sub']);
327 15
        } else {
328 30
            $definition = ["\x00field" => $definition];
329
        }
330
331 30
        return [$name => $definition];
332
    }
333
334
    /**
335
     * @param string $name Field name
336
     * @param array  $data Field data
337
     *
338
     * @return DefinitionElementInterface
339
     */
340 30
    private function processFieldHierarchyRecursive($name, $data)
341
    {
342
        // array field
343 30
        if (isset($data["\x00array"])) {
344 28
            return new JsonDefinitionArray(
345 14
                $name,
346 28
                $this->processFieldHierarchyRecursive($name, $data["\x00array"])
347 14
            );
348
        }
349
350
        // simple field
351 30
        if (array_keys($data) === ["\x00field"]) {
352 30
            return $this->processSimpleField($name, $data["\x00field"]);
353
        }
354
355
356
        // hash field
357 30
        $fields = [];
358 30
        $definition = null;
359 30
        foreach ($data as $subname => $subdata) {
360 30
            if ($subname === "\x00field") {
361 6
                $definition = $subdata;
362 3
            } else {
363 30
                $fields[$subname] = $this->processFieldHierarchyRecursive($subname, $subdata);
0 ignored issues
show
Documentation introduced by
$subdata is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
364
            }
365 15
        }
366 30
        return new JsonDefinitionHash($name, $this, $fields, $definition);
367
    }
368
369
    /**
370
     * @param string       $name       Field name
371
     * @param Schema\Field $definition Field
372
     *
373
     * @return DefinitionElementInterface
374
     */
375 30
    private function processSimpleField($name, Schema\Field $definition)
376
    {
377 30
        if (strpos($definition->getType(), 'class:') === 0) {
378 24
            $field = new JsonDefinitionRel($name, $definition, $this->getRelation($name));
379 12
        } else {
380 28
            $field = new JsonDefinitionField($name, $definition);
381
        }
382
383 30
        if (substr($definition->getType(), -2) === '[]') {
384 24
            $field = new JsonDefinitionArray($name, $field);
385 12
        }
386
387 30
        return $field;
388
    }
389
390
    /**
391
     * Get target relations which are explictly defined
392
     *
393
     * @return Schema\Relation[] relations
394
     */
395 32
    public function getRelations()
396
    {
397 32
        if ($this->def->getTarget() === null) {
398
            return [];
399
        }
400
401 32
        $relations = [];
402 32
        foreach ($this->def->getTarget()->getRelations() as $relation) {
403 6
            $relations[$relation->getLocalProperty()] = $relation;
404 16
        }
405
406 32
        return $relations;
407
    }
408
409
    /**
410
     * Get relation by field name
411
     *
412
     * @param string $field Field name
413
     * @return Schema\Relation|null
414
     */
415 24
    private function getRelation($field)
416
    {
417 24
        $relations = $this->getRelations();
418 24
        return isset($relations[$field]) ? $relations[$field] : null;
419
    }
420
421
    /**
422
     * Provides the role set defined in the service section.
423
     *
424
     * @return array
425
     */
426 2
    public function getRoles()
427
    {
428 2
        if ($this->def->getService() === null) {
429
            return [];
430
        }
431
432 2
        return $this->def->getService()->getRoles();
433
    }
434
435
    /**
436
     * Can record origin be modified
437
     *
438
     * @return bool
439
     */
440
    public function isRecordOriginModifiable()
441
    {
442
        $retVal = false;
443
        if ($this->isRecordOriginFlagSet()) {
444
            $retVal = $this->def->getService()->getRecordOriginModifiable();
445
        }
446
447
        return $retVal;
448
    }
449
450
    /**
451
     * check if the RecordOriginModifiable flag is set
452
     *
453
     * @return bool
454
     */
455
    public function isRecordOriginFlagSet()
456
    {
457
        $retVal = false;
458
        if ($this->def->getService() !== null
459
            && is_object($this->def->getService())
460
            && $this->def->getService()->getRecordOriginModifiable() !== null) {
461
            $retVal = true;
462
        }
463
464
        return $retVal;
465
    }
466
467
    /**
468
     * @return string
469
     */
470
    public function getServiceCollection()
471
    {
472
        $collectionName = $this->getId();
473
474
        if ($this->def->getService() instanceof Service
475
            && $this->def->getService()->getCollectionName()) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
476
477
            $collectionName = $this->def->getService()->getCollectionName();
478
        }
479
480
        return $collectionName;
481
    }
482
483
    /**
484
     * @return string[]
485
     */
486 2
    public function getIndexes()
487
    {
488 2
        $indexes = [];
489 2
        if ($this->def->getTarget()->getIndexes()) {
490
            $indexes = $this->def->getTarget()->getIndexes();
491
        }
492 2
        return $indexes;
493
    }
494
495
    /**
496
     * @return string[]
497
     */
498
    public function getSearchables()
499
    {
500
        $indexes = [];
501
        if ($fields = $this->def->getTarget()->getFields()) {
502
            foreach ($fields as $field) {
503
                if ($value = (int) $field->getSearchable()) {
504
                    $indexes[$field->getName()] = $value;
505
                }
506
            }
507
        }
508
        return $indexes;
509
    }
510
511
    /**
512
     * @return string[]
513
     */
514 2
    public function getTextIndexes()
515
    {
516 2
        $indexes = [];
517 2
        if ($keys = $this->def->getTarget()->getTextIndexes()) {
518
            foreach ($keys as $key) {
519
                if ($value = (int) $key['weight']) {
520
                    $indexes[$key['field']] = $value;
521
                }
522
            }
523
        }
524 2
        return $indexes;
525
    }
526
527
    /**
528
     * Combine in one array the Search text indexes
529
     * 
530
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
531
     */
532
    public function getAllTextIndexes()
533
    {
534
        return array_merge(
535
            $this->getSearchables(),
536
            $this->getTextIndexes()
537
        );
538
    }
539
}
540