Completed
Push — master ( e75277...6d67f1 )
by Juuso
05:02
created

AlgoliaManager::initIndices()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 16
ccs 8
cts 8
cp 1
rs 9.4285
cc 2
eloc 8
nc 1
nop 1
crap 2
1
<?php
2
3
namespace leinonen\Yii2Algolia;
4
5
use AlgoliaSearch\Client;
6
use AlgoliaSearch\Index;
7
use leinonen\Yii2Algolia\ActiveRecord\ActiveRecordFactory;
8
9
/**
10
 * @method setConnectTimeout(int $connectTimeout, int $timeout = 30, int $searchTimeout = 5)
11
 * @method enableRateLimitForward(string $adminAPIKey, string $endUserIP, string $rateLimitAPIKey)
12
 * @method setForwarderFor(string $ip)
13
 * @method setAlgoliaUserToken(string $token)
14
 * @method disableRateLimitForward()
15
 * @method isAlive()
16
 * @method setExtraHeader(string $key, string $value)
17
 * @method mixed multipleQueries(array $queries, string $indexNameKey = "indexName", string $strategy = "none")
18
 * @method mixed listIndexes()
19
 * @method deleteIndex(string $indexName)
20
 * @method mixed moveIndex(string $srcIndexName, string $dstIndexName)
21
 * @method mixed copyIndex(string $srcIndexName, string $dstIndexName)
22
 * @method mixed getLogs(int $offset = 0, int $length = 10, string $type = "all")
23
 * @method Index initIndex(string $indexName)
24
 * @method mixed listUserKeys()
25
 * @method mixed getUserKeyACL(string $key)
26
 * @method mixed deleteUserKey(string $key)
27
 * @method mixed addUserKey(array $obj, int $validity = 0, int $maxQueriesPerIPPerHour = 0, int $maxHitsPerQuery = 0, array $indexes = null)
28
 * @method mixed updateUserKey(string $key, array $obj, int $validity = 0, int $maxQueriesPerIPPerHour = 0, int $maxHitsPerQuery = 0, array $indexes = null)
29
 * @method mixed batch(array $requests)
30
 * @method string generateSecuredApiKey(string $privateApiKey, mixed $query, string $userToken = null)
31
 * @method string buildQuery(array $args)
32
 * @method mixed request(\AlgoliaSearch\ClientContext $context, string $method, string $path, array $params, array $data, array $hostsArray, int $connectTimeout, int $readTimeout)
33
 * @method mixed doRequest(\AlgoliaSearch\ClientContext $context, string $method, string $path, array $params, array $data, array $hostsArray, int $connectTimeout, int $readTimeout)
34
 * @method \AlgoliaSearch\PlacesIndex initPlaces(string $appId, string $appKey, array $hostsArray = null, array $options = [])
35
 * @see Client
36
 */
