Skip to content
This repository has been archived by the owner on Nov 21, 2022. It is now read-only.

Commit

Permalink
add factory for Elasticsearch clients (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
mimmi20 authored May 18, 2022
1 parent 17e187e commit 3ceaf2a
Show file tree
Hide file tree
Showing 19 changed files with 1,399 additions and 14 deletions.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
},
"require-dev": {
"actived/microsoft-teams-notifier": "^1.2.0",
"aws/aws-sdk-php": "^3.222.12",
"aws/aws-sdk-php": "^3.222.15",
"bartlett/monolog-callbackfilterhandler": "^2.0.0",
"cmdisp/monolog-microsoft-teams": "^1.2.0",
"doctrine/couchdb": "1.0.0-beta4",
"elasticsearch/elasticsearch": "^v7.17.0 || ^v8.2.0",
"graylog2/gelf-php": "^1.7.1",
"guzzlehttp/guzzle": "^7.4.2",
"guzzlehttp/psr7": "^2.2.0",
"guzzlehttp/psr7": "^2.2.1",
"jk/monolog-request-header-processor": "^1.0.0",
"laminas/laminas-config": "^3.7.0",
"laminas/laminas-dependency-plugin": "^2.2.0",
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ parameters:
- '~Parameter #2 \$channelToActionLevel of class Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy constructor expects~'
- '~overridden property~'
- '~Elastic\\Elasticsearch\\Client(Builder)?~'
- '~Elastic\\\\Elasticsearch\\\\Client~'
- '~deprecated class Monolog\\Handler\\SwiftMailerHandler~'
105 changes: 105 additions & 0 deletions src/Client/ElasticsearchV7Factory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php
/**
* This file is part of the mimmi20/monolog-laminas-factory package.
*
* Copyright (c) 2021-2022, Thomas Mueller <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types = 1);

namespace Mimmi20\LoggerFactory\Client;

use Elasticsearch\Client as V7Client;
use Elasticsearch\ClientBuilder;
use Elasticsearch\Common\Exceptions\AuthenticationConfigException;
use Interop\Container\Exception\ContainerException;
use Laminas\ServiceManager\Exception\ServiceNotCreatedException;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Monolog\Logger;
use Psr\Container\ContainerInterface;

use function array_filter;
use function array_key_exists;
use function assert;
use function is_array;
use function is_string;

/**
* @phpstan-import-type Level from Logger
* @phpstan-import-type LevelName from Logger
*/
final class ElasticsearchV7Factory implements FactoryInterface
{
/**
* @param string $requestedName
* @param array<string, (int|array|bool|string)>|null $options
* @phpstan-param array{hosts?: bool|array<int|string|array{host?: string|int, port?: int|numeric-string, scheme?: string, path?: string, user?: string, pass?: string}>, retries?: int, api-id?: string, api-key?: string, username?: string, password?: string, metadata?: bool}|null $options
*
* @throws ServiceNotCreatedException if an exception is raised when creating a service
* @throws ContainerException if any other error occurs
* @throws AuthenticationConfigException
*
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): V7Client
{
if (!is_array($options)) {
throw new ServiceNotCreatedException('Options must be an Array');
}

if (!array_key_exists('hosts', $options)) {
throw new ServiceNotCreatedException('No Hosts provided');
}

if (!is_array($options['hosts'])) {
throw new ServiceNotCreatedException('No Host data provided');
}

$metadata = true;

$builder = ClientBuilder::create();
$builder->setHosts(
array_filter(
$options['hosts'],
/**
* @param array|int|string $host
*/
static function ($host): bool {
if (is_string($host)) {
return true;
}

return is_array($host) && array_key_exists('host', $host);
}
)
);

if (array_key_exists('retries', $options)) {
$builder->setRetries($options['retries']);
}

if (array_key_exists('api-id', $options) && array_key_exists('api-key', $options)) {
assert(is_string($options['api-id']));
assert(is_string($options['api-key']));

$builder->setApiKey($options['api-id'], $options['api-key']);
} elseif (array_key_exists('username', $options) && array_key_exists('password', $options)) {
assert(is_string($options['username']));
assert(is_string($options['password']));

$builder->setBasicAuthentication($options['username'], $options['password']);
}

if (array_key_exists('metadata', $options)) {
$metadata = (bool) $options['metadata'];
}

$builder->setElasticMetaHeader($metadata);

