Skip to content

Commit 9c5c0d3

Browse files
committed
feat(connection): enhance Elasticsearch connection handling and validation
- Added `_sanitizeConfig` to merge config with default settings. - Implemented `_validateConnection` to verify required config parameters based on `auth_type`. - Combined HTTP and Cloud connection setups into `buildConnection` method. - Updated visibility and type hints for class properties and methods. - Introduced new tests for connection handling, disconnect, and validation scenarios. Making connections so smooth, Arnold would be proud! 💪
1 parent 49e8a06 commit 9c5c0d3

File tree

2 files changed

+160
-82
lines changed

2 files changed

+160
-82
lines changed

src/Connection.php

Lines changed: 115 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use PDPhilip\Elasticsearch\DSL\Results;
1313
use RuntimeException;
1414

15+
use function array_replace_recursive;
16+
1517
/**
1618
* @method bool indexModify(array $settings)
1719
* @method bool indexCreate(array $settings = [])
@@ -46,7 +48,10 @@ class Connection extends BaseConnection
4648
{
4749
const VALID_AUTH_TYPES = ['http', 'cloud'];
4850

49-
protected Client $client;
51+
/**
52+
* The Elasticsearch connection handler.
53+
*/
54+
protected ?Client $client;
5055

5156
protected string $index = '';
5257

@@ -81,7 +86,10 @@ public function __construct(array $config)
8186

8287
$this->config = $config;
8388

84-
$this->setOptions($config);
89+
$this->_sanitizeConfig();
90+
$this->_validateConnection();
91+
92+
$this->setOptions();
8593

8694
$this->client = $this->buildConnection();
8795

@@ -92,46 +100,31 @@ public function __construct(array $config)
92100
$this->useDefaultQueryGrammar();
93101
}
94102

