Completed
Pull Request — master (#23)
by James
02:03
created

AbstractSqlRepository::buildQueryFromRules()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 7

Importance

Changes 7
Bugs 1 Features 1
Metric Value
c 7
b 1
f 1
dl 0
loc 28
ccs 21
cts 21
cp 1
rs 6.7272
cc 7
eloc 18
nc 4
nop 6
crap 7
1
<?php
2
3
namespace Percy\Repository;
4
5
use Aura\Sql\ExtendedPdoInterface;
6
use InvalidArgumentException;
7
use Percy\Decorator\DecoratorTrait;
8
use Percy\Entity\Collection;
9
use Percy\Entity\CollectionBuilderTrait;
10
use Percy\Entity\EntityInterface;
11
use Percy\Http\QueryStringParserTrait;
12
use Percy\Store\StoreInterface;
13
use Psr\Http\Message\ServerRequestInterface;
14
use RuntimeException;
15
16
abstract class AbstractSqlRepository implements RepositoryInterface
17
{
18
    use CollectionBuilderTrait;
19
    use DecoratorTrait;
20
    use QueryStringParserTrait;
21
22
    /**
23
     * @var \Aura\Sql\ExtendedPdoInterface
24
     */
25
    protected $dbal;
26
27
    /**
28
     *
29
     * @var mixed
30
     */
31
    protected $relationships = [];
32
33
    /**
34
     * Construct.
35
     *
36
     * @param \Aura\Sql\ExtendedPdoInterface $dbal
37
     */
38 2
    public function __construct(ExtendedPdoInterface $dbal)
39
    {
40 2
        $this->dbal = $dbal;
41 2
    }
42
43
    /**
44
     * {@inheritdoc}
45
     */
46 1
    public function countFromRequest(ServerRequestInterface $request, $joins = '', $conditionals = '', $end = '')
47
    {
48 1
        $rules = $this->parseQueryString($request->getUri()->getQuery());
49
50 1
        list($query, $params) = $this->buildQueryFromRules(
51 1
            $rules,
52 1
            'SELECT COUNT(*) as total FROM',
53 1
            $joins,
54 1
            $conditionals,
55
            $end
56 1
        );
57
58 1
        return (int) $this->dbal->fetchOne($query, $params)['total'];
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64 1
    public function getFromRequest(
65
        ServerRequestInterface $request,
66
        $start        = 'SELECT * FROM',
67
        $joins        = '',
68
        $conditionals = '',
69
        $end          = ''
70
    ) {
71 1
        $rules = $this->parseQueryString($request->getUri()->getQuery());
72
73 1
        list($query, $params) = $this->buildQueryFromRules($rules, $start, $joins, $conditionals, $end);
74
75 1
        if (array_key_exists('sort', $rules)) {
76 1
            $query .= sprintf(' ORDER BY %s ', $rules['sort']);
77 1
            $query .= (array_key_exists('sort_direction', $rules)) ? $rules['sort_direction'] : 'ASC';
78 1
        }
79
80 1
        if (array_key_exists('limit', $rules)) {
81 1
            $query .= ' LIMIT ';
82 1
            $query .= (array_key_exists('offset', $rules)) ? sprintf('%d,', $rules['offset']) : '';
83 1
            $query .= $rules['limit'];
84 1
        }
85
86 1
        $collection = $this->buildCollection($this->dbal->fetchAll($query, $params))
87 1
                           ->setTotal($this->countFromRequest($request, $joins, $conditionals, $end));
88
89 1
        $this->decorate($collection, StoreInterface::ON_READ);
90
91 1
        return $collection;
92
    }
93
94
    /**
95
     * Build a base query without sorting and limits from filter rules.
96
     *
97
     * @param array       $rules
98
     * @param string      $start
99
     * @param string      $joins
100
     * @param string      $conditionals
101
     * @param string      $end
102
     * @param string|null $table
103
     *
104
     * @return array
105
     */
106 1
    protected function buildQueryFromRules(array $rules, $start, $joins, $conditionals, $end, $table = null)
107
    {
108 1
        $table = (is_null($table)) ? $this->getTable() : $table;
109 1
        $query = sprintf(
110 1
            '%s %s %s %s',
111 1
            trim($start),
112 1
            trim($table),
113 1
            trim($joins),
114 1
            trim($conditionals)
115 1
        );
116
117 1
        $params = [];
118
119 1
        if (array_key_exists('filter', $rules)) {
120 1
            foreach ($rules['filter'] as $key => $where) {
121 1
                $keyword   = ($key === 0 || $conditionals !== '') ? ' WHERE' : ' AND';
122 1
                $delimiter = strtoupper($where['delimiter']);
123 1
                $binding   = (in_array($delimiter, ['IN', 'NOT IN'])) ? sprintf('(:%s)', $where['binding']) : ':' . $where['binding'];
124 1
                $query    .= sprintf('%s %s %s %s', $keyword, $where['field'], $delimiter, $binding);
125
126 1
                $params[$where['binding']] = $where['value'];
127 1
            }
128 1
        }
129
130 1
        $query .= " {$end}";
131
132 1
        return [$query, $params];
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138 1
    public function countByField($field, $value)
139
    {
140 1
        $query = sprintf(
141 1
            'SELECT COUNT(*) as total FROM %s WHERE %s.%s IN (:%s)',
142 1
            $this->getTable(),
143 1
            $this->getTable(),
144 1
            $field,
145
            $field
146 1
        );
147
148
        $params = [
149 1
            $field => implode(',', (array) $value)
150 1
        ];
151
152 1
        return (int) $this->dbal->fetchOne($query, $params)['total'];
153
    }
154
155
    /**
156
     * {@inheritdoc}
157
     */
158 1
    public function getByField($field, $value)
159
    {
160 1
        $query = sprintf(
161 1
            'SELECT * FROM %s WHERE %s.%s IN (:%s)',
162 1
            $this->getTable(),
163 1
            $this->getTable(),
164 1
            $field,
165
            $field
166 1
        );
167
168
        $params = [
169 1
            $field => implode(',', (array) $value)
170 1
        ];
171
172 1
        $collection = $this->buildCollection($this->dbal->fetchAll($query, $params))
173 1
                           ->setTotal($this->countByField($field, $value));
174
175 1
        $this->decorate($collection, StoreInterface::ON_READ);
176
177 1
        return $collection;
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183
    public function getRelationshipsFor(Collection $collection, array $relationships = [])
184
    {
185
        $relCollection = new Collection;
186
187
        foreach ($collection->getIterator() as $entity) {
188
            $rels = $entity->getRelationships();
189
            array_walk($rels, [$this, 'getEntityRelationships'], [
190
                'entity'     => $entity,
191
                'collection' => $relCollection,
192
                'include'    => $relationships
193
            ]);
194
        }
195
196
        return $relCollection;
197
    }
198
199
    /**
200
     * Attach relationships to a specific entity.
201
     *
202
     * @param string $entityType
203
     * @param string $relationship
204
     * @param array  $userData
205
     *
206
     * @return void
207
     */
208
    protected function getEntityRelationships($entityType, $relationship, array $userData)
209
    {
210
        $collection = $userData['collection'];
211
        $include    = $userData['include'];
212
        $entity     = $userData['entity'];
213
        $map        = $this->getRelationshipMap($relationship);
214
215
        if (! in_array($relationship, $include)) {
216
            return false;
217
        }
218
219
        $start = sprintf(
0 ignored issues
show
Unused Code introduced by
$start is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
220
            'SELECT * FROM %s LEFT JOIN %s ON %s.%s = %s.%s WHERE %s = :%s',
221
            $map['defined_in']['table'],
222
            $map['target']['table'],
223
            $map['target']['table'],
224
            $map['target']['primary'],
225
            $map['defined_in']['table'],
226
            $map['target']['relationship'],
227
            $map['defined_in']['primary'],
228
            $map['defined_in']['entity']
229
        );
230
231
        $result = $this->dbal->fetchAll($query, [
0 ignored issues
show
Bug introduced by
The variable $query does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
232
            $map['defined_in']['entity'] => $entity[$map['defined_in']['entity']]
233
        ]);
234
235
        $remove = [$map['defined_in']['primary'], $map['target']['relationship']];
236
237
        foreach ($result as $resource) {
238
            $resource = array_filter($resource, function ($key) use ($remove) {
239
                return (! in_array($key, $remove));
240
            }, ARRAY_FILTER_USE_KEY);
241
242
            $collection->addEntity((new $entityType)->hydrate($resource));
243
        }
244
    }
245
246
    /**
247
     * Get possible relationships and the properties attached to them.
248
     *
249
     * @param string $relationship
250
     *
251
     * @throws \InvalidArgumentException when requested relationship is not defined
252
     * @throws \RuntimeException when map structure is defined incorrectly
253
     *
254
     * @return array
255
     */
256
    public function getRelationshipMap($relationship)
257
    {
258
        if (! array_key_exists($relationship, $this->relationships)) {
259
            throw new InvalidArgumentException(
260
                sprintf('(%s) is not defined in the relationship map on (%s)', $relationship, get_class($this))
261
            );
262
        }
263
264
        $map = $this->relationships[$relationship];
265
266
        foreach ([
267
            'defined_in' => ['table', 'primary', 'entity'],
268
            'target'     => ['table', 'primary', 'relationship']
269
        ] as $key => $value) {
270
            if (! array_key_exists($key, $map) || ! is_array($map[$key])) {
271
                throw new RuntimeException(
272
                    sprintf(
273
                        'Relationship (%s) should contain the (%s) key and should be of type array on (%s)',
274
                        $relationship, $key, get_class($this)
275
                    )
276
                );
277
            }
278
279
            if (! empty(array_diff($value, array_keys($map[$key])))) {
280
                throw new RuntimeException(
281
                    sprintf(
282
                        '(%s) for relationship (%s) should contain keys (%s) on (%s)',
283
                        $key, $relationship, implode(', ', $value), get_class($this)
284
                    )
285
                );
286
            }
287
        }
288
289
        return $map;
290
    }
291
292
    /**
293
     * Returns table that repository is reading from.
294
     *
295
     * @return string
296
     */
297
    abstract protected function getTable();
298
}
299