Issues (18)

src/Settings/LocalFactory.php (1 issue)

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