Completed
Push — master ( c7e226...39e6a7 )
by Juan
07:25
created

NativeQueryBuilderHelper::getQueryFileContent()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 19
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
namespace Micayael\NativeQueryFromFileBuilderBundle\Helper;
4
5
use Adbar\Dot;
6
use Micayael\NativeQueryFromFileBuilderBundle\Event\NativeQueryFromFileBuilderEvents;
7
use Micayael\NativeQueryFromFileBuilderBundle\Event\ProcessQueryParamsEvent;
8
use Micayael\NativeQueryFromFileBuilderBundle\Exception\NonExistentQueryDirectoryException;
9
use Micayael\NativeQueryFromFileBuilderBundle\Exception\NonExistentQueryFileException;
10
use Micayael\NativeQueryFromFileBuilderBundle\Exception\NonExistentQueryKeyException;
11
use Symfony\Component\Cache\Adapter\AdapterInterface;
12
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
13
use Symfony\Component\Filesystem\Filesystem;
14
use Symfony\Component\Yaml\Yaml;
15
16
class NativeQueryBuilderHelper
17
{
18
    const REQUIRED_ID_PATTERN = '/@{.+?}/';
19
20
    const OPTIONAL_ID_PATTERN = "/@\[.+?\]/";
21
22
    const KEY_PATTERN = '/[a-z0-9._]+/';
23
24
    /**
25
     * @var null|EventDispatcherInterface
26
     */
27
    private $eventDispatcher;
28
29
    /**
30
     * @var null|AdapterInterface
31
     */
32
    private $cache;
33
34
    private $queryDir;
35
36
    private $fileExtension;
37
38
    public function __construct(?EventDispatcherInterface $eventDispatcher, ?AdapterInterface $cache, string $queryDir, string $fileExtension = 'yaml')
39
    {
40
        $this->eventDispatcher = $eventDispatcher;
41
        $this->cache = $cache;
42
43
        $this->queryDir = $queryDir;
44
        $this->fileExtension = $fileExtension;
45
    }
46
47
    /**
48
     * Get a SQL query from a query key in a yaml file.
49
     *
50
     * @param string $queryFullKey
51
     * @param array  $params
52
     *
53
     * @return string
54
     *
55
     * @throws NonExistentQueryDirectoryException
56
     * @throws NonExistentQueryFileException
57
     * @throws NonExistentQueryKeyException
58
     */
59
    public function getSqlFromYamlKey(string $queryFullKey, array &$params = []): string
60
    {
61
        $queryFullKey = explode(':', $queryFullKey);
62
63
        $fileKey = $queryFullKey[0];
64
        $queryKey = $queryFullKey[1];
65
66
        if ($this->cache) {
67
            $cacheItem = $this->cache->getItem('nqbff_'.$fileKey);
68
69
            if (!$cacheItem->isHit()) {
70
                $dot = $this->getQueryFileContent($fileKey);
71
72
                $cacheItem->set($dot);
73
                $this->cache->save($cacheItem);
74
            }
75
76
            $dot = $cacheItem->get();
77
        } else {
78
            $dot = $this->getQueryFileContent($fileKey);
79
        }
80
81
        if (!$dot->has($queryKey)) {
82
            throw new NonExistentQueryKeyException($queryKey);
83
        }
84
85
        $sql = $dot->get($queryKey);
86
87
        $sqlParts = $this->resolveRequiredKeys($dot, $sql);
88
89
        array_push($sqlParts, $sql);
90
91
        $sql = $this->resolveOptionalKeys($dot, $sqlParts, $params);
92
93
        // Reemplaza espacios adicionales
94
        $sql = trim(preg_replace('/\s+/', ' ', $sql));
95
96
        return $sql;
97
    }
98
99
    /**
100
     * @param string $fileKey
101
     *
102
     * @return Dot
103
     *
104
     * @throws NonExistentQueryDirectoryException
105
     * @throws NonExistentQueryFileException
106
     */
107
    private function getQueryFileContent(string $fileKey): Dot
108
    {
109
        $fileSystem = new Filesystem();
110
111
        if (!$fileSystem->exists($this->queryDir)) {
112
            throw new NonExistentQueryDirectoryException($this->queryDir);
113
        }
114
115
        $filename = sprintf('%s/%s.%s', $this->queryDir, $fileKey, $this->fileExtension);
116
117
        if (!$fileSystem->exists($filename)) {
118
            throw new NonExistentQueryFileException($filename);
119
        }
120
121
        $data = Yaml::parseFile($filename);
122
123
        $dot = new Dot($data);
124
125
        return $dot;
126
    }
127
128
    /**
129
     * @param Dot    $dot
130
     * @param string $sql
131
     *
132
     * @return array An array of query parts depending on required keys @{file:query}
133
     *
134
     * @throws NonExistentQueryKeyException
135
     */
136
    private function resolveRequiredKeys(Dot $dot, string $sql): array
137
    {
138
        $queryParts = [];
139
140
        $snippetIds = [];
141
142
        preg_match_all(self::REQUIRED_ID_PATTERN, $sql, $snippetIds);
143
144
        foreach ($snippetIds[0] as $snippetId) {
145
            preg_match(self::KEY_PATTERN, $snippetId, $matches);
146
147
            $snippetKey = $matches[0];
148
149
            if (empty($snippetKey)) {
150
                continue;
151
            }
152
153
            if (!$dot->has($snippetKey)) {
154
                throw new NonExistentQueryKeyException($snippetKey);
155
            }
156
157
            $snippetSql = $dot->get($snippetKey);
158
159
            $queryParts[$snippetId] = trim(preg_replace('/\s+/', ' ', $snippetSql));
160
        }
161
162
        return $queryParts;
163
    }
164
165
    /**
166
     * @param Dot   $dot
167
     * @param array $sqlParts
168
     * @param array $params
169
     *
170
     * @return string Processed SQL with parameters defined by @[file:query:params]
171
     *
172
     * @throws NonExistentQueryKeyException
173
     */
174
    private function resolveOptionalKeys(Dot $dot, array $sqlParts, array &$params = []): string
175
    {
176
        $paramKeys = array_keys($params);
177
178
        // Se recorren los sqlParts y por cada uno se van agregando los filtros opcionales.
179
        // El query principal se evalúa al final
180
        foreach ($sqlParts as $sqlPartKey => $sql) {
181
            $snippetIds = [];
182
183
            $whereConnector = (false === strpos(strtoupper($sql), 'WHERE')) ? 'WHERE ' : 'AND ';
184
185
            preg_match_all(self::OPTIONAL_ID_PATTERN, $sql, $snippetIds);
186
187
            foreach ($snippetIds[0] as $snippetId) {
188
                $requestedSnippets = [];
189
190
                preg_match(self::KEY_PATTERN, $snippetId, $matches);
191
192
                $snippetKey = $matches[0];
193
194
                if (empty($snippetKey)) {
195
                    continue;
196
                }
197
198
                if (!$dot->has($snippetKey)) {
199
                    throw new NonExistentQueryKeyException($snippetKey);
200
                }
201
202
                $snippets = $dot->get($snippetKey);
203
204
                foreach ($snippets as $filter) {
205
                    $filterType = null;
206
207
                    if (is_array($filter)) {
208
                        $filterType = key($filter);
209
                        $filter = $filter[$filterType];
210
                    }
211
212
                    foreach ($paramKeys as $paramKey) {
213
                        if (false !== strpos($filter, ':'.$paramKey)) {
214
                            $event = new ProcessQueryParamsEvent($snippetKey, $filterType, $paramKey, $params, $filter);
215
216
                            if ($this->eventDispatcher) {
217
                                $this->eventDispatcher->dispatch(NativeQueryFromFileBuilderEvents::PROCESS_QUERY_PARAMS, $event);
218
                            }
219
220
                            $params = $event->getProcessedParams();
221
                            $filter = $event->getProcessedFilter();
222
223
                            // Si no existe en la lista de filtros a usar, se agrega
224
                            if (!in_array($filter, array_keys($requestedSnippets))) {
225
                                $requestedSnippets[$filter] = $whereConnector.'('.$filter.')';
226
                            }
227
228
                            $whereConnector = 'AND ';
229
                        }
230
                    }
231
                }
232
233
                if (!empty($requestedSnippets)) {
234
                    $snippetSql = implode(' ', $requestedSnippets);
235
236
                    $sqlParts[$sqlPartKey] = str_replace($snippetId, $snippetSql, $sql);
237
                } else {
238
                    $sqlParts[$sqlPartKey] = str_replace($snippetId, '', $sql);
239
                }
240
            }
241
        }
242
243
        // Obtiene la última posición del array que corresponde al SQL base
244
        $sql = array_pop($sqlParts);
245
246
        // Recorre las partes y las va reemplazando en el SQL base
247
        foreach ($sqlParts as $sqlPartKey => $sqlPart) {
248
            $sql = str_replace($sqlPartKey, $sqlPart, $sql);
249
        }
250
251
        return $sql;
252
    }
253
}
254