Completed
Pull Request — develop (#552)
by
unknown
14:01 queued 09:14
created

JsonDefinition::isVersionedService()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
rs 9.4285
ccs 0
cts 4
cp 0
cc 3
eloc 4
nc 2
nop 0
crap 12
1
<?php
2
namespace Graviton\GeneratorBundle\Definition;
3
4
use Graviton\GeneratorBundle\Definition\Schema\Constraint;
5
use Graviton\GeneratorBundle\Definition\Schema\Service;
6
7
/**
8
 * This class represents the json file that defines the structure
9
 * of a mongo collection that exists and serves as a base to generate
10
 * a bundle.
11
 *
12
 * @todo     if this json format serves in more places; move this class
13
 * @todo     validate json
14
 *
15
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
16
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
17
 * @link     http://swisscom.ch
18
 */
19
class JsonDefinition
20
{
21
    /**
22
     * Schema
23
     *
24
     * @var Schema\Definition
25
     */
26
    private $def;
27
28
    /**
29
     * Composed namespace of this definition, must be explicitly set
30
     *
31
     * @var string
32
     */
33
    private $namespace;
34
35
    /**
36
     * Constructor
37
     *
38
     * @param Schema\Definition $definition
39
     */
40 64
    public function __construct(Schema\Definition $definition)
41
    {
42 64
        $this->def = $definition;
43 64
    }
44
45
    /**
46
     * @return Schema\Definition
47
     */
48
    public function getDef()
49
    {
50
        return $this->def;
51
    }
52
53
    /**
54
     * Returns this loads ID
55
     *
56
     * @return string ID
57
     */
58 24
    public function getId()
59
    {
60 24
        if ($this->def->getId() === null) {
61 2
            throw new \RuntimeException('No id found for document');
62
        }
63
64 22
        return $this->def->getId();
65
    }
66
67
    /**
68
     * Returns the description
69
     *
70
     * @return string Description
71
     */
72 2
    public function getDescription()
73
    {
74 2
        return $this->def->getDescription();
75
    }
76
77
    /**
78
     * Returns the title
79
     *
80
     * @return string Title
81
     */
82
    public function getTitle()
83
    {
84
        return $this->def->getTitle();
85
    }
86
87
    /**
88
     * Returns whether this definition requires the generation
89
     * of a controller. normally yes, but sometimes not ;-)
90
     *
91
     * @return bool true if yes, false if no
92
     */
93 4
    public function hasController()
94
    {
95 4
        return $this->def->getService() !== null &&
96 4
            $this->def->getService()->getRouterBase() !== null;
97
    }
98
99
    /**
100
     * This is a method that allows us to distinguish between a full json spec
101
     * and a hash defined in a full spec which was divided into a seperate Document (thus, a SubDocument).
102
     * To be aware what it is mainly serves for the generator to generate them as embedded documents,
103
     * as subdocuments are always embedded.
104
     *
105
     * @return bool true if yes, false if not
106
     */
107 6
    public function isSubDocument()
108
    {
109 6
        return $this->def->getIsSubDocument();
110
    }
111
112
    /**
113
     * Gets the namespace
114
     *
115
     * @return string namespace
116
     */
117 14
    public function getNamespace()
118
    {
119 14
        return $this->namespace;
120
    }
121
122
    /**
123
     * Sets the namespace
124
     *
125
     * @param string $namespace namespace
126
     *
127
     * @return void
128
     */
129 10
    public function setNamespace($namespace)
130
    {
131
        // normalize namespace
132 10
        $namespace = str_replace('/', '\\', $namespace);
133
134 10
        if (substr($namespace, -1) == '\\') {
135 2
            $namespace = substr($namespace, 0, -1);
136 1
        }
137
138 10
        $this->namespace = $namespace;
139 10
    }
140
141
    /**
142
     * Returns whether this service is read-only
143
     *
144
     * @return bool true if yes, false if not
145
     */
146 4
    public function isReadOnlyService()
147
    {
148 4
        if ($this->def->getService() === null) {
149
            return false;
150
        }
151
152 4
        return $this->def->getService()->getReadOnly();
153
    }
154
155
    /**
156
     * Returns whether this service is versioning
157
     *
158
     * @return bool true if yes, false if not
159
     */
160
    public function isVersionedService()
161
    {
162
        if ($this->def->getService() === null || !$this->def->getService()->getVersioning()) {
163
            return false;
164
        }
165
166
        return $this->def->getService()->getVersioning();
167
    }
168
169
    /**
170
     * Returns whether this service has fixtures
171
     *
172
     * @return bool true if yes, false if not
173
     */
174 4
    public function hasFixtures()
175
    {
176 4
        return count($this->getFixtures()) > 0;
177
    }
178
179
    /**
180
     * Returns the fixtures or empty array if none
181
     *
182
     * @return array fixtures
183
     */
184 4
    public function getFixtures()
185
    {
186 4
        if ($this->def->getService() === null) {
187
            return [];
188
        }
189
190 4
        return $this->def->getService()->getFixtures();
191
    }
192
193
    /**
194
     * Returns the order number at which order this fixture should be loaded.
195
     * this is needed if we have relations/references between the fixtures..
196
     *
197
     * @return int order
198
     */
199 4
    public function getFixtureOrder()
200
    {
201 4
        if ($this->def->getService() === null ||
202 4
            $this->def->getService()->getFixtureOrder() === null) {
203 2
            return 100;
204
        }
205
206 2
        return $this->def->getService()->getFixtureOrder();
207
    }
208
209
    /**
210
     * Returns a router base path. false if default should be used.
211
     *
212
     * @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...
213
     */
214 6
    public function getRouterBase()
215
    {
216 6
        if ($this->def->getService() === null ||
217 6
            $this->def->getService()->getRouterBase() === null) {
218 2
            return false;
219
        }
220
221 4
        $routerBase = $this->def->getService()->getRouterBase();
222 4
        if (substr($routerBase, 0, 1) !== '/') {
223 2
            $routerBase = '/' . $routerBase;
224 1
        }
225 4
        if (substr($routerBase, -1) === '/') {
226 2
            $routerBase = substr($routerBase, 0, -1);
227 1
        }
228
229 4
        return $routerBase;
230
    }
231
232
    /**
233
     * Returns the Controller classname this services' controller shout inherit.
234
     * Defaults to the RestController of the RestBundle of course.
235
     *
236
     * @return string base controller
237
     */
238 4
    public function getBaseController()
239
    {
240 4
        if ($this->def->getService() === null ||
241 4
            $this->def->getService()->getBaseController() === null) {
242 2
            return 'RestController';
243
        }
244
245 2
        return $this->def->getService()->getBaseController();
246
    }
247
248
    /**
249
     * Returns the parent service to use when adding the service xml
250
     *
251
     * Defaults to graviton.rest.controller
252
     *
253
     * @return string base controller
254
     */
255
    public function getParentService()
256
    {
257
        if ($this->def->getService() === null ||
258
            $this->def->getService()->getParent() === null) {
259
            return 'graviton.rest.controller';
260
        }
261
262
        return $this->def->getService()->getParent();
263
    }
264
265
    /**
266
     * Returns a specific field or null
267
     *
268
     * @param string $name Field name
269
     *
270
     * @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...
271
     */
272 30
    public function getField($name)
273
    {
274 30
        $fields = $this->getFields();
275 30
        return isset($fields[$name]) ? $fields[$name] : null;
276
    }
277
278
    /**
279
     * Returns the field definition
280
     *
281
     * @return DefinitionElementInterface[] Fields
282
     */
283 32
    public function getFields()
284
    {
285 32
        $hierarchy = [];
286 32
        foreach ($this->def->getTarget()->getFields() as $field) {
287 30
            $hierarchy = array_merge_recursive(
288 15
                $hierarchy,
289 30
                $this->createFieldHierarchyRecursive($field, $field->getName())
290 15
            );
291 16
        }
292
293 32
        $fields = [];
294 32
        foreach ($hierarchy as $name => $definition) {
295 30
            $fields[$name] = $this->processFieldHierarchyRecursive($name, $definition);
296 16
        }
297
298
        // Versioning field, for version control.
299 32
        if ($this->def->getService() && $this->def->getService()->getVersioning()) {
300
            $definition = new Schema\Field();
301
            $constraint = new Constraint();
302
            $constraint->setName('versioning');
303
            $definition->setName('version')->setTitle('Version')->setType('int')->setConstraints([$constraint])
304
                ->setDescription('Version control, auto-increment on update');
305
            $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...
306
        }
307
308 32
        return $fields;
309
    }
310
311
    /**
312
     * @param Schema\Field $definition Raw field definition
313
     * @param string       $path       Relative field path
314
     * @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...
315
     * @throws \InvalidArgumentException
316
     */
317 30
    private function createFieldHierarchyRecursive(Schema\Field $definition, $path)
318
    {
319 30
        if (!preg_match('/^(?P<name>[^\.]+)(\.(?P<sub>.+))?$/', $path, $matches)) {
320
            throw new \InvalidArgumentException(sprintf('Invalid field name "%s" definition', $definition->getName()));
321
        }
322
323 30
        $name = ctype_digit($matches['name']) ? "\x00array" : $matches['name'];
324 30
        if (isset($matches['sub'])) {
325 30
            $definition = $this->createFieldHierarchyRecursive($definition, $matches['sub']);
326 15
        } else {
327 30
            $definition = ["\x00field" => $definition];
328
        }
329
330 30
        return [$name => $definition];
331
    }
332
333
    /**
334
     * @param string $name Field name
335
     * @param array  $data Field data
336
     *
337
     * @return DefinitionElementInterface
338
     */
339 30
    private function processFieldHierarchyRecursive($name, $data)
340
    {
341
        // array field
342 30
        if (isset($data["\x00array"])) {
343 28
            return new JsonDefinitionArray(
344 14
                $name,
345 28
                $this->processFieldHierarchyRecursive($name, $data["\x00array"])
346 14
            );
347
        }
348
349
        // simple field
350 30
        if (array_keys($data) === ["\x00field"]) {
351 30
            return $this->processSimpleField($name, $data["\x00field"]);
352
        }
353
354
355
        // hash field
356 30
        $fields = [];
357 30
        $definition = null;
358 30
        foreach ($data as $subname => $subdata) {
359 30
            if ($subname === "\x00field") {
360 6
                $definition = $subdata;
361 3
            } else {
362 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...
363
            }
364 15
        }
365 30
        return new JsonDefinitionHash($name, $this, $fields, $definition);
366
    }
367
368
    /**
369
     * @param string       $name       Field name
370
     * @param Schema\Field $definition Field
371
     *
372
     * @return DefinitionElementInterface
373
     */
374 30
    private function processSimpleField($name, Schema\Field $definition)
375
    {
376 30
        if (strpos($definition->getType(), 'class:') === 0) {
377 24
            $field = new JsonDefinitionRel($name, $definition, $this->getRelation($name));
378 12
        } else {
379 28
            $field = new JsonDefinitionField($name, $definition);
380
        }
381
382 30
        if (substr($definition->getType(), -2) === '[]') {
383 24
            $field = new JsonDefinitionArray($name, $field);
384 12
        }
385
386 30
        return $field;
387
    }
388
389
    /**
390
     * Get target relations which are explictly defined
391
     *
392
     * @return Schema\Relation[] relations
393
     */
394 32
    public function getRelations()
395
    {
396 32
        if ($this->def->getTarget() === null) {
397
            return [];
398
        }
399
400 32
        $relations = [];
401 32
        foreach ($this->def->getTarget()->getRelations() as $relation) {
402 6
            $relations[$relation->getLocalProperty()] = $relation;
403 16
        }
404
405 32
        return $relations;
406
    }
407
408
    /**
409
     * Get relation by field name
410
     *
411
     * @param string $field Field name
412
     * @return Schema\Relation|null
413
     */
414 24
    private function getRelation($field)
415
    {
416 24
        $relations = $this->getRelations();
417 24
        return isset($relations[$field]) ? $relations[$field] : null;
418
    }
419
420
    /**
421
     * Provides the role set defined in the service section.
422
     *
423
     * @return array
424
     */
425 2
    public function getRoles()
426
    {
427 2
        if ($this->def->getService() === null) {
428
            return [];
429
        }
430
431 2
        return $this->def->getService()->getRoles();
432
    }
433
434
    /**
435
     * Can record origin be modified
436
     *
437
     * @return bool
438
     */
439
    public function isRecordOriginModifiable()
440
    {
441
        $retVal = false;
442
        if ($this->isRecordOriginFlagSet()) {
443
            $retVal = $this->def->getService()->getRecordOriginModifiable();
444
        }
445
446
        return $retVal;
447
    }
448
449
    /**
450
     * check if the RecordOriginModifiable flag is set
451
     *
452
     * @return bool
453
     */
454
    public function isRecordOriginFlagSet()
455
    {
456
        $retVal = false;
457
        if ($this->def->getService() !== null
458
            && is_object($this->def->getService())
459
            && $this->def->getService()->getRecordOriginModifiable() !== null) {
460
            $retVal = true;
461
        }
462
463
        return $retVal;
464
    }
465
466
    /**
467
     * @return string
468
     */
469
    public function getServiceCollection()
470
    {
471
        $collectionName = $this->getId();
472
473
        if ($this->def->getService() instanceof Service
474
            && $this->def->getService()->getCollectionName()) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
475
476
            $collectionName = $this->def->getService()->getCollectionName();
477
        }
478
479
        return $collectionName;
480
    }
481
482
    /**
483
     * @return string[]
484
     */
485 2
    public function getIndexes()
486
    {
487 2
        $indexes = [];
488 2
        if ($this->def->getTarget()->getIndexes()) {
489
            $indexes = $this->def->getTarget()->getIndexes();
490
        }
491 2
        return $indexes;
492
    }
493
494
    /**
495
     * @return string[]
496
     */
497
    public function getSearchables()
498
    {
499
        $indexes = [];
500
        if ($fields = $this->def->getTarget()->getFields()) {
501
            foreach ($fields as $field) {
502
                if ($value = (int) $field->getSearchable()) {
503
                    $indexes[$field->getName()] = $value;
504
                }
505
            }
506
        }
507
        return $indexes;
508
    }
509
510
    /**
511
     * @return string[]
512
     */
513 2
    public function getTextIndexes()
514
    {
515 2
        $indexes = [];
516 2
        if ($keys = $this->def->getTarget()->getTextIndexes()) {
517
            foreach ($keys as $key) {
518
                if ($value = (int) $key['weight']) {
519
                    $indexes[$key['field']] = $value;
520
                }
521
            }
522
        }
523 2
        return $indexes;
524
    }
525
526
    /**
527
     * Combine in one array the Search text indexes
528
     * 
529
     * @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...
530
     */
531
    public function getAllTextIndexes()
532
    {
533
        return array_merge(
534
            $this->getSearchables(),
535
            $this->getTextIndexes()
536
        );
537
    }
538
}
539