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 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
![]() |
|||||||
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 |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.