Completed
Push — master ( b0984e...c79602 )
by Dmitry
03:30
created

Type::getTuple()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 4
Ratio 26.67 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 4
loc 15
ccs 9
cts 9
cp 1
rs 8.8571
cc 5
eloc 9
nc 4
nop 1
crap 5
1
<?php
2
3
namespace Tarantool\Mapper\Schema;
4
5
use Tarantool\Mapper\Contracts;
6
use Tarantool\Mapper\Entity;
7
use LogicException;
8
9
class Type implements Contracts\Type
10
{
11
    private $convention;
12
    private $properties = [];
13
    private $indexes = [];
14
    private $types = [];
15
16
    private $manager;
17
    private $space;
18
    private $spaceId;
19
    private $name;
20
21
    private $entityClass = Entity::class;
22
23 55
    public function __construct(Contracts\Manager $manager, $name, array $properties, array $types, array $indexes)
24
    {
25 55
        $this->manager = $manager;
26 55
        $this->name = $name;
27 55
        $this->convention = $manager->getMeta()->getConvention();
28 55
        $this->spaceId = $manager->getSchema()->getSpaceId($name);
29
30 55
        $this->properties = $properties;
31 55
        $this->indexes = $indexes;
32 55
        $this->types = $types;
33 55
    }
34
35 55
    public function getSpace()
36
    {
37 55
        if (!$this->space) {
38 55
            $this->space = $this->getManager()->getClient()->getSpace($this->spaceId);
39
        }
40
41 55
        return $this->space;
42
    }
43
44 55
    public function getSpaceId()
45
    {
46 55
        return $this->spaceId;
47
    }
48
49 55
    public function getManager()
50
    {
51 55
        return $this->manager;
52
    }
53
54 55
    public function getName()
55
    {
56 55
        return $this->name;
57
    }
58
59 1
    public function setEntityClass($class)
60
    {
61 1
        $this->entityClass = $class;
62 1
    }
63
64 55
    public function getEntityClass()
65
    {
66 55
        return $this->entityClass;
67
    }
68
69 55
    public function addIndex($properties, array $arguments = null)
70
    {
71 55
        $properties = (array) $properties;
72 55
        foreach ($properties as $property) {
73 55
            if (!$this->hasProperty($property)) {
74 55
                throw new LogicException("Unknown property $property for ".$this->name);
75
            }
76
        }
77
78 55
        $schema = $this->manager->getSchema();
79
80 55
        $indexName = implode('_', $properties);
81 55
        if (strlen($indexName) > 32) {
82 1
            $indexName = md5($indexName);
83 1
            while (is_numeric($indexName[0])) {
84 1
                $indexName = md5($indexName);
85
            }
86
        }
87
88 55
        if ($schema->hasIndex($this->getName(), $indexName)) {
89 1
            throw new LogicException("Index $indexName already exists!");
90
        }
91
92 55
        if (!$arguments) {
93 55
            $arguments = [];
94
        }
95
96 55
        if (!array_key_exists('parts', $arguments) || !count($arguments['parts'])) {
97 55
            $arguments['parts'] = [];
98 55
            foreach ($properties as $property) {
99 55
                $arguments['parts'][] = array_search($property, $this->properties) + 1;
100 55
                $arguments['parts'][] = $this->convention->getTarantoolType($this->types[$property]);
101
            }
102
        }
103
104 55
        $num = $schema->createIndex($this->getName(), $indexName, $arguments);
105 55
        $this->indexes[$num] = $properties;
106
107 55
        unset($this->requiredProperties);
108
109 55
        return $this;
110
    }
111
112
    /**
113
     * @param $property name
114
     *
115
     * @return Type
116
     */
117 55
    public function addProperty($name, $type = null)
118
    {
119 55
        if ($this->hasProperty($name)) {
120 1
            throw new LogicException("Duplicate property $name");
121
        }
122 55
        if (!$type) {
123 55
            $type = $this->manager->getMeta()->getConvention()->getType($name);
124
        }
125 55
        $this->types[$name] = $type;
126 55
        $property = $this->manager->create('property', [
127 55
            'space' => $this->spaceId,
128 55
            'index' => count($this->properties),
129 55
            'name' => $name,
130 55
            'type' => $this->types[$name],
131
        ]);
132
133 55
        $this->properties[$property->index] = $name;
0 ignored issues
show
Bug introduced by
Accessing index on the interface Tarantool\Mapper\Contracts\Entity suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
134
135 55
        return $this;
136
    }
137
138 55
    public function hasProperty($name)
139
    {
140 55
        return in_array($name, $this->properties);
141
    }
142
143 55
    public function getProperties()
144
    {
145 55
        return $this->properties;
146
    }
147
148 55
    public function getPropertyType($property)
149
    {
150 55
        return $this->types[$property];
151
    }
152
153 9
    public function setPropertyType($property, $type)
154
    {
155 9
        if (is_array($property)) {
156 2
            foreach ($property as $prop) {
157 2
                $this->setPropertyType($prop, $type);
158
            }
159
160 2
            return $this;
161
        }
162
163 9
        $this->types[$property] = $type;
164
165
        // update entity
166 9
        $row = $this->getManager()->get('property')->findOne([
0 ignored issues
show
Bug introduced by
The method findOne does only exist in Tarantool\Mapper\Contracts\Repository, but not in Tarantool\Mapper\Contracts\Entity.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
167 9
            'space' => $this->spaceId,
168 9
            'index' => array_search($property, $this->properties),
169
        ]);
170 9
        $row->type = $type;
171 9
        $this->getManager()->save($row);
172
173 9
        return $this;
174
    }
175
176 1
    public function renameProperty($name, $new)
177
    {
178 1
        if (!$this->hasProperty($name)) {
179
            throw new LogicException("Unknown property $name");
180
        }
181
182 1
        $index = array_search($name, $this->properties);
183 1
        $this->properties[$index] = $new;
184
185 1
        $row = $this->getManager()->get('property')->findOne([
0 ignored issues
show
Bug introduced by
The method findOne does only exist in Tarantool\Mapper\Contracts\Repository, but not in Tarantool\Mapper\Contracts\Entity.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
186 1
            'space' => $this->spaceId,
187 1
            'index' => $index,
188
        ]);
189 1
        $row->name = $new;
190 1
        $this->getManager()->save($row);
191
192 1
        $this->types[$new] = $this->types[$name];
193 1
        unset($this->types[$name]);
194
195
        // flush required cache
196 1
        unset($this->requiredProperties);
197
198 1
        foreach ($this->indexes as $index => $fields) {
199 1
            if (in_array($name, $fields)) {
200 1
                $this->indexes[$index][array_search($name, $fields)] = $new;
201
            }
202
        }
203 1
    }
204
205 5
    public function removeProperty($name)
206
    {
207 5
        if (!$this->hasProperty($name)) {
208
            throw new LogicException("Unknown property $name");
209
        }
210
211 5
        foreach ($this->indexes as $index => $fields) {
212 3
            if ($fields != [$name] && in_array($name, $fields)) {
213 3
                throw new LogicException("Property is used by composite index $index");
214
            }
215
        }
216
217 4
        foreach ($this->indexes as $index => $fields) {
218 2
            if ($fields == [$name]) {
219 2
                unset($this->indexes[$index]);
220
            }
221
        }
222
223 4
        $index = array_search($name, $this->properties);
224
225 4
        unset($this->properties[$index]);
226 4
        unset($this->types[$name]);
227
228 4
        $property = $this->manager->get('property')->findOne([
0 ignored issues
show
Bug introduced by
The method findOne does only exist in Tarantool\Mapper\Contracts\Repository, but not in Tarantool\Mapper\Contracts\Entity.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
229 4
            'space' => $this->spaceId,
230 4
            'index' => $index,
231
        ]);
232
233 4
        $this->manager->remove($property);
234 4
    }
235
236 6
    public function reference(Contracts\Type $foreign, $property = null)
237
    {
238 6
        if (!$property) {
239 5
            $property = $foreign->getName();
240
        }
241
242 6
        $this->addProperty($property);
243 6
        $this->setPropertyType($property, $foreign->getName());
244 6
        $this->addIndex($property, ['unique' => false]);
245
246 6
        return $this;
247
    }
248
249 55
    public function isReference($property)
250
    {
251 55
        return !$this->convention->isPrimitive($this->types[$property]);
252
    }
253
254 4
    public function getReferenceProperty(Contracts\Type $type)
255
    {
256 4
        $properties = [];
257 4
        foreach ($this->types as $property => $propertyType) {
258 4
            if ($type->getName() == $propertyType) {
259 4
                $properties[] = $property;
260
            }
261
        }
262 4
        if (!count($properties)) {
263 1
            throw new LogicException('Type '.$this->getName().' is not related with '.$type->getName());
264
        }
265 3
        if (count($properties) > 1) {
266 1
            throw new LogicException('Multiple type reference found');
267
        }
268
269 2
        return $properties[0];
270
    }
271
272 1
    public function getReferences()
273
    {
274 1
        $references = [];
275 1
        $convention = $this->manager->getMeta()->getConvention();
276 1
        foreach ($this->types as $property => $type) {
277 1
            if (!$convention->isPrimitive($type)) {
278 1
                $references[$property] = $type;
279
            }
280
        }
281
282 1
        return $references;
283
    }
284
285 55
    public function getRequiredProperties()
286
    {
287 55
        if (!isset($this->requiredProperties)) {
288 55
            $this->requiredProperties = ['id' => 1];
0 ignored issues
show
Bug introduced by
The property requiredProperties does not seem to exist. Did you mean properties?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
289 55
            $indexList = $this->manager->getSchema()->listIndexes($this->getName());
290 55
            foreach ($indexList as $name => $fields) {
291 55
                foreach ($fields as $num) {
292 55
                    $this->requiredProperties[$this->properties[$num]] = true;
0 ignored issues
show
Bug introduced by
The property requiredProperties does not seem to exist. Did you mean properties?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
293
                }
294
            }
295 55
            $this->requiredProperties = array_keys($this->requiredProperties);
0 ignored issues
show
Bug introduced by
The property requiredProperties does not seem to exist. Did you mean properties?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
296
        }
297
298 55
        return $this->requiredProperties;
0 ignored issues
show
Bug introduced by
The property requiredProperties does not seem to exist. Did you mean properties?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
299
    }
300
301 1
    public function getIndex($num)
302
    {
303 1
        return $this->indexes[$num];
304
    }
305
306 3
    public function dropIndex($num)
307
    {
308 3
        if (is_array($num)) {
309 1
            $num = $this->findIndex($num);
310
        }
311 3
        $this->getManager()->getSchema()->dropIndex($this->spaceId, $num);
312 3
        unset($this->indexes[$num]);
313 3
    }
314
315 3
    public function getIndexes()
316
    {
317 3
        return $this->indexes;
318
    }
319
320 55
    public function findIndex($query)
321
    {
322 55
        if (!count($query)) {
323 5
            return 0;
324
        }
325
326 55
        sort($query);
327 55
        foreach ($this->indexes as $name => $fields) {
328 55
            if ($fields == $query) {
329 55
                return $name;
330
            }
331
        }
332
333
        // cast partial index
334 6
        $casting = [];
335
336 6
        foreach ($this->indexes as $indexId => $fields) {
337 6
            if (!count(array_diff($query, $fields))) {
338 5
                for ($i = 0; $i < count($query); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
339 5
                    if (!in_array($fields[$i], $query)) {
340 1
                        continue 2;
341
                    }
342
                }
343 6
                $casting[count(array_diff($fields, $query))] = $indexId;
344
            }
345
        }
346 6
        if (!count($casting)) {
347 2
            return;
348
        }
349 5
        ksort($casting);
350
351 5
        return array_shift($casting);
352
    }
353
354 55
    public function getIndexTuple($index, $params)
355
    {
356 55
        $tuple = [];
357 55
        foreach ($this->indexes[$index] as $property) {
358 55
            if (array_key_exists($property, $params)) {
359 55
                $tuple[array_search($property, $this->indexes[$index])] = $params[$property];
360
            }
361
        }
362
363 55
        return $tuple;
364
    }
365
366 55
    public function getCompleteTuple($input)
367
    {
368 55
        $tuple = $this->getTuple($input);
369 55
        $required = $this->getRequiredProperties();
370
371 55
        foreach ($this->getProperties() as $index => $field) {
372 55 View Code Duplication
            if (in_array($field, $required) && !array_key_exists($index, $tuple)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
373
                $propertyType = $this->getPropertyType($field);
374 55
                $tuple[$index] = $this->convention->getDefaultValue($propertyType);
375
            }
376
        }
377
378
        // normalize tuple
379 55
        if (array_values($tuple) != $tuple) {
380
            // index was skipped
381 2
            $max = max(array_keys($tuple));
382 2
            foreach (range(0, $max) as $index) {
383 2
                if (!array_key_exists($index, $tuple)) {
384 2
                    $tuple[$index] = null;
385
                }
386
            }
387 2
            ksort($tuple);
388
        }
389
390 55
        return $tuple;
391
    }
392
393 55
    public function getTuple($input)
394
    {
395 55
        $output = [];
396 55
        foreach ($this->getProperties() as $index => $name) {
397 55
            if (array_key_exists($name, $input)) {
398 55
                $output[$index] = $this->encodeProperty($name, $input[$name]);
399 55 View Code Duplication
                if (is_null($output[$index]) && in_array($name, $this->getRequiredProperties())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
400 1
                    $propertyType = $this->getPropertyType($name);
401 55
                    $output[$index] = $this->convention->getDefaultValue($propertyType);
402
                }
403
            }
404
        }
405
406 55
        return $output;
407
    }
408
409 55
    public function fromTuple($input)
410
    {
411 55
        $output = [];
412 55
        foreach ($this->getProperties() as $index => $name) {
413 55
            if (array_key_exists($index, $input)) {
414 55
                $output[$name] = $this->decodeProperty($name, $input[$index]);
415
            }
416
        }
417
418 55
        return $output;
419
    }
420
421 55
    public function encodeProperty($name, $value)
422
    {
423 55
        return $this->convention->encode($this->types[$name], $value);
424
    }
425
426 55
    public function decodeProperty($name, $value)
427
    {
428 55
        return $this->convention->decode($this->types[$name], $value);
429
    }
430
}
431