Skip to content

Commit 32a1bb2

Browse files
authored
Maintability update and dependency simplify. (#19)
* Maintability update and dependency simplify. * Fix dependecies
1 parent 3f4c79d commit 32a1bb2

12 files changed

+282
-96
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"nette/utils": "^3.2",
1515
"doctrine/orm": "^2.7",
1616
"doctrine/dbal": "^2.9",
17+
"psr/log": "^2.0 || ^3.0",
1718
"ramsey/uuid": "^4.1",
1819
"ramsey/uuid-doctrine": "^1.7"
1920
},

src/Core/Analytics.php

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,19 @@
1010
use Doctrine\ORM\EntityRepository;
1111
use Doctrine\ORM\NonUniqueResultException;
1212
use Doctrine\ORM\NoResultException;
13-
use Nette\Caching\Cache;
14-
use Nette\Utils\Strings;
15-
use Tracy\Debugger;
16-
use Tracy\ILogger;
1713

1814
final class Analytics
1915
{
2016
public function __construct(
17+
private Container $container,
2118
private EntityManagerInterface $entityManager,
22-
private ?Cache $cache = null
2319
) {
2420
}
2521

2622

2723
public function save(string $query, int $results): void
2824
{
29-
($queryEntity = $this->getSearchQuery(Strings::toAscii($query), $results))
25+
($queryEntity = $this->getSearchQuery(Helpers::toAscii($query), $results))
3026
->addFrequency()
3127
->setResults($results)
3228
->setScore($this->countScore($queryEntity->getFrequency(), $results))
@@ -35,8 +31,9 @@ public function save(string $query, int $results): void
3531
try {
3632
$this->entityManager->getUnitOfWork()->commit($queryEntity);
3733
} catch (\Throwable $e) {
38-
if (\class_exists(Debugger::class) === true) {
39-
Debugger::log($e, ILogger::EXCEPTION);
34+
$logger = $this->container->getLogger();
35+
if ($logger !== null) {
36+
$logger->critical($e->getMessage(), ['exception' => $e]);
4037
}
4138
trigger_error('Can not save search Analytics: ' . $e->getMessage());
4239
}
@@ -62,12 +59,12 @@ public function getQueryScore(?string $query = null): array
6259
->setParameter('query', mb_substr($query, 0, 3, 'UTF-8') . '%');
6360
}
6461

65-
/** @var string[][] $result */
62+
/** @var array<int, array{query: string, score: int}> $result */
6663
$result = $queryBuilder->getQuery()->getArrayResult();
6764

6865
$return = [];
6966
foreach ($result as $_query) {
70-
$return[$_query['query']] = (int) $_query['score'];
67+
$return[$_query['query']] = $_query['score'];
7168
}
7269

7370
return $return;
@@ -127,8 +124,9 @@ private function getSearchQuery(string $query, int $results): SearchQuery
127124
static $cache = [];
128125
$cacheKey = 'analyticsSearchQuery-' . md5($query);
129126
$ttl = 0;
127+
$cacheService = $this->container->getCache();
130128

131-
while ($this->cache !== null && $this->cache->load($cacheKey) !== null && $ttl <= 100) { // Conflict treatment
129+
while ($cacheService->load($cacheKey) !== null && $ttl <= 100) { // Conflict treatment
132130
usleep(50_000);
133131
$ttl++;
134132

@@ -151,20 +149,19 @@ private function getSearchQuery(string $query, int $results): SearchQuery
151149
} catch (\Throwable) {
152150
usleep(200_000);
153151
}
154-
if ($this->cache === null || $this->cache->load($cacheKey) === null) {
155-
if ($this->cache !== null) {
156-
$this->cache->save($cacheKey, \time(), [
157-
Cache::EXPIRE => '5 seconds',
158-
]);
159-
}
152+
if ($cacheService->load($cacheKey) === null) {
153+
$cacheService->save($cacheKey, \time(), [
154+
'expire' => '5 seconds',
155+
]);
160156

161157
$cache[$query] = new SearchQuery($query, $results, $this->countScore(1, $results));
162158
$this->entityManager->persist($cache[$query]);
163159
try {
164160
$this->entityManager->getUnitOfWork()->commit($cache[$query]);
165161
} catch (\Throwable $e) { // flush to analytics can fail
166-
if (\class_exists(Debugger::class) === true) {
167-
Debugger::log($e, ILogger::CRITICAL);
162+
$logger = $this->container->getLogger();
163+
if ($logger !== null) {
164+
$logger->critical($e->getMessage(), ['exception' => $e]);
168165
}
169166
}
170167
break;

src/Core/Container.php

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Baraja\Search;
6+
7+
8+
use Baraja\Search\QueryNormalizer\IQueryNormalizer;
9+
use Baraja\Search\QueryNormalizer\QueryNormalizer;
10+
use Baraja\Search\ScoreCalculator\IScoreCalculator;
11+
use Baraja\Search\ScoreCalculator\ScoreCalculator;
12+
use Doctrine\ORM\EntityManagerInterface;
13+
use Nette\Caching\Cache;
14+
use Nette\Caching\Storage;
15+
use Nette\Caching\Storages\FileStorage;
16+
use Nette\Utils\FileSystem;
17+
use Psr\Container\ContainerInterface;
18+
use Psr\Log\LoggerInterface;
19+
20+
final class Container implements ContainerInterface
21+
{
22+
private EntityManagerInterface $entityManager;
23+
24+
private IQueryNormalizer $queryNormalizer;
25+
26+
private IScoreCalculator $scoreCalculator;
27+
28+
private Core $core;
29+
30+
private Cache $cache;
31+
32+
private Analytics $analytics;
33+
34+
private ?LoggerInterface $logger;
35+
36+
private int $searchTimeout;
37+
38+
39+
public function __construct(
40+
EntityManagerInterface $entityManager,
41+
?IQueryNormalizer $queryNormalizer = null,
42+
?IScoreCalculator $scoreCalculator = null,
43+
?Storage $cacheStorage = null,
44+
?LoggerInterface $logger = null,
45+
?int $searchTimeout = null,
46+
) {
47+
$this->entityManager = $entityManager;
48+
$this->queryNormalizer = $queryNormalizer ?? new QueryNormalizer;
49+
$this->scoreCalculator = $scoreCalculator ?? new ScoreCalculator;
50+
$this->core = new Core(new QueryBuilder($entityManager), $this->scoreCalculator);
51+
if ($cacheStorage === null) {
52+
$cacheDir = sys_get_temp_dir() . '/baraja-doctrine/' . md5(__FILE__);
53+
FileSystem::createDir($cacheDir);
54+
$cacheStorage = new FileStorage($cacheDir);
55+
}
56+
$this->cache = new Cache($cacheStorage, 'baraja-doctrine-fulltext-search');
57+
$this->analytics = new Analytics($this, $entityManager);
58+
$this->logger = $logger;
59+
$this->setSearchTimeout($searchTimeout ?? 2_500);
60+
}
61+
62+
63+
public function get(string $id): mixed
64+
{
65+
throw new \LogicException('Method has not implemented, use direct method.');
66+
}
67+
68+
69+
public function has(string $id): bool
70+
{
71+
return true;
72+
}
73+
74+
75+
public function getEntityManager(): EntityManagerInterface
76+
{
77+
return $this->entityManager;
78+
}
79+
80+
81+
public function getQueryNormalizer(): IQueryNormalizer
82+
{
83+
return $this->queryNormalizer;
84+
}
85+
86+
87+
public function getScoreCalculator(): IScoreCalculator
88+
{
89+
return $this->scoreCalculator;
90+
}
91+
92+
93+
public function getCore(): Core
94+
{
95+
return $this->core;
96+
}
97+
98+
99+
public function getCache(): Cache
100+
{
101+
return $this->cache;
102+
}
103+
104+
105+
public function getAnalytics(): Analytics
106+
{
107+
return $this->analytics;
108+
}
109+
110+
111+
public function getLogger(): ?LoggerInterface
112+
{
113+
return $this->logger;
114+
}
115+
116+
117+
public function getSearchTimeout(): int
118+
{
119+
return $this->searchTimeout;
120+
}
121+
122+
123+
public function setSearchTimeout(int $searchTimeout): void
124+
{
125+
if ($searchTimeout < 0) {
126+
$searchTimeout = 0;
127+
}
128+
$this->searchTimeout = $searchTimeout;
129+
}
130+
}

src/Core/Core.php

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,13 @@ public function __construct(
3232
public function processCandidateSearch(string $query, string $entity, array $columns, array $userConditions): array
3333
{
3434
$columnGetters = $this->getColumnGetters($columns);
35-
$query = strtolower(trim(Strings::toAscii($query)));
35+
$query = strtolower(trim(Helpers::toAscii($query, useCache: true)));
3636

3737
/** @var object[] $candidateResults */
38-
$candidateResults = $this->queryBuilder->build($query, $entity, $columns, $userConditions)->getQuery()->getResult();
38+
$candidateResults = $this->queryBuilder
39+
->build($query, $entity, $columns, $userConditions)
40+
->getQuery()
41+
->getResult();
3942

4043
$return = [];
4144
foreach ($candidateResults as $candidateResult) {
@@ -92,7 +95,7 @@ public function processCandidateSearch(string $query, string $entity, array $col
9295
}
9396
} elseif ($emptyRequiredParameters === false) { // Use property loading if method can not be called
9497
try {
95-
$propertyRef = new \ReflectionProperty($candidateResultClass, Strings::firstLower($getterColumn));
98+
$propertyRef = new \ReflectionProperty($candidateResultClass, Helpers::firstLower($getterColumn));
9699
$propertyRef->setAccessible(true);
97100
$columnDatabaseValue = $propertyRef->getValue($candidateResult);
98101
} catch (\ReflectionException $e) {
@@ -118,7 +121,7 @@ public function processCandidateSearch(string $query, string $entity, array $col
118121
}
119122
}
120123

121-
$score = $this->scoreCalculator->process(strtolower(Strings::toAscii($rawColumnValue)), $query, $mode);
124+
$score = $this->scoreCalculator->process(strtolower(Helpers::toAscii($rawColumnValue)), $query, $mode);
122125
if ($mode === ':' && $title === null) {
123126
$title = $rawColumnValue;
124127
}
@@ -136,11 +139,11 @@ public function processCandidateSearch(string $query, string $entity, array $col
136139

137140
$snippet = Helpers::implodeSnippets($snippets);
138141
$return[] = new SearchItem(
139-
$candidateResult,
140-
$query,
141-
$title ?? Strings::truncate($snippet, 64),
142-
$snippet,
143-
$finalScore,
142+
entity: $candidateResult,
143+
query: $query,
144+
title: $title ?? Strings::truncate($snippet, 64),
145+
snippet: $snippet,
146+
score: $finalScore,
144147
);
145148
}
146149

@@ -191,7 +194,7 @@ private function getColumnGetters(array $columns): array
191194
throw new \InvalidArgumentException('Column "' . $column . '" has invalid syntax.');
192195
}
193196

194-
$return[$column] = Strings::firstUpper($columnGetter ?? $columnNormalize);
197+
$return[$column] = Helpers::firstUpper($columnGetter ?? $columnNormalize);
195198
}
196199

197200
return $return;
@@ -212,8 +215,8 @@ private function getValueByRelation(string $column, ?object $candidateEntity = n
212215
$getterValue = $candidateEntity;
213216
} else {
214217
$method = preg_match('/^(?<column>[^(]+)(\((?<getter>[^)]*)\))$/', $columnRelation, $columnParser) === 1
215-
? 'get' . Strings::firstUpper($columnParser['getter'])
216-
: 'get' . Strings::firstUpper($columnRelation);
218+
? 'get' . Helpers::firstUpper($columnParser['getter'])
219+
: 'get' . Helpers::firstUpper($columnRelation);
217220
/** @phpstan-ignore-next-line */
218221
$getterValue = $candidateEntity->{$method}();
219222
}

src/Core/QueryBuilder.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Doctrine\ORM\EntityManagerInterface;
99
use Doctrine\ORM\EntityRepository;
1010
use Doctrine\ORM\QueryBuilder as DoctrineQueryBuilder;
11-
use Nette\Utils\Strings;
1211

1312
final class QueryBuilder
1413
{
@@ -24,8 +23,8 @@ public function __construct(
2423

2524

2625
/**
27-
* @param string[] $columns
28-
* @param string[] $userConditions use as AND with automated WHERE.
26+
* @param array<int, string> $columns
27+
* @param array<int, string> $userConditions use as AND with automated WHERE.
2928
*/
3029
public function build(string $query, string $entity, array $columns, array $userConditions): DoctrineQueryBuilder
3130
{
@@ -88,7 +87,7 @@ public function build(string $query, string $entity, array $columns, array $user
8887
*
8988
* The method returns a valid DQL query compatible with Doctrine syntax.
9089
*
91-
* @param string[] $columns
90+
* @param array<int, string> $columns
9291
*/
9392
private function buildWhere(string $query, array $columns, bool $ignoreAccents = false): string
9493
{
@@ -109,7 +108,7 @@ private function buildWhere(string $query, array $columns, bool $ignoreAccents =
109108
$simpleQuery = str_replace(
110109
['.', '?', '"'],
111110
['. ', ' ', '\''],
112-
$ignoreAccents === true ? Strings::toAscii($simpleQuery) : $simpleQuery,
111+
$ignoreAccents === true ? Helpers::toAscii($simpleQuery) : $simpleQuery,
113112
);
114113

115114
// Simple query match with normal keywords in query
@@ -149,8 +148,8 @@ private function buildWhere(string $query, array $columns, bool $ignoreAccents =
149148
/**
150149
* Create most effective join selector by internal magic.
151150
*
152-
* @param string[] $partialColumns
153-
* @return string[]
151+
* @param array<int, string> $partialColumns
152+
* @return array<int, string>
154153
*/
155154
private function buildJoin(DoctrineQueryBuilder $queryBuilder, array $partialColumns): array
156155
{

src/Core/SelectorBuilder.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ final class SelectorBuilder
2020
* 'content' => '!',
2121
* ]
2222
*
23-
* @var string[][]
23+
* @var array<string, array<string, string>>
2424
*/
2525
private array $map = [];
2626

27-
/** @var string[] */
27+
/** @var array<int, string> */
2828
private array $userConditions = [];
2929

3030

@@ -54,7 +54,7 @@ public function search(): SearchResult
5454
/**
5555
* Process search engine and get items.
5656
*
57-
* @return SearchItem[]
57+
* @return array<int, SearchItem>
5858
*/
5959
public function getItems(int $limit = 10, int $offset = 0): array
6060
{
@@ -65,7 +65,7 @@ public function getItems(int $limit = 10, int $offset = 0): array
6565
/**
6666
* Compute current entity search map by selector preferences.
6767
*
68-
* @return string[][]
68+
* @return array<string, array<int, string>>
6969
* @internal
7070
*/
7171
public function getMap(): array
@@ -84,8 +84,7 @@ public function getMap(): array
8484

8585

8686
/**
87-
* @param string[] $columns
88-
* @return SelectorBuilder
87+
* @param array<int, string> $columns
8988
*/
9089
public function addEntity(string $entity, array $columns = []): self
9190
{

0 commit comments

Comments
 (0)