Completed
Push — master ( 1e32a0...e93ee4 )
by Dmitry
03:26
created

Type::removeProperty()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 7.0099

Importance

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