95-
public function setOptions($config): void
103+
public function setOptions(): void
96104
{
97-
$this->indexPrefix = $config['index_prefix'] ?? '';
105+
$this->indexPrefix = $this->config['index_prefix'] ?? '';
98106

99-
if (isset($config['options']['allow_id_sort'])) {
100-
$this->allowIdSort = $config['options']['allow_id_sort'];
107+
if (isset($this->config['options']['allow_id_sort'])) {
108+
$this->allowIdSort = $this->config['options']['allow_id_sort'];
101109
}
102-
if (isset($config['options']['ssl_verification'])) {
103-
$this->sslVerification = $config['options']['ssl_verification'];
110+
if (isset($this->config['options']['ssl_verification'])) {
111+
$this->sslVerification = $this->config['options']['ssl_verification'];
104112
}
105-
if (! empty($config['options']['retires'])) {
106-
$this->retires = $config['options']['retires'];
113+
if (! empty($this->config['options']['retires'])) {
114+
$this->retires = $this->config['options']['retires'];
107115
}
108-
if (isset($config['options']['meta_header'])) {
109-
$this->elasticMetaHeader = $config['options']['meta_header'];
116+
if (isset($this->config['options']['meta_header'])) {
117+
$this->elasticMetaHeader = $this->config['options']['meta_header'];
110118
}
111119

112-
if (! empty($config['error_log_index'])) {
120+
if (! empty($this->config['error_log_index'])) {
113121
$this->errorLoggingIndex = $this->indexPrefix
114-
? $this->indexPrefix.'_'.$config['error_log_index']
115-
: $config['error_log_index'];
122+
? $this->indexPrefix.'_'.$this->config['error_log_index']
123+
: $this->config['error_log_index'];
116124
}
117125

118126
}
119127

120-
protected function buildConnection(): Client
121-
{
122-
$type = strtolower(config('database.connections.elasticsearch.auth_type', ''));
123-
$this->validateAuthType($type);
124-
125-
return $this->{'_'.$type.'Connection'}();
126-
}
127-
128-
private function validateAuthType(string $type): void
129-
{
130-
if (! in_array($type, self::VALID_AUTH_TYPES)) {
131-
throw new RuntimeException('Invalid [auth_type] in database config. Must be: http, cloud or api');
132-
}
133-
}
134-
135128
/** {@inheritdoc} */
136129
public function getTablePrefix(): ?string
137130
{
@@ -197,7 +190,7 @@ public function getSchemaBuilder(): Schema\Builder
197190
/** {@inheritdoc} */
198191
public function disconnect(): void
199192
{
200-
unset($this->connection);
193+
$this->client = null;
201194
}
202195

203196
/** {@inheritdoc} */
@@ -211,7 +204,7 @@ public function rebuildConnection(): void
211204
$this->rebuild = true;
212205
}
213206

214-
public function getClient(): Client
207+
public function getClient(): ?Client
215208
{
216209
return $this->client;
217210
}
@@ -251,10 +244,6 @@ protected function getDefaultPostProcessor(): Query\Processor
251244
return new Query\Processor;
252245
}
253246

254-
//----------------------------------------------------------------------
255-
// Connection Builder
256-
//----------------------------------------------------------------------
257-
258247
/** {@inheritdoc} */
259248
protected function getDefaultQueryGrammar(): Query\Grammar
260249
{
@@ -267,25 +256,95 @@ protected function getDefaultSchemaGrammar(): Schema\Grammar
267256
return new Schema\Grammar;
268257
}
269258

270-
protected function _httpConnection(): Client
259+
/**
260+
* Sanitizes the configuration array by merging it with a predefined array of default configuration settings.
261+
* This ensures that all required configuration keys exist, even if they are set to null or default values.
262+
*/
263+
private function _sanitizeConfig(): void
271264
{
272-
$hosts = config('database.connections.'.$this->connectionName.'.hosts') ?? null;
273-
$username = config('database.connections.'.$this->connectionName.'.username') ?? null;
274-
$pass = config('database.connections.'.$this->connectionName.'.password') ?? null;
275-
$apiId = config('database.connections.'.$this->connectionName.'.api_id') ?? null;
276-
$apiKey = config('database.connections.'.$this->connectionName.'.api_key') ?? null;
277-
$cb = ClientBuilder::create()->setHosts($hosts);
265+
266+
$this->config = array_replace_recursive(
267+
[
268+
'name' => null,
269+
'auth_type' => '',
270+
'cloud_id' => null,
271+
'hosts' => [],
272+
'username' => null,
273+
'password' => null,
274+
'api_key' => null,
275+
'api_id' => null,
276+
'index_prefix' => null,
277+
'ssl_cert' => null,
278+
'options' => [
279+
'allow_id_sort' => null,
280+
'ssl_verification' => null,
281+
'retires' => null,
282+
'error_log_index' => null,
283+
'meta_header' => null,
284+
],
285+
'ssl' => [
286+
'key' => null,
287+
'password' => null,
288+
'cert' => null,
289+
'cert_password' => null,
290+
],
291+
],
292+
$this->config
293+
);
294+
295+
$this->config['auth_type'] = strtolower($this->config['auth_type']);
296+
297+
}
298+
299+
//----------------------------------------------------------------------
300+
// Connection Builder
301+
//----------------------------------------------------------------------
302+
303+
protected function buildConnection(): Client
304+
{
305+
306+
$this->_validateConnection();
307+
308+
$cb = ClientBuilder::create();
309+
310+
// Set the connection type
311+
if ($this->config['auth_type'] === 'http') {
312+
$cb = $cb->setHosts($this->config['hosts']);
313+
} else {
314+
$cb = $cb->setElasticCloudId($this->config['cloud_id']);
315+
}
316+
317+
// Set Builder options
278318
$cb = $this->_builderOptions($cb);
279-
if ($username && $pass) {
280-
$cb->setBasicAuthentication($username, $pass);
319+
320+
// Set Authentication
321+
if ($this->config['username'] && $this->config['password']) {
322+
$cb->setBasicAuthentication($this->config['username'], $this->config['password']);
281323
}
282-
if ($apiKey) {
283-
$cb->setApiKey($apiKey, $apiId);
324+
325+
if ($this->config['api_key']) {
326+
$cb->setApiKey($this->config['api_key'], $this->config['api_id']);
284327
}
285328

286329
return $cb->build();
287330
}
288331

332+
private function _validateConnection(): void
333+
{
334+
if (! in_array($this->config['auth_type'], self::VALID_AUTH_TYPES)) {
335+
throw new RuntimeException('Invalid [auth_type] in database config. Must be: http or cloud');
336+
}
337+
338+
if ($this->config['auth_type'] === 'cloud' && ! $this->config['cloud_id']) {
339+
throw new RuntimeException('auth_type of `cloud` requires `cloud_id` to be set');
340+
}
341+
342+
if ($this->config['auth_type'] === 'http' && ! $this->config['hosts']) {
343+
throw new RuntimeException('auth_type of `http` requires `hosts` to be set');
344+
}
345+
346+
}
347+
289348
protected function _builderOptions($cb)
290349
{
291350
$cb->setSSLVerification($this->sslVerification);
@@ -296,45 +355,19 @@ protected function _builderOptions($cb)
296355
if (isset($this->retires)) {
297356
$cb->setRetries($this->retires);
298357
}
299-
$caBundle = config('database.connections.'.$this->connectionName.'.ssl_cert') ?? null;
300-
if ($caBundle) {
301-
$cb->setCABundle($caBundle);
302-
}
303-
$sslCert = config('database.connections.'.$this->connectionName.'.ssl.cert') ?? null;
304-
$sslCertPassword = config('database.connections.'.$this->connectionName.'.ssl.cert_password') ?? null;
305-
$sslKey = config('database.connections.'.$this->connectionName.'.ssl.key') ?? null;
306-
$sslKeyPassword = config('database.connections.'.$this->connectionName.'.ssl.key_password') ?? null;
307-
if ($sslCert) {
308-
$cb->setSSLCert($sslCert, $sslCertPassword);
309-
}
310-
if ($sslKey) {
311-
$cb->setSSLKey($sslKey, $sslKeyPassword);
312-
}
313-
314-
return $cb;
315-
}
316358

317-
//----------------------------------------------------------------------
318-
// Dynamic call routing to DSL bridge
319-
//----------------------------------------------------------------------
320-
321-
protected function _cloudConnection(): Client
322-
{
323-
$cloudId = config('database.connections.'.$this->connectionName.'.cloud_id') ?? null;
324-
$username = config('database.connections.'.$this->connectionName.'.username') ?? null;
325-
$pass = config('database.connections.'.$this->connectionName.'.password') ?? null;
326-
$apiId = config('database.connections.'.$this->connectionName.'.api_id') ?? null;
327-
$apiKey = config('database.connections.'.$this->connectionName.'.api_key') ?? null;
359+
if ($this->config['ssl_cert']) {
360+
$cb->setCABundle($this->config['ssl_cert']);
361+
}
328362

329-
$cb = ClientBuilder::create()->setElasticCloudId($cloudId);
330-
$cb = $this->_builderOptions($cb);
331-
if ($username && $pass) {
332-
$cb->setBasicAuthentication($username, $pass);
363+
if ($this->config['ssl']['cert']) {
364+
$cb->setSSLCert($this->config['ssl']['cert'], $this->config['ssl']['cert_password']);
333365
}
334-
if ($apiKey) {
335-
$cb->setApiKey($apiKey, $apiId);
366+
367+
if ($this->config['ssl']['key']) {
368+
$cb->setSSLKey($this->config['ssl']['key'], $this->config['ssl']['key_password']);
336369
}
337370

338-
return $cb->build();
371+
return $cb;
339372
}
340373
}

tests/ConnectionTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Support\Facades\DB;
66
use PDPhilip\Elasticsearch\Connection;
77
use PDPhilip\Elasticsearch\Schema\Builder as SchemaBuilder;
8+
use Elastic\Elasticsearch\Client;
89

910
test('Connection', function () {
1011
$connection = DB::connection('elasticsearch');
@@ -25,6 +26,50 @@
2526
expect(spl_object_hash($c1) !== spl_object_hash($c2))->toBeTrue();
2627
});
2728

29+
test('Disconnect And Create New Connection', function () {
30+
$connection = DB::connection('elasticsearch');
31+
expect($connection)->toBeInstanceOf(Connection::class);
32+
$client = $connection->getClient();
33+
expect($client)->toBeInstanceOf(Client::class);
34+
35+
$connection->disconnect();
36+
$client = $connection->getClient();
37+
$this->assertNull($client);
38+
DB::purge('elasticsearch');
39+
40+
$connection = DB::connection('elasticsearch');
41+
expect($connection)->toBeInstanceOf(Connection::class);
42+
$client = $connection->getClient();
43+
expect($client)->toBeInstanceOf(Client::class);
44+
45+
});
46+
47+
test('DB', function () {
48+
$connection = DB::connection('elasticsearch');
49+
$this->assertInstanceOf(Client::class, $connection->getClient());
50+
});
51+
52+
test('Connection Without auth_type', function () {
53+
$this->expectException(RuntimeException::class);
54+
$this->expectExceptionMessage('Invalid [auth_type] in database config. Must be: http or cloud');
55+
56+
new Connection(['name' => 'test']);
57+
});
58+
59+
test('Cloud Connection Without cloud_id', function () {
60+
$this->expectException(RuntimeException::class);
61+
$this->expectExceptionMessage('auth_type of `cloud` requires `cloud_id` to be set');
62+
63+
new Connection(['name' => 'test', 'auth_type' => 'cloud']);
64+
});
65+
66+
test('Http Connection Without hosts', function () {
67+
$this->expectException(RuntimeException::class);
68+
$this->expectExceptionMessage('auth_type of `http` requires `hosts` to be set');
69+
70+
new Connection(['name' => 'test', 'auth_type' => 'http']);
71+
});
72+
2873
test('Schema Builder', function () {
2974
$schema = DB::connection('elasticsearch')->getSchemaBuilder();
3075
expect($schema)->toBeInstanceOf(SchemaBuilder::class);

0 commit comments

Comments
 (0)