Issues (4)

src/Helper/NativeQueryBuilderHelper.php (2 issues)

1
<?php
2
3
namespace Micayael\NativeQueryFromFileBuilderBundle\Helper;
4
5
use Adbar\Dot;
6
use Micayael\NativeQueryFromFileBuilderBundle\Event\ProcessQueryParamsEvent;
7
use Micayael\NativeQueryFromFileBuilderBundle\Exception\NonExistentQueryDirectoryException;
8
use Micayael\NativeQueryFromFileBuilderBundle\Exception\NonExistentQueryFileException;
9
use Micayael\NativeQueryFromFileBuilderBundle\Exception\NonExistentQueryKeyException;
10
use Psr\Cache\CacheItemPoolInterface;
11
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
12
use Symfony\Component\Filesystem\Filesystem;
13
use Symfony\Component\Yaml\Yaml;
14
use Symfony\Contracts\Cache\ItemInterface;
15
16
class NativeQueryBuilderHelper
17
{
18
    public const REQUIRED_ID_PATTERN = '/@{.+?}/';
19
20
    public const OPTIONAL_ID_PATTERN = "/@\[.+?\]/";
21
22
    public const KEY_PATTERN = '/[a-z0-9._]+/';
23
24
    /**
25
     * @var EventDispatcherInterface|null
26
     */
27
    private $eventDispatcher;
28
29
    /**
30
     * @var CacheItemPoolInterface|null
31
     */
32
    private $cache;
33
34
    private $queryDir;
35
36
    private $fileExtension;
37
38
    public function __construct(?EventDispatcherInterface $eventDispatcher, ?CacheItemPoolInterface $cache, array $bundleConfig)
39
    {
40
        $this->eventDispatcher = $eventDispatcher;
41
        $this->cache = null;
42
43
        if ($bundleConfig['cache_sql']) {
44
            $this->cache = $cache;
45
        }
46
47
        $this->queryDir = $bundleConfig['sql_queries_dir'];
48
        $this->fileExtension = $bundleConfig['file_extension'];
49
    }
50
51
    /**
52
     * Get a SQL query from a query key in a yaml file.
53
     *
54
     * @throws NonExistentQueryDirectoryException
55
     * @throws NonExistentQueryFileException
56
     * @throws NonExistentQueryKeyException
57
     */
58
    public function getSqlFromYamlKey(string $queryFullKey, array &$params = []): string
59
    {
60
        $queryFullKey = explode(':', $queryFullKey);
61
62
        $fileKey = $queryFullKey[0];
63
        $queryKey = $queryFullKey[1];
64
65
        if ($this->cache) {
66
            $dot = $this->cache->get('nqbff_'.$fileKey, function (ItemInterface $item) use ($fileKey) {
0 ignored issues
show
The parameter $item is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

66
            $dot = $this->cache->get('nqbff_'.$fileKey, function (/** @scrutinizer ignore-unused */ ItemInterface $item) use ($fileKey) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The method get() does not exist on Psr\Cache\CacheItemPoolInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Symfony\Component\Cache\Adapter\AdapterInterface or Doctrine\Common\Cache\Psr6\CacheAdapter or Symfony\Component\Cache\...agAwareAdapterInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
            /** @scrutinizer ignore-call */ 
67
            $dot = $this->cache->get('nqbff_'.$fileKey, function (ItemInterface $item) use ($fileKey) {
Loading history...
67
                return $this->getQueryFileContent($fileKey);
68
            });
69
        } else {
70
            $dot = $this->getQueryFileContent($fileKey);
71
        }
72
73
        if (!$dot->has($queryKey)) {
74
            throw new NonExistentQueryKeyException($queryKey);
75
        }
76
77
        $sql = $dot->get($queryKey);
78
79
        $sqlParts = $this->resolveRequiredKeys($dot, $sql);
80
81
        array_push($sqlParts, $sql);
82
83
        $sql = $this->resolveOptionalKeys($dot, $sqlParts, $params);
84
85
        // Reemplaza espacios adicionales
86
        $sql = trim(preg_replace('/\s+/', ' ', $sql));
87
88
        if (isset($params['orderby'])) {
89
            $sql = preg_replace('/:\w+:/', $params['orderby'], $sql);
90
            $sql = str_replace(':orderby', $params['orderby'], $sql);
91
        }
92
93
        return $sql;
94
    }
95
96
    /**
97
     * @throws NonExistentQueryDirectoryException
98
     * @throws NonExistentQueryFileException
99
     */
100
    private function getQueryFileContent(string $fileKey): Dot
101
    {
102
        $fileSystem = new Filesystem();
103
104
        if (!$fileSystem->exists($this->queryDir)) {
105
            throw new NonExistentQueryDirectoryException($this->queryDir);
106
        }
107
108
        $filename = sprintf('%s/%s.%s', $this->queryDir, $fileKey, $this->fileExtension);
109
110
        if (!$fileSystem->exists($filename)) {
111
            throw new NonExistentQueryFileException($filename);
112
        }
113
114
        $data = Yaml::parseFile($filename);
115
116
        $dot = new Dot($data);
117
118
        return $dot;
119
    }
120
121
    /**
122
     * @return array An array of query parts depending on required keys @{file:query}
123
     *
124
     * @throws NonExistentQueryKeyException
125
     */
126
    private function resolveRequiredKeys(Dot $dot, string $sql): array
127
    {
128
        $queryParts = [];
129
130
        $snippetIds = [];
131
132
        preg_match_all(self::REQUIRED_ID_PATTERN, $sql, $snippetIds);
133
134
        foreach ($snippetIds[0] as $snippetId) {
135
            preg_match(self::KEY_PATTERN, $snippetId, $matches);
136
137
            $snippetKey = $matches[0];
138
139
            if (empty($snippetKey)) {
140
                continue;
141
            }
142
143
            if (!$dot->has($snippetKey)) {
144
                throw new NonExistentQueryKeyException($snippetKey);
145
            }
146
147
            $snippetSql = $dot->get($snippetKey);
148
149
            $queryParts[$snippetId] = trim(preg_replace('/\s+/', ' ', $snippetSql));
150
        }
151
152
        return $queryParts;
153
    }
154
155
    /**
156
     * @return string Processed SQL with parameters defined by @[file:query:params]
157
     *
158
     * @throws NonExistentQueryKeyException
159
     */
160
    private function resolveOptionalKeys(Dot $dot, array $sqlParts, array &$params = []): string
161
    {
162
        $paramKeys = array_keys($params);
163
164
        // Se recorren los sqlParts y por cada uno se van agregando los filtros opcionales.
165
        // El query principal se evalúa al final
166
        foreach ($sqlParts as $sqlPartKey => $sql) {
167
            $snippetIds = [];
168
169
            $whereConnector = (false === strpos(strtoupper($sql), 'WHERE')) ? 'WHERE ' : 'AND ';
170
171
            preg_match_all(self::OPTIONAL_ID_PATTERN, $sql, $snippetIds);
172
173
            foreach ($snippetIds[0] as $snippetId) {
174
                $requestedSnippets = [];
175
176
                preg_match(self::KEY_PATTERN, $snippetId, $matches);
177
178
                $snippetKey = $matches[0];
179
180
                if (empty($snippetKey)) {
181
                    continue;
182
                }
183
184
                if (!$dot->has($snippetKey)) {
185
                    throw new NonExistentQueryKeyException($snippetKey);
186
                }
187
188
                $snippets = $dot->get($snippetKey);
189
190
                foreach ($snippets as $filter) {
191
                    $filterType = null;
192
193
                    if (is_array($filter)) {
194
                        $filterType = key($filter);
195
                        $filter = $filter[$filterType];
196
                    }
197
198
                    foreach ($paramKeys as $paramKey) {
199
                        if (false !== strpos($filter, ':'.$paramKey)) {
200
                            $event = new ProcessQueryParamsEvent($snippetKey, $filterType, $paramKey, $params, $filter);
201
202
                            if ($this->eventDispatcher) {
203
                                $this->eventDispatcher->dispatch($event);
204
                            }
205
206
                            $params = $event->getProcessedParams();
207
                            $filter = $event->getProcessedFilter();
208
209
                            // Si no existe en la lista de filtros a usar, se agrega
210
                            if (!in_array($filter, array_keys($requestedSnippets))) {
211
                                $requestedSnippets[$filter] = $whereConnector.'('.$filter.')';
212
                            }
213
214
                            $whereConnector = 'AND ';
215
                        }
216
                    }
217
                }
218
219
                if (!empty($requestedSnippets)) {
220
                    $snippetSql = implode(' ', $requestedSnippets);
221
222
                    $sqlParts[$sqlPartKey] = str_replace($snippetId, $snippetSql, $sql);
223
                } else {
224
                    $sqlParts[$sqlPartKey] = str_replace($snippetId, '', $sql);
225
                }
226
            }
227
        }
228
229
        // Obtiene la última posición del array que corresponde al SQL base
230
        $sql = array_pop($sqlParts);
231
232
        // Recorre las partes y las va reemplazando en el SQL base
233
        foreach ($sqlParts as $sqlPartKey => $sqlPart) {
234
            $sql = str_replace($sqlPartKey, $sqlPart, $sql);
235
        }
236
237
        return $sql;
238
    }
239
}
240