Skip to content

Commit 4d446b5

Browse files
committed
Merge remote-tracking branch 'origin/dev-l9' into dev-l8
2 parents 13d35a0 + 90e7adc commit 4d446b5

File tree

13 files changed

+1026
-208
lines changed

13 files changed

+1026
-208
lines changed

README.md

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
<img align="left" width="70" height="70" src="https://cdn.snipform.io/pdphilip/elasticsearch/laravel-x-es.png">
22

3-
# Laravel x Elasticsearch
3+
# Laravel-Elasticsearch
44

5-
This package extends Laravel's Eloquent model and query builder with seamless integration of Elasticsearch functionalities. Designed to feel native to Laravel, this plugin enables you to work with Eloquent models while leveraging the
5+
[![Latest Stable Version](http://img.shields.io/github/release/pdphilip/laravel-elasticsearch.svg)](https://packagist.org/packages/pdphilip/elasticsearch)
6+
[![Total Downloads](http://img.shields.io/packagist/dm/pdphilip/elasticsearch.svg)](https://packagist.org/packages/pdphilip/elasticsearch)
7+
8+
### An Elasticsearch implementation of Laravel's Eloquent ORM
9+
10+
This package extends Laravel's Eloquent model and query builder with seamless integration of Elasticsearch functionalities. Designed to feel native to Laravel, this package enables you to work with Eloquent models while leveraging the
611
powerful search and analytics capabilities of Elasticsearch.
712

13+
```php
14+
$logs = UserLog::where('type', UserLogType::LOGIN)->where('created_at','>=',Carbon::now()->subDays(30))->get();
15+
```
16+
817
### Read the [Documentation](https://elasticsearch.pdphilip.com/)
918

1019
---
@@ -44,7 +53,115 @@ composer require pdphilip/elasticsearch
4453
| Laravel 7.x | `composer require pdphilip/elasticsearch:~1.7` ||
4554
| Laravel 6.x (5.8) | `composer require pdphilip/elasticsearch:~1.6` ||
4655

47-
Next, [Configuration](https://elasticsearch.pdphilip.com/#configuration)
56+
## Configuration
57+
58+
1. Set up your `.env` with the following Elasticsearch settings:
59+
60+
```ini
61+
ES_AUTH_TYPE=http
62+
ES_HOSTS="http://localhost:9200"
63+
ES_USERNAME=
64+
ES_PASSWORD=
65+
ES_CLOUD_ID=
66+
ES_API_ID=
67+
ES_API_KEY=
68+
ES_SSL_CA=
69+
ES_INDEX_PREFIX=my_app
70+
# prefix will be added to all indexes created by the package with an underscore
71+
# ex: my_app_user_logs for UserLog.php model
72+
ES_SSL_CERT=
73+
ES_SSL_CERT_PASSWORD=
74+
ES_SSL_KEY=
75+
ES_SSL_KEY_PASSWORD=
76+
# Options
77+
ES_OPT_ID_SORTABLE=false
78+
ES_OPT_VERIFY_SSL=true
79+
ES_OPT_RETRIES=
80+
ES_OPT_META_HEADERS=true
81+
```
82+
83+
For multiple nodes, pass in as comma-separated:
84+
85+
```ini
86+
ES_HOSTS="http://es01:9200,http://es02:9200,http://es03:9200"
87+
```
88+
89+
<details>
90+
<summary>Example cloud config .env: (Click to expand)</summary>
91+
92+
```ini
93+
ES_AUTH_TYPE=cloud
94+
ES_HOSTS="https://xxxxx-xxxxxx.es.europe-west1.gcp.cloud.es.io:9243"
95+
ES_USERNAME=elastic
96+
ES_PASSWORD=XXXXXXXXXXXXXXXXXXXX
97+
ES_CLOUD_ID=XXXXX:ZXVyb3BlLXdl.........SQwYzM1YzU5ODI5MTE0NjQ3YmEyNDZlYWUzOGNkN2Q1Yg==
98+
ES_API_ID=
99+
ES_API_KEY=
100+
ES_SSL_CA=
101+
ES_INDEX_PREFIX=my_app
102+
```
103+
104+
</details>
105+
106+
2. In `config/database.php`, add the elasticsearch connection:
107+
108+
```php
109+
'elasticsearch' => [
110+
'driver' => 'elasticsearch',
111+
'auth_type' => env('ES_AUTH_TYPE', 'http'), //http or cloud
112+
'hosts' => explode(',', env('ES_HOSTS', 'http://localhost:9200')),
113+
'username' => env('ES_USERNAME', ''),
114+
'password' => env('ES_PASSWORD', ''),
115+
'cloud_id' => env('ES_CLOUD_ID', ''),
116+
'api_id' => env('ES_API_ID', ''),
117+
'api_key' => env('ES_API_KEY', ''),
118+
'ssl_cert' => env('ES_SSL_CA', ''),
119+
'ssl' => [
120+
'cert' => env('ES_SSL_CERT', ''),
121+
'cert_password' => env('ES_SSL_CERT_PASSWORD', ''),
122+
'key' => env('ES_SSL_KEY', ''),
123+
'key_password' => env('ES_SSL_KEY_PASSWORD', ''),
124+
],
125+
'index_prefix' => env('ES_INDEX_PREFIX', false),
126+
'options' => [
127+
'allow_id_sort' => env('ES_OPT_ID_SORTABLE', false),
128+
'ssl_verification' => env('ES_OPT_VERIFY_SSL', true),
129+
'retires' => env('ES_OPT_RETRIES', null),
130+
'meta_header' => env('ES_OPT_META_HEADERS', true),
131+
],
132+
'query_log' => [
133+
'index' => false, //Or provide a name for the logging index ex: 'laravel_query_logs'
134+
'error_only' => true, //If false, then all queries are logged if the query_log index is set
135+
],
136+
],
137+
```
138+
139+
### 3. If packages are not autoloaded, add the service provider:
140+
141+
For **Laravel 10 and below**:
142+
143+
```php
144+
//config/app.php
145+
'providers' => [
146+
...
147+
...
148+
PDPhilip\Elasticsearch\ElasticServiceProvider::class,
149+
...
150+
151+
```
152+
153+
For **Laravel 11**:
154+
155+
```php
156+
//bootstrap/providers.php
157+
<?php
158+
return [
159+
App\Providers\AppServiceProvider::class,
160+
PDPhilip\Elasticsearch\ElasticServiceProvider::class,
161+
];
162+
```
163+
164+
Now, you're all set to use Elasticsearch with Laravel as if it were native to the framework.
48165

49166
---
50167

@@ -63,8 +180,9 @@ Next, [Configuration](https://elasticsearch.pdphilip.com/#configuration)
63180
- [Deleting Models](https://elasticsearch.pdphilip.com/deleting-models)
64181
- [Ordering and Pagination](https://elasticsearch.pdphilip.com/ordering-and-pagination)
65182
- [Distinct and GroupBy](https://elasticsearch.pdphilip.com/distinct)
66-
- [Aggregations](https://elasticsearch.pdphilip.com/aggregations)
183+
- [Aggregations](https://elasticsearch.pdphilip.com/aggregation)
67184
- [Chunking](https://elasticsearch.pdphilip.com/chunking)
185+
- [Nested Queries](https://elasticsearch.pdphilip.com/nested-queries) **New in Version 3**
68186
- [Elasticsearch Specific Queries](https://elasticsearch.pdphilip.com/es-specific)
69187
- [Full-Text Search](https://elasticsearch.pdphilip.com/full-text-search)
70188
- [Dynamic Indices](https://elasticsearch.pdphilip.com/dynamic-indices)

src/Connection.php

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ class Connection extends BaseConnection
1717
protected $index;
1818
protected $maxSize;
1919
protected $indexPrefix;
20-
20+
protected $retires = null; //null will use default
21+
protected $sslVerification = true;
22+
protected $elasticMetaHeader = true;
23+
protected $rebuild = false;
24+
protected $allowIdSort = false;
2125

2226
public function __construct(array $config)
2327
{
2428
$this->config = $config;
2529

26-
if (!empty($config['index_prefix'])) {
27-
$this->indexPrefix = $config['index_prefix'];
28-
}
30+
$this->setOptions($config);
2931

3032
$this->client = $this->buildConnection();
3133

@@ -37,7 +39,26 @@ public function __construct(array $config)
3739

3840
}
3941

40-
public function getIndexPrefix(): string
42+
public function setOptions($config)
43+
{
44+
if (!empty($config['index_prefix'])) {
45+
$this->indexPrefix = $config['index_prefix'];
46+
}
47+
if (!empty($config['options']['allow_id_sort'])) {
48+
$this->allowIdSort = $config['options']['allow_id_sort'];
49+
}
50+
if (!empty($config['options']['ssl_verification'])) {
51+
$this->sslVerification = $config['options']['ssl_verification'];
52+
}
53+
if (!empty($config['options']['retires'])) {
54+
$this->retires = $config['options']['retires'];
55+
}
56+
if (!empty($config['options']['meta_header'])) {
57+
$this->elasticMetaHeader = $config['options']['meta_header'];
58+
}
59+
}
60+
61+
public function getIndexPrefix(): string|null
4162
{
4263
return $this->indexPrefix;
4364
}
@@ -48,7 +69,7 @@ public function setIndexPrefix($newPrefix): void
4869
}
4970

5071

51-
public function getTablePrefix(): string
72+
public function getTablePrefix(): string|null
5273
{
5374
return $this->getIndexPrefix();
5475
}
@@ -57,7 +78,7 @@ public function setIndex($index): string
5778
{
5879
$this->index = $index;
5980
if ($this->indexPrefix) {
60-
if (!(strpos($this->index, $this->indexPrefix.'_') !== false)) {
81+
if (!(str_contains($this->index, $this->indexPrefix.'_'))) {
6182
$this->index = $this->indexPrefix.'_'.$index;
6283
}
6384
}
@@ -138,6 +159,26 @@ protected function getDefaultSchemaGrammar()
138159
return new Schema\Grammar();
139160
}
140161

162+
public function rebuildConnection()
163+
{
164+
$this->rebuild = true;
165+
}
166+
167+
public function getClient()
168+
{
169+
return $this->client;
170+
}
171+
172+
public function getMaxSize()
173+
{
174+
return $this->maxSize;
175+
}
176+
177+
public function getAllowIdSort()
178+
{
179+
return $this->allowIdSort;
180+
}
181+
141182

142183
//----------------------------------------------------------------------
143184
// Connection Builder
@@ -160,13 +201,15 @@ protected function _httpConnection(): Client
160201
$hosts = config('database.connections.elasticsearch.hosts') ?? null;
161202
$username = config('database.connections.elasticsearch.username') ?? null;
162203
$pass = config('database.connections.elasticsearch.password') ?? null;
163-
$certPath = config('database.connections.elasticsearch.ssl_cert') ?? null;
204+
$apiId = config('database.connections.elasticsearch.api_id') ?? null;
205+
$apiKey = config('database.connections.elasticsearch.api_key') ?? null;
164206
$cb = ClientBuilder::create()->setHosts($hosts);
207+
$cb = $this->_builderOptions($cb);
165208
if ($username && $pass) {
166-
$cb->setBasicAuthentication($username, $pass)->build();
209+
$cb->setBasicAuthentication($username, $pass);
167210
}
168-
if ($certPath) {
169-
$cb->setCABundle($certPath);
211+
if ($apiKey) {
212+
$cb->setApiKey($apiKey, $apiId);
170213
}
171214

172215
return $cb->build();
@@ -179,20 +222,45 @@ protected function _cloudConnection(): Client
179222
$pass = config('database.connections.elasticsearch.password') ?? null;
180223
$apiId = config('database.connections.elasticsearch.api_id') ?? null;
181224
$apiKey = config('database.connections.elasticsearch.api_key') ?? null;
182-
$certPath = config('database.connections.elasticsearch.ssl_cert') ?? null;
225+
183226
$cb = ClientBuilder::create()->setElasticCloudId($cloudId);
184-
if ($apiId && $apiKey) {
185-
$cb->setApiKey($apiKey, $apiId)->build();
186-
} elseif ($username && $pass) {
187-
$cb->setBasicAuthentication($username, $pass)->build();
227+
$cb = $this->_builderOptions($cb);
228+
if ($username && $pass) {
229+
$cb->setBasicAuthentication($username, $pass);
188230
}
189-
if ($certPath) {
190-
$cb->setSSLVerification($certPath);
231+
if ($apiKey) {
232+
$cb->setApiKey($apiKey, $apiId);
191233
}
192234

235+
193236
return $cb->build();
194237
}
195238

239+
protected function _builderOptions($cb)
240+
{
241+
$cb->setSSLVerification($this->sslVerification);
242+
$cb->setElasticMetaHeader($this->elasticMetaHeader);
243+
if ($this->retires) {
244+
$cb->setRetries($this->retires);
245+
}
246+
$caBundle = config('database.connections.elasticsearch.ssl_cert') ?? null;
247+
if ($caBundle) {
248+
$cb->setCABundle($caBundle);
249+
}
250+
$sslCert = config('database.connections.elasticsearch.ssl.cert') ?? null;
251+
$sslCertPassword = config('database.connections.elasticsearch.ssl.cert_password') ?? null;
252+
$sslKey = config('database.connections.elasticsearch.ssl.key') ?? null;
253+
$sslKeyPassword = config('database.connections.elasticsearch.ssl.key_password') ?? null;
254+
if ($sslCert) {
255+
$cb->setSSLCert($sslCert, $sslCertPassword);
256+
}
257+
if ($sslKey) {
258+
$cb->setSSLKey($sslKey, $sslKeyPassword);
259+
}
260+
261+
return $cb;
262+
}
263+
196264

197265
//----------------------------------------------------------------------
198266
// Dynamic call routing to DSL bridge
@@ -203,8 +271,11 @@ public function __call($method, $parameters)
203271
if (!$this->index) {
204272
$this->index = $this->indexPrefix.'*';
205273
}
206-
207-
$bridge = new Bridge($this->client, $this->index, $this->maxSize, $this->indexPrefix);
274+
if ($this->rebuild) {
275+
$this->client = $this->buildConnection();
276+
$this->rebuild = false;
277+
}
278+
$bridge = new Bridge($this);
208279

209280
return $bridge->{'process'.Str::studly($method)}(...$parameters);
210281
}

0 commit comments

Comments
 (0)