Completed
Push — master ( 5a85ab...aabe2c )
by Dmitry
03:43
created

Type::findIndex()   D

Complexity

Conditions 9
Paths 14

Size

Total Lines 33
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9

Importance

Changes 4
Bugs 0 Features 2
Metric Value
c 4
b 0
f 2
dl 0
loc 33
ccs 18
cts 18
cp 1
rs 4.909
cc 9
eloc 18
nc 14
nop 1
crap 9
1
<?php
2
3
namespace Tarantool\Mapper\Schema;
4
5
use Tarantool\Mapper\Contracts;
6
use LogicException;
7
8
class Type implements Contracts\Type
9
{
10
    private $convention;
11
    private $properties = [];
12
    private $indexes = [];
13
    private $types = [];
14
15
    private $manager;
16
    private $space;
17
    private $spaceId;
18
    private $name;
19
20 49
    public function __construct(Contracts\Manager $manager, $name, array $properties, array $types, array $indexes)
21
    {
22 49
        $this->manager = $manager;
23 49
        $this->name = $name;
24 49
        $this->convention = $manager->getMeta()->getConvention();
25 49
        $this->spaceId = $manager->getSchema()->getSpaceId($name);
26
27 49
        $this->properties = $properties;
28 49
        $this->indexes = $indexes;
29 49
        $this->types = $types;
30 49
    }
31
32 49
    public function getSpace()
33
    {
34 49
        if (!$this->space) {
35 49
            $this->space = $this->getManager()->getClient()->getSpace($this->spaceId);
36
        }
37
38 49
        return $this->space;
39
    }
40
41 49
    public function getSpaceId()
42
    {
43 49
        return $this->spaceId;
44
    }
45
46 49
    public function getManager()
47
    {
48 49
        return $this->manager;
49
    }
50
51 49
    public function getName()
52
    {
53 49
        return $this->name;
54
    }
55
56 49
    public function addIndex($properties, array $arguments = null)
57
    {
58 49
        $properties = (array) $properties;
59 49
        foreach ($properties as $property) {
60 49
            if (!$this->hasProperty($property)) {
61 49
                throw new LogicException("Unknown property $property for ".$this->name);
62
            }
63
        }
64
65 49
        $schema = $this->manager->getSchema();
66
67 49
        $indexName = implode('_', $properties);
68 49
        if (strlen($indexName) > 32) {
69 2
            $indexName = md5($indexName);
70 2
            while (is_numeric($indexName[0])) {
71 2
                $indexName = md5($indexName);
72
            }
73
        }
74
75 49
        if ($schema->hasIndex($this->getName(), $indexName)) {
76 1
            throw new LogicException("Index $indexName already exists!");
77
        }
78
79 49
        if (!$arguments) {
80 49
            $arguments = [];
81
        }
82
83 49
        if (!array_key_exists('parts', $arguments) || !count($arguments['parts'])) {
84 49
            $arguments['parts'] = [];
85 49
            foreach ($properties as $property) {
86 49
                $arguments['parts'][] = array_search($property, $this->properties) + 1;
87 49
                $arguments['parts'][] = $this->convention->getTarantoolType($this->types[$property]);
88
            }
89
        }
90
91 49
        $num = $schema->createIndex($this->getName(), $indexName, $arguments);
92 49
        $this->indexes[$num] = $properties;
93
94 49
        return $this;
95
    }
96
97
    /**
98
     * @param $property name
99
     *
100
     * @return Type
101
     */
102 49
    public function addProperty($name, $type = null)
103
    {
104 49
        if ($this->hasProperty($name)) {
105 1
            throw new LogicException("Duplicate property $name");
106
        }
107 49
        if (!$type) {
108 49
            $type = $this->manager->getMeta()->getConvention()->getType($name);
109
        }
110 49
        $this->types[$name] = $type;
111 49
        $property = $this->manager->create('property', [
112 49
            'space' => $this->spaceId,
113 49
            'index' => count($this->properties),
114 49
            'name' => $name,
115 49
            'type' => $this->types[$name],
116
        ]);
117
118 49
        $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...
119
120 49
        return $this;
121
    }
122
123 49
    public function hasProperty($name)
124
    {
125 49
        return in_array($name, $this->properties);
126
    }
127
128 49
    public function getProperties()
129
    {
130 49
        return $this->properties;
131
    }
132
133 2
    public function getPropertyType($property)
134
    {
135 2
        return $this->types[$property];
136
    }
137
138 8
    public function setPropertyType($property, $type)
139
    {
140 8
        if (is_array($property)) {
141 1
            foreach ($property as $prop) {
142 1
                $this->setPropertyType($prop, $type);
143
            }
144
145 1
            return $this;
146
        }
147
148 8
        $this->types[$property] = $type;
149
150
        // update entity
151 8
        $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...
152 8
            'space' => $this->spaceId,
153 8
            'index' => array_search($property, $this->properties),
154
        ]);
