Completed
Push — master ( a9b98c...0233e0 )
by Ondřej
03:55
created

RelationMacros   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 196
Duplicated Lines 8.16 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
wmc 36
c 0
b 0
f 0
lcom 1
cbo 12
dl 16
loc 196
rs 8.8

14 Methods

Rating   Name   Duplication   Size   Complexity  
project() 0 1 ?
tuple() 0 1 ?
A col() 0 4 1
A computeColNameMap() 0 11 4
A toSet() 0 12 3
A extend() 0 8 2
A toArray() 0 8 2
A value() 0 4 1
C _colImpl() 0 25 7
A assoc() 0 10 1
A map() 0 10 1
B multimap() 8 31 5
C assocImpl() 8 40 8
A getIterator() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
namespace Ivory\Relation;
3
4
use Ivory\Data\Map\ArrayRelationMap;
5
use Ivory\Data\Map\ArrayValueMap;
6
use Ivory\Data\Map\IRelationMap;
7
use Ivory\Data\Map\ITupleMap;
8
use Ivory\Data\Map\IValueMap;
9
use Ivory\Data\Map\IWritableValueMap;
10
use Ivory\Data\Set\DictionarySet;
11
use Ivory\Data\Set\ISet;
12
use Ivory\Exception\UndefinedColumnException;
13
use Ivory\Relation\Alg\ITupleEvaluator;
14
use Ivory\Data\Map\ArrayTupleMap;
15
16
/**
17
 * Standard implementations of IRelation operations which are defined solely in terms of other operations.
18
 */
