Test Failed
Branch develop (dd818d)
by Nuno
03:56
created

LocalFactory::isUnretrievableAttributes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of Scout Extended.
7
 *
8
 * (c) Algolia Team <[email protected]>
9
 *
10
 *  For the full copyright and license information, please view the LICENSE
11
 *  file that was distributed with this source code.
12
 */
13
14
namespace Algolia\ScoutExtended\Settings;
15
16
use function in_array;
17
use function is_string;
18
use Algolia\AlgoliaSearch\Index;
19
use Illuminate\Database\QueryException;
20
use Algolia\ScoutExtended\Searchable\Aggregator;
21
use Algolia\ScoutExtended\Exceptions\ModelNotFoundException;
22
use Algolia\ScoutExtended\Repositories\RemoteSettingsRepository;
23
use Illuminate\Database\Eloquent\ModelNotFoundException as BaseModelNotFoundException;
24
25
/**
26
 * @internal
27
 */
28
final class LocalFactory
29
{
30
    /**
31
     * @var \Algolia\ScoutExtended\Repositories\RemoteSettingsRepository
32
     */
33
    private $remoteRepository;
34
35
    /**
36
     * @var string[]
37
     */
38
    private static $customRankingKeys = [
39
        '*ed_at',
40
        '*count_*',
41
        '*_count*',
42
    ];
43
44
    /**
45
     * @var string[]
46
     */
47
    private static $unsearchableAttributesKeys = [
48
        'id',
49
        '*_id',
50
        'id_*',
51
        '*ed_at',
52
        'count',
53
        '*_count',
54
        'count_*',
55
        '*image*',
56
        '*url*',
57
        '*link*',
58
        '*password*',
59
        '*token*',
60
        '*hash*',
61
    ];
62
63
    /**
64
     * @var string[]
65
     */
66
    private static $attributesForFacetingKeys = [
67
        '*category*',
68
        '*list*',
69
        '*country*',
70
        '*city*',
71
        '*type*',
72
    ];
73
74
    /**
75
     * @var string[]
76
     */
77
    private static $unretrievableAttributes = [
78
        '*password*',
79
        '*token*',
80
        '*secret*',
81
        '*hash*',
82
    ];
83
84
    /**
85
     * @var string[]
86
     */
87
    private static $unsearchableAttributesValues = [
88
        'http://*',
89
        'https://*',
90
    ];
91
92
    /**
93
     * @var string[]
94
     */
95
    private static $disableTypoToleranceOnAttributesKeys = [
96
        'slug',
97
        '*_slug',
98
        'slug_*',
99
        '*code*',
100
        '*sku*',
101
        '*reference*',
102
    ];
103
104
    /**
105
     * SettingsFactory constructor.
106
     *
107
     * @param \Algolia\ScoutExtended\Repositories\RemoteSettingsRepository $remoteRepository
108
     *
109
     * @return void
110
     */
111 3
    public function __construct(RemoteSettingsRepository $remoteRepository)
112
    {
113 3
        $this->remoteRepository = $remoteRepository;
114 3
    }
115
116
    /**
117
     * Creates settings for the given model.
118
     *
119
     * @param \Algolia\AlgoliaSearch\Index $index
120
     * @param string $model
121
     *
122
     * @return \Algolia\ScoutExtended\Settings\Settings
123
     */
124 3
    public function create(Index $index, string $model): Settings
125
    {
126 3
        $attributes = $this->getAttributes($model);
127 2
        $searchableAttributes = [];
128 2
        $attributesForFaceting = [];
129 2
        $customRanking = [];
130 2
        $disableTypoToleranceOnAttributes = [];
131 2
        $unretrievableAttributes = [];
132 2
        foreach ($attributes as $key => $value) {
133 2
            $key = (string) $key;
134
135 2
            if ($this->isSearchableAttributes($key, $value)) {
136 2
                $searchableAttributes[] = $key;
137
            }
138
139 2
            if ($this->isAttributesForFaceting($key, $value)) {
140 2
                $attributesForFaceting[] = $key;
141
            }
142
143 2
            if ($this->isCustomRanking($key, $value)) {
144 2
                $customRanking[] = "desc({$key})";
145
            }
146
147 2
            if ($this->isDisableTypoToleranceOnAttributes($key, $value)) {
148
                $disableTypoToleranceOnAttributes[] = $key;
149
            }
150
151 2
            if ($this->isUnretrievableAttributes($key, $value)) {
152 2
                $unretrievableAttributes[] = $key;
153
            }
154
        }
155
156
        $detectedSettings = [
157 2
            'searchableAttributes' => ! empty($searchableAttributes) ? $searchableAttributes : null,
158 2
            'attributesForFaceting' => ! empty($attributesForFaceting) ? $attributesForFaceting : null,
159 2
            'customRanking' => ! empty($customRanking) ? $customRanking : null,
160 2
            'disableTypoToleranceOnAttributes' => ! empty($disableTypoToleranceOnAttributes) ?
161
                $disableTypoToleranceOnAttributes : null,
162 2
            'unretrievableAttributes' => ! empty($unretrievableAttributes) ? $unretrievableAttributes : null,
163 2
            'queryLanguages' => array_unique([config('app.locale'), config('app.fallback_locale')]),
164
        ];
165
166 2
        $settings = array_merge($this->remoteRepository->find($index)->compiled(), $detectedSettings);
167
168 2
        return new Settings($settings, $this->remoteRepository->defaults());
169
    }
170
171
    /**
172
     * Checks if the given key/value is a 'searchableAttributes'.
173
     *
174
     * @param  string $key
175
     * @param  mixed $value
176
     *
177
     * @return bool
178
     */
179 2
    public function isSearchableAttributes(string $key, $value): bool
180
    {
181 2
        return ! is_object($value) && ! is_array($value) &&
182 2
            ! str_is(self::$unsearchableAttributesKeys, $key) &&
183 2
            ! str_is(self::$unsearchableAttributesValues, $value);
184
    }
185
186
    /**
187
     * Checks if the given key/value is a 'attributesForFaceting'.
188
     *
189
     * @param  string $key
190
     * @param  mixed $value
191
     *
192
     * @return bool
193
     */
194 2
    public function isAttributesForFaceting(string $key, $value): bool
195
    {
196 2
        return str_is(self::$attributesForFacetingKeys, $key);
197
    }
198
199
    /**
200
     * Checks if the given key/value is a 'customRanking'.
201
     *
202
     * @param  string $key
203
     * @param  mixed $value
204
     *
205
     * @return bool
206
     */
207 2
    public function isCustomRanking(string $key, $value): bool
208
    {
209 2
        return str_is(self::$customRankingKeys, $key);
210
    }
211
212
    /**
213
     * Checks if the given key/value is a 'disableTypoToleranceOnAttributes'.
214
     *
215
     * @param  string $key
216
     * @param  mixed $value
217
     *
218
     * @return bool
219
     */
220 2
    public function isDisableTypoToleranceOnAttributes(string $key, $value): bool
221
    {
222 2
        return is_string($key) && str_is(self::$disableTypoToleranceOnAttributesKeys, $key);
223
    }
224
225
    /**
226
     * Checks if the given key/value is a 'unretrievableAttributes'.
227
     *
228
     * @param  string $key
229
     * @param  mixed $value
230
     *
231
     * @return bool
232
     */
233 2
    public function isUnretrievableAttributes(string $key, $value): bool
234
    {
235 2
        return is_string($key) && str_is(self::$unretrievableAttributes, $key);
236
    }
237
238
    /**
239
     * Tries to get attributes from the searchable class.
240
     *
241
     * @param  string $searchable
242
     *
243
     * @return array
244
     */
245 3
    private function getAttributes(string $searchable): array
246
    {
247 3
        $attributes = [];
248
249 3
        if (in_array(Aggregator::class, class_parents($searchable), true)) {
250
            foreach (($instance = new $searchable)->getModels() as $model) {
0 ignored issues
show
Unused Code introduced by
The assignment to $instance is dead and can be removed.
Loading history...
251
                $attributes = array_merge($attributes, $this->getAttributes($model));
252
            }
253
        } else {
254 3
            $instance = null;
255
256
            try {
257 3
                $instance = $searchable::firstOrFail();
258 1
            } catch (QueryException | BaseModelNotFoundException $e) {
259 1
                throw tap(new ModelNotFoundException())->setModel($searchable);
260
            }
261
262 2
            $attributes = method_exists($instance, 'toSearchableArray') ? $instance->toSearchableArray() :
263 2
                $instance->toArray();
264
        }
265
266 2
        return $attributes;
267
    }
268
}
269