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