37
class AlgoliaManager
38
{
39
    /**
40
     * @var AlgoliaFactory
41
     */
42
    protected $factory;
43
44
    /**
45
     * @var array
46
     */
47
    protected $config;
48
49
    /**
50
     * @var null|Client
51
     */
52
    protected $client;
53
54
    /**
55
     * @var ActiveRecordFactory
56
     */
57
    protected $activeRecordFactory;
58
59
    /**
60
     * @var null|string
61
     */
62
    protected $env;
63
64
    /**
65
     * Initiates a new AlgoliaManager.
66
     *
67
     * @param AlgoliaFactory $algoliaFactory
68
     * @param ActiveRecordFactory $activeRecordFactory
69
     * @param array $config Configurations for the Algolia Client.
70
     */
71 37
    public function __construct(AlgoliaFactory $algoliaFactory, ActiveRecordFactory $activeRecordFactory, array $config = [])
72
    {
73 37
        $this->factory = $algoliaFactory;
74 37
        $this->config = $config;
75 37
        $this->activeRecordFactory = $activeRecordFactory;
76 37
    }
77
78
    /**
79
     * Returns the Algolia Client.
80
     *
81
     * @return Client
82
     */
83 17
    public function getClient()
84
    {
85 17
        if (is_null($this->client)) {
86 17
            $this->client = $this->factory->make($this->config);
87 17
        }
88
89 17
        return $this->client;
90
    }
91
92
    /**
93
     * Returns the config array.
94
     *
95
     * @return array
96
     */
97 2
    public function getConfig()
98
    {
99 2
        return $this->config;
100
    }
101
102
    /**
103
     * Sets the environment for the manager.
104
     *
105
     * @param string $env
106
     */
107 37
    public function setEnv($env)
108
    {
109 37
        $this->env = $env;
110 37
    }
111
112
    /**
113
     * Returns the environment for the manager.
114
     *
115
     * @return null|string
116
     */
117 1
    public function getEnv()
118
    {
119 1
        return $this->env;
120
    }
121
122
    /**
123
     * Indexes a searchable model to all indices.
124
     *
125
     * @param SearchableInterface $searchableModel
126
     *
127
     * @return array
128
     */
129 5
    public function pushToIndices(SearchableInterface $searchableModel)
130
    {
131 5
        $indices = $this->initIndices($searchableModel);
132 5
        $response = [];
133
134 5
        foreach ($indices as $index) {
135 5
            $record = $searchableModel->getAlgoliaRecord();
136 5
            $response[$index->indexName] = $index->addObject($record, $searchableModel->getObjectID());
137 5
        }
138
139 5
        return $response;
140
    }
141
142
    /**
143
     * Indexes multiple searchable models in a batch. The given searchable models must be of the same class.
144
     *
145
     * @param SearchableInterface[] $searchableModels
146
     *
147
     * @return array
148
     */
149 3
    public function pushMultipleToIndices(array $searchableModels)
150
    {
151 3
        list($indices, $algoliaRecords) = $this->getIndicesAndAlgoliaRecordsFromSearchableModelArray($searchableModels);
152 2
        $response = [];
153
154 2
        foreach ($indices as $index) {
155
            /** @var Index $index  */
156 2
            $response[$index->indexName] = $index->addObjects($algoliaRecords);
157 2
        }
158
159 2
        return $response;
160
    }
161
162
    /**
163
     * Updates the models data in all indices.
164
     *
165
     * @param SearchableInterface $searchableModel
166
     *
167
     * @return array
168
     */
169 3
    public function updateInIndices(SearchableInterface $searchableModel)
170
    {
171 3
        $indices = $this->initIndices($searchableModel);
172 3
        $response = [];
173
174 3
        foreach ($indices as $index) {
175 3
            $record = $searchableModel->getAlgoliaRecord();
176 3
            $record['objectID'] = $searchableModel->getObjectID();
177 3
            $response[$index->indexName] = $index->saveObject($record);
178 3
        }
179
180 3
        return $response;
181
    }
182
183
    /**
184
     * Updates multiple models data in all indices.  The given searchable models must be of the same class.
185
     *
186
     * @param SearchableInterface[] $searchableModels
187
     *
188
     * @return array
189
     */
190 2
    public function updateMultipleInIndices(array $searchableModels)
191
    {
192 2
        list($indices, $algoliaRecords) = $this->getIndicesAndAlgoliaRecordsFromSearchableModelArray($searchableModels);
193 1
        $response = [];
194
195 1
        foreach ($indices as $index) {
196
            /** @var Index $index  */
197 1
            $response[$index->indexName] = $index->saveObjects($algoliaRecords);
198 1
        }
199
200 1
        return $response;
201
    }
202
203
    /**
204
     * Removes a searchable model from indices.
205
     *
206
     * @param SearchableInterface $searchableModel
207
     *
208
     * @return array
209
     * @throws \Exception
210
     */
211 2
    public function removeFromIndices(SearchableInterface $searchableModel)
212
    {
213 2
        $indices = $indices = $this->initIndices($searchableModel);
214 2
        $response = [];
215
216 2
        foreach ($indices as $index) {
217 2
            $objectID = $searchableModel->getObjectID();
218 2
            $response[$index->indexName] = $index->deleteObject($objectID);
219 2
        }
220
221 2
        return $response;
222
    }
223
224
    /**
225
     * Re-indexes the indices safely for the given ActiveRecord Class.
226
     *
227
     * @param string $className The name of the ActiveRecord to be indexed.
228
     *
229
     * @return array
230
     */
231 3
    public function reindex($className)
232
    {
233 3
        $this->checkImplementsSearchableInterface($className);
234 2
        $activeRecord = $this->activeRecordFactory->make($className);
235 2
        $response = [];
236
237
        /** @var SearchableInterface[] $activeRecordEntities */
238 2
        $activeRecordEntities = $activeRecord->find()->all();
239
240
        /* @var SearchableInterface $activeRecord */
241 2
        $indices = $indices = $this->initIndices($activeRecord);
242 2
        $records = [];
243
244 2
        foreach ($activeRecordEntities as $activeRecordEntity) {
245 2
            $record = $activeRecordEntity->getAlgoliaRecord();
246 2
            $record['objectID'] = $activeRecordEntity->getObjectID();
247 2
            $records[] = $record;
248 2
        }
249
250 2
        foreach ($indices as $index) {
251 2
            $temporaryIndexName = 'tmp_' . $index->indexName;
252
253
            /** @var Index $temporaryIndex */
254 2
            $temporaryIndex = $this->initIndex($temporaryIndexName);
255 2
            $temporaryIndex->addObjects($records);
256
257 2
            $settings = $index->getSettings();
258
259
            // Temporary index overrides all the settings on the main one.
260
            // So let's set the original settings on the temporary one before atomically moving the index.
261 2
            $temporaryIndex->setSettings($settings);
262
263 2
            $response[$index->indexName] = $this->moveIndex($temporaryIndexName, $index->indexName);
264
265 2
        }
266
267 2
        return $response;
268
    }
269
270
    /**
271
     * Clears the indices for the given Class that implements SearchableInterface.
272
     *
273
     * @param string $className The name of the Class which indices are to be cleared.
274
     *
275
     * @return array
276
     */
277 2
    public function clearIndices($className)
278
    {
279 2
        $this->checkImplementsSearchableInterface($className);
280 1
        $activeRecord = $this->activeRecordFactory->make($className);
281 1
        $response = [];
282
283
        /* @var SearchableInterface $activeRecord */
284 1
        $indices = $indices = $this->initIndices($activeRecord);
285
286 1
        foreach ($indices as $index) {
287 1
            $response[$index->indexName] = $index->clearIndex();
288 1
        }
289
290 1
        return $response;
291
    }
292
293
    /**
294
     * Dynamically pass methods to the Algolia Client.
295
     *
296
     * @param string $method
297
     * @param array $parameters
298
     *
299
     * @return mixed
300
     */
301 16
    public function __call($method, $parameters)
302
    {
303 16
        return call_user_func_array([$this->getClient(), $method], $parameters);
304
    }
305
306
    /**
307
     * Checks if the given class implements SearchableInterface.
308
     *
309
     * @param string $class Either name or instance of the class to be checked.
310
     */
311 5
    private function checkImplementsSearchableInterface($class)
312
    {
313 5
        $reflectionClass = new \ReflectionClass($class);
314
315 5
        if (! $reflectionClass->implementsInterface(SearchableInterface::class)) {
316 2
            throw new \InvalidArgumentException("The class: {$reflectionClass->name} doesn't implement leinonen\\Yii2Algolia\\SearchableInterface");
317
        }
318 3
    }
319
320
    /**
321
     * Returns the name of the class for given object.
322
     * 
323
     * @param $class
324
     * 
325
     * @return string
326
     */
327 5
    private function getClassName($class)
328
    {
329 5
        $reflectionClass = new \ReflectionClass($class);
330
        
331 5
        return $reflectionClass->name;
332
    }
333
334
    /**
335
     * Initializes indices for the given SearchableModel.
336
     *
337
     * @param SearchableInterface $searchableModel
338
     *
339
     * @return Index[]
340
     */
341 15
    private function initIndices(SearchableInterface $searchableModel)
342
    {
343 15
        $indexNames = $searchableModel->getIndices();
344
345
        $indices = array_map(function($indexName){
346
347 15
            if($this->env !== null){
348 3
                $indexName = $this->env . '_' . $indexName;
349 3
            }
350
351 15
            return $this->initIndex($indexName);
352 15
        }, $indexNames);
353
354 15
        return $indices;
355
356
    }
357
358
    /**
359
     * Maps an array of searchable models into an Algolia friendly array. Returns also indices for the searchable model
360
     * which the array consists of.
361
     *
362
     * @param SearchableInterface[] $searchableModels
363
     *
364
     * @return array
365
     */
366 5
    private function getIndicesAndAlgoliaRecordsFromSearchableModelArray(array $searchableModels)
367
    {
368
        // Use the first element of the array to define what kind of models we are indexing.
369 5
        $arrayType = $this->getClassName($searchableModels[0]);
370 5
        $indices = $this->initIndices($this->factory->makeSearchableObject($arrayType));
371
372 5
        $algoliaRecords = array_map(function ($searchableModel) use ($arrayType) {
373
            /** @var $searchableModel SearchableInterface */
374 5
            if (!$searchableModel instanceof $arrayType) {
375 2
                throw new \InvalidArgumentException("The given array should not contain multiple different classes");
376
            }
377
378 5
            $algoliaRecord = $searchableModel->getAlgoliaRecord();
379 5
            $algoliaRecord['objectID'] = $searchableModel->getObjectID();
380
381 5
            return $algoliaRecord;
382
383 5
        }, $searchableModels);
384
385 3
        return [$indices, $algoliaRecords];
386
    }
387
}
388