Completed
Push — master ( 26e407...24bafe )
by Juuso
04:14
created

AlgoliaManager   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 351
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 97.75%

Importance

Changes 17
Bugs 4 Features 3
Metric Value
wmc 29
c 17
b 4
f 3
lcom 1
cbo 6
dl 0
loc 351
ccs 87
cts 89
cp 0.9775
rs 10

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getClient() 0 8 2
A getConfig() 0 4 1
A setEnv() 0 4 1
A getEnv() 0 4 1
A pushToIndices() 0 12 2
A pushMultipleToIndices() 0 12 2
A updateInIndices() 0 13 2
A updateMultipleInIndices() 0 12 2
A removeFromIndices() 0 12 2
B reindex() 0 38 3
A clearIndices() 0 15 2
A __call() 0 4 1
A checkImplementsSearchableInterface() 0 8 2
A getClassName() 0 6 1
A initIndices() 0 16 2
A getIndicesAndAlgoliaRecordsFromSearchableModelArray() 0 21 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 26
    public function __construct(AlgoliaFactory $algoliaFactory, ActiveRecordFactory $activeRecordFactory, array $config = [])
72
    {
73 26
        $this->factory = $algoliaFactory;
74 26
        $this->config = $config;
75 26
        $this->activeRecordFactory = $activeRecordFactory;
76 26
    }
77
78
    /**
79
     * Returns the Algolia Client.
80
     *
81
     * @return Client
82
     */
83 9
    public function getClient()
