1 | <?php |
||
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) |
|
39 | |||
40 | /** |
||
41 | * {@inheritdoc} |
||
42 | */ |
||
43 | 1 | public function countFromRequest(ServerRequestInterface $request) |
|
50 | |||
51 | /** |
||
52 | * {@inheritdoc} |
||
53 | */ |
||
54 | 1 | public function getFromRequest(ServerRequestInterface $request, $start = 'SELECT * FROM ', $end = '') |
|
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 |