19
trait RelationMacros
20
{
21
    abstract public function project($columns): IRelation;
22
23
    abstract public function tuple(int $offset = 0): ITuple;
24
25
    public function col($offsetOrNameOrEvaluator): IColumn
26
    {
27
        return $this->_colImpl($offsetOrNameOrEvaluator, $this->columns, $this->colNameMap, $this);
0 ignored issues
show
Bug introduced by
The property columns does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Bug introduced by
The property colNameMap does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
28
    }
29
30
31
    protected static function computeColNameMap(IRelation $relation)
32
    {
33
        $result = [];
34
        foreach ($relation->getColumns() as $i => $col) {
35
            $name = $col->getName();
36
            if ($name !== null && !isset($result[$name])) {
37
                $result[$name] = $i;
38
            }
39
        }
40
        return $result;
41
    }
42
43
    public function toSet($colOffsetOrNameOrEvaluator, ISet $set = null): ISet
44
    {
45
        if ($set === null) {
46
            $set = new DictionarySet();
47
        }
48
49
        foreach ($this->col($colOffsetOrNameOrEvaluator) as $value) {
50
            $set->add($value);
51
        }
52
53
        return $set;
54
    }
55
56
    final public function extend($extraColumns): IRelation
57
    {
58
        if ($extraColumns instanceof \Traversable) {
59
            $extraColumns = iterator_to_array($extraColumns);
60
        }
61
62
        return $this->project(array_merge(['*'], $extraColumns));
63
    }
64
65
    final public function toArray(): array
66
    {
67
        $result = [];
68
        foreach ($this as $tuple) { /** @var ITuple $tuple */
0 ignored issues
show
Bug introduced by
The expression $this of type this<Ivory\Relation\RelationMacros> is not traversable.
Loading history...
69
            $result[] = $tuple->toMap();
70
        }
71
        return $result;
72
    }
73
74
    final public function value($colOffsetOrNameOrEvaluator = 0, int $tupleOffset = 0)
75
    {
76
        return $this->tuple($tupleOffset)->value($colOffsetOrNameOrEvaluator);
77
    }
78
79
    final protected function _colImpl($offsetOrNameOrEvaluator, $columns, $colNameMap, IRelation $relation)
80
    {
81
        if (is_scalar($offsetOrNameOrEvaluator)) {
82
            if (filter_var($offsetOrNameOrEvaluator, FILTER_VALIDATE_INT) !== false) {
83
                if (isset($columns[$offsetOrNameOrEvaluator])) {
84
                    return $columns[$offsetOrNameOrEvaluator];
85
                } else {
86
                    throw new UndefinedColumnException("No column at offset $offsetOrNameOrEvaluator");
87
                }
88
            } else {
89
                if (isset($colNameMap[$offsetOrNameOrEvaluator])) {
90
                    return $columns[$colNameMap[$offsetOrNameOrEvaluator]];
91
                } else {
92
                    throw new UndefinedColumnException("No column named $offsetOrNameOrEvaluator");
93
                }
94
            }
95
        } elseif (
96
            $offsetOrNameOrEvaluator instanceof ITupleEvaluator ||
97
            $offsetOrNameOrEvaluator instanceof \Closure
98
        ) {
99
            return new Column($relation, $offsetOrNameOrEvaluator, null, null);
100
        } else {
101
            throw new \InvalidArgumentException('$offsetOrNameOrEvaluator');
102
        }
103
    }
104
105
    public function assoc($col1, $col2, ...$moreCols): IValueMap
106
    {
107
        return $this->assocImpl(
108
            function () {
109
                // FIXME: depending on the data type of the keys, either use an array-based implementation, or an object hashing implementation
110
                return new ArrayValueMap();
111
            },
112
            true, array_merge([$col1, $col2], $moreCols)
113
        );
114
    }
115
116
    public function map($mappingCol, ...$moreMappingCols): ITupleMap
117
    {
118
        return $this->assocImpl(
119
            function () {
120
                // FIXME: depending on the data type of the keys, either use an array-based implementation, or an object hashing implementation
121
                return new ArrayTupleMap();
122
            },
123
            false, array_merge([$mappingCol], $moreMappingCols)
124
        );
125
    }
126
127
    public function multimap($mappingCol, ...$moreMappingCols): IRelationMap
128
    {
129
        $mappingCols = array_merge([$mappingCol], $moreMappingCols);
130
        $multiDimKeyCols = array_slice($mappingCols, 0, -1);
131
        $lastKeyCol = $mappingCols[count($mappingCols) - 1];
132
133
        // FIXME: depending on the data type of the keys, either use an array-based implementation, or an object hashing implementation
134
        $map = new ArrayRelationMap();
135
        $emptyMap = new ArrayRelationMap();
136
        foreach ($this as $tupleOffset => $tuple) { /** @var ITuple $tuple */
0 ignored issues
show
Bug introduced by
The expression $this of type this<Ivory\Relation\RelationMacros> is not traversable.
Loading history...
137
            $m = $map;
138 View Code Duplication
            foreach ($multiDimKeyCols as $col) {
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...
139
                $key = $tuple->value($col);
140
                $added = $m->putIfNotExists($key, $emptyMap);
141
                if ($added) {
142
                    $emptyMap = new ArrayRelationMap(); // prepare a new copy for the following iterations
143
                }
144
                $m = $m[$key];
145
            }
146
147
            $key = $tuple->value($lastKeyCol);
148
            $rel = $m->maybe($key);
149
            if ($rel === null) {
150
                $rel = new CherryPickedRelation($this, []);
151
                $m->put($key, $rel);
152
            }
153
            $rel->cherryPick($tupleOffset);
154
        }
155
156
        return $map;
157
    }
158
159
    /**
160
     * @param \Closure $emptyMapFactory closure creating an empty map for storing the data
161
     * @param bool $lastForValues whether the last of <tt>$cols</tt> shall be used for mapped values
162
     * @param array $cols
163
     * @return IWritableValueMap|ITupleMap
164
     */
165
    private function assocImpl(\Closure $emptyMapFactory, bool $lastForValues, array $cols)
166
    {
167
        $multiDimKeyCols = array_slice($cols, 0, -1 - (int)$lastForValues);
168
        $lastKeyCol = $cols[count($cols) - 1 - (int)$lastForValues];
169
        $valueCol = ($lastForValues ? $cols[count($cols) - 1] : null);
170
171
        /** @var IWritableValueMap $map */
172
        $map = $emptyMapFactory();
173
        $emptyMap = $emptyMapFactory();
174
        foreach ($this as $tuple) { /** @var ITuple $tuple */
0 ignored issues
show
Bug introduced by
The expression $this of type this<Ivory\Relation\RelationMacros> is not traversable.
Loading history...
175
            $m = $map;
176 View Code Duplication
            foreach ($multiDimKeyCols as $col) {
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...
177
                $key = $tuple->value($col);
178
                $added = $m->putIfNotExists($key, $emptyMap);
179
                if ($added) {
180
                    $emptyMap = $emptyMapFactory(); // prepare a new copy for the following iterations
181
                }
182
                $m = $m[$key];
183
            }
184
185
            $key = $tuple->value($lastKeyCol);
186
            $val = ($lastForValues ? $tuple->value($valueCol) : $tuple);
187
            $added = $m->putIfNotExists($key, $val);
188
189
            if (!$added) {
190
                $keys = [];
191
                foreach ($multiDimKeyCols as $col) {
192
                    $keys[] = $tuple->value($col);
193
                }
194
                $keys[] = $tuple->value($lastKeyCol);
195
                $keyDesc = implode(', ', $keys);
196
                trigger_error(
197
                    "Duplicate entry under key ($keyDesc). Skipping. Consider using multimap() instead.",
198
                    E_USER_WARNING
199
                );
200
            }
201
        }
202
203
        return $map;
204
    }
205
206
    //region \IteratorAggregate
207
208
    public function getIterator()
209
    {
210
        return new RelationSeekableIterator($this);
211
    }
212
213
    //endregion
214
}
215