return $builder->build();
}
}
97 changes: 97 additions & 0 deletions src/Client/ElasticsearchV8Factory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php
/**
* This file is part of the mimmi20/monolog-laminas-factory package.
*
* Copyright (c) 2021-2022, Thomas Mueller <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types = 1);

namespace Mimmi20\LoggerFactory\Client;

use Elastic\Elasticsearch\Client as V8Client;
use Elastic\Elasticsearch\ClientBuilder;
use Interop\Container\Exception\ContainerException;
use Laminas\ServiceManager\Exception\ServiceNotCreatedException;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Monolog\Logger;
use Psr\Container\ContainerInterface;

use function array_filter;
use function array_key_exists;
use function assert;
use function is_array;
use function is_string;

/**
* @phpstan-import-type Level from Logger
* @phpstan-import-type LevelName from Logger
*/
final class ElasticsearchV8Factory implements FactoryInterface
{
/**
* @param string $requestedName
* @param array<string, (int|array<string>|bool|string)>|null $options
* @phpstan-param array{hosts?: bool|array<string>, retries?: int, api-id?: string, api-key?: string, username?: string, password?: string, metadata?: bool}|null $options
*
* @throws ServiceNotCreatedException if an exception is raised when creating a service
* @throws ContainerException if any other error occurs
*
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): V8Client
{
if (!is_array($options)) {
throw new ServiceNotCreatedException('Options must be an Array');
}

if (!array_key_exists('hosts', $options)) {
throw new ServiceNotCreatedException('No Hosts provided');
}

if (!is_array($options['hosts'])) {
throw new ServiceNotCreatedException('No Host data provided');
}

$metadata = true;

$builder = ClientBuilder::create();
$builder->setHosts(
array_filter(
$options['hosts'],
/**
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
*/
static fn (string $host): bool => true
)
);

if (array_key_exists('retries', $options)) {
$builder->setRetries($options['retries']);
}

if (array_key_exists('api-id', $options) && array_key_exists('api-key', $options)) {
assert(is_string($options['api-id']));
assert(is_string($options['api-key']));

$builder->setApiKey($options['api-key'], $options['api-id']);
} elseif (array_key_exists('username', $options) && array_key_exists('password', $options)) {
assert(is_string($options['username']));
assert(is_string($options['password']));

$builder->setBasicAuthentication($options['username'], $options['password']);
}

if (array_key_exists('metadata', $options)) {
$metadata = (bool) $options['metadata'];
}

$builder->setElasticMetaHeader($metadata);

return $builder->build();
}
}
26 changes: 26 additions & 0 deletions src/ClientPluginManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/**
* This file is part of the mimmi20/monolog-laminas-factory package.
*
* Copyright (c) 2021-2022, Thomas Mueller <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types = 1);

namespace Mimmi20\LoggerFactory;

use Laminas\ServiceManager\AbstractPluginManager;

final class ClientPluginManager extends AbstractPluginManager
{
/**
* Allow many processors of the same type (v3)
*
* @var bool
* @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint
*/
protected $sharedByDefault = false;
}
71 changes: 71 additions & 0 deletions src/ClientPluginManagerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php
/**
* This file is part of the mimmi20/monolog-laminas-factory package.
*
* Copyright (c) 2021-2022, Thomas Mueller <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types = 1);

namespace Mimmi20\LoggerFactory;

use Interop\Container\Exception\ContainerException;
use Laminas\ServiceManager\Config;
use Laminas\ServiceManager\Exception\ServiceNotFoundException;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;

use function assert;
use function is_array;
use function sprintf;

final class ClientPluginManagerFactory implements FactoryInterface
{
/**
* @param string $requestedName
* @param array<mixed>|null $options
*
* @throws ServiceNotFoundException if unable to resolve the service
* @throws ContainerException if any other error occurs
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
*/
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): ClientPluginManager
{
$pluginManager = new ClientPluginManager($container, $options ?: []);

// If this is in a laminas-mvc application, the ServiceListener will inject
// merged configuration during bootstrap.
if ($container->has('ServiceListener')) {
return $pluginManager;
}

// If we do not have a config service, nothing more to do
if (!$container->has('config')) {
return $pluginManager;
}

try {
$config = $container->get('config');
} catch (ContainerExceptionInterface $e) {
throw new ServiceNotFoundException(sprintf('Could not find service %s', 'config'), 0, $e);
}

assert(is_array($config));

// If we do not have client configuration, nothing more to do
if (!isset($config['monolog_service_clients']) || !is_array($config['monolog_service_clients'])) {
return $pluginManager;
}

// Wire service configuration for client
(new Config($config['monolog_service_clients']))->configureServiceManager($pluginManager);

return $pluginManager;
}
}
26 changes: 26 additions & 0 deletions src/ClientProviderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/**
* This file is part of the mimmi20/monolog-laminas-factory package.
*
* Copyright (c) 2021-2022, Thomas Mueller <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types = 1);

namespace Mimmi20\LoggerFactory;

use Laminas\ServiceManager\Config;

interface ClientProviderInterface
{
/**
* Expected to return \Laminas\ServiceManager\Config object or array to
* seed such an object.
*
* @return array<string, array<int, string>>|Config
*/
public function getMonologClientConfig();
}
Loading

0 comments on commit 3ceaf2a

Please sign in to comment.