Skip to content

Commit

Permalink
Maintability update and dependency simplify. (#19)
Browse files Browse the repository at this point in the history
* Maintability update and dependency simplify.

* Fix dependecies
  • Loading branch information
janbarasek authored Oct 24, 2021
1 parent 3f4c79d commit 32a1bb2
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 96 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"nette/utils": "^3.2",
"doctrine/orm": "^2.7",
"doctrine/dbal": "^2.9",
"psr/log": "^2.0 || ^3.0",
"ramsey/uuid": "^4.1",
"ramsey/uuid-doctrine": "^1.7"
},
Expand Down
35 changes: 16 additions & 19 deletions src/Core/Analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,19 @@
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Nette\Caching\Cache;
use Nette\Utils\Strings;
use Tracy\Debugger;
use Tracy\ILogger;

final class Analytics
{
public function __construct(
private Container $container,
private EntityManagerInterface $entityManager,
private ?Cache $cache = null
) {
}


public function save(string $query, int $results): void
{
($queryEntity = $this->getSearchQuery(Strings::toAscii($query), $results))
($queryEntity = $this->getSearchQuery(Helpers::toAscii($query), $results))
->addFrequency()
->setResults($results)
->setScore($this->countScore($queryEntity->getFrequency(), $results))
Expand All @@ -35,8 +31,9 @@ public function save(string $query, int $results): void
try {
$this->entityManager->getUnitOfWork()->commit($queryEntity);
} catch (\Throwable $e) {
if (\class_exists(Debugger::class) === true) {
Debugger::log($e, ILogger::EXCEPTION);
$logger = $this->container->getLogger();
if ($logger !== null) {
$logger->critical($e->getMessage(), ['exception' => $e]);
}
trigger_error('Can not save search Analytics: ' . $e->getMessage());
}
Expand All @@ -62,12 +59,12 @@ public function getQueryScore(?string $query = null): array
->setParameter('query', mb_substr($query, 0, 3, 'UTF-8') . '%');
}

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

$return = [];
foreach ($result as $_query) {
$return[$_query['query']] = (int) $_query['score'];
$return[$_query['query']] = $_query['score'];
}

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

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

Expand All @@ -151,20 +149,19 @@ private function getSearchQuery(string $query, int $results): SearchQuery
} catch (\Throwable) {
usleep(200_000);
}
if ($this->cache === null || $this->cache->load($cacheKey) === null) {
if ($this->cache !== null) {
$this->cache->save($cacheKey, \time(), [
Cache::EXPIRE => '5 seconds',
]);
}
if ($cacheService->load($cacheKey) === null) {
$cacheService->save($cacheKey, \time(), [
'expire' => '5 seconds',
]);

$cache[$query] = new SearchQuery($query, $results, $this->countScore(1, $results));
$this->entityManager->persist($cache[$query]);
try {
$this->entityManager->getUnitOfWork()->commit($cache[$query]);
} catch (\Throwable $e) { // flush to analytics can fail
if (\class_exists(Debugger::class) === true) {
Debugger::log($e, ILogger::CRITICAL);
$logger = $this->container->getLogger();
if ($logger !== null) {
$logger->critical($e->getMessage(), ['exception' => $e]);
}
}
break;
Expand Down
130 changes: 130 additions & 0 deletions src/Core/Container.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

declare(strict_types=1);

namespace Baraja\Search;


use Baraja\Search\QueryNormalizer\IQueryNormalizer;
use Baraja\Search\QueryNormalizer\QueryNormalizer;
use Baraja\Search\ScoreCalculator\IScoreCalculator;
use Baraja\Search\ScoreCalculator\ScoreCalculator;
use Doctrine\ORM\EntityManagerInterface;
use Nette\Caching\Cache;
use Nette\Caching\Storage;
use Nette\Caching\Storages\FileStorage;
use Nette\Utils\FileSystem;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;

final class Container implements ContainerInterface
{
private EntityManagerInterface $entityManager;

private IQueryNormalizer $queryNormalizer;

private IScoreCalculator $scoreCalculator;

private Core $core;

private Cache $cache;

private Analytics $analytics;

private ?LoggerInterface $logger;

private int $searchTimeout;


public function __construct(
EntityManagerInterface $entityManager,
?IQueryNormalizer $queryNormalizer = null,
?IScoreCalculator $scoreCalculator = null,
?Storage $cacheStorage = null,
?LoggerInterface $logger = null,
?int $searchTimeout = null,
) {
$this->entityManager = $entityManager;
$this->queryNormalizer = $queryNormalizer ?? new QueryNormalizer;
$this->scoreCalculator = $scoreCalculator ?? new ScoreCalculator;
$this->core = new Core(new QueryBuilder($entityManager), $this->scoreCalculator);
if ($cacheStorage === null) {
$cacheDir = sys_get_temp_dir() . '/baraja-doctrine/' . md5(__FILE__);
FileSystem::createDir($cacheDir);
$cacheStorage = new FileStorage($cacheDir);
}
$this->cache = new Cache($cacheStorage, 'baraja-doctrine-fulltext-search');
$this->analytics = new Analytics($this, $entityManager);
$this->logger = $logger;
$this->setSearchTimeout($searchTimeout ?? 2_500);
}


public function get(string $id): mixed
{
throw new \LogicException('Method has not implemented, use direct method.');
}


public function has(string $id): bool
{
return true;
}


public function getEntityManager(): EntityManagerInterface
{
return $this->entityManager;
}


public function getQueryNormalizer(): IQueryNormalizer
{
return $this->queryNormalizer;
}


public function getScoreCalculator(): IScoreCalculator
{
return $this->scoreCalculator;
}


public function getCore(): Core
{
return $this->core;
}


public function getCache(): Cache
{
return $this->cache;
}


public function getAnalytics(): Analytics
{
return $this->analytics;
}


public function getLogger(): ?LoggerInterface
{
return $this->logger;
}


public function getSearchTimeout(): int
{
return $this->searchTimeout;
}


public function setSearchTimeout(int $searchTimeout): void
{
if ($searchTimeout < 0) {
$searchTimeout = 0;
}
$this->searchTimeout = $searchTimeout;
}
}
27 changes: 15 additions & 12 deletions src/Core/Core.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,13 @@ public function __construct(
public function processCandidateSearch(string $query, string $entity, array $columns, array $userConditions): array
{
$columnGetters = $this->getColumnGetters($columns);
$query = strtolower(trim(Strings::toAscii($query)));
$query = strtolower(trim(Helpers::toAscii($query, useCache: true)));

/** @var object[] $candidateResults */
$candidateResults = $this->queryBuilder->build($query, $entity, $columns, $userConditions)->getQuery()->getResult();
$candidateResults = $this->queryBuilder
->build($query, $entity, $columns, $userConditions)
->getQuery()
->getResult();

$return = [];
foreach ($candidateResults as $candidateResult) {
Expand Down Expand Up @@ -92,7 +95,7 @@ public function processCandidateSearch(string $query, string $entity, array $col
}
} elseif ($emptyRequiredParameters === false) { // Use property loading if method can not be called
try {
$propertyRef = new \ReflectionProperty($candidateResultClass, Strings::firstLower($getterColumn));
$propertyRef = new \ReflectionProperty($candidateResultClass, Helpers::firstLower($getterColumn));
$propertyRef->setAccessible(true);
$columnDatabaseValue = $propertyRef->getValue($candidateResult);
} catch (\ReflectionException $e) {
Expand All @@ -118,7 +121,7 @@ public function processCandidateSearch(string $query, string $entity, array $col
}
}

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

$snippet = Helpers::implodeSnippets($snippets);
$return[] = new SearchItem(
$candidateResult,
$query,
$title ?? Strings::truncate($snippet, 64),
$snippet,
$finalScore,
entity: $candidateResult,
query: $query,
title: $title ?? Strings::truncate($snippet, 64),
snippet: $snippet,
score: $finalScore,
);
}

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

$return[$column] = Strings::firstUpper($columnGetter ?? $columnNormalize);
$return[$column] = Helpers::firstUpper($columnGetter ?? $columnNormalize);
}

return $return;
Expand All @@ -212,8 +215,8 @@ private function getValueByRelation(string $column, ?object $candidateEntity = n
$getterValue = $candidateEntity;
} else {
$method = preg_match('/^(?<column>[^(]+)(\((?<getter>[^)]*)\))$/', $columnRelation, $columnParser) === 1
? 'get' . Strings::firstUpper($columnParser['getter'])
: 'get' . Strings::firstUpper($columnRelation);
? 'get' . Helpers::firstUpper($columnParser['getter'])
: 'get' . Helpers::firstUpper($columnRelation);
/** @phpstan-ignore-next-line */
$getterValue = $candidateEntity->{$method}();
}
Expand Down
13 changes: 6 additions & 7 deletions src/Core/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder as DoctrineQueryBuilder;
use Nette\Utils\Strings;

final class QueryBuilder
{
Expand All @@ -24,8 +23,8 @@ public function __construct(


/**
* @param string[] $columns
* @param string[] $userConditions use as AND with automated WHERE.
* @param array<int, string> $columns
* @param array<int, string> $userConditions use as AND with automated WHERE.
*/
public function build(string $query, string $entity, array $columns, array $userConditions): DoctrineQueryBuilder
{
Expand Down Expand Up @@ -88,7 +87,7 @@ public function build(string $query, string $entity, array $columns, array $user
*
* The method returns a valid DQL query compatible with Doctrine syntax.
*
* @param string[] $columns
* @param array<int, string> $columns
*/
private function buildWhere(string $query, array $columns, bool $ignoreAccents = false): string
{
Expand All @@ -109,7 +108,7 @@ private function buildWhere(string $query, array $columns, bool $ignoreAccents =
$simpleQuery = str_replace(
['.', '?', '"'],
['. ', ' ', '\''],
$ignoreAccents === true ? Strings::toAscii($simpleQuery) : $simpleQuery,
$ignoreAccents === true ? Helpers::toAscii($simpleQuery) : $simpleQuery,
);

// Simple query match with normal keywords in query
Expand Down Expand Up @@ -149,8 +148,8 @@ private function buildWhere(string $query, array $columns, bool $ignoreAccents =
/**
* Create most effective join selector by internal magic.
*
* @param string[] $partialColumns
* @return string[]
* @param array<int, string> $partialColumns
* @return array<int, string>
*/
private function buildJoin(DoctrineQueryBuilder $queryBuilder, array $partialColumns): array
{
Expand Down
11 changes: 5 additions & 6 deletions src/Core/SelectorBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ final class SelectorBuilder
* 'content' => '!',
* ]
*
* @var string[][]
* @var array<string, array<string, string>>
*/
private array $map = [];

/** @var string[] */
/** @var array<int, string> */
private array $userConditions = [];


Expand Down Expand Up @@ -54,7 +54,7 @@ public function search(): SearchResult
/**
* Process search engine and get items.
*
* @return SearchItem[]
* @return array<int, SearchItem>
*/
public function getItems(int $limit = 10, int $offset = 0): array
{
Expand All @@ -65,7 +65,7 @@ public function getItems(int $limit = 10, int $offset = 0): array
/**
* Compute current entity search map by selector preferences.
*
* @return string[][]
* @return array<string, array<int, string>>
* @internal
*/
public function getMap(): array
Expand All @@ -84,8 +84,7 @@ public function getMap(): array


/**
* @param string[] $columns
* @return SelectorBuilder
* @param array<int, string> $columns
*/
public function addEntity(string $entity, array $columns = []): self
{
Expand Down
Loading

0 comments on commit 32a1bb2

Please sign in to comment.