Total Complexity | 55 |
Total Lines | 438 |
Duplicated Lines | 0 % |
Changes | 4 | ||
Bugs | 0 | Features | 0 |
Complex classes like AccountSearchService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use AccountSearchService, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
57 | final class AccountSearchService extends Service |
||
58 | { |
||
59 | /** |
||
60 | * Regex filters for special searching |
||
61 | */ |
||
62 | const FILTERS = [ |
||
63 | 'condition' => [ |
||
64 | 'subject' => ['is', 'not'], |
||
65 | 'condition' => ['expired', 'private'] |
||
66 | ], |
||
67 | 'items' => [ |
||
68 | 'subject' => ['id', 'user', 'group', 'file', 'owner', 'maingroup', 'client', 'category', 'name_regex'], |
||
69 | 'condition' => null |
||
70 | ], |
||
71 | 'operator' => [ |
||
72 | 'subject' => ['op'], |
||
73 | 'condition' => ['and', 'or'] |
||
74 | ] |
||
75 | ]; |
||
76 | |||
77 | const COLORS_CACHE_FILE = CACHE_PATH . DIRECTORY_SEPARATOR . 'colors.cache'; |
||
78 | |||
79 | /** |
||
80 | * Cache expire time |
||
81 | */ |
||
82 | const CACHE_EXPIRE = 86400; |
||
83 | |||
84 | /** |
||
85 | * Colores para resaltar las cuentas |
||
86 | */ |
||
87 | const COLORS = [ |
||
88 | '2196F3', |
||
89 | '03A9F4', |
||
90 | '00BCD4', |
||
91 | '009688', |
||
92 | '4CAF50', |
||
93 | '8BC34A', |
||
94 | 'CDDC39', |
||
95 | 'FFC107', |
||
96 | '795548', |
||
97 | '607D8B', |
||
98 | '9E9E9E', |
||
99 | 'FF5722', |
||
100 | 'F44336', |
||
101 | 'E91E63', |
||
102 | '9C27B0', |
||
103 | '673AB7', |
||
104 | '3F51B5', |
||
105 | ]; |
||
106 | /** |
||
107 | * @var AccountFilterUser |
||
108 | */ |
||
109 | private $accountFilterUser; |
||
110 | /** |
||
111 | * @var AccountAclService |
||
112 | */ |
||
113 | private $accountAclService; |
||
114 | /** |
||
115 | * @var ConfigData |
||
116 | */ |
||
117 | private $configData; |
||
118 | /** |
||
119 | * @var AccountToTagRepository |
||
120 | */ |
||
121 | private $accountToTagRepository; |
||
122 | /** |
||
123 | * @var AccountToUserRepository |
||
124 | */ |
||
125 | private $accountToUserRepository; |
||
126 | /** |
||
127 | * @var AccountToUserGroupRepository |
||
128 | */ |
||
129 | private $accountToUserGroupRepository; |
||
130 | /** |
||
131 | * @var FileCacheInterface |
||
132 | */ |
||
133 | private $colorCache; |
||
134 | /** |
||
135 | * @var array |
||
136 | */ |
||
137 | private $accountColor; |
||
138 | /** |
||
139 | * @var AccountRepository |
||
140 | */ |
||
141 | private $accountRepository; |
||
142 | /** |
||
143 | * @var string |
||
144 | */ |
||
145 | private $cleanString; |
||
146 | /** |
||
147 | * @var string |
||
148 | */ |
||
149 | private $filterOperator; |
||
150 | |||
151 | /** |
||
152 | * Procesar los resultados de la búsqueda y crear la variable que contiene los datos de cada cuenta |
||
153 | * a mostrar. |
||
154 | * |
||
155 | * @param AccountSearchFilter $accountSearchFilter |
||
156 | * |
||
157 | * @return QueryResult |
||
158 | * @throws ConstraintException |
||
159 | * @throws QueryException |
||
160 | * @throws SPException |
||
161 | */ |
||
162 | public function processSearchResults(AccountSearchFilter $accountSearchFilter) |
||
163 | { |
||
164 | $accountSearchFilter->setStringFilters($this->analyzeQueryFilters($accountSearchFilter->getTxtSearch())); |
||
165 | |||
166 | if ($accountSearchFilter->getFilterOperator() === null |
||
167 | || $this->filterOperator !== null |
||
168 | ) { |
||
169 | $accountSearchFilter->setFilterOperator($this->filterOperator); |
||
170 | } |
||
171 | |||
172 | $accountSearchFilter->setCleanTxtSearch($this->cleanString); |
||
173 | |||
174 | $queryResult = $this->accountRepository->getByFilter($accountSearchFilter, $this->accountFilterUser->getFilter($accountSearchFilter->getGlobalSearch())); |
||
|
|||
175 | |||
176 | // Variables de configuración |
||
177 | $maxTextLength = $this->configData->isResultsAsCards() ? 40 : 60; |
||
178 | |||
179 | $accountLinkEnabled = $this->context->getUserData()->getPreferences()->isAccountLink() || $this->configData->isAccountLink(); |
||
180 | $favorites = $this->dic->get(AccountToFavoriteService::class)->getForUserId($this->context->getUserData()->getId()); |
||
181 | |||
182 | $accountsData = []; |
||
183 | |||
184 | /** @var AccountSearchVData $accountSearchData */ |
||
185 | foreach ($queryResult->getDataAsArray() as $accountSearchData) { |
||
186 | $cache = $this->getCacheForAccount($accountSearchData); |
||
187 | |||
188 | // Obtener la ACL de la cuenta |
||
189 | $accountAcl = $this->accountAclService->getAcl( |
||
190 | Acl::ACCOUNT_SEARCH, |
||
191 | AccountAclDto::makeFromAccountSearch($accountSearchData, $cache->getUsers(), $cache->getUserGroups()) |
||
192 | ); |
||
193 | |||
194 | // Propiedades de búsqueda de cada cuenta |
||
195 | $accountsSearchItem = new AccountSearchItem($accountSearchData, $accountAcl, $this->configData); |
||
196 | |||
197 | if (!$accountSearchData->getIsPrivate()) { |
||
198 | $accountsSearchItem->setUsers($cache->getUsers()); |
||
199 | $accountsSearchItem->setUserGroups($cache->getUserGroups()); |
||
200 | } |
||
201 | |||
202 | $accountsSearchItem->setTags($this->accountToTagRepository->getTagsByAccountId($accountSearchData->getId())->getDataAsArray()); |
||
203 | $accountsSearchItem->setTextMaxLength($maxTextLength); |
||
204 | $accountsSearchItem->setColor($this->pickAccountColor($accountSearchData->getClientId())); |
||
205 | $accountsSearchItem->setLink($accountLinkEnabled); |
||
206 | $accountsSearchItem->setFavorite(isset($favorites[$accountSearchData->getId()])); |
||
207 | |||
208 | $accountsData[] = $accountsSearchItem; |
||
209 | } |
||
210 | |||
211 | return QueryResult::fromResults($accountsData, $queryResult->getTotalNumRows()); |
||
212 | } |
||
213 | |||
214 | /** |
||
215 | * Analizar la cadena de consulta por eqituetas especiales y devolver un objeto |
||
216 | * QueryCondition con los filtros |
||
217 | * |
||
218 | * @param $string |
||
219 | * |
||
220 | * @return QueryCondition |
||
221 | * @throws ContainerExceptionInterface |
||
222 | * @throws NotFoundExceptionInterface |
||
223 | */ |
||
224 | public function analyzeQueryFilters($string) |
||
225 | { |
||
226 | $this->cleanString = null; |
||
227 | $this->filterOperator = null; |
||
228 | |||
229 | $queryCondition = new QueryCondition(); |
||
230 | |||
231 | $match = preg_match_all( |
||
232 | '/(?<search>(?<!:)\b[^:]+\b(?!:))|(?<filter_subject>[a-zа-я_]+):(?!\s]*)"?(?<filter_condition>[^":]+)"?/u', |
||
233 | $string, |
||
234 | $filters |
||
235 | ); |
||
236 | |||
237 | if ($match !== false && $match > 0) { |
||
238 | if (!empty($filters['search'][0])) { |
||
239 | $this->cleanString = Filter::safeSearchString(trim($filters['search'][0])); |
||
240 | } |
||
241 | |||
242 | $filtersAndValues = array_filter( |
||
243 | array_combine( |
||
244 | $filters['filter_subject'], |
||
245 | $filters['filter_condition'] |
||
246 | ) |
||
247 | ); |
||
248 | |||
249 | if (!empty($filtersAndValues)) { |
||
250 | $filtersItem = array_filter($filtersAndValues, function ($value, $key) { |
||
251 | return in_array($key, self::FILTERS['items']['subject'], true) |
||
252 | && $value !== ''; |
||
253 | }, ARRAY_FILTER_USE_BOTH); |
||
254 | |||
255 | if (!empty($filtersItem)) { |
||
256 | $this->processFilterItems($filtersItem, $queryCondition); |
||
257 | } |
||
258 | |||
259 | $filtersOperator = array_filter($filtersAndValues, function ($value, $key) { |
||
260 | return in_array($key, self::FILTERS['operator']['subject'], true) |
||
261 | && in_array($value, self::FILTERS['operator']['condition'], true); |
||
262 | }, ARRAY_FILTER_USE_BOTH); |
||
263 | |||
264 | if (!empty($filtersOperator)) { |
||
265 | $this->processFilterOperator($filtersOperator); |
||
266 | } |
||
267 | |||
268 | $filtersCondition = array_filter(array_map(function ($subject, $condition) { |
||
269 | if (in_array($subject, self::FILTERS['condition']['subject'], true) |
||
270 | && in_array($condition, self::FILTERS['condition']['condition'], true) |
||
271 | ) { |
||
272 | return $subject . ':' . $condition; |
||
273 | } |
||
274 | |||
275 | return null; |
||
276 | }, $filters['filter_subject'], $filters['filter_condition'])); |
||
277 | |||
278 | if (!empty($filtersCondition)) { |
||
279 | $this->processFilterIs($filtersCondition, $queryCondition); |
||
280 | } |
||
281 | } |
||
282 | } |
||
283 | |||
284 | return $queryCondition; |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * @param array $filters |
||
289 | * @param QueryCondition $queryCondition |
||
290 | */ |
||
291 | private function processFilterItems(array $filters, QueryCondition $queryCondition) |
||
292 | { |
||
293 | foreach ($filters as $filter => $text) { |
||
294 | try { |
||
295 | switch ($filter) { |
||
296 | case 'user': |
||
297 | $userData = $this->dic->get(UserService::class)->getByLogin(Filter::safeSearchString($text)); |
||
298 | |||
299 | if (is_object($userData)) { |
||
300 | $queryCondition->addFilter( |
||
301 | 'Account.userId = ? OR Account.userGroupId = ? OR Account.id IN |
||
302 | (SELECT AccountToUser.accountId FROM AccountToUser WHERE AccountToUser.accountId = Account.id AND AccountToUser.userId = ? |
||
303 | UNION |
||
304 | SELECT AccountToUserGroup.accountId FROM AccountToUserGroup WHERE AccountToUserGroup.accountId = Account.id AND AccountToUserGroup.userGroupId = ?)', |
||
305 | [$userData->getId(), $userData->getUserGroupId(), $userData->getId(), $userData->getUserGroupId()]); |
||
306 | } |
||
307 | break; |
||
308 | case 'owner': |
||
309 | $text = '%' . Filter::safeSearchString($text) . '%'; |
||
310 | $queryCondition->addFilter( |
||
311 | 'Account.userLogin LIKE ? OR Account.userName LIKE ?', |
||
312 | [$text, $text]); |
||
313 | break; |
||
314 | case 'group': |
||
315 | $userGroupData = $this->dic->get(UserGroupService::class)->getByName(Filter::safeSearchString($text)); |
||
316 | |||
317 | if (is_object($userGroupData)) { |
||
318 | $queryCondition->addFilter( |
||
319 | 'Account.userGroupId = ? OR Account.id IN (SELECT AccountToUserGroup.accountId FROM AccountToUserGroup WHERE AccountToUserGroup.accountId = id AND AccountToUserGroup.userGroupId = ?)', |
||
320 | [$userGroupData->getId(), $userGroupData->getId()]); |
||
321 | } |
||
322 | break; |
||
323 | case 'maingroup': |
||
324 | $queryCondition->addFilter('Account.userGroupName LIKE ?', ['%' . Filter::safeSearchString($text) . '%']); |
||
325 | break; |
||
326 | case 'file': |
||
327 | $queryCondition->addFilter('Account.id IN (SELECT AccountFile.accountId FROM AccountFile WHERE AccountFile.name LIKE ?)', ['%' . $text . '%']); |
||
328 | break; |
||
329 | case 'id': |
||
330 | $queryCondition->addFilter('Account.id = ?', [(int)$text]); |
||
331 | break; |
||
332 | case 'client': |
||
333 | $queryCondition->addFilter('Account.clientName LIKE ?', ['%' . Filter::safeSearchString($text) . '%']); |
||
334 | break; |
||
335 | case 'category': |
||
336 | $queryCondition->addFilter('Account.categoryName LIKE ?', ['%' . Filter::safeSearchString($text) . '%']); |
||
337 | break; |
||
338 | case 'name_regex': |
||
339 | $queryCondition->addFilter('Account.name REGEXP ?', [$text]); |
||
340 | break; |
||
341 | } |
||
342 | } catch (Exception $e) { |
||
343 | processException($e); |
||
344 | } |
||
345 | } |
||
346 | } |
||
347 | |||
348 | /** |
||
349 | * @param array $filters |
||
350 | */ |
||
351 | private function processFilterOperator(array $filters) |
||
352 | { |
||
353 | switch ($filters['op']) { |
||
354 | case 'and': |
||
355 | $this->filterOperator = QueryCondition::CONDITION_AND; |
||
356 | break; |
||
357 | case 'or': |
||
358 | $this->filterOperator = QueryCondition::CONDITION_OR; |
||
359 | break; |
||
360 | } |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * @param array $filters |
||
365 | * @param QueryCondition $queryCondition |
||
366 | */ |
||
367 | private function processFilterIs(array $filters, QueryCondition $queryCondition) |
||
368 | { |
||
369 | foreach ($filters as $filter) { |
||
370 | switch ($filter) { |
||
371 | case 'is:expired': |
||
372 | $queryCondition->addFilter( |
||
373 | 'Account.passDateChange > 0 AND UNIX_TIMESTAMP() > Account.passDateChange', |
||
374 | []); |
||
375 | break; |
||
376 | case 'not:expired': |
||
377 | $queryCondition->addFilter( |
||
378 | 'Account.passDateChange = 0 OR Account.passDateChange IS NULL OR UNIX_TIMESTAMP() < Account.passDateChange', |
||
379 | []); |
||
380 | break; |
||
381 | case 'is:private': |
||
382 | $queryCondition->addFilter( |
||
383 | '(Account.isPrivate = 1 AND Account.userId = ?) OR (Account.isPrivateGroup = 1 AND Account.userGroupId = ?)', |
||
384 | [$this->context->getUserData()->getId(), $this->context->getUserData()->getUserGroupId()]); |
||
385 | break; |
||
386 | case 'not:private': |
||
387 | $queryCondition->addFilter( |
||
388 | '(Account.isPrivate = 0 OR Account.isPrivate IS NULL) AND (Account.isPrivateGroup = 0 OR Account.isPrivateGroup IS NULL)'); |
||
389 | break; |
||
390 | } |
||
391 | } |
||
392 | } |
||
393 | |||
394 | /** |
||
395 | * Devolver los accesos desde la caché |
||
396 | * |
||
397 | * @param AccountSearchVData $accountSearchData |
||
398 | * |
||
399 | * @return AccountCache |
||
400 | * @throws ConstraintException |
||
401 | * @throws QueryException |
||
402 | */ |
||
403 | protected function getCacheForAccount(AccountSearchVData $accountSearchData) |
||
404 | { |
||
405 | $accountId = $accountSearchData->getId(); |
||
406 | |||
407 | /** @var AccountCache[] $cache */ |
||
408 | $cache = $this->context->getAccountsCache(); |
||
409 | |||
410 | $hasCache = $cache !== null; |
||
411 | |||
412 | if ($cache === false |
||
413 | || !isset($cache[$accountId]) |
||
414 | || $cache[$accountId]->getTime() < (int)strtotime($accountSearchData->getDateEdit()) |
||
415 | ) { |
||
416 | $cache[$accountId] = new AccountCache( |
||
417 | $accountId, |
||
418 | $this->accountToUserRepository->getUsersByAccountId($accountId)->getDataAsArray(), |
||
419 | $this->accountToUserGroupRepository->getUserGroupsByAccountId($accountId)->getDataAsArray()); |
||
420 | |||
421 | if ($hasCache) { |
||
422 | $this->context->setAccountsCache($cache); |
||
423 | } |
||
424 | } |
||
425 | |||
426 | return $cache[$accountId]; |
||
427 | } |
||
428 | |||
429 | /** |
||
430 | * Seleccionar un color para la cuenta |
||
431 | * |
||
432 | * @param int $id El id del elemento a asignar |
||
433 | * |
||
434 | * @return string |
||
435 | */ |
||
436 | private function pickAccountColor($id) |
||
437 | { |
||
438 | if ($this->accountColor !== null && isset($this->accountColor[$id])) { |
||
439 | return $this->accountColor[$id]; |
||
440 | } |
||
441 | |||
442 | // Se asigna el color de forma aleatoria a cada id |
||
443 | $this->accountColor[$id] = '#' . self::COLORS[array_rand(self::COLORS)]; |
||
444 | |||
445 | try { |
||
446 | $this->colorCache->save($this->accountColor); |
||
447 | |||
448 | logger('Saved accounts color cache'); |
||
449 | |||
450 | return $this->accountColor[$id]; |
||
451 | } catch (FileException $e) { |
||
452 | processException($e); |
||
453 | |||
454 | return ''; |
||
455 | } |
||
456 | } |
||
457 | |||
458 | /** |
||
459 | * @return string |
||
460 | */ |
||
461 | public function getCleanString() |
||
464 | } |
||
465 | |||
466 | /** |
||
467 | * @throws ContainerExceptionInterface |
||
468 | * @throws NotFoundExceptionInterface |
||
469 | */ |
||
470 | protected function initialize() |
||
482 | } |
||
483 | |||
484 | /** |
||
485 | * Load colors from cache |
||
486 | */ |
||
487 | private function loadColors() |
||
495 | } |
||
496 | } |
||
497 | } |