SearchTerm   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 49
dl 0
loc 153
ccs 67
cts 67
cp 1
rs 10
c 0
b 0
f 0
wmc 18

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getTermIterator() 0 5 2
A getColumnIterator() 0 11 2
A getCriteria() 0 18 3
A createCriteria() 0 25 2
A getColumns() 0 13 3
A getSearchTerms() 0 27 5
A getTerm() 0 6 1
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * /src/Rest/SearchTerm.php
5
 *
6
 * @author TLe, Tarmo Leppänen <[email protected]>
7
 */
8
9
namespace App\Rest;
10
11
use App\Rest\Interfaces\SearchTermInterface;
12
use Closure;
13
use function array_filter;
14
use function array_map;
15
use function array_merge;
16
use function array_unique;
17
use function array_values;
18
use function explode;
19
use function is_array;
20
use function is_string;
21
use function preg_match_all;
22
use function preg_replace;
23
use function str_contains;
24
use function str_replace;
25
use function trim;
26
27
/**
28
 * Class SearchTerm
29
 *
30
 * @package App\Rest
31
 * @author TLe, Tarmo Leppänen <[email protected]>
32
 */
33
final class SearchTerm implements SearchTermInterface
34
{
35 90
    public static function getCriteria(
36
        array | string | null $column,
37
        array | string | null $search,
38
        ?string $operand = null,
39
        ?int $mode = null,
40
    ): ?array {
41 90
        $operand ??= self::OPERAND_OR;
42 90
        $mode ??= self::MODE_FULL;
43
44 90
        $columns = self::getColumns($column);
45 90
        $searchTerms = self::getSearchTerms($search);
46
47
        // Fallback to OR operand if not supported one given
48 90
        if ($operand !== self::OPERAND_AND && $operand !== self::OPERAND_OR) {
49 4
            $operand = self::OPERAND_OR;
50
        }
51
52 90
        return self::createCriteria($columns, $searchTerms, $operand, $mode);
53
    }
54
55
    /**
56
     * Helper method to create used criteria array with given columns and search terms.
57
     *
58
     * @param array<int, string> $columns
59
     * @param array<int, string> $searchTerms
60
     *
61
     * @return array<string, array<string, array<string, mixed>>>|null
62
     */
63 90
    private static function createCriteria(array $columns, array $searchTerms, string $operand, int $mode): ?array
64
    {
65 90
        $iteratorTerm = self::getTermIterator($columns, $mode);
66
67
        /**
68
         * Get criteria
69
         *
70
         * @var array<string, array<string, array<int, string>>> $criteria
71
         */
72 90
        $criteria = array_filter(array_map($iteratorTerm, $searchTerms));
73
74
        // Initialize output
75 90
        $output = null;
76
77
        // We have some generated criteria
78 90
        if ($criteria !== []) {
79
            // Create used criteria array
80 61
            $output = [
81 61
                'and' => [
82 61
                    $operand => array_merge(...array_values($criteria)),
83 61
                ],
84 61
            ];
85
        }
86
87 90
        return $output;
88
    }
89
90
    /**
91
     * Method to get term iterator closure.
92
     *
93
     * @param array<int, string> $columns
94
     */
95 90
    private static function getTermIterator(array $columns, int $mode): Closure
96
    {
97 90
        return static fn (string $term): ?array => $columns === []
98 8
            ? null
99 90
            : array_map(self::getColumnIterator($term, $mode), $columns);
100
    }
101
102
    /**
103
     * Method to get column iterator closure.
104
     */
105 61
    private static function getColumnIterator(string $term, int $mode): Closure
106
    {
107
        /*
108
         * Lambda function to create actual criteria for specified column + term + mode combo.
109
         *
110
         * @param string $column
111
         *
112
         * @return array<int, string>
113
         */
114 61
        return static fn (string $column): array => [
115 61
            str_contains($column, '.') ? $column : 'entity.' . $column, 'like', self::getTerm($mode, $term),
116 61
        ];
117
    }
118
119
    /**
120
     * Method to get search term clause for 'LIKE' query for specified mode.
121
     */
122 61
    private static function getTerm(int $mode, string $term): string
123
    {
124 61
        return match ($mode) {
125 61
            self::MODE_STARTS_WITH => $term . '%',
126 61
            self::MODE_ENDS_WITH => '%' . $term,
127 61
            default => '%' . $term . '%', // self::MODE_FULL
128 61
        };
129
    }
130
131
    /**
132
     * @param string|array<int, string|null>|null $column search column(s), could be a string or an array of strings
133
     *
134
     * @return array<int, string>
135
     */
136 90
    private static function getColumns(array | string | null $column): array
137
    {
138
        // Normalize columns
139 90
        return $column === null
0 ignored issues
show
introduced by
The condition $column === null is always false.
Loading history...
140 4
            ? []
141 90
            : array_filter(
142 90
                array_map(
143 90
                    'trim',
144 90
                    (is_array($column)
0 ignored issues
show
introduced by
The condition is_array($column) is always true.
Loading history...
145 34
                        ? array_filter($column, static fn (string | null $column): bool => is_string($column))
146 86
                        : (array)$column),
147 90
                ),
148 90
                static fn (string $value): bool => trim($value) !== '',
149 90
            );
150
    }
151
152
    /**
153
     * Method to get search terms.
154
     *
155
     * @param string|array<int, string|null>|null $search search term(s), could be a string or an array of strings
156
     *
157
     * @return array<int, string>
158
     */
159 90
    private static function getSearchTerms(array | string | null $search): array
160
    {
161 90
        if (is_string($search)) {
0 ignored issues
show
introduced by
The condition is_string($search) is always false.
Loading history...
162 57
            preg_match_all('#([^\"]\S*|\".+?\")\s*#', trim($search), $matches);
163
164 57
            if ($matches[1]) {
165 49
                $search = array_map(
166 49
                    static fn (string $term): string => trim(str_replace('"', '', $term)),
167 49
                    $matches[1],
168 49
                );
169
            }
170
        }
171
172 90
        return $search === null
0 ignored issues
show
introduced by
The condition $search === null is always false.
Loading history...
173 4
            ? []
174 90
            : array_unique(
175 90
                array_filter(
176 90
                    array_map(
177 90
                        static fn (string $term): string => (string)preg_replace('#\s+#', ' ', $term),
178 90
                        array_map(
179 90
                            'trim',
180 90
                            (is_array($search)
0 ignored issues
show
introduced by
The condition is_array($search) is always true.
Loading history...
181 78
                                ? array_filter($search, static fn (string | null $term): bool => is_string($term))
182 86
                                : explode(' ', $search)),
183 90
                        ),
184 90
                    ),
185 90
                    static fn (string $value): bool => trim($value) !== ''
186 90
                )
187 90
            );
188
    }
189
}
190