Completed
Pull Request — master (#18)
by Phil
12:45 queued 10:48
created

AbstractSqlRepository::getRelationshipsFor()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 4
Bugs 2 Features 0
Metric Value
cc 2
eloc 9
c 4
b 2
f 0
nc 2
nop 2
dl 0
loc 15
ccs 0
cts 10
cp 0
crap 6
rs 9.4285
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)
44
    {
45 1
        $rules = $this->parseQueryString($request->getUri()->getQuery());
46 1
        list($query, $params) = $this->buildQueryFromRules($rules, 'SELECT COUNT(*) as total FROM ', '');
47
48 1
        return (int) $this->dbal->fetchOne($query, $params)['total'];
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54 1
    public function getFromRequest(ServerRequestInterface $request, $start = 'SELECT * FROM ', $end = '')
55
    {
56 1
        $rules = $this->parseQueryString($request->getUri()->getQuery());
57
58 1
        list($query, $params) = $this->buildQueryFromRules($rules, $start, $end);
59
60 1
        if (array_key_exists('sort', $rules)) {
61 1
            $query .= sprintf(' ORDER BY %s ', $rules['sort']);
62 1
            $query .= (array_key_exists('sort_direction', $rules)) ? $rules['sort_direction'] : 'ASC';
63 1
        }
64
65 1
        if (array_key_exists('limit', $rules)) {
66 1
            $query .= ' LIMIT ';
67 1
            $query .= (array_key_exists('offset', $rules)) ? sprintf('%d,', $rules['offset']) : '';
68 1
            $query .= $rules['limit'];
69 1
        }
70
71 1
        return $this->buildCollection($this->dbal->fetchAll($query, $params))
72 1
                    ->setTotal($this->countFromRequest($request));
73
    }
74
75
    /**
76
     * Build a base query without sorting and limits from filter rules.
77
     *
78
     * @param array  $rules
79
     * @param string $start
80
     * @param string $end
81
     *
82
     * @return array
83
     */
84 1
    protected function buildQueryFromRules(array $rules, $start, $end)
85
    {
86 1
        $query = $start . $this->getTable();
87
88 1
        $params = [];
89
90 1
        if (array_key_exists('filter', $rules)) {
91 1
            foreach ($rules['filter'] as $key => $where) {
92 1
                $keyword   = ($key === 0) ? ' WHERE' : ' AND';
93 1
                $delimiter = strtoupper($where['delimiter']);
94 1
                $binding   = (in_array($delimiter, ['IN', 'NOT IN'])) ? sprintf('(:%s)', $where['binding']) : ':' . $where['binding'];
95 1
                $query    .= sprintf('%s %s %s %s', $keyword, $where['field'], $delimiter, $binding);
96
97 1
                $params[$where['binding']] = $where['value'];
98 1
            }
99 1
        }
100
101 1
        $query .= $end;
102
103 1
        return [$query, $params];
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109 1
    public function countByField($field, $value)
110
    {
111 1
        $query = sprintf(
112 1
            'SELECT COUNT(*) as total FROM %s WHERE %s.%s IN (:%s)',
113 1
            $this->getTable(),
114 1
            $this->getTable(),
115 1
            $field,
116
            $field
117 1
        );
118
119
        $params = [
120 1
            $field => implode(',', (array) $value)
121 1
        ];
122
123 1
        return (int) $this->dbal->fetchOne($query, $params)['total'];
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129 1
    public function getByField($field, $value)
130
    {
131 1
        $query = sprintf(
132 1
            'SELECT * FROM %s WHERE %s.%s IN (:%s)',
133 1
            $this->getTable(),
134 1
            $this->getTable(),
135 1
            $field,
136
            $field
137 1
        );
138
139
        $params = [
140 1
            $field => implode(',', (array) $value)
141 1
        ];
142
143 1
        return $this->buildCollection($this->dbal->fetchAll($query, $params))
144 1
                    ->setTotal($this->countByField($field, $value));
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150
    public function getRelationshipsFor(Collection $collection, array $relationships = [])
151
    {
152
        $relCollection = new Collection;
153
154
        foreach ($collection->getIterator() as $entity) {
155
            $rels = $entity->getRelationships();
156
            array_walk($rels, [$this, 'getEntityRelationships'], [
157
                'entity'     => $entity,
158
                'collection' => $relCollection,
159
                'include'    => $relationships
160
            ]);
161
        }
162
163
        return $relCollection;
164
    }
165
166
    /**
167
     * Attach relationships to a specific entity.
168
     *
169
     * @param string $entityType
170
     * @param string $relationship
171
     * @param array  $userData
172
     *
173
     * @return void
174
     */
175
    protected function getEntityRelationships($entityType, $relationship, array $userData)
176
    {
177
        $collection = $userData['collection'];
178
        $include    = $userData['include'];
179
        $entity     = $userData['entity'];
180
        $map        = $this->getRelationshipMap($relationship);
181
182
        if (! in_array($relationship, $include)) {
183
            return false;
184
        }
185
186
        $query = sprintf(
187
            'SELECT * FROM %s LEFT JOIN %s ON %s.%s = %s.%s WHERE %s = :%s',
188
            $map['defined_in']['table'],
189
            $map['target']['table'],
190
            $map['target']['table'],
191
            $map['target']['primary'],
192
            $map['defined_in']['table'],
193
            $map['target']['relationship'],
194
            $map['defined_in']['primary'],
195
            $map['defined_in']['entity']
196
        );
197
198
        $result = $this->dbal->fetchAll($query, [
199
            $map['defined_in']['entity'] => $entity[$map['defined_in']['entity']]
200
        ]);
201
202
        $remove = [$map['defined_in']['primary'], $map['target']['relationship']];
203
204
        foreach ($result as $resource) {
205
            $resource = array_filter($resource, function ($key) use ($remove) {
206
                return (! in_array($key, $remove));
207
            }, ARRAY_FILTER_USE_KEY);
208
209
            $collection->addEntity((new $entityType)->hydrate($resource));
210
        }
211
    }
212
213
    /**
214
     * Get possible relationships and the properties attached to them.
215
     *
216
     * @param string $relationship
217
     *
218
     * @throws \InvalidArgumentException when requested relationship is not defined
219
     * @throws \RuntimeException when map structure is defined incorrectly
220
     *
221
     * @return array
222
     */
223
    public function getRelationshipMap($relationship)
224
    {
225
        if (! array_key_exists($relationship, $this->relationships)) {
226
            throw new InvalidArgumentException(
227
                sprintf('(%s) is not defined in the relationship map on (%s)', $relationship, get_class($this))
228
            );
229
        }
230
231
        $map = $this->relationships[$relationship];
232
233
        foreach ([
234
            'defined_in' => ['table', 'primary', 'entity'],
235
            'target'     => ['table', 'primary', 'relationship']
236
        ] as $key => $value) {
237
            if (! array_key_exists($key, $map) || ! is_array($map[$key])) {
238
                throw new RuntimeException(
239
                    sprintf(
240
                        'Relationship (%s) should contain the (%s) key and should be of type array on (%s)',
241
                        $relationship, $key, get_class($this)
242
                    )
243
                );
244
            }
245
246
            if (! empty(array_diff($value, array_keys($map[$key])))) {
247
                throw new RuntimeException(
248
                    sprintf(
249
                        '(%s) for relationship (%s) should contain keys (%s) on (%s)',
250
                        $key, $relationship, implode(', ', $value), get_class($this)
251
                    )
252
                );
253
            }
254
        }
255
256
        return $map;
257
    }
258
259
    /**
260
     * Returns table that repository is reading from.
261
     *
262
     * @return string
263
     */
264
    abstract protected function getTable();
265
}
266