Completed
Push — master ( e8017f...b79f6f )
by Dmitry
04:07
created

Type::addIndex()   D

Complexity

Conditions 9
Paths 21

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 9

Importance

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