Completed
Pull Request — master (#21)
by Phil
03:23
created

AbstractSqlRepository   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 274
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 51.52%

Importance

Changes 27
Bugs 5 Features 2
Metric Value
wmc 27
c 27
b 5
f 2
lcom 1
cbo 6
dl 0
loc 274
ccs 68
cts 132
cp 0.5152
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A countFromRequest() 0 14 1
B getFromRequest() 0 25 5
C buildQueryFromRules() 0 28 7
A countByField() 0 16 1
A getByField() 0 17 1
A getRelationshipsFor() 0 15 2
B getEntityRelationships() 0 37 3
B getRelationshipMap() 0 35 6
getTable() 0 1 ?
1
<?php
2
3
namespace Percy\Repository;
4
5
use Aura\Sql\ExtendedPdoInterface;
6
use InvalidArgumentException;
7
use Percy\Entity\Collection;
8
use Percy\Entity\CollectionBuilderTrait;
9
use Percy\Entity\EntityInterface;
10
use Percy\Http\QueryStringParserTrait;
11
use Psr\Http\Message\ServerRequestInterface;
12
use RuntimeException;
13
14
abstract class AbstractSqlRepository implements RepositoryInterface
15
{
16
    use CollectionBuilderTrait;
17
    use QueryStringParserTrait;
18
19
    /**
20
     * @var \Aura\Sql\ExtendedPdoInterface
21
     */
22
    protected $dbal;
23
24
    /**
25
     *
26
     * @var mixed
27
     */
28
    protected $relationships = [];
29
30
    /**
31
     * Construct.
32
     *
33
     * @param \Aura\Sql\ExtendedPdoInterface $dbal
34
     */
35 2
    public function __construct(ExtendedPdoInterface $dbal)
36
    {
37 2
        $this->dbal = $dbal;
38 2
    }
39
40
    /**
41
     * {@inheritdoc}
42
     */
43 1
    public function countFromRequest(ServerRequestInterface $request, $joins = '', $conditionals = '', $end = '')
44
    {
45 1
        $rules = $this->parseQueryString($request->getUri()->getQuery());
46
47 1
        list($query, $params) = $this->buildQueryFromRules(
48 1
            $rules,
49 1
            'SELECT COUNT(*) as total FROM',
50 1
            $joins,
51 1
            $conditionals,
52
            $end
53 1
        );
54
55 1
        return (int) $this->dbal->fetchOne($query, $params)['total'];
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61 1
    public function getFromRequest(
62
        ServerRequestInterface $request,
63
        $start        = 'SELECT * FROM',
64
        $joins        = '',
65
        $conditionals = '',
66
        $end          = ''
67
    ) {
68 1
        $rules = $this->parseQueryString($request->getUri()->getQuery());
69
70 1
        list($query, $params) = $this->buildQueryFromRules($rules, $start, $joins, $conditionals, $end);
71
72 1
        if (array_key_exists('sort', $rules)) {
73 1
            $query .= sprintf(' ORDER BY %s ', $rules['sort']);
74 1
            $query .= (array_key_exists('sort_direction', $rules)) ? $rules['sort_direction'] : 'ASC';
75 1
        }
76
77 1
        if (array_key_exists('limit', $rules)) {
78 1
            $query .= ' LIMIT ';
79 1
            $query .= (array_key_exists('offset', $rules)) ? sprintf('%d,', $rules['offset']) : '';
80 1
            $query .= $rules['limit'];
81 1
        }
82
83 1
        return $this->buildCollection($this->dbal->fetchAll($query, $params))
84 1
                    ->setTotal($this->countFromRequest($request, $joins, $conditionals, $end));
85
    }
86
87
    /**
88
     * Build a base query without sorting and limits from filter rules.
89
     *
90
     * @param array       $rules
91
     * @param string      $start
92
     * @param string      $joins
93
     * @param string      $conditionals
94
     * @param string      $end
95
     * @param string|null $table
96
     *
97
     * @return array
98
     */
99 1
    protected function buildQueryFromRules(array $rules, $start, $joins, $conditionals, $end, $table = null)
100
    {
101 1
        $table = (is_null($table)) ? $this->getTable() : $table;
102 1
        $query = sprintf(
103 1
            '%s %s %s %s',
104 1
            trim($start),
105 1
            trim($table),
106 1
            trim($joins),
107 1
            trim($conditionals)
108 1
        );
109
110 1
        $params = [];
111
112 1
        if (array_key_exists('filter', $rules)) {
113 1
            foreach ($rules['filter'] as $key => $where) {
114 1
                $keyword   = ($key === 0 || $conditionals !== '') ? ' WHERE' : ' AND';
115 1
                $delimiter = strtoupper($where['delimiter']);
116 1
                $binding   = (in_array($delimiter, ['IN', 'NOT IN'])) ? sprintf('(:%s)', $where['binding']) : ':' . $where['binding'];
117 1
                $query    .= sprintf('%s %s %s %s', $keyword, $where['field'], $delimiter, $binding);
118
119 1
                $params[$where['binding']] = $where['value'];
120 1
            }
121 1
        }
122
123 1
        $query .= " {$end}";
124
125 1
        return [$query, $params];
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131 1
    public function countByField($field, $value)
132
    {
133 1
        $query = sprintf(
134 1
            'SELECT COUNT(*) as total FROM %s WHERE %s.%s IN (:%s)',
135 1
            $this->getTable(),
136 1
            $this->getTable(),
137 1
            $field,
138
            $field
139 1
        );
140
141
        $params = [
142 1
            $field => implode(',', (array) $value)
143 1
        ];
144
145 1
        return (int) $this->dbal->fetchOne($query, $params)['total'];
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151 1
    public function getByField($field, $value)
152
    {
153 1
        $query = sprintf(
154 1
            'SELECT * FROM %s WHERE %s.%s IN (:%s)',
155 1
            $this->getTable(),
156 1
            $this->getTable(),
157 1
            $field,
158
            $field
159 1
        );
160
161
        $params = [
162 1
            $field => implode(',', (array) $value)
163 1
        ];
164
165 1
        return $this->buildCollection($this->dbal->fetchAll($query, $params))
166 1
                    ->setTotal($this->countByField($field, $value));
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function getRelationshipsFor(Collection $collection, array $relationships = [])
173
    {
174
        $relCollection = new Collection;
175
176
        foreach ($collection->getIterator() as $entity) {
177
            $rels = $entity->getRelationships();
178
            array_walk($rels, [$this, 'getEntityRelationships'], [
179
                'entity'     => $entity,
180
                'collection' => $relCollection,
181
                'include'    => $relationships
182
            ]);
183
        }
184
185
        return $relCollection;
186
    }
187
188
    /**
189
     * Attach relationships to a specific entity.
190
     *
191
     * @param string $entityType
192
     * @param string $relationship
193
     * @param array  $userData
194
     *
195
     * @return void
196
     */
197
    protected function getEntityRelationships($entityType, $relationship, array $userData)
198
    {
199
        $collection = $userData['collection'];
200
        $include    = $userData['include'];
201
        $entity     = $userData['entity'];
202
        $map        = $this->getRelationshipMap($relationship);
203
204
        if (! in_array($relationship, $include)) {
205
            return false;
206
        }
207
208
        $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...
209
            'SELECT * FROM %s LEFT JOIN %s ON %s.%s = %s.%s WHERE %s = :%s',
210
            $map['defined_in']['table'],
211
            $map['target']['table'],
212
            $map['target']['table'],
213
            $map['target']['primary'],
214
            $map['defined_in']['table'],
215
            $map['target']['relationship'],
216
            $map['defined_in']['primary'],
217
            $map['defined_in']['entity']
218
        );
219
220
        $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...
221
            $map['defined_in']['entity'] => $entity[$map['defined_in']['entity']]
222
        ]);
223
224
        $remove = [$map['defined_in']['primary'], $map['target']['relationship']];
225
226
        foreach ($result as $resource) {
227
            $resource = array_filter($resource, function ($key) use ($remove) {
228
                return (! in_array($key, $remove));
229
            }, ARRAY_FILTER_USE_KEY);
230
231
            $collection->addEntity((new $entityType)->hydrate($resource));
232
        }
233
    }
234
235
    /**
236
     * Get possible relationships and the properties attached to them.
237
     *
238
     * @param string $relationship
239
     *
240
     * @throws \InvalidArgumentException when requested relationship is not defined
241
     * @throws \RuntimeException when map structure is defined incorrectly
242
     *
243
     * @return array
244
     */
245
    public function getRelationshipMap($relationship)
246
    {
247
        if (! array_key_exists($relationship, $this->relationships)) {
248
            throw new InvalidArgumentException(
249
                sprintf('(%s) is not defined in the relationship map on (%s)', $relationship, get_class($this))
250
            );
251
        }
252
253
        $map = $this->relationships[$relationship];
254
255
        foreach ([
256
            'defined_in' => ['table', 'primary', 'entity'],
257
            'target'     => ['table', 'primary', 'relationship']
258
        ] as $key => $value) {
259
            if (! array_key_exists($key, $map) || ! is_array($map[$key])) {
260
                throw new RuntimeException(
261
                    sprintf(
262
                        'Relationship (%s) should contain the (%s) key and should be of type array on (%s)',
263
                        $relationship, $key, get_class($this)
264
                    )
265
                );
266
            }
267
268
            if (! empty(array_diff($value, array_keys($map[$key])))) {
269
                throw new RuntimeException(
270
                    sprintf(
271
                        '(%s) for relationship (%s) should contain keys (%s) on (%s)',
272
                        $key, $relationship, implode(', ', $value), get_class($this)
273
                    )
274
                );
275
            }
276
        }
277
278
        return $map;
279
    }
280
281
    /**
282
     * Returns table that repository is reading from.
283
     *
284
     * @return string
285
     */
286
    abstract protected function getTable();
287
}
288