Completed
Push — master ( f73b2f...9dda48 )
by Phil
03:42
created

AbstractSqlRepository   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 60.29%

Importance

Changes 6
Bugs 0 Features 1
Metric Value
wmc 16
c 6
b 0
f 1
lcom 1
cbo 6
dl 0
loc 176
ccs 41
cts 68
cp 0.6029
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 9 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
            // @todo sort filtering of requested relationships
127
            array_walk($entity->getRelationshipKeys(), [$this, 'attachEntityRelationships'], $entity);
0 ignored issues
show
Bug introduced by
$entity->getRelationshipKeys() cannot be passed to array_walk() as the parameter $array expects a reference.
Loading history...
128
        }
129
130
        return $collection;
131
    }
132
133
    /**
134
     * Attach relationships to a specific entity.
135
     *
136
     * @param string                        $entityType
137
     * @param string                        $relationship
138
     * @param \Percy\Entity\EntityInterface $entity
139
     *
140
     * @throws \RuntimeException when relationship has not been properly defined
141
     *
142
     * @return void
143
     */
144
    protected function attachEntityRelationships($entityType, $relationship, EntityInterface $entity)
145
    {
146
        if (! array_key_exists($relationship, $this->getRelationshipMap())) {
147
            throw new RuntimeException(
148
                sprintf('(%s) is not defined in the (%s) relationship map', $relationship, get_class($this))
149
            );
150
        }
151
152
        $map = $this->getRelationshipMap()[$relationship];
153
154
        // @todo integrity check on structure of relationship map
155
156
        $query = sprintf(
157
            'SELECT * FROM %s LEFT JOIN %s ON %s.%s = %s.%s WHERE %s = :%s',
158
            $map['defined_in']['table'],
159
            $map['target']['table'],
160
            $map['target']['table'],
161
            $map['target']['primary'],
162
            $map['defined_in']['table'],
163
            $map['target']['relationship'],
164
            $map['defined_in']['primary'],
165
            $map['defined_in']['entity']
166
        );
167
168
        $result = $this->dbal->execute($query, [
169
            $map['defined_in']['entity'] => $entity[$map['defined_in']['entity']]
170
        ]);
171
172
        $entity[$relationship] = $this->buildCollection($result, $entityType);
173
    }
174
175
    /**
176
     * Get possible relationships and the properties attached to them.
177
     *
178
     * @return array
179
     */
180
    abstract protected function getRelationshipMap();
181
182
    /**
183
     * Returns table that repository is reading from.
184
     *
185
     * @return string
186
     */
187
    abstract protected function getTable();
188
}
189