155 8
        $row->type = $type;
156 8
        $this->getManager()->save($row);
157
158 8
        return $this;
159
    }
160
161 5
    public function removeProperty($name)
162
    {
163 5
        if (!$this->hasProperty($name)) {
164
            throw new LogicException("Unknown property $name");
165
        }
166
167 5
        foreach ($this->indexes as $index => $fields) {
168 3
            if ($fields != [$name] && in_array($name, $fields)) {
169 3
                throw new LogicException("Property is used by composite index $index");
170
            }
171
        }
172
173 4
        foreach ($this->indexes as $index => $fields) {
174 2
            if ($fields == [$name]) {
175 2
                unset($this->indexes[$index]);
176
            }
177
        }
178
179 4
        $index = array_search($name, $this->properties);
180
181 4
        unset($this->properties[$index]);
182 4
        unset($this->types[$name]);
183
184 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...
185 4
            'space' => $this->spaceId,
186 4
            'index' => $index,
187
        ]);
188
189 4
        $this->manager->remove($property);
190 4
    }
191
192 6
    public function reference(Contracts\Type $foreign, $property = null)
193
    {
194 6
        if (!$property) {
195 5
            $property = $foreign->getName();
196
        }
197
198 6
        $this->addProperty($property);
199 6
        $this->setPropertyType($property, $foreign->getName());
200 6
        $this->addIndex($property, ['unique' => false]);
201
202 6
        return $this;
203
    }
204
205 49
    public function isReference($property)
206
    {
207 49
        return !$this->convention->isPrimitive($this->types[$property]);
208
    }
209
210 4
    public function getReferenceProperty(Contracts\Type $type)
211
    {
212 4
        $properties = [];
213 4
        foreach ($this->types as $property => $propertyType) {
214 4
            if ($type->getName() == $propertyType) {
215 4
                $properties[] = $property;
216
            }
217
        }
218 4
        if (!count($properties)) {
219 1
            throw new LogicException('Type '.$this->getName().' is not related with '.$type->getName());
220
        }
221 3
        if (count($properties) > 1) {
222 1
            throw new LogicException('Multiple type reference found');
223
        }
224
225 2
        return $properties[0];
226
    }
227
228 1
    public function getReferences()
229
    {
230 1
        $references = [];
231 1
        $convention = $this->manager->getMeta()->getConvention();
232 1
        foreach ($this->types as $property => $type) {
233 1
            if (!$convention->isPrimitive($type)) {
234 1
                $references[$property] = $type;
235
            }
236
        }
237
238 1
        return $references;
239
    }
240
241 49
    public function getRequiredProperties()
242
    {
243 49
        if (!isset($this->requiredProperties)) {
244 49
            $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...
245 49
            $indexList = $this->manager->getSchema()->listIndexes($this->getName());
246 49
            foreach ($indexList as $name => $fields) {
247 49
                foreach ($fields as $num) {
248 49
                    $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...
249
                }
250
            }
251 49
            $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...
252
        }
253
254 49
        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...
255
    }
