ComplexQueryHydrator::hydrateRelation()   B
last analyzed

Complexity

Conditions 8
Paths 20

Size

Total Lines 60
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 8

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 60
ccs 29
cts 29
cp 1
rs 7.0677
cc 8
eloc 28
nc 20
nop 3
crap 8

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the "RocketORM" package.
5
 *
6
 * https://github.com/RocketORM/ORM
7
 *
8
 * For the full license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Rocket\ORM\Model\Query\Hydrator;
13
14
use Rocket\ORM\Model\Object\RocketObject;
15
use Rocket\ORM\Rocket;
16
17
/**
18
 * @author Sylvain Lorinet <[email protected]>
19
 */
20
class ComplexQueryHydrator implements QueryHydratorInterface
21
{
22
    /**
23
     * @var string
24
     */
25
    protected $modelNamespace;
26
27
    /**
28
     * @var string
29
     */
30
    protected $alias;
31
32
    /**
33
     * @var array
34
     */
35
    protected $joins;
36
37
    /**
38
     * @var array|RocketObject[]
39
     */
40
    protected $objects;
41
42
    /**
43
     * @var array|RocketObject[]
44
     */
45
    protected $objectsByAlias;
46
47
    /**
48
     * @var array|RocketObject[]
49
     */
50
    protected $objectsByAliasByRow;
51
52
53
    /**
54
     * @param string $modelNamespace
55
     * @param string $alias
56
     * @param array  $joins
57
     */
58 4
    public function __construct($modelNamespace, $alias, array $joins)
59
    {
60 4
        $this->modelNamespace = $modelNamespace;
61 4
        $this->alias = $alias;
62 4
        $this-> joins = $joins;
63
64
        // The main objects array, returning at the end
65 4
        $this->objects = [];
66
67
        // An array of objects indexed by the query alias, to know if the object has been already
68
        // instantiated and avoid multiple instantiations
69 4
        $this->objectsByAlias = [];
70
71
        // An array of objects indexed by query alias by row (cleared after each loop),
72
        // to know where the relation object should be placed
73 4
        $this->objectsByAliasByRow = [];
74 4
    }
75
76
    /**
77
     * @inheritdoc
78
     */
79 4
    public function hydrate(\PDOStatement $stmt)
80
    {
81 4
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
82 4
            $data = $this->getRowDataByAlias($row);
83
84 4
            foreach ($data as $alias => $item) {
85
                // The main object
86 4
                if ($this->alias === $alias) {
87 4
                    $this->hydrateObject($item);
88 4
                } else {
89 4
                    $this->hydrateRelation($data, $item, $alias);
90
                }
91 4
            }
92 4
        }
93
94 4
        unset($this->objectsByAlias);
95 4
        $this->objectsByAlias = [];
96
97 4
        return $this->objects;
98
    }
99
100
    /**
101
     * Hydrate a main object (called in "<code>FROM</code>" SQL clause)
102
     *
103
     * @param array $item
104
     */
105 4
    protected function hydrateObject(array $item)
106
    {
107 4
        $objectHash = Rocket::getTableMap($this->modelNamespace)->getPrimaryKeysHash($item);
108
109 4
        if (!isset($this->objectsByAlias[$this->alias][$objectHash])) {
110 4
            $object = new RocketObject($item, $this->modelNamespace);
111
112
            // Saving object for relations
113 4
            $this->objectsByAlias[$this->alias][$objectHash] = ['object' => $object];
114 4
            $this->objectsByAliasByRow[$this->alias] = $object;
115 4
            $this->objects[] = $object;
116 4
        } else {
117 3
            $this->objectsByAliasByRow[$this->alias] = $this->objectsByAlias[$this->alias][$objectHash]['object'];
118
        }
119 4
    }
120
121
    /**
122
     * Hydrate a relation object (called by "<code>JOIN</code>" SQL clause)
123
     *
124
     * @param array  $data
125
     * @param array  $item
126
     * @param string $alias
127
     */
128 4
    protected function hydrateRelation(array $data, array $item, $alias)
