sanitizeSearchQuery()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Explicit Architecture POC,
7
 * which is created on top of the Symfony Demo application.
8
 *
9
 * (c) Herberto Graça <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Acme\App\Core\Component\Blog\Application\Query\DQL;
16
17
use Acme\App\Core\Component\Blog\Application\Query\FindPostsBySearchRequestQueryInterface;
18
use Acme\App\Core\Component\Blog\Application\Query\PostsBySearchRequestDto;
19
use Acme\App\Core\Component\Blog\Domain\Post\Post;
20
use Acme\App\Core\Port\Persistence\DQL\DqlQueryBuilderInterface;
21
use Acme\App\Core\Port\Persistence\QueryServiceInterface;
22
use Acme\App\Core\Port\Persistence\QueryServiceRouterInterface;
23
use Acme\App\Core\Port\Persistence\ResultCollection;
24
use Acme\App\Core\Port\Persistence\ResultCollectionInterface;
25
26
final class FindPostsBySearchRequestQuery implements FindPostsBySearchRequestQueryInterface
27
{
28
    /**
29
     * @var DqlQueryBuilderInterface
30
     */
31
    private $dqlQueryBuilder;
32
33
    /**
34
     * @var QueryServiceInterface
35
     */
36
    private $queryService;
37
38
    public function __construct(
39
        DqlQueryBuilderInterface $dqlQueryBuilder,
40
        QueryServiceRouterInterface $queryService
41
    ) {
42
        $this->dqlQueryBuilder = $dqlQueryBuilder;
43
        $this->queryService = $queryService;
0 ignored issues
show
Documentation Bug introduced by
It seems like $queryService of type object<Acme\App\Core\Por...ServiceRouterInterface> is incompatible with the declared type object<Acme\App\Core\Por...\QueryServiceInterface> of property $queryService.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
44
    }
45
46
    /**
47
     * @throws \Exception
48
     *
49
     * @return PostsBySearchRequestDto[]
50
     */
51
    public function execute(string $queryString, int $limit = self::NUM_ITEMS): ResultCollectionInterface
52
    {
53
        $queryString = $this->sanitizeSearchQuery($queryString);
54
        $searchTerms = $this->extractSearchTerms($queryString);
55
56
        if (\count($searchTerms) === 0) {
57
            return new ResultCollection();
58
        }
59
60
        $this->dqlQueryBuilder->create(Post::class, 'Post')
61
            ->select(
62
                'Post.title',
63
                'Post.publishedAt',
64
                'Post.summary',
65
                'Post.slug',
66
                'Author.fullName'
67
            )
68
            // This join with 'User:User' is the same as a join with User::class. The main difference is that this way
69
            // we are not depending directly on the User entity, but on a configurable alias. The advantage is that we
70
            // can change where the user data is stored and this query will remain the same. For example we could move
71
            // this component into a microservice, with its own curated user data, and we wouldn't need to change this
72
            // query, only the doctrine configuration.
73
            ->join('User:User', 'Author', 'WITH', 'Author.id = Post.authorId');
74
75
        foreach ($searchTerms as $key => $term) {
76
            $this->dqlQueryBuilder->orWhere('Post.title LIKE :t_' . $key)
77
                ->setParameter('t_' . $key, '%' . $term . '%');
78
        }
79
80
        $this->dqlQueryBuilder->orderBy('Post.publishedAt', 'DESC')->setMaxResults($limit);
81
82
        $result = $this->queryService
0 ignored issues
show
Bug introduced by
The method query() does not seem to exist on object<Acme\App\Core\Por...\QueryServiceInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
83
            ->query($this->dqlQueryBuilder->build());
84
85
        return $result->hydrateResultItemsAs(PostsBySearchRequestDto::class);
86
    }
87
88
    /**
89
     * Removes all non-alphanumeric characters except whitespaces.
90
     */
91
    private function sanitizeSearchQuery(string $query): string
92
    {
93
        return trim(preg_replace('/[[:space:]]+/', ' ', $query));
94
    }
95
96
    /**
97
     * Splits the search query into terms and removes the ones which are irrelevant.
98
     */
99
    private function extractSearchTerms(string $searchQuery): array
100
    {
101
        $terms = array_unique(explode(' ', $searchQuery));
102
103
        return array_filter(
104
            $terms,
105
            function ($term) {
106
                return 2 <= mb_strlen($term);
107
            }
108
        );
109
    }
110
}
111