Completed
Push — master ( 9dda48...567a16 )
by Phil
02:16
created

AbstractSqlRepository   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 177
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 59.42%

Importance

Changes 7
Bugs 1 Features 1
Metric Value
wmc 16
c 7
b 1
f 1
lcom 1
cbo 6
dl 0
loc 177
ccs 41
cts 69
cp 0.5942
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A countFromRequest() 0 7 1
B getFromRequest() 0 19 5
A buildQueryFromRules() 0 15 3
A countByField() 0 10 1
A getByField() 0 11 1
A attachRelationships() 0 10 2
B attachEntityRelationships() 0 30 2
getRelationshipMap() 0 1 ?
getTable() 0 1 ?
1
<?php
2
3
namespace Percy\Repository;
4
5
use Percy\Dbal\DbalInterface;
6
use Percy\Entity\Collection;
7
use Percy\Entity\CollectionBuilderTrait;
8
use Percy\Entity\EntityInterface;
9
use Percy\Http\QueryStringParserTrait;
10
use Psr\Http\Message\ServerRequestInterface;
11
use RuntimeException;
12
13
abstract class AbstractSqlRepository implements RepositoryInterface
14
{
15
    use CollectionBuilderTrait;
16
    use QueryStringParserTrait;
17
18
    /**
19
     * @var \Percy\Dbal\DbalInterface
20
     */
21
    protected $dbal;
22
23
    /**
24
     * Construct.
25
     *
26
     * @param \Percy\Dbal\DbalInterface $dbal
27
     */
28 2
    public function __construct(DbalInterface $dbal)
29
    {
30 2
        $this->dbal = $dbal;
31 2
    }
32
33
    /**
34
     * {@inheritdoc}
35
     */
36 1
    public function countFromRequest(ServerRequestInterface $request)
37
    {
38 1
        $rules = $this->parseQueryString($request->getUri()->getQuery());
39 1
        list($query, $params) = $this->buildQueryFromRules($rules, 'SELECT COUNT(*) as total FROM ');
40
41 1
        return $this->dbal->execute($query, $params)['total'];
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47 1
    public function getFromRequest(ServerRequestInterface $request)
48
    {
49 1
        $rules = $this->parseQueryString($request->getUri()->getQuery());
50 1
        list($query, $params) = $this->buildQueryFromRules($rules);
51
52 1
        if (array_key_exists('sort', $rules)) {
53 1
            $query .= sprintf(' ORDER BY %s ', $rules['sort']);
54 1
            $query .= (array_key_exists('sort_direction', $rules)) ? $rules['sort_direction'] : 'ASC';
55 1
        }
56
57 1
        if (array_key_exists('limit', $rules)) {
58 1
            $query .= ' LIMIT ';
59 1
            $query .= (array_key_exists('offset', $rules)) ? sprintf('%d,', $rules['offset']) : '';
60 1
            $query .= $rules['limit'];
61 1
        }
62
63 1
        return $this->buildCollection($this->dbal->execute($query, $params))
64 1
                    ->setTotal($this->countFromRequest($request));
65
    }
66
67
    /**
68
     * Build a base query without sorting and limits from filter rules.
69
     *
70
     * @param array  $rules
71
     * @param string $start
72
     *
73
     * @return array
74
     */
75 1
    protected function buildQueryFromRules(array $rules, $start = 'SELECT * FROM ')
76
    {
77 1
        $query = $start . $this->getTable();
78
79 1
        $params = [];
80
81 1
        foreach ($rules['filter'] as $key => $where) {
82 1
            $keyword = ($key === 0) ? ' WHERE' : ' AND';
83 1
            $query  .= sprintf('%s %s %s :%s', $keyword, $where['field'], $where['delimiter'], $where['field']);
84
85 1
            $params[$where['field']] = $where['value'];
86 1
        }
87
88 1
        return [$query, $params];
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 1
    public function countByField($field, $value)
95
    {
96 1
        $query = sprintf('SELECT COUNT(*) as total FROM %s WHERE %s IN (:%s)', $this->getTable(), $field, $field);
97
98
        $params = [
99 1
            $field => implode(',', (array) $value)
100 1
        ];
101
102 1
        return $this->dbal->execute($query, $params)['total'];
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108 1
    public function getByField($field, $value)
109
    {
110 1
        $query = sprintf('SELECT * FROM %s WHERE %s IN (:%s)', $this->getTable(), $field, $field);
111
112
        $params = [
113 1
            $field => implode(',', (array) $value)
114 1
        ];
115
116 1
        return $this->buildCollection($this->dbal->execute($query, $params))
117 1
                    ->setTotal($this->countByField($field, $value));
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function attachRelationships(Collection $collection, array $relationships = [])
124
    {
125
        foreach ($collection->getIterator() as $entity) {
126
            $rels = $entity->getRelationshipKeys();
127
            // @todo sort filtering of requested relationships
128
            array_walk($rels, [$this, 'attachEntityRelationships'], $entity);
129
        }
130
131
        return $collection;
132
    }
133
134
    /**
135
     * Attach relationships to a specific entity.
136
     *
137
     * @param string                        $entityType
138
     * @param string                        $relationship
139
     * @param \Percy\Entity\EntityInterface $entity
140
     *
141
     * @throws \RuntimeException when relationship has not been properly defined
142
     *
143
     * @return void
144
     */
145
    protected function attachEntityRelationships($entityType, $relationship, EntityInterface $entity)
146
    {
147
        if (! array_key_exists($relationship, $this->getRelationshipMap())) {
148
            throw new RuntimeException(
149
                sprintf('(%s) is not defined in the (%s) relationship map', $relationship, get_class($this))
150
            );
151
        }
152
153
        $map = $this->getRelationshipMap()[$relationship];
154
155
        // @todo integrity check on structure of relationship map
156
157
        $query = sprintf(
158
            'SELECT * FROM %s LEFT JOIN %s ON %s.%s = %s.%s WHERE %s = :%s',
159
            $map['defined_in']['table'],
160
            $map['target']['table'],
161
            $map['target']['table'],
162
            $map['target']['primary'],
163
            $map['defined_in']['table'],
164
            $map['target']['relationship'],
165
            $map['defined_in']['primary'],
166
            $map['defined_in']['entity']
167
        );
168
169
        $result = $this->dbal->execute($query, [
170
            $map['defined_in']['entity'] => $entity[$map['defined_in']['entity']]
171
        ]);
172
173
        $entity[$relationship] = $this->buildCollection($result, $entityType);
174
    }
175
176
    /**
177
     * Get possible relationships and the properties attached to them.
178
     *
179
     * @return array
180
     */
181
    abstract protected function getRelationshipMap();
182
183
    /**
184
     * Returns table that repository is reading from.
185
     *
186
     * @return string
187
     */
188
    abstract protected function getTable();
189
}
190