NativeQueryBuilderHelper::resolveOptionalKeys()   C
last analyzed

Complexity

Conditions 14
Paths 116

Size

Total Lines 78
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 38
c 1
b 0
f 0
nc 116
nop 3
dl 0
loc 78
rs 6.1333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
Unused Code introduced by
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...
Bug introduced by
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