84
    {
85 9
        if (is_null($this->client)) {
86 9
            $this->client = $this->factory->make($this->config);
87 9
        }
88
89 9
        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 26
    public function setEnv($env)
108
    {
109 26
        $this->env = $env;
110 26
    }
111
112
    /**
113
     * Returns the environment for the manager.
114
     *
115
     * @return null|string
116
     */
117
    public function getEnv()
118
    {
119
        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 2
    public function pushToIndices(SearchableInterface $searchableModel)
130
    {
131 2
        $indices = $this->initIndices($searchableModel);
132 2
        $response = [];
133
134 2
        foreach ($indices as $index) {
135 2
            $record = $searchableModel->getAlgoliaRecord();
136 2
            $response[$index->indexName] = $index->addObject($record, $searchableModel->getObjectID());
137 2
        }
138
139 2
        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 2
    public function pushMultipleToIndices(array $searchableModels)
150
    {
151 2
        list($indices, $algoliaRecords) = $this->getIndicesAndAlgoliaRecordsFromSearchableModelArray($searchableModels);
152 2
        $response = [];
153
154 2
        foreach ($indices as $index) {
155 2
            /** @var Index $index  */
156 2
            $response[$index->indexName] = $index->addObjects($algoliaRecords);
157 2
        }
158 2
159
        return $response;
160 2
    }
161
162
    /**
163
     * Updates the models data in all indices.
164
     *
165
     * @param SearchableInterface $searchableModel
166
     *
167
     * @return array
168
     */
169
    public function updateInIndices(SearchableInterface $searchableModel)
170
    {
171 2
        $indices = $this->initIndices($searchableModel);
172
        $response = [];
173 2
174 2
        foreach ($indices as $index) {
175
            $record = $searchableModel->getAlgoliaRecord();
176 2
            $record['objectID'] = $searchableModel->getObjectID();
177 2
            $response[$index->indexName] = $index->saveObject($record);
178 2
        }
179 2
180
        return $response;
181 2
    }
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
    public function updateMultipleInIndices(array $searchableModels)
191 3
    {
192
        list($indices, $algoliaRecords) = $this->getIndicesAndAlgoliaRecordsFromSearchableModelArray($searchableModels);
193 3
        $response = [];
194 2
195 2
        foreach ($indices as $index) {
196
            /** @var Index $index  */
197
            $response[$index->indexName] = $index->saveObjects($algoliaRecords);
198 2
        }
199
200
        return $response;
201 2
    }
202 2
203
    /**
204 2
     * Removes a searchable model from indices.
205 2
     *
206 2
     * @param SearchableInterface $searchableModel
207 2
     *
208 2
     * @return array
209
     * @throws \Exception
210 2
     */
211 2
    public function removeFromIndices(SearchableInterface $searchableModel)
212
    {
213
        $indices = $indices = $this->initIndices($searchableModel);
214 2
        $response = [];
215 2
216
        foreach ($indices as $index) {
217 2
            $objectID = $searchableModel->getObjectID();
218
            $response[$index->indexName] = $index->deleteObject($objectID);
219
        }
220
221 2
        return $response;
222
    }
223 2
224
    /**
225 2
     * Re-indexes the indices safely for the given ActiveRecord Class.
226
     *
227 2
     * @param string $className The name of the ActiveRecord to be indexed.
228
     *
229
     * @return array
230
     */
231
    public function reindex($className)
232
    {
233
        $this->checkImplementsSearchableInterface($className);
234
        $activeRecord = $this->activeRecordFactory->make($className);
235
        $response = [];
236
237 2
        /** @var SearchableInterface[] $activeRecordEntities */
238
        $activeRecordEntities = $activeRecord->find()->all();
239 2
240 1
        /* @var SearchableInterface $activeRecord */
241 1
        $indices = $indices = $this->initIndices($activeRecord);
242
        $records = [];
243
244 1
        foreach ($activeRecordEntities as $activeRecordEntity) {
245
            $record = $activeRecordEntity->getAlgoliaRecord();
246 1
            $record['objectID'] = $activeRecordEntity->getObjectID();
247 1
            $records[] = $record;
248 1
        }
249
250 1
        foreach ($indices as $index) {
251
            $temporaryIndexName = 'tmp_' . $index->indexName;
252
253
            /** @var Index $temporaryIndex */
254
            $temporaryIndex = $this->initIndex($temporaryIndexName);
255
            $temporaryIndex->addObjects($records);
256
257
            $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 8
            $temporaryIndex->setSettings($settings);
262
263 8
            $response[$index->indexName] = $this->moveIndex($temporaryIndexName, $index->indexName);
264
265
        }
266
267
        return $response;
268
    }
269
270
    /**
271 5
     * Clears the indices for the given Class that implements SearchableInterface.
272
     *
273 5
     * @param string $className The name of the Class which indices are to be cleared.
274
     *
275 5
     * @return array
276 2
     */
277
    public function clearIndices($className)
278 3
    {
279
        $this->checkImplementsSearchableInterface($className);
280
        $activeRecord = $this->activeRecordFactory->make($className);
281
        $response = [];
282
283
        /* @var SearchableInterface $activeRecord */
284
        $indices = $indices = $this->initIndices($activeRecord);
285
286
        foreach ($indices as $index) {
287 7
            $response[$index->indexName] = $index->clearIndex();
288
        }
289 7
290
        return $response;
291 7
    }
292
293 7
    /**
294 7
     * Dynamically pass methods to the Algolia Client.
295 2
     *
296 2
     * @param string $method
297
     * @param array $parameters
298 7
     *
299 7
     * @return mixed
300
     */
301 7
    public function __call($method, $parameters)
302
    {
303
        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
    private function checkImplementsSearchableInterface($class)
312
    {
313
        $reflectionClass = new \ReflectionClass($class);
314
315
        if (! $reflectionClass->implementsInterface(SearchableInterface::class)) {
316
            throw new \InvalidArgumentException("The class: {$reflectionClass->name} doesn't implement leinonen\\Yii2Algolia\\SearchableInterface");
317
        }
318
    }
319
320
    /**
321
     * Returns the name of the class for given object.
322
     * 
323
     * @param $class
324
     * 
325
     * @return string
326
     */
327
    private function getClassName($class)
328
    {
329
        $reflectionClass = new \ReflectionClass($class);
330
        
331
        return $reflectionClass->name;
332
    }
333
334
    /**
335
     * Initializes indices for the given SearchableModel.
336
     *
337
     * @param SearchableInterface $searchableModel
338
     *
339
     * @return Index[]
340
     */
341
    private function initIndices(SearchableInterface $searchableModel)
342
    {
343
        $indexNames = $searchableModel->getIndices();
344
345
        $indices = array_map(function($indexName){
346
347
            if($this->env !== null){
348
                $indexName = $this->env . '_' . $indexName;
349
            }
350
351
            return $this->initIndex($indexName);
352
        }, $indexNames);
353
354
        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
    private function getIndicesAndAlgoliaRecordsFromSearchableModelArray(array $searchableModels)
367
    {
368
        // Use the first element of the array to define what kind of models we are indexing.
369
        $arrayType = $this->getClassName($searchableModels[0]);
370
        $indices = $this->initIndices($this->factory->makeSearchableObject($arrayType));
371
372
        $algoliaRecords = array_map(function ($searchableModel) use ($arrayType) {
373
            /** @var $searchableModel SearchableInterface */
374
            if (!$searchableModel instanceof $arrayType) {
375
                throw new \InvalidArgumentException("The given array should not contain multiple different classes");
376
            }
377
378
            $algoliaRecord = $searchableModel->getAlgoliaRecord();
379
            $algoliaRecord['objectID'] = $searchableModel->getObjectID();
380
381
            return $algoliaRecord;
382
383
        }, $searchableModels);
384
385
        return [$indices, $algoliaRecords];
386
    }
387
}
388