256
257 1
    public function getIndex($num)
258
    {
259 1
        return $this->indexes[$num];
260
    }
261
262 3
    public function dropIndex($num)
263
    {
264 3
        if (is_array($num)) {
265 1
            $num = $this->findIndex($num);
266
        }
267 3
        $this->getManager()->getSchema()->dropIndex($this->spaceId, $num);
268 3
        unset($this->indexes[$num]);
269 3
    }
270
271 3
    public function getIndexes()
272
    {
273 3
        return $this->indexes;
274
    }
275
276 49
    public function findIndex($query)
277
    {
278 49
        if (!count($query)) {
279 5
            return 0;
280
        }
281
282 49
        sort($query);
283 49
        foreach ($this->indexes as $name => $fields) {
284 49
            if ($fields == $query) {
285 49
                return $name;
286
            }
287
        }
288
289
        // cast partial index
290 6
        $casting = [];
291
292 6
        foreach ($this->indexes as $indexId => $fields) {
293 6
            if (!count(array_diff($query, $fields))) {
294 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...
295 5
                    if (!in_array($fields[$i], $query)) {
296 1
                        break 2;
297
                    }
298
                }
299 6
                $casting[count(array_diff($fields, $query))] = $indexId;
300
            }
301
        }
302 6
        if (!count($casting)) {
303 2
            return;
304
        }
305 4
        ksort($casting);
306
307 4
        return array_shift($casting);
308
    }
309
310 49
    public function getIndexTuple($index, $params)
311
    {
312 49
        $tuple = [];
313 49
        foreach ($this->indexes[$index] as $property) {
314 49
            if (array_key_exists($property, $params)) {
315 49
                $tuple[array_search($property, $this->indexes[$index])] = $params[$property];
316
            }
317
        }
318
319 49
        return $tuple;
320
    }
321
322 49
    public function getCompleteTuple($input)
323
    {
324 49
        $tuple = $this->getTuple($input);
325 49
        $required = $this->getRequiredProperties();
326
327 49
        foreach ($this->getProperties() as $index => $field) {
328 49
            if (in_array($field, $required) && !array_key_exists($index, $tuple)) {
329 1
                if ($this->isReference($field)) {
330 1
                    $tuple[$index] = 0;
331
                } else {
332 49
                    $tuple[$index] = '';
333
                }
334
            }
335
        }
336
337
        // normalize tuple
338 49
        if (array_values($tuple) != $tuple) {
339
            // index was skipped
340 3
            $max = max(array_keys($tuple));
341 3
            foreach (range(0, $max) as $index) {
342 3
                if (!array_key_exists($index, $tuple)) {
343 3
                    $tuple[$index] = null;
344
                }
345
            }
346 3
            ksort($tuple);
347
        }
348
349 49
        return $tuple;
350
    }
351
352 49 View Code Duplication
    public function getTuple($input)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
353
    {
354 49
        $output = [];
355 49
        foreach ($this->getProperties() as $index => $name) {
356 49
            if (array_key_exists($name, $input)) {
357 49
                $output[$index] = $this->encodeProperty($name, $input[$name]);
358
            }
359
        }
360
361 49
        return $output;
362
    }
363
364 49 View Code Duplication
    public function fromTuple($input)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
365
    {
366 49
        $output = [];
367 49
        foreach ($this->getProperties() as $index => $name) {
368 49
            if (array_key_exists($index, $input)) {
369 49
                $output[$name] = $this->decodeProperty($name, $input[$index]);
370
            }
371
        }
372
373 49
        return $output;
374
    }
375
376 49
    public function encodeProperty($name, $value)
377
    {
378 49
        return $this->convention->encode($this->types[$name], $value);
379
    }
380
381 49
    public function decodeProperty($name, $value)
382
    {
383 49
        return $this->convention->decode($this->types[$name], $value);
384
    }
385
}
386