├── .gitattributes
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml.dist
└── src
├── AlgoliaEloquentTrait.php
├── AlgoliaServiceProvider.php
├── EloquentSubscriber.php
└── ModelHelper.php
/.gitattributes:
--------------------------------------------------------------------------------
1 | /tests export-ignore
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.lock
3 | phpunit.xml
4 | *.sublime*
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.5.9
5 | - 5.5
6 | - 5.6
7 | - 7.0
8 | - 7.1
9 | - hhvm
10 |
11 | matrix:
12 | fast_finish: true
13 |
14 | sudo: false
15 |
16 | install:
17 | - travis_retry composer install --no-interaction --prefer-source
18 |
19 | script:
20 | - vendor/bin/phpunit $PHPUNIT_FLAGS
21 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 1.7.1 (release 2017-01-31)
4 | - Make compatible with Laravel 5.4
5 |
6 | ## 1.1.0 (release 2016-05-03)
7 |
8 | - Add option to dynamically set `autoIndex` and/or `autoDelete` by methods in model.
9 |
10 | ## 1.0.5 (release 2015-10-27)
11 |
12 | - Fix slaves + env
13 |
14 | ## 1.0.3 (release 2015-07-07)
15 |
16 | - Improve Documentation
17 | - Add constistency for trait attributes
18 | - Add browse and browseFrom method
19 |
20 | ## 1.0.2 (release 2015-06-30)
21 |
22 | - Update vinkla/algolia
23 |
24 | ## 1.0.1 (released 2015-06-18)
25 |
26 | - Fix issue in __callStatic of the trait
27 | - Fix issue with static call inside model with static or self keyword
28 |
29 | ## 1.0.0 (released 2015-06-12)
30 |
31 | - Initial stable release
32 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # CONTRIBUTING
2 |
3 | Contributions are welcome, and are accepted via pull requests. Please review these guidelines before submitting any pull requests.
4 |
5 | ## Guidelines
6 |
7 | * Please follow the [PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) and [PHP-FIG Naming Conventions](https://github.com/php-fig/fig-standards/blob/master/bylaws/002-psr-naming-conventions.md).
8 | * Ensure that the current tests pass, and if you've added something new, add the tests where relevant.
9 | * Remember that we follow [SemVer](http://semver.org). If you are changing the behaviour, or the public api, you may need to update the docs.
10 | * Send a coherent commit history, making sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash](http://git-scm.com/book/en/Git-Tools-Rewriting-History) them before submitting.
11 | * You may also need to [rebase](http://git-scm.com/book/en/Git-Branching-Rebasing) to avoid merge conflicts.
12 |
13 |
14 | ## Running Tests
15 |
16 | You will need an install of [Composer](https://getcomposer.org) before continuing.
17 |
18 | First, install the dependencies:
19 |
20 | ```bash
21 | $ composer install
22 | ```
23 |
24 | Then run phpunit:
25 |
26 | ```bash
27 | $ vendor/bin/phpunit
28 | ```
29 |
30 | If the test suite passes on your local machine you should be good to go.
31 |
32 | When you make a pull request, the tests will automatically be run again by [Travis CI](https://travis-ci.org/) on multiple php versions and hhvm.
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Algolia
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [DEPRECATED] Algolia Search API Client for Laravel
2 |
3 | [Algolia Search](https://www.algolia.com) is a hosted full-text, numerical, and faceted search engine capable of delivering realtime results from the first keystroke.
4 |
5 | ---
6 |
7 | **This package is deprecated, we recommend you to use [Laravel Scout](https://laravel.com/docs/5.4/scout)**. If you want to extend Scout capabilities, please refer to [our dedicated documentation](https://www.algolia.com/doc/api-client/laravel/algolia-and-scout/)
8 |
9 | ---
10 |
11 | [](https://travis-ci.org/algolia/algoliasearch-laravel)
12 | [](https://github.com/algolia/algoliasearch-laravel/releases)
13 | [](https://packagist.org/packages/algolia/algoliasearch-laravel)
14 |
15 |
16 | This PHP package integrates the Algolia Search API into the Laravel Eloquent ORM. It's based on the [algoliasearch-client-php](https://github.com/algolia/algoliasearch-client-php) package.
17 |
18 | **Note:** If you're using Laravel 4, checkout the [algoliasearch-laravel-4](https://github.com/algolia/algoliasearch-laravel-4) repository.
19 |
20 |
21 |
22 |
23 | ## API Documentation
24 |
25 | You can find the full reference on [Algolia's website](https://www.algolia.com/doc/api-client/laravel/).
26 |
27 |
28 | ## Table of Contents
29 |
30 |
31 | 1. **[Install](#install)**
32 |
33 | * [Install via composer](#install-via-composer)
34 | * [Service provider](#service-provider)
35 | * [Publish vendor](#publish-vendor)
36 |
37 | 1. **[Quick Start](#quick-start)**
38 |
39 | * [Quick Start](#quick-start)
40 | * [Ranking & Relevance](#ranking--relevance)
41 | * [Frontend Search (realtime experience)](#frontend-search-realtime-experience)
42 | * [Backend Search](#backend-search)
43 |
44 | 1. **[Options](#options)**
45 |
46 | * [Auto-indexing & Asynchronism](#auto-indexing--asynchronism)
47 | * [Custom Index Name](#custom-index-name)
48 | * [Per-environment Indexes](#per-environment-indexes)
49 | * [Custom `objectID`](#custom-objectid)
50 | * [Restrict Indexing to a Subset of Your Data](#restrict-indexing-to-a-subset-of-your-data)
51 |
52 | 1. **[Relationships](#relationships)**
53 |
54 | * [Relationships](#relationships)
55 |
56 | 1. **[Indexing](#indexing)**
57 |
58 | * [Manual Indexing](#manual-indexing)
59 | * [Manual Removal](#manual-removal)
60 | * [Reindexing](#reindexing)
61 | * [Clearing an Index](#clearing-an-index)
62 |
63 | 1. **[Manage indices](#manage-indices)**
64 |
65 | * [Primary/Replica](#primaryreplica)
66 | * [Target Multiple Indexes](#target-multiple-indexes)
67 |
68 | 1. **[Eloquent compatibility](#eloquent-compatibility)**
69 |
70 | * [Eloquent compatibility](#eloquent-compatibility)
71 | * [Compatibility](#compatibility)
72 |
73 |
74 |
75 |
76 | # Install
77 |
78 |
79 |
80 | ## Install via composer
81 | Add `algolia/algoliasearch-laravel` to your `composer.json` file:
82 |
83 | ```bash
84 | composer require algolia/algoliasearch-laravel
85 | ```
86 |
87 | ## Service provider
88 | Add the service provider to `config/app.php` in the `providers` array.
89 |
90 | ```php
91 | AlgoliaSearch\Laravel\AlgoliaServiceProvider::class
92 | ```
93 |
94 | ## Publish vendor
95 |
96 | Laravel Algolia requires a connection configuration. To get started, you'll need to publish all vendor assets:
97 |
98 | ```bash
99 | php artisan vendor:publish
100 | ```
101 |
102 | You can add the ```--provider="Vinkla\Algolia\AlgoliaServiceProvider"``` option to only publish assets of the Algolia package.
103 |
104 | This will create a `config/algolia.php` file in your app that you can modify to set your configuration. Also, make sure you check for changes compared to the original config file after an upgrade.
105 |
106 |
107 | # Quick Start
108 |
109 |
110 |
111 | ## Quick Start
112 |
113 | The following code adds search capabilities to your `Contact` model creating a `Contact` index:
114 |
115 | ```php
116 | use Illuminate\Database\Eloquent\Model;
117 | use AlgoliaSearch\Laravel\AlgoliaEloquentTrait;
118 |
119 | class Contact extends Model
120 | {
121 | use AlgoliaEloquentTrait;
122 | }
123 | ```
124 |
125 | By default all visible attributes are sent. If you want to send specific attributes you can do something like:
126 |
127 | ```php
128 | use Illuminate\Database\Eloquent\Model;
129 |
130 | class Contact extends Model
131 | {
132 | use AlgoliaEloquentTrait;
133 |
134 | public function getAlgoliaRecord()
135 | {
136 | return array_merge($this->toArray(), [
137 | 'custom_name' => 'Custom Name'
138 | ]);
139 | }
140 | }
141 | ```
142 |
143 | After setting up your model, you need to manually do an initial import of your data. You can do this by calling `reindex` on your model class. Using our previous example, this would be:
144 |
145 | ```php
146 | Contact::reindex();
147 | ```
148 |
149 | ## Ranking & Relevance
150 |
151 | We provide many ways to configure your index settings to tune the overall relevancy, but the most important ones are the **searchable attributes** and the attributes reflecting the **record popularity**. You can configure them with the following code:
152 |
153 | ```php
154 | use Illuminate\Database\Eloquent\Model;
155 |
156 | class Contact extends Model
157 | {
158 | use AlgoliaEloquentTrait;
159 |
160 | public $algoliaSettings = [
161 | 'searchableAttributes' => [
162 | 'id',
163 | 'name',
164 | ],
165 | 'customRanking' => [
166 | 'desc(popularity)',
167 | 'asc(name)',
168 | ],
169 | ];
170 | }
171 | ```
172 |
173 | You can propagate (save) the settings to algolia by using the `setSetting` method:
174 |
175 | ```php
176 | Contact::setSettings();
177 | ```
178 |
179 | #### Synonyms
180 |
181 | Synonyms are used to tell the engine about words or expressions that should be considered equal in regard to the textual relevance.
182 |
183 | Our [synonyms API](https://www.algolia.com/doc/relevance/synonyms) has been designed to manage as easily as possible a large set of synonyms for an index and its replicas.
184 |
185 | You can use the synonyms API by adding a `synonyms` in `$algoliaSettings` class property like this:
186 |
187 | ```php
188 | use Illuminate\Database\Eloquent\Model;
189 |
190 | class Contact extends Model
191 | {
192 | use AlgoliaEloquentTrait;
193 |
194 | public $algoliaSettings = [
195 | 'synonyms' => [
196 | [
197 | 'objectID' => 'red-color',
198 | 'type' => 'synonym',
199 | 'synonyms' => ['red', 'another red', 'yet another red']
200 | ]
201 | ]
202 | ];
203 | }
204 | ```
205 |
206 | You can propagate (save) the settings to algolia using the `setSetting` method:
207 |
208 | ```php
209 | Contact::setSettings();
210 | ```
211 |
212 | ## Frontend Search (realtime experience)
213 |
214 | Traditional search implementations tend to have search logic and functionality on the backend. This made sense when the search experience consisted of a user entering a search query, executing that search, and then being redirected to a search result page.
215 |
216 | Implementing search on the backend is no longer necessary. In fact, in most cases it is harmful to performance because of the extra network and processing latency. We highly recommend the usage of our [JavaScript API Client](https://github.com/algolia/algoliasearch-client-javascript) issuing all search requests directly from the end user's browser, mobile device, or client. It will reduce the overall search latency while offloading your servers at the same time.
217 |
218 | In your JavaScript code you can do:
219 |
220 | ```js
221 | var client = algoliasearch('ApplicationID', 'Search-Only-API-Key');
222 | var index = client.initIndex('YourIndexName');
223 | index.search('something', function(success, hits) {
224 | console.log(success, hits)
225 | }, { hitsPerPage: 10, page: 0 });
226 | ```
227 |
228 | ## Backend Search
229 |
230 | You could also use the `search` method, but it's not recommended to implement an instant/realtime search experience from the backend (having a frontend search gives a better user experience):
231 |
232 | ```php
233 | Contact::search('jon doe');
234 | ```
235 |
236 |
237 | # Options
238 |
239 |
240 |
241 | ## Auto-indexing & Asynchronism
242 |
243 | Each time a record is saved; it will be - asynchronously - indexed. On the other hand, each time a record is destroyed, it will be - asynchronously - removed from the index.
244 |
245 | You can disable the auto-indexing and auto-removing by setting the following options:
246 |
247 | ```php
248 | use Illuminate\Database\Eloquent\Model;
249 |
250 | class Contact extends Model
251 | {
252 | use AlgoliaEloquentTrait;
253 |
254 | public static $autoIndex = false;
255 | public static $autoDelete = false;
256 | }
257 | ```
258 |
259 | You can temporarily disable auto-indexing. This is often done for performance reasons.
260 |
261 | ```php
262 | Contact::$autoIndex = false;
263 | Contact::clearIndices();
264 |
265 | for ($i = 0; $i < 10000; $i++) {
266 | $contact = Contact::firstOrCreate(['name' => 'Jean']);
267 | }
268 |
269 | Contact::reindex(); // Will use batch operations.
270 | Contact::$autoIndex = true;
271 | ```
272 |
273 | You can also make a dynamic condition for those two parameters by creating an `autoIndex` and/or `autoDelete method`
274 | on your model
275 |
276 | ```php
277 | use Illuminate\Database\Eloquent\Model;
278 |
279 | class Contact extends Model
280 | {
281 | use AlgoliaEloquentTrait;
282 |
283 | public function autoIndex()
284 | {
285 | if (\App::environment() === 'test') {
286 | return false;
287 | }
288 |
289 | return true;
290 | }
291 |
292 | public static autoDelete()
293 | {
294 | if (\App::environment() === 'test') {
295 | return false;
296 | }
297 |
298 | return true;
299 | }
300 | }
301 | ```
302 |
303 | Be careful to define those two methods in AlgoliaEloquentTrait.
304 | When putting those methods in a parent class they will be "erased" by AlgoliaEloquentTrait if used in a child class
305 | (because of php inheritance).
306 |
307 | ## Custom Index Name
308 |
309 | By default, the index name will be the pluralized class name, e.g. "Contacts". You can customize the index name by using the `$indices` option:
310 |
311 | ```php
312 | use Illuminate\Database\Eloquent\Model;
313 |
314 | class Contact extends Model
315 | {
316 | use AlgoliaEloquentTrait;
317 |
318 | public $indices = ['contact_all'];
319 | }
320 | ```
321 |
322 | ## Per-environment Indexes
323 |
324 | You can suffix the index name with the current App environment using the following option:
325 |
326 | ```php
327 | use Illuminate\Database\Eloquent\Model;
328 |
329 | class Contact extends Model
330 | {
331 | use AlgoliaEloquentTrait;
332 |
333 | public static $perEnvironment = true; // Index name will be 'Contacts_{\App::environnement()}';
334 | }
335 | ```
336 |
337 | ## Custom `objectID`
338 |
339 | By default, the `objectID` is based on your record's `keyName` (`id` by default). You can change this behavior specifying the `objectIdKey` option (be sure to use a uniq field).
340 |
341 | ```php
342 | use Illuminate\Database\Eloquent\Model;
343 |
344 | class Contact extends Model
345 | {
346 | use AlgoliaEloquentTrait;
347 |
348 | public static $objectIdKey = 'new_key';
349 | }
350 | ```
351 |
352 | ## Restrict Indexing to a Subset of Your Data
353 |
354 | You can add constraints controlling if a record must be indexed by defining the `indexOnly()` method.
355 |
356 | ```php
357 | use Illuminate\Database\Eloquent\Model;
358 |
359 | class Contact extends Model
360 | {
361 | use AlgoliaEloquentTrait;
362 |
363 | public function indexOnly($index_name)
364 | {
365 | return (bool) $condition;
366 | }
367 | }
368 | ```
369 |
370 |
371 | # Relationships
372 |
373 |
374 |
375 | ## Relationships
376 |
377 | By default the Algolia package will fetch the **loaded** relationships.
378 |
379 | If you want to index records that haven't yet loaded any relations, you can do it by loading them in the ```getAlgoliaRecord``` that you can create in your model.
380 |
381 | It will look like:
382 |
383 | ```php
384 | public function getAlgoliaRecord()
385 | {
386 | /**
387 | * Load the categories relation so that it's available
388 | * in the laravel toArray method
389 | */
390 | $this->categories;
391 |
392 | return $this->toArray();
393 | }
394 | ```
395 |
396 | In the resulted object, you will have categories converted to array by Laravel. If you want a custom relation structure you will instead do something like:
397 |
398 | ```php
399 | public function getAlgoliaRecord()
400 | {
401 | /**
402 | * Load the categories relation so that it's available
403 | * in the laravel toArray method
404 | */
405 | $extra_data = [];
406 | $extra_data['categories'] = array_map(function ($data) {
407 | return $data['name'];
408 | }, $this->categories->toArray());
409 |
410 | return array_merge($this->toArray(), $extra_data);
411 | }
412 | ```
413 |
414 |
415 | # Indexing
416 |
417 | ## Visibility
418 |
419 | By default, Algolia will only be able to access **visible** attributes of your model. So, for example, you will receive a `No content in PUT request` exception when using this example code, because `invisible_attribute` key returns an empty/null variable.
420 |
421 | ```php
422 | protected $visible = ['visible_attribute', 'other_visible_attribute'];
423 |
424 | public function getAlgoliaRecord()
425 | {
426 | return [
427 | 'invisible_attribute' => $this->invisible_attribute
428 | ];
429 | }
430 | ```
431 |
432 | Before Indexing, be sure to have correctly listed your visible attributes. To bypass this safety mask imposed by Laravel, you may use `$this->attributes['invisible_attribute']` to access directly to the attribute even if is not visible, but the recommendation is to avoid this type of access to attributes in your Model.
433 |
434 | ## Manual Indexing
435 |
436 | You can trigger indexing using the `pushToIndex` instance method.
437 |
438 | ```php
439 | $contact = Contact::firstOrCreate(['name' => 'Jean']);
440 | $contact->pushToIndex();
441 | ```
442 |
443 | ## Manual Removal
444 |
445 | And trigger the removal using the `removeFromIndex` instance method.
446 |
447 | ```php
448 | $contact = Contact::firstOrCreate(['name' => 'Jean']);
449 | $contact->removeFromIndex();
450 | ```
451 |
452 | ## Reindexing
453 |
454 | To *safely* reindex all your records (index to a temporary index + move the temporary index to the current one atomically), use the `reindex` class method:
455 |
456 | ```php
457 | Contact::reindex();
458 | ```
459 |
460 | To reindex all your records (in place, without deleting outdated records):
461 |
462 | ```php
463 | Contact::reindex(false);
464 | ```
465 |
466 | To set settings during the reindexing process:
467 |
468 | ```php
469 | Contact::reindex(true, true);
470 | ```
471 |
472 | To keep settings that you set on the Algolia dashboard when reindexing and changing settings:
473 |
474 | ```php
475 | Contact::reindex(true, true, true);
476 | ```
477 |
478 | To implement a callback that gets called everytime a batch of entities is indexed:
479 |
480 | ```php
481 | Contact::reindex(true, true, false, function ($entities)
482 | {
483 | foreach ($entities as $entity)
484 | {
485 | var_dump($entity->id); // Contact::$id
486 | }
487 | });
488 | ```
489 |
490 | ## Clearing an Index
491 |
492 | To clear an index, use the `clearIndices` class method:
493 |
494 | ```ruby
495 | Contact::clearIndices();
496 | ```
497 |
498 | # Manage indices
499 |
500 |
501 |
502 | ## Primary/Replica
503 |
504 | You can define replica indexes using the `$algolia_settings` variable:
505 |
506 | ```php
507 | use Illuminate\Database\Eloquent\Model;
508 |
509 | class Contact extends Model
510 | {
511 | use AlgoliaEloquentTrait;
512 |
513 | public $algoliaSettings = [
514 | 'searchableAttributes' => [
515 | 'id',
516 | 'name',
517 | ],
518 | 'customRanking' => [
519 | 'desc(popularity)',
520 | 'asc(name)',
521 | ],
522 | 'replicas' => [
523 | 'contacts_desc',
524 | ],
525 | ];
526 |
527 | public $replicasSettings = [
528 | 'contacts_desc' => [
529 | 'ranking' => [
530 | 'desc(name)',
531 | 'typo',
532 | 'geo',
533 | 'words',
534 | 'proximity',
535 | 'attribute',
536 | 'exact',
537 | 'custom'
538 | ]
539 | ]
540 | ];
541 | }
542 | ```
543 |
544 | To search using a replica, use the following code:
545 |
546 | ```php
547 | Book::search('foo bar', ['index' => 'contacts_desc']);
548 | ```
549 |
550 | ## Target Multiple Indexes
551 |
552 | You can index a record in several indexes using the $indices
property:
553 |
554 | ```php
555 | use Illuminate\Database\Eloquent\Model;
556 |
557 | class Contact extends Model
558 | {
559 | use AlgoliaEloquentTrait;
560 |
561 | public $indices = [
562 | 'contact_public',
563 | 'contact_private',
564 | ];
565 |
566 | public function indexOnly($indexName)
567 | {
568 | if ($indexName == 'contact_public')
569 | return true;
570 |
571 | return $this->private;
572 | }
573 |
574 | }
575 | ```
576 |
577 | To search using an extra index, use the following code:
578 |
579 | ```php
580 | Book::search('foo bar', ['index' => 'contacts_private']);
581 | ```
582 |
583 |
584 | # Eloquent compatibility
585 |
586 |
587 |
588 | ## Eloquent compatibility
589 |
590 | Doing:
591 |
592 | ```php
593 | Ad::where('id', $id)->update($attributes);
594 | ```
595 |
596 | will not trigger anything in the model (so no update will happen in Algolia). This is because it is not an Eloquent call. It is just a convenient way to generate the query hidden behind the model.
597 |
598 | To make this query work with Algolia you need to do it like this:
599 |
600 | ```php
601 | Ad::find($id)->update($attributes);
602 | ```
603 |
604 | ## Compatibility
605 |
606 | Compatible with 5.x applications
607 |
608 |
609 |
610 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "algolia/algoliasearch-laravel",
3 | "description": "Laravel Algolia extension",
4 | "license": "MIT",
5 | "keywords": [
6 | "laravel",
7 | "algolia",
8 | "search",
9 | "api"
10 | ],
11 | "abandoned": "laravel/scout",
12 | "require": {
13 | "php": ">=5.5.9",
14 | "vinkla/algolia": "~2.0"
15 | },
16 | "require-dev": {
17 | "phpunit/phpunit": "~4.0",
18 | "mockery/mockery": "~0.9",
19 | "orchestra/testbench": "3.1.*"
20 | },
21 | "autoload": {
22 | "psr-4": {
23 | "AlgoliaSearch\\Laravel\\": "src/"
24 | }
25 | },
26 | "autoload-dev": {
27 | "psr-4": {
28 | "AlgoliaSearch\\Tests\\": "tests/"
29 | }
30 | },
31 | "extra": {
32 | "branch-alias": {
33 | "dev-master": "1.0-dev"
34 | }
35 | },
36 | "minimum-stability": "dev",
37 | "prefer-stable": true
38 | }
39 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | ./tests
20 |
21 |
22 |
23 |
24 | ./src
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/AlgoliaEloquentTrait.php:
--------------------------------------------------------------------------------
1 | getIndices($this);
27 | $indicesTmp = $safe ? $modelHelper->getIndicesTmp($this) : $indices;
28 |
29 | if ($setSettings === true) {
30 | $setToTmpIndices = ($safe === true);
31 | $this->_setSettings($setToTmpIndices, $mergeOldSettings);
32 | }
33 |
34 | static::chunk(100, function ($models) use ($indicesTmp, $modelHelper, $onInsert) {
35 | /** @var \AlgoliaSearch\Index $index */
36 | foreach ($indicesTmp as $index) {
37 | $records = [];
38 | $recordsAsEntity = [];
39 |
40 | foreach ($models as $model) {
41 | if ($modelHelper->indexOnly($model, $index->indexName)) {
42 | $records[] = $model->getAlgoliaRecordDefault($index->indexName);
43 |
44 | if ($onInsert && is_callable($onInsert)) {
45 | $recordsAsEntity[] = $model;
46 | }
47 | }
48 | }
49 |
50 | $index->addObjects($records);
51 |
52 | if ($onInsert && is_callable($onInsert)) {
53 | call_user_func_array($onInsert, [$recordsAsEntity]);
54 | }
55 | }
56 |
57 | });
58 |
59 | if ($safe) {
60 | for ($i = 0; $i < count($indices); $i++) {
61 | $modelHelper->algolia->moveIndex($indicesTmp[$i]->indexName, $indices[$i]->indexName);
62 | }
63 |
64 | $this->_setSettings(false); // To a setSettings to set the slave on the master
65 | }
66 | }
67 |
68 | public function _clearIndices()
69 | {
70 | /** @var \AlgoliaSearch\Laravel\ModelHelper $modelHelper */
71 | $modelHelper = App::make('\AlgoliaSearch\Laravel\ModelHelper');
72 |
73 | $indices = $modelHelper->getIndices($this);
74 |
75 | /** @var \AlgoliaSearch\Index $index */
76 | foreach ($indices as $index) {
77 | $index->clearIndex();
78 | }
79 | }
80 |
81 | /**
82 | * @param $query
83 | * @param array $parameters
84 | * @param $cursor
85 | *
86 | * @return mixed
87 | */
88 | public function _browseFrom($query, $parameters = [], $cursor = null)
89 | {
90 | /** @var \AlgoliaSearch\Laravel\ModelHelper $modelHelper */
91 | $modelHelper = App::make('\AlgoliaSearch\Laravel\ModelHelper');
92 |
93 | $index = null;
94 |
95 | if (isset($parameters['index'])) {
96 | $index = $modelHelper->getIndices($this, $parameters['index'])[0];
97 | unset($parameters['index']);
98 | } else {
99 | $index = $modelHelper->getIndices($this)[0];
100 | }
101 |
102 | $result = $index->browseFrom($query, $parameters, $cursor);
103 |
104 | return $result;
105 | }
106 |
107 | /**
108 | * @param $query
109 | * @param array $parameters
110 | *
111 | * @return mixed
112 | */
113 | public function _browse($query, $parameters = [])
114 | {
115 | /** @var \AlgoliaSearch\Laravel\ModelHelper $modelHelper */
116 | $modelHelper = App::make('\AlgoliaSearch\Laravel\ModelHelper');
117 |
118 | $index = null;
119 |
120 | if (isset($parameters['index'])) {
121 | $index = $modelHelper->getIndices($this, $parameters['index'])[0];
122 | unset($parameters['index']);
123 | } else {
124 | $index = $modelHelper->getIndices($this)[0];
125 | }
126 |
127 | $result = $index->browse($query, $parameters);
128 |
129 | return $result;
130 | }
131 |
132 | /**
133 | * @param $query
134 | * @param array $parameters
135 | *
136 | * @return mixed
137 | */
138 | public function _search($query, $parameters = [])
139 | {
140 | /** @var \AlgoliaSearch\Laravel\ModelHelper $modelHelper */
141 | $modelHelper = App::make('\AlgoliaSearch\Laravel\ModelHelper');
142 |
143 | $index = null;
144 |
145 | if (isset($parameters['index'])) {
146 | $index = $modelHelper->getIndices($this, $parameters['index'])[0];
147 | unset($parameters['index']);
148 | } else {
149 | $index = $modelHelper->getIndices($this)[0];
150 | }
151 |
152 | $result = $index->search($query, $parameters);
153 |
154 | return $result;
155 | }
156 |
157 | public function _setSettings($setToTmpIndices = false, $mergeOldSettings = false)
158 | {
159 | /** @var \AlgoliaSearch\Laravel\ModelHelper $modelHelper */
160 | $modelHelper = App::make('\AlgoliaSearch\Laravel\ModelHelper');
161 |
162 | $settings = $modelHelper->getSettings($this);
163 |
164 | if ($setToTmpIndices === false) {
165 | $indices = $modelHelper->getIndices($this);
166 | }
167 | else {
168 | $indices = $modelHelper->getIndicesTmp($this);
169 | }
170 |
171 | $replicas_settings = $modelHelper->getReplicasSettings($this);
172 | $replicas = isset($settings['replicas']) ? $settings['replicas'] : [];
173 |
174 | // Backward compatibility
175 | if ($replicas === [] && isset($settings['slaves'])) {
176 | $replicas = $settings['slaves'];
177 | }
178 |
179 | $b = true;
180 |
181 | /** @var \AlgoliaSearch\Index $index */
182 | foreach ($indices as $key => $index) {
183 | if ($mergeOldSettings) {
184 | $old_indices = $modelHelper->getIndices($this);
185 | $old_index = $old_indices[$key];
186 |
187 | try {
188 | $oldSettings = $old_index->getSettings();
189 | }
190 | catch (\Exception $e) {
191 | $oldSettings = [];
192 | }
193 |
194 | unset($oldSettings['replicas']);
195 | unset($oldSettings['slaves']);
196 |
197 | $newSettings = $oldSettings;
198 |
199 | foreach ($settings as $settingName => $settingValue) {
200 | $newSettings[$settingName] = $settingValue;
201 | }
202 |
203 | $settings = $newSettings;
204 | }
205 |
206 | if ($b && isset($settings['replicas'])) {
207 | $settings['replicas'] = array_map(function ($indexName) use ($modelHelper) {
208 | return $modelHelper->getFinalIndexName($this, $indexName);
209 | }, $settings['replicas']);
210 | } elseif ($b && isset($settings['slaves'])) {
211 | // Backward compatibility
212 | $settings['slaves'] = array_map(function ($indexName) use ($modelHelper) {
213 | return $modelHelper->getFinalIndexName($this, $indexName);
214 | }, $settings['slaves']);
215 | }
216 |
217 | if (isset($settings['synonyms'])) {
218 | $index->batchSynonyms($settings['synonyms'], true, true);
219 | }
220 | else {
221 | // If no synonyms are passed, clear all synonyms from index
222 | $index->clearSynonyms(true);
223 | }
224 |
225 | // If we move the index the setSettings should not contains slave or replica.
226 | if ($setToTmpIndices && $b) {
227 | $b = false;
228 | unset($settings['replicas']);
229 | unset($settings['slaves']); // backward compatibility
230 | }
231 |
232 | if (count(array_keys($settings)) > 0) {
233 | // Synonyms cannot be pushed into "setSettings", it's got rejected from API and throwing exception
234 | // Synonyms cannot be removed directly from $settings var, because then synonym would not be set to other indices
235 | $settingsWithoutSynonyms = $settings;
236 | unset($settingsWithoutSynonyms['synonyms']);
237 |
238 | $index->setSettings($settingsWithoutSynonyms);
239 | }
240 |
241 | if ($b) {
242 | $b = false;
243 | unset($settings['replicas']);
244 | unset($settings['slaves']); // backward compatibility
245 | }
246 | }
247 |
248 | foreach ($replicas as $replica) {
249 | if (isset($replicas_settings[$replica])) {
250 | $index = $modelHelper->getIndices($this, $replica)[0];
251 |
252 | $s = array_merge($settings, $replicas_settings[$replica]);
253 | unset($s['synonyms']);
254 |
255 | if (count(array_keys($s)) > 0)
256 | $index->setSettings($s);
257 | }
258 | }
259 | }
260 |
261 | /**
262 | * @param $method
263 | * @param $parameters
264 | * @return mixed
265 | */
266 | public static function __callStatic($method, $parameters)
267 | {
268 | $instance = new static();
269 | $overload_method = '_'.$method;
270 |
271 | if (method_exists($instance, $overload_method)) {
272 | return call_user_func_array([$instance, $overload_method], $parameters);
273 | }
274 |
275 | return parent::__callStatic($method, $parameters);
276 | }
277 |
278 | /**
279 | * @param $method
280 | * @param $parameters
281 | * @return mixed
282 | *
283 | * Catch static calls call from within a class. Example : static::method();
284 | */
285 | public function __call($method, $parameters)
286 | {
287 | $overload_method = '_'.$method;
288 |
289 | if (method_exists($this, $overload_method)) {
290 | return call_user_func_array([$this, $overload_method], $parameters);
291 | }
292 |
293 | return parent::__call($method, $parameters);
294 | }
295 |
296 | /**
297 | * Methods.
298 | */
299 | public function getAlgoliaRecordDefault($indexName)
300 | {
301 | /** @var \AlgoliaSearch\Laravel\ModelHelper $modelHelper */
302 | $modelHelper = App::make('\AlgoliaSearch\Laravel\ModelHelper');
303 |
304 | $record = null;
305 |
306 | if (method_exists($this, self::$methodGetName)) {
307 | $record = $this->{self::$methodGetName}($indexName);
308 | } else {
309 | $record = $this->toArray();
310 | }
311 |
312 | if (isset($record['objectID']) == false) {
313 | $record['objectID'] = $modelHelper->getObjectId($this);
314 | }
315 |
316 | return $record;
317 | }
318 |
319 | public function pushToIndex()
320 | {
321 | /** @var \AlgoliaSearch\Laravel\ModelHelper $modelHelper */
322 | $modelHelper = App::make('\AlgoliaSearch\Laravel\ModelHelper');
323 |
324 | $indices = $modelHelper->getIndices($this);
325 |
326 | /** @var \AlgoliaSearch\Index $index */
327 | foreach ($indices as $index) {
328 | if ($modelHelper->indexOnly($this, $index->indexName)) {
329 | $index->addObject($this->getAlgoliaRecordDefault($index->indexName));
330 | }
331 | }
332 | }
333 |
334 | public function removeFromIndex()
335 | {
336 | /** @var \AlgoliaSearch\Laravel\ModelHelper $modelHelper */
337 | $modelHelper = App::make('\AlgoliaSearch\Laravel\ModelHelper');
338 |
339 | $indices = $modelHelper->getIndices($this);
340 |
341 | /** @var \AlgoliaSearch\Index $index */
342 | foreach ($indices as $index) {
343 | $index->deleteObject($modelHelper->getObjectId($this));
344 | }
345 | }
346 |
347 | public function autoIndex()
348 | {
349 | return (property_exists($this, 'autoIndex') == false || $this::$autoIndex === true);
350 | }
351 |
352 | public function autoDelete()
353 | {
354 | return (property_exists($this, 'autoDelete') == false || $this::$autoDelete === true);
355 | }
356 | }
357 |
--------------------------------------------------------------------------------
/src/AlgoliaServiceProvider.php:
--------------------------------------------------------------------------------
1 | registerManager();
19 |
20 | Event::subscribe('\AlgoliaSearch\Laravel\EloquentSubscriber');
21 | }
22 |
23 | private function registerManager()
24 | {
25 | $this->app->register('Vinkla\Algolia\AlgoliaServiceProvider');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/EloquentSubscriber.php:
--------------------------------------------------------------------------------
1 | modelHelper = $modelHelper;
14 | }
15 |
16 | public function saved($eventName, $payload = null)
17 | {
18 | $model = $this->getModelFromParams($eventName, $payload);
19 |
20 | if (!$this->modelHelper->isAutoIndex($model)) {
21 | return true;
22 | }
23 |
24 | /** @var \AlgoliaSearch\Index $index */
25 | foreach ($this->modelHelper->getIndices($model) as $index) {
26 | if ($this->modelHelper->indexOnly($model, $index->indexName)) {
27 | $index->addObject($this->modelHelper->getAlgoliaRecord($model, $index->indexName), $this->modelHelper->getObjectId($model));
28 | } elseif ($this->modelHelper->wouldBeIndexed($model, $index->indexName)) {
29 | $index->deleteObject($this->modelHelper->getObjectId($model));
30 | }
31 | }
32 |
33 | return true;
34 | }
35 |
36 | public function deleted($eventName, $payload = null)
37 | {
38 | $model = $this->getModelFromParams($eventName, $payload);
39 |
40 | if (!$this->modelHelper->isAutoDelete($model)) {
41 | return true;
42 | }
43 |
44 | /** @var \AlgoliaSearch\Index $index */
45 | foreach ($this->modelHelper->getIndices($model) as $index) {
46 | $index->deleteObject($this->modelHelper->getObjectId($model));
47 | }
48 |
49 | return true;
50 | }
51 |
52 | /**
53 | * @param string|Model $eventName
54 | * @param array|null $payload
55 | *
56 | * @return Model
57 | */
58 | private function getModelFromParams($eventName, $payload = null)
59 | {
60 | if($eventName instanceof Model) {
61 | // Laravel < 5.4
62 | return $eventName;
63 | }
64 |
65 | // Laravel >= 5.4
66 | return $payload[0];
67 | }
68 |
69 | public function subscribe($events)
70 | {
71 | $events->listen('eloquent.saved*', '\AlgoliaSearch\Laravel\EloquentSubscriber@saved');
72 | $events->listen('eloquent.deleted*', '\AlgoliaSearch\Laravel\EloquentSubscriber@deleted');
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/ModelHelper.php:
--------------------------------------------------------------------------------
1 | algolia = $algolia;
15 | }
16 |
17 | private function getIndexName(Model $model)
18 | {
19 | return $model->getTable();
20 | }
21 |
22 | private function hasAlgoliaTrait(Model $class, $autoload = false)
23 | {
24 | $traits = [];
25 |
26 | // Get traits of all parent classes
27 | do {
28 | $traits = array_merge(class_uses($class, $autoload), $traits);
29 | } while ($class = get_parent_class($class));
30 |
31 | // Get traits of all parent traits
32 | $traitsToSearch = $traits;
33 | while (!empty($traitsToSearch)) {
34 | $newTraits = class_uses(array_pop($traitsToSearch), $autoload);
35 | $traits = array_merge($newTraits, $traits);
36 | $traitsToSearch = array_merge($newTraits, $traitsToSearch);
37 | };
38 |
39 | foreach ($traits as $trait => $same) {
40 | $traits = array_merge(class_uses($trait, $autoload), $traits);
41 | }
42 |
43 | $traits = array_unique($traits);
44 |
45 | return (isset($traits['AlgoliaSearch\Laravel\AlgoliaEloquentTrait']));
46 | }
47 |
48 | public function wouldBeIndexed(Model $model, $index_name)
49 | {
50 | if (! method_exists($model, 'indexOnly')) {
51 | return false;
52 | }
53 |
54 | $cloned = clone $model;
55 |
56 | $cloned->setRawAttributes($cloned->getOriginal());
57 |
58 | return $cloned->indexOnly($index_name) === true;
59 | }
60 |
61 | public function isAutoIndex(Model $model)
62 | {
63 | return ($this->hasAlgoliaTrait($model) && $model->autoIndex());
64 | }
65 |
66 | public function isAutoDelete(Model $model)
67 | {
68 | return ($this->hasAlgoliaTrait($model) && $model->autoDelete());
69 | }
70 |
71 | public function getKey(Model $model)
72 | {
73 | return $model->getKey();
74 | }
75 |
76 | public function indexOnly(Model $model, $index_name)
77 | {
78 | return !method_exists($model, 'indexOnly') || $model->indexOnly($index_name);
79 | }
80 |
81 | public function getObjectId(Model $model)
82 | {
83 | return $model->{$this->getObjectIdKey($model)};
84 | }
85 |
86 | public function getObjectIdKey(Model $model)
87 | {
88 | return property_exists($model, 'objectIdKey') ? $model::$objectIdKey : $model->getKeyName();
89 | }
90 |
91 | public function getSettings(Model $model)
92 | {
93 | return property_exists($model, 'algoliaSettings') ? $model->algoliaSettings : [];
94 | }
95 |
96 | public function getReplicasSettings(Model $model)
97 | {
98 | $replicas_settings = property_exists($model, 'replicasSettings') ? $model->replicasSettings : [];
99 |
100 | // Backward compatibility
101 | if ($replicas_settings === [] && property_exists($model, 'slavesSettings')) {
102 | $replicas_settings = $model->slavesSettings;
103 | }
104 |
105 | return $replicas_settings;
106 | }
107 |
108 | public function getSlavesSettings(Model $model)
109 | {
110 | trigger_error("getSlavesSettings was renamed to getReplicasSettings", E_USER_DEPRECATED);
111 |
112 | return $this->getReplicasSettings($model);
113 | }
114 |
115 | public function getFinalIndexName(Model $model, $indexName)
116 | {
117 | $env_suffix = property_exists($model, 'perEnvironment') && $model::$perEnvironment === true ? '_'.\App::environment() : '';
118 |
119 | return $indexName.$env_suffix;
120 | }
121 |
122 | /**
123 | * @return \AlgoliaSearch\Index
124 | */
125 | public function getIndices(Model $model, $indexName = null)
126 | {
127 | $indicesName = $this->buildIndices($model, $indexName);
128 |
129 | $indices = array_map(function ($index_name) use ($model) {
130 | return $this->algolia->initIndex($this->getFinalIndexName($model, $index_name));
131 | }, $indicesName);
132 |
133 | return $indices;
134 | }
135 |
136 | protected function buildIndices(Model $model, $indexName = null)
137 | {
138 | if ($indexName !== null) {
139 | return [$indexName];
140 | }
141 |
142 | if (method_exists($model, 'indices') && is_array($model->indices())) {
143 | return $model->indices();
144 | }
145 |
146 | if (property_exists($model, 'indices') && is_array($model->indices)) {
147 | return $model->indices;
148 | }
149 |
150 | return [$this->getIndexName($model)];
151 | }
152 |
153 | public function getIndicesTmp(Model $model, $indexName = null)
154 | {
155 | $indicesName = $this->buildIndices($model, $indexName);
156 |
157 | $indices = array_map(function ($index_name) use ($model) {
158 | return $this->algolia->initIndex($this->getFinalIndexName($model, $index_name).'_tmp');
159 | }, $indicesName);
160 |
161 | return $indices;
162 | }
163 |
164 | public function getAlgoliaRecord(Model $model, $indexName)
165 | {
166 | return $model->getAlgoliaRecordDefault($indexName);
167 | }
168 | }
169 |
--------------------------------------------------------------------------------