Completed
Push — master ( f8fe78...3db7c0 )
by Phil
04:31
created

AbstractSqlRepository::attachRelationships()   D

Complexity

Conditions 10
Paths 7

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 13.7026

Importance

Changes 4
Bugs 2 Features 0
Metric Value
c 4
b 2
f 0
dl 0
loc 30
ccs 12
cts 18
cp 0.6667
rs 4.8197
cc 10
eloc 16
nc 7
nop 3
crap 13.7026

How to fix   Complexity   

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
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 6
    public function __construct(ExtendedPdoInterface $dbal)
36
    {
37 6
        $this->dbal = $dbal;
38 6
    }
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)
55
    {
56 1
        $rules = $this->parseQueryString($request->getUri()->getQuery());
57
58 1
        list($query, $params) = $this->buildQueryFromRules($rules);
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
     *
81
     * @return array
82
     */
83 1
    protected function buildQueryFromRules(array $rules, $start = 'SELECT * FROM ')
84
    {
85 1
        $query = $start . $this->getTable();
86
87 1
        $params = [];
88
89 1
        if (array_key_exists('filter', $rules)) {
90 1
            foreach ($rules['filter'] as $key => $where) {
91 1
                $keyword = ($key === 0) ? ' WHERE' : ' AND';
92 1
                $query  .= sprintf('%s %s %s :%s', $keyword, $where['field'], $where['delimiter'], $where['field']);
93
94 1
                $params[$where['field']] = $where['value'];
95 1
            }
96 1
        }
97
98 1
        return [$query, $params];
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104 1
    public function countByField($field, $value)
105
    {
106 1
        $query = sprintf('SELECT COUNT(*) as total FROM %s WHERE %s IN (:%s)', $this->getTable(), $field, $field);
107
108
        $params = [
109 1
            $field => implode(',', (array) $value)
110 1
        ];
111
112 1
        return (int) $this->dbal->fetchOne($query, $params)['total'];
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118 1
    public function getByField($field, $value)
119
    {
120 1
        $query = sprintf('SELECT * FROM %s WHERE %s IN (:%s)', $this->getTable(), $field, $field);
121
122
        $params = [
123 1
            $field => implode(',', (array) $value)
124 1
        ];
125
126 1
        return $this->buildCollection($this->dbal->fetchAll($query, $params))
127 1
                    ->setTotal($this->countByField($field, $value));
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133 4
    public function attachRelationships(Collection $collection, array $relationships = [], $exclude = false)
134
    {
135 4
        if ($exclude === false && empty($relationships)) {
136
            return $collection;
137
        }
138
139 4
        foreach ($collection->getIterator() as $entity) {
140 4
            $rels = $entity->getRelationships();
141
142 4
            foreach ($rels as $key => $val) {
143 4
                if (empty($relationships)) {
144
                    continue;
145
                }
146
147 4
                if ($exclude === false && ! in_array($key, $relationships)) {
148
                    unset($rels[$key]);
149
                    continue;
150
                }
151
152 4
                if ($exclude === true && in_array($key, $relationships)) {
153
                    unset($rels[$key]);
154
                    continue;
155
                }
156 4
            }
157
158 4
            array_walk($rels, [$this, 'attachEntityRelationships'], $entity);
159 1
        }
160
161 1
        return $collection;
162
    }
163
164
    /**
165
     * Attach relationships to a specific entity.
166
     *
167
     * @param string                        $entityType
168
     * @param string                        $relationship
169
     * @param \Percy\Entity\EntityInterface $entity
170
     *
171
     * @throws \RuntimeException when relationship has not been properly defined
172
     *
173
     * @return void
174
     */
175 4
    protected function attachEntityRelationships($entityType, $relationship, EntityInterface $entity)
176
    {
177 4
        $map = $this->getRelationshipMap($relationship);
178
179 1
        $query = sprintf(
180 1
            'SELECT * FROM %s LEFT JOIN %s ON %s.%s = %s.%s WHERE %s = :%s',
181 1
            $map['defined_in']['table'],
182 1
            $map['target']['table'],
183 1
            $map['target']['table'],
184 1
            $map['target']['primary'],
185 1
            $map['defined_in']['table'],
186 1
            $map['target']['relationship'],
187 1
            $map['defined_in']['primary'],
188 1
            $map['defined_in']['entity']
189 1
        );
190
191 1
        $result = $this->dbal->fetchAll($query, [
192 1
            $map['defined_in']['entity'] => $entity[$map['defined_in']['entity']]
193 1
        ]);
194
195 1
        $remove = [$map['defined_in']['primary'], $map['target']['relationship']];
196
197 1
        foreach ($result as &$resource) {
198
            $resource = array_filter($resource, function ($key) use ($remove) {
199
                return (! in_array($key, $remove));
200
            }, ARRAY_FILTER_USE_KEY);
201 1
        }
202
203 1
        $entity[$relationship] = $this->buildCollection($result, $entityType);
204 1
    }
205
206
    /**
207
     * Get possible relationships and the properties attached to them.
208
     *
209
     * @param string $relationship
210
     *
211
     * @throws \InvalidArgumentException when requested relationship is not defined
212
     * @throws \RuntimeException when map structure is defined incorrectly
213
     *
214
     * @return array
215
     */
216 4
    protected function getRelationshipMap($relationship)
217
    {
218 4
        if (! array_key_exists($relationship, $this->relationships)) {
219 1
            throw new InvalidArgumentException(
220 1
                sprintf('(%s) is not defined in the relationship map on (%s)', $relationship, get_class($this))
221 1
            );
222
        }
223
224 3
        $map = $this->relationships[$relationship];
225
226
        foreach ([
227 3
            'defined_in' => ['table', 'primary', 'entity'],
228 3
            'target'     => ['table', 'primary', 'relationship']
229 3
        ] as $key => $value) {
230 3
            if (! array_key_exists($key, $map) || ! is_array($map[$key])) {
231 1
                throw new RuntimeException(
232 1
                    sprintf(
233 1
                        'Relationship (%s) should contain the (%s) key and should be of type array on (%s)',
234 1
                        $relationship, $key, get_class($this)
235 1
                    )
236 1
                );
237
            }
238
239 2
            if (! empty(array_diff($value, array_keys($map[$key])))) {
240 1
                throw new RuntimeException(
241 1
                    sprintf(
242 1
                        '(%s) for relationship (%s) should contain keys (%s) on (%s)',
243 1
                        $key, $relationship, implode(', ', $value), get_class($this)
244 1
                    )
245 1
                );
246
            }
247 1
        }
248
249 1
        return $map;
250
    }
251
252
    /**
253
     * Returns table that repository is reading from.
254
     *
255
     * @return string
256
     */
257
    abstract protected function getTable();
258
}
259