Completed
Push — master ( 00ae50...77d591 )
by Narcotic
27:26 queued 18:13
created

JsonDefinitionHash   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 252
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 99.04%

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 7
dl 0
loc 252
ccs 103
cts 104
cp 0.9904
rs 9.8
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A cloneFieldDefinition() 0 6 1
A __construct() 0 7 1
A getName() 0 4 1
A getType() 0 4 1
A getTypeDoctrine() 0 4 1
A getTypeSerializer() 0 4 1
C getJsonDefinition() 0 23 7
B processFieldDefinitionsRecursive() 0 18 5
A processParentRelation() 0 11 2
A getClassName() 0 9 2
B isRequired() 0 16 5
B getDefAsArray() 0 31 3
A isAnonymous() 0 4 1
1
<?php
2
namespace Graviton\GeneratorBundle\Definition;
3
use Graviton\GeneratorBundle\Definition\Schema\Field;
4
5
/**
6
 * Represents a hash of fields as defined in the JSON format
7
 *
8
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
9
 * @license  https://opensource.org/licenses/MIT MIT License
10
 * @link     http://swisscom.ch
11
 */
12
class JsonDefinitionHash implements DefinitionElementInterface
13
{
14
    /**
15
     * @var string Name of this hash
16
     */
17
    private $name;
18
    /**
19
     * @var JsonDefinition
20
     */
21
    private $parent;
22
    /**
23
     * @var DefinitionElementInterface[] Array of fields..
24
     */
25
    private $fields = [];
26
    /**
27
     * @var Schema\Field Field definition
28
     */
29
    private $definition;
30
31
    /**
32
     * Constructor
33
     *
34
     * @param string                       $name       Name of this hash
35
     * @param JsonDefinition               $parent     Parent definiton
36
     * @param DefinitionElementInterface[] $fields     Fields of the hash
37
     * @param Schema\Field                 $definition Field definition
0 ignored issues
show
Documentation introduced by
Should the type for parameter $definition not be null|Field?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
38
     */
39 42
    public function __construct($name, JsonDefinition $parent, array $fields, Schema\Field $definition = null)
40
    {
41 42
        $this->name = $name;
42 42
        $this->parent = $parent;
43 42
        $this->fields = $fields;
44 42
        $this->definition = $definition;
45 42
    }
46
47
    /**
48
     * Returns the hash name
49
     *
50
     * @return string Name
51
     */
52 22
    public function getName()
53
    {
54 22
        return $this->name;
55
    }
56
57
    /**
58
     * Returns the definition as array..
59
     *
60
     * @return array the definition
61
     */
62 6
    public function getDefAsArray()
63
    {
64 6
        return array_replace(
65
            [
66 6
                'name'              => $this->getName(),
67 6
                'type'              => $this->getType(),
68 6
                'exposedName'       => $this->getName(),
69 6
                'doctrineType'      => $this->getTypeDoctrine(),
70 6
                'serializerType'    => $this->getTypeSerializer(),
71 6
                'relType'           => self::REL_TYPE_EMBED,
72 3
                'isClassType'       => true,
73 3
                'constraints'       => [],
74 3
                'required'          => false,
75 6
                'searchable'        => 0,
76 3
            ],
77 6
            $this->definition === null ? [
78 4
                'required'          => $this->isRequired()
79 2
            ] : [
80 2
                'exposedName'       => $this->definition->getExposeAs() ?: $this->getName(),
81 2
                'title'             => $this->definition->getTitle(),
82 2
                'description'       => $this->definition->getDescription(),
83 2
                'readOnly'          => $this->definition->getReadOnly(),
84 2
                'required'          => $this->definition->getRequired(),
85 2
                'searchable'        => $this->definition->getSearchable(),
86 2
                'constraints'       => array_map(
87 2
                    [Utils\ConstraintNormalizer::class, 'normalize'],
88 4
                    $this->definition->getConstraints()
89 1
                ),
90
            ]
91 3
        );
92
    }
93
94
    /**
95
     * {@inheritDoc}
96
     *
97
     * @return string type
98
     */
99 8
    public function getType()
100
    {
101 8
        return self::TYPE_HASH;
102
    }
103
104
    /**
105
     * Whether this hash is anonymous, so has no own field definition (properly only defined
106
     * by definitions such as "object.field", not an own definition)
107
     *
108
     * @return bool true if yes, false otherwise
109
     */
110 4
    public function isAnonymous()
111
    {
112 4
        return ($this->definition === null);
113
    }
114
115
    /**
116
     * in an 'anonymous' hash situation, we will check if any children are required
117
     *
118
     * @return bool if required or not
119
     */
120 4
    public function isRequired()
121
    {
122 4
        $isRequired = false;
123
124
        // see if on the first level of fields we have a required=true in the definition
125 4
        foreach ($this->fields as $field) {
126 2
            if ($field instanceof JsonDefinitionField &&
127 2
                $field->getDef() instanceof Field &&
128 2
                $field->getDef()->getRequired() === true
129 1
            ) {
130 2
                $isRequired = true;
131 1
            }
132 2
        }
133
134 4
        return $isRequired;
135
    }
136
137
    /**
138
     * {@inheritDoc}
139
     *
140
     * @return string type
141
     */
142 8
    public function getTypeDoctrine()
143
    {
144 8
        return $this->getClassName(true);
145
    }
146
147
    /**
148
     * Returns the field type in a serializer-understandable way..
149
     *
150
     * @return string Type
151
     */
152 8
    public function getTypeSerializer()
153
    {
154 8
        return $this->getClassName(true);
155
    }
156
157
    /**
158
     * Returns the field definition of this hash from "local perspective",
159
     * meaning that we only include fields inside this hash BUT with all
160
     * the stuff from the json file. this is needed to generate a Document/Model
161
     * from this hash (generate a json file again)
162
     *
163
     * @return JsonDefinition the definition of this hash in a standalone array ready to be json_encoded()
164
     */
165 10
    public function getJsonDefinition()
166
    {
167 10
        $definition = (new Schema\Definition())
168 10
            ->setId($this->getClassName())
169 10
            ->setDescription($this->definition === null ? null : $this->definition->getDescription())
170 10
            ->setTitle($this->definition === null ? null : $this->definition->getTitle())
171 10
            ->setIsSubDocument(true)
172 10
            ->setTarget(new Schema\Target());
173
174 10
        foreach ($this->fields as $field) {
175 10
            foreach ($this->processFieldDefinitionsRecursive($field) as $definitions) {
176 10
                $definition->getTarget()->addField($definitions);
177 5
            }
178 5
        }
179 10
        foreach ($this->parent->getRelations() as $relation) {
180 4
            $relation = $this->processParentRelation($relation);
181 4
            if ($relation !== null) {
182 4
                $definition->getTarget()->addRelation($relation);
183 2
            }
184 5
        }
185
186 10
        return new JsonDefinition($definition);
187
    }
188
189
    /**
190
     * Method getFieldDefinitionsRecursive
191
     *
192
     * @param DefinitionElementInterface $field
193
     * @return Schema\Field[]
194
     */
195 10
    private function processFieldDefinitionsRecursive(DefinitionElementInterface $field)
196
    {
197 10
        if ($field instanceof JsonDefinitionField) {
198 10
            return [$this->cloneFieldDefinition($field->getDef())];
199 8
        } elseif ($field instanceof JsonDefinitionArray) {
200 8
            return $this->processFieldDefinitionsRecursive($field->getElement());
201 4
        } elseif ($field instanceof JsonDefinitionHash) {
202 4
            return array_reduce(
203 4
                $field->fields,
204 4
                function (array $subfields, DefinitionElementInterface $subfield) {
205 4
                    return array_merge($subfields, $this->processFieldDefinitionsRecursive($subfield));
206 4
                },
207 4
                $field->definition === null ? [] : [$this->cloneFieldDefinition($field->definition)]
208 2
            );
209
        }
210
211
        throw new \InvalidArgumentException(sprintf('Unknown field type "%s"', get_class($field)));
212
    }
213
214
    /**
215
     * Clone field definition
216
     *
217
     * @param Schema\Field $field Field
218
     * @return Schema\Field
219
     */
220 10
    private function cloneFieldDefinition(Schema\Field $field)
221
    {
222 10
        $clone = clone $field;
223 10
        $clone->setName(preg_replace('/^'.preg_quote($this->name, '/').'\.(\d+\.)*/', '', $clone->getName()));
224 10
        return $clone;
225
    }
226
227
    /**
228
     * Process parent relation
229
     *
230
     * @param Schema\Relation $relation Parent relation
231
     * @return Schema\Relation|null
232
     */
233 4
    private function processParentRelation(Schema\Relation $relation)
234
    {
235 4
        $prefixRegex = '/^'.preg_quote($this->name, '/').'\.(\d+\.)*(?P<sub>.*)/';
236 4
        if (!preg_match($prefixRegex, $relation->getLocalProperty(), $matches)) {
237 2
            return null;
238
        }
239
240 4
        $clone = clone $relation;
241 4
        $clone->setLocalProperty($matches['sub']);
242 4
        return $clone;
243
    }
244
245
    /**
246
     * Returns the class name of this hash, possibly
247
     * taking the parent element into the name. this
248
     * string here results in the name of the generated Document.
249
     *
250
     * @param boolean $fq if true, we'll return the class name full qualified
251
     *
252
     * @return string
253
     */
254 20
    private function getClassName($fq = false)
255
    {
256 20
        $className = ucfirst($this->parent->getId()).ucfirst($this->getName());
257 20
        if ($fq) {
258 10
            $className = $this->parent->getNamespace().'\\Document\\'.$className;
259 5
        }
260
261 20
        return $className;
262
    }
263
}
264