129
    {
130 4
        $hash = Rocket::getTableMap($this->joins[$alias]['relation']['namespace'])->getPrimaryKeysHash($item);
131 4
        $relationFrom = $this->joins[$alias]['from'];
132 4
        $relationPhpName = $this->joins[$alias]['relation']['phpName'];
133
134 4
        if (isset($this->joins[$relationFrom])) {
135 2
            $parentHash = Rocket::getTableMap($this->joins[$relationFrom]['relation']['namespace'])->getPrimaryKeysHash($data[$relationFrom]);
136 2
        } else {
137
            // Parent is the main object
138 4
            $parentHash = Rocket::getTableMap($this->modelNamespace)->getPrimaryKeysHash($data[$this->alias]);
139
        }
140
141
        // Item for the current row is empty
142 4
        if (null == $hash) {
143 2
            $this->hydrateNullRelation($alias, $relationFrom, $relationPhpName);
144
145 2
            return;
146
        }
147
148
        // Relation has already been added
149 4
        if (isset($this->objectsByAlias[$relationFrom][$parentHash]['childs'][$hash])) {
150 2
            return;
151
        }
152
153
        // Object does not exist : create it, otherwise use the object reference
154 4
        if (!isset($this->objectsByAlias[$alias][$hash])) {
155 4
            $this->objectsByAlias[$alias][$hash] = [
156 4
                'object' => new RocketObject($item, $this->joins[$alias]['relation']['namespace'])
157 4
            ];
158 4
        }
159
160 4
        $this->objectsByAliasByRow[$alias] = $this->objectsByAlias[$alias][$hash]['object'];
161
162
        // If the parent has not been processed yet
163 4
        if (!isset($this->objectsByAliasByRow[$relationFrom])) {
164
            // @codeCoverageIgnoreStart
165
            // Should never append, because Rocket does not allow to SELECT a relation before his parent table
166
            throw new \LogicException(
167
                'The parent object for the relation "' . $relationPhpName . '"'
168
                . ' (from: "' . $relationFrom . '") does not exist'
169
            );
170
            // @codeCoverageIgnoreEnd
171
        }
172
173
        // If many, put the object into another array, otherwise just set the $relationPhpName array key with the object
174 4
        if ($this->joins[$alias]['relation']['is_many']) {
175
            // Create the array if doesn't exist
176 3
            if (!isset($this->objectsByAliasByRow[$relationFrom][$relationPhpName])) {
177 3
                $this->objectsByAliasByRow[$relationFrom][$relationPhpName] = [];
178 3
            }
179
180 3
            $this->objectsByAliasByRow[$relationFrom][$relationPhpName][] = $this->objectsByAlias[$alias][$hash]['object'];
181 3
        } else {
182 2
            $this->objectsByAliasByRow[$relationFrom][$relationPhpName] = $this->objectsByAlias[$alias][$hash]['object'];
183
        }
184
185
        // Avoid duplicate relation objects
186 4
        $this->objectsByAlias[$relationFrom][$parentHash]['childs'][$hash] = $this->objectsByAlias[$alias][$hash]['object'];
187 4
    }
188
189
    /**
190
     * Hydrate non required relation where the row is NULL, e.g. LEFT JOIN relation
191
     *
192
     * @param string $alias
193
     * @param string $relationFrom
194
     * @param string $relationPhpName
195
     */
196 2
    protected function hydrateNullRelation($alias, $relationFrom, $relationPhpName)
197
    {
198 2
        if (!$this->joins[$alias]['relation']['is_many']) {
199 2
            $this->objectsByAliasByRow[$relationFrom][$relationPhpName] = null;
200 2
        } else {
201 1
            $this->objectsByAliasByRow[$relationFrom][$relationPhpName] = [];
202
        }
203
204
        // Do not forget to unset the current row
205 2
        unset($this->objectsByAliasByRow[$alias]);
206 2
    }
207
208
    /**
209
     * Get an array of row data indexed by the object alias, example :
210
     *
211
     * <code>
212
     * ['a.foo' => 1, 'b.bar' => 2] // become :
213
     *
214
     * [
215
     *   'a' => [
216
     *     'foo' => 1
217
     *   ], [
218
     *   'b' => [
219
     *     'bar' => 2
220
     *   ]
221
     * ]
222
     * </code>
223
     *
224
     * @param array $row
225
     *
226
     * @return array
227
     */
228 4
    protected function getRowDataByAlias(array $row)
229
    {
230 4
        $data = [];
231
232
        // Create an array of columns indexed by the query alias
233 4
        foreach ($row as $columnName => $value) {
234 4
            if (false !== strpos($columnName, '.')) {
235 4
                $params = explode('.', $columnName);
236 4
                $data[$params[0]][$params[1]] = $value;
237 4
            }
238
            else {
239 4
                $data[$this->alias][$columnName] = $value;
240
            }
241 4
        }
242
243 4
        return $data;
244
    }
245
}
246