AnonSec Shell
Server IP : 172.67.157.199  /  Your IP : 3.143.25.80   [ Reverse IP ]
Web Server : Apache
System : Linux b70eb322-3aee-0c53-7c82-0db91281f2c6.secureserver.net 6.1.90-1.el9.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 2 12:09:22 EDT 2024 x86_64
User : root ( 0)
PHP Version : 8.0.30.2
Disable Function : NONE
Domains : 0 Domains
MySQL : ON  |  cURL : ON  |  WGET : ON  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /var/www/wp-content/mu-plugins/object-cache-pro/src/Configuration/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ HOME ]     [ BACKUP SHELL ]     [ JUMPING ]     [ MASS DEFACE ]     [ SCAN ROOT ]     [ SYMLINK ]     

Current File : /var/www/wp-content/mu-plugins/object-cache-pro/src/Configuration/Configuration.php
<?php
/**
 * Copyright © 2019-2024 Rhubarb Tech Inc. All Rights Reserved.
 *
 * The Object Cache Pro Software and its related materials are property and confidential
 * information of Rhubarb Tech Inc. Any reproduction, use, distribution, or exploitation
 * of the Object Cache Pro Software and its related materials, in whole or in part,
 * is strictly forbidden unless prior permission is obtained from Rhubarb Tech Inc.
 *
 * In addition, any reproduction, use, distribution, or exploitation of the Object Cache Pro
 * Software and its related materials, in whole or in part, is subject to the End-User License
 * Agreement accessible in the included `LICENSE` file, or at: https://objectcache.pro/eula
 */

declare(strict_types=1);

namespace RedisCachePro\Configuration;

use Throwable;
use BadMethodCallException;

use RedisCachePro\Loggers\Logger;
use RedisCachePro\Loggers\ArrayLogger;
use RedisCachePro\Loggers\CallbackLogger;
use RedisCachePro\Loggers\ErrorLogLogger;
use RedisCachePro\Loggers\LoggerInterface;

use RedisCachePro\Exceptions\ConfigurationInvalidException;
use RedisCachePro\Exceptions\ConnectionDetailsMissingException;

use RedisCachePro\Connectors\PhpRedisConnector;
use RedisCachePro\Connectors\ConnectorInterface;

use RedisCachePro\ObjectCaches\PhpRedisObjectCache;
use RedisCachePro\ObjectCaches\ObjectCacheInterface;

/**
 * @property-read ?string $token
 * @property-read class-string<\RedisCachePro\Connectors\ConnectorInterface> $connector
 * @property-read class-string<\RedisCachePro\ObjectCaches\ObjectCacheInterface> $cache
 * @property-read string|callable|null $tracer
 * @property-read \RedisCachePro\Loggers\LoggerInterface $logger
 * @property-read ?array $log_levels
 * @property-read string $scheme
 * @property-read string $host
 * @property-read int $port
 * @property-read int $database
 * @property-read ?string $username
 * @property-read ?string $password
 * @property-read string|array|null $cluster
 * @property-read string $cluster_failover
 * @property-read array $servers
 * @property-read string $replication_strategy
 * @property-read array $sentinels
 * @property-read ?string $service
 * @property-read ?string $prefix
 * @property-read int $maxttl
 * @property-read float $timeout
 * @property-read float $read_timeout
 * @property-read int $retry_interval
 * @property-read int $retries
 * @property-read string $backoff
 * @property-read bool $persistent
 * @property-read ?bool $shared
 * @property-read bool $async_flush
 * @property-read string $group_flush
 * @property-read string $network_flush
 * @property-read bool $prefetch
 * @property-read bool $split_alloptions
 * @property-read string $serializer
 * @property-read string $compression
 * @property-read array $global_groups
 * @property-read array $non_persistent_groups
 * @property-read array $non_prefetchable_groups
 * @property-read array $tls_options
 * @property-read \RedisCachePro\Support\AnalyticsConfiguration $analytics
 * @property-read \RedisCachePro\Support\RelayConfiguration $relay
 * @property-read bool $updates
 * @property-read bool $save_commands
 * @property-read bool $debug
 * @property-read bool $strict
 * @property-read ?\Throwable $initException
 */
final class Configuration
{
    use Concerns\Cluster,
        Concerns\Sentinel,
        Concerns\Replication;

    /**
     * Serialize data using PHP's serialize/unserialize functions.
     *
     * @var string
     */
    const SERIALIZER_PHP = 'php';

    /**
     * Serialize data using igbinary.
     *
     * @var string
     */
    const SERIALIZER_IGBINARY = 'igbinary';

    /**
     * Don't compress data.
     *
     * @var string
     */
    const COMPRESSION_NONE = 'none';

    /**
     * Compress data using the LZF compression algorithm.
     *
     * @var string
     */
    const COMPRESSION_LZF = 'lzf';

    /**
     * Compress data using the LZ4 compression algorithm.
     *
     * @var string
     */
    const COMPRESSION_LZ4 = 'lz4';

    /**
     * Compress data using the Zstandard compression algorithm.
     *
     * @var string
     */
    const COMPRESSION_ZSTD = 'zstd';

    /**
     * Selectively flush only the current site's data.
     *
     * @var string
     */
    const NETWORK_FLUSH_SITE = 'site';

    /**
     * Selectively flush only the current site's data as well as global groups.
     *
     * @var string
     */
    const NETWORK_FLUSH_GLOBAL = 'global';

    /**
     * Always flush all data.
     *
     * @var string
     */
    const NETWORK_FLUSH_ALL = 'all';

    /**
     * Flush groups/patterns atomically using a Lua script that uses `SCAN` and deletes in chunks.
     *
     * @var string
     */
    const GROUP_FLUSH_SCAN = 'scan';

    /**
     * Flush groups/patterns atomically using a Lua script that uses `KEYS` and deletes in chunks.
     *
     * @var string
     */
    const GROUP_FLUSH_KEYS = 'keys';

    /**
     * Flush groups/patterns non-atomically using PHP that calls `SCAN` and deletes in chunks.
     * Useful for massive datasets that complain about slow Lua scripts.
     *
     * @var string
     */
    const GROUP_FLUSH_INCREMENTAL = 'incremental';

    /**
     * The smart backoff algorithm uses decorrelated jitter with `retry_interval`
     * as base for backoff computation and `read_timeout` as backoff time cap.
     *
     * @var string
     */
    const BACKOFF_SMART = 'smart';

    /**
     * No backoff.
     *
     * @var string
     */
    const BACKOFF_NONE = 'none';

    /**
     * New Relic tracer.
     *
     * @var string
     */
    const TRACER_NEWRELIC = 'newrelic';

    /**
     * New Relic tracer.
     *
     * @var string
     */
    const TRACER_OPENTELEMETRY = 'opentelemetry';

    /**
     * No tracer.
     *
     * @var string
     */
    const TRACER_NONE = 'none';

    /**
     * Values considered truthy.
     *
     * @var array<mixed>
     */
    const Truthy = [true, 1, '1', 'true', 'on', 'yes'];

    /**
     * Values considered falsy.
     *
     * @var array<mixed>
     */
    const Falsy = [false, 0, '0', 'false', 'off', 'no'];

    /**
     * The Object Cache Pro license token.
     *
     * @var ?string
     */
    protected $token;

    /**
     * The connector class name.
     *
     * @var class-string
     */
    protected $connector = PhpRedisConnector::class;

    /**
     * The object cache class name.
     *
     * @var class-string
     */
    protected $cache = PhpRedisObjectCache::class;

    /**
     * The client name.
     *
     * @internal Used only internally, not a configuration option.
     * @var string
     */
    protected $clientName;

    /**
     * The logger class name.
     *
     * @var \RedisCachePro\Loggers\LoggerInterface|null
     */
    protected $logger;

    /**
     * The log levels.
     *
     * @var array<string>|null
     */
    protected $log_levels = [
        Logger::EMERGENCY,
        Logger::ALERT,
        Logger::CRITICAL,
        Logger::ERROR,
        Logger::WARNING,
    ];

    /**
     * The protocol scheme.
     *
     * @var string
     */
    protected $scheme = 'tcp';

    /**
     * The instance hostname.
     *
     * @var string
     */
    protected $host;

    /**
     * The instance port.
     *
     * @var int
     */
    protected $port;

    /**
     * The database.
     *
     * @var int
     */
    protected $database = 0;

    /**
     * The connection's username (Redis 6+).
     *
     * @var ?string
     */
    protected $username;

    /**
     * The connection's password.
     *
     * @var ?string
     */
    protected $password;

    /**
     * The key prefix.
     *
     * @var ?string
     */
    protected $prefix;

    /**
     * The maximum time-to-live in seconds.
     *
     * @var int
     */
    protected $maxttl;

    /**
     * Connection timeout in seconds.
     *
     * @var float
     */
    protected $timeout = 2.5;

    /**
     * Read timeout in seconds.
     *
     * @var float
     */
    protected $read_timeout = 2.5;

    /**
     * Retry interval in milliseconds.
     *
     * @var int
     */
    protected $retry_interval = 25;

    /**
     * The amount of retries.
     *
     * @var int
     */
    protected $retries = 3;

    /**
     * The backoff algorithm.
     *
     * @var string
     */
    protected $backoff = self::BACKOFF_SMART;

    /**
     * Whether the connection is persistent.
     *
     * @var bool
     */
    protected $persistent = false;

    /**
     * Whether the Redis server/cluster is shared between multiple apps,
     * or dedicated to a single WordPress installation.
     *
     * This affects how memory and key counts are displayed.
     *
     * @var ?bool
     */
    protected $shared = null;

    /**
     * Whether flushing is asynchronous.
     *
     * @var bool
     */
    protected $async_flush = false;

    /**
     * Method of flushing groups and patterns.
     *
     * @var string
     */
    protected $group_flush = self::GROUP_FLUSH_KEYS;

    /**
     * The cache flushing strategy in multisite network environments.
     *
     * @var string
     */
    protected $network_flush = self::NETWORK_FLUSH_ALL;

    /**
     * The tracer name.
     *
     * @var string|callable|null
     */
    protected $tracer;

    /**
     * The data serializer.
     *
     * @var string
     */
    protected $serializer = self::SERIALIZER_PHP;

    /**
     * The data compression format.
     *
     * @var string
     */
    protected $compression = self::COMPRESSION_NONE;

    /**
     * The list of global cache groups that are not blog-specific in a network environment.
     *
     * @var array<string>
     */
    protected $global_groups;

    /**
     * The non-persistent groups that will only be cached for the duration of a request.
     *
     * @var array<string>
     */
    protected $non_persistent_groups;

    /**
     * The non-prefetchable groups that will not be prefetched.
     *
     * @var array<string>
     */
    protected $non_prefetchable_groups;

    /**
     * Whether debug mode is enabled.
     *
     * @var bool
     */
    protected $debug = false;

    /**
     * Whether all executed commands should be logged.
     *
     * @var bool
     */
    protected $save_commands = false;

    /**
     * Whether to prefetch keys for requests.
     *
     * @var bool
     */
    protected $prefetch = false;

    /**
     * Whether to enable plugin updates.
     *
     * @var bool
     */
    protected $updates = true;

    /**
     * The analytics configuration.
     *
     * - `enabled`: (bool) Whether to collect and display analytics
     * - `persist`: (bool) Whether to restore analytics data after cache flushes
     * - `retention`: (int) The number of seconds to keep analytics before purging them
     * - `sample_rate`: (float) The sample rate for analytics in the range of 0 to 100
     * - `footnote`: (bool) Whether to print a HTML comment with non-sensitive metrics
     *
     * @var \RedisCachePro\Support\AnalyticsConfiguration
     */
    protected $analytics = [
        'enabled' => true,
        'persist' => true,
        'retention' => 60 * 60 * 2,
        'sample_rate' => 100,
        'footnote' => true,
    ];

    /**
     * The Relay configuration options.
     *
     * - `cache`: (bool) Whether to use Relay's in-memory cache
     * - `listeners`: (bool) Whether to register Relay event listeners
     * - `invalidations`: (bool) Whether to enable client-side invalidation
     * - `allowed`: (array) When set, only keys matching these patterns will be cached in Relay's in-memory cache, unless they match `relay.ignored`.
     * - `ignored`: (array) Keys matching these patterns will not be cached in Relay's in-memory cache.
     *
     * @var \RedisCachePro\Support\RelayConfiguration
     */
    protected $relay = [
        'cache' => true,
        'listeners' => false,
        'invalidations' => true,
        'allowed' => null,
        'ignored' => ['*:analytics:*'],
    ];

    /**
     * Whether the `alloptions` key should be split into individual keys and stored in a hash.
     *
     * @var bool
     */
    protected $split_alloptions = false;

    /**
     * The TLS context options, such as `verify_peer` and `ciphers`.
     *
     * @link https://www.php.net/manual/context.ssl.php
     *
     * @var array<mixed>
     */
    protected $tls_options;

    /**
     * Whether to enable strict mode.
     *
     * @var bool
     */
    protected $strict = true;

    /**
     * Holds the exception thrown during instantiation.
     *
     * @see \RedisCachePro\Configuration\Configuration::safelyFrom()
     *
     * @var ?\Throwable
     */
    private $initException;

    /**
     * Initialize a new configuration instance.
     *
     * @return self
     */
    public function init()
    {
        $this->relay = (object) $this->relay;
        $this->analytics = (object) $this->analytics;

        if (! $this->logger) {
            $this->logger = $this->debug || $this->save_commands
                ? new ArrayLogger
                : new ErrorLogLogger;
        }

        if ($this->log_levels && method_exists($this->logger, 'setLevels')) {
            $this->logger->setLevels($this->log_levels);
        }

        if (! $this->tracer) {
            $useNewRelic = \function_exists('\newrelic_record_datastore_segment')
                && \ini_get('newrelic.enabled');

            $this->setTracer($useNewRelic ? self::TRACER_NEWRELIC : self::TRACER_NONE);
        }

        return $this;
    }

    /**
     * Validate the configuration.
     *
     * @return self
     */
    public function validate()
    {
        $hasHost = ! empty($this->host);
        $hasPort = ! empty($this->port);

        $isInstance = $hasHost && $hasPort;
        $isSocket = $hasHost && $this->host[0] === '/';
        $isCluster = ! empty($this->cluster);
        $isReplicated = ! empty($this->servers);
        $isSentinel = ! empty($this->sentinels) && ! empty($this->service);

        if (! $isInstance && ! $isSocket && ! $isCluster && ! $isReplicated && ! $isSentinel) {
            throw new ConnectionDetailsMissingException;
        }

        return $this;
    }

    /**
     * Create a new configuration instance from given data.
     *
     * @param  mixed  $config
     * @return self
     */
    public static function from($config): self
    {
        if (\is_array($config)) {
            return static::fromArray($config)->init();
        }

        throw new ConfigurationInvalidException(
            \sprintf('Invalid config format: %s', \gettype($config))
        );
    }

    /**
     * Create a new configuration instance from given data,
     * and fallback to empty instance instead of throwing exceptions.
     *
     * @param  mixed  $config
     * @return self
     */
    public static function safelyFrom($config): self
    {
        try {
            return static::from($config);
        } catch (Throwable $exception) {
            $instance = static::fromArray([
                'client' => false,
            ])->init();

            $instance->initException = $exception;

            return $instance;
        }
    }

    /**
     * Create a new configuration instance from an array.
     *
     * @param  array<mixed>  $array
     * @return self
     */
    protected static function fromArray(array $array): self
    {
        $config = new static;

        // determine and set the client first, it's used by other options
        $client = $config->determineClient($array['client'] ?? null);
        $array = ['client' => $client] + $array;

        // determine strict mode dynamically
        $array['strict'] = $config->determineStrictMode($array);

        foreach ($array as $name => $value) {
            $method = \str_replace('_', ' ', \strtolower($name));
            $method = \str_replace(' ', '', \ucwords($method));

            $config->{"set{$method}"}($value);
        }

        return $config;
    }

    /**
     * Set the license token.
     *
     * @param  string  $token
     * @return void
     */
    public function setToken($token)
    {
        if (\is_null($token)) {
            $this->token = null;

            return;
        }

        if (! \is_string($token) || strlen($token) !== 60) {
            throw new ConfigurationInvalidException('License `token` must be a 60 characters long string');
        }

        $this->token = (string) $token;
    }

    /**
     * Set the connector and cache using client name.
     *
     * @param  string|false  $client
     * @return void
     */
    public function setClient($client)
    {
        if ($client === false) {
            return;
        }

        if (! \is_string($client) || empty($client)) {
            throw new ConfigurationInvalidException('`client` must be a string');
        }

        $client = \str_replace(
            ['phpredis', 'relay'],
            ['PhpRedis', 'Relay'],
            \strtolower($client)
        );

        if (! \in_array($client, ['PhpRedis', 'Relay'])) {
            throw new ConfigurationInvalidException("Client `{$client}` is not supported");
        }

        $cache = "RedisCachePro\ObjectCaches\\{$client}ObjectCache";
        $connector = "RedisCachePro\Connectors\\{$client}Connector";

        if (! \class_exists($cache) || ! \class_exists($connector)) {
            throw new ConfigurationInvalidException("Class {$cache}|{$connector} does not exist");
        }

        $this->cache = $cache;
        $this->connector = $connector;

        $this->connector::boot();
        $this->clientName = $client;
    }

    /**
     * Set the connector instance.
     *
     * @param  class-string<\RedisCachePro\Connectors\ConnectorInterface>|string  $connector
     * @return void
     */
    public function setConnector($connector)
    {
        if (! \is_string($connector) || empty($connector)) {
            throw new ConfigurationInvalidException('`connector` must be a fully qualified class name');
        }

        if (! \class_exists($connector)) {
            throw new ConfigurationInvalidException("`connector` class `{$connector}` was not found");
        }

        if (! \in_array(ConnectorInterface::class, (array) \class_implements($connector))) {
            throw new ConfigurationInvalidException(
                \sprintf('`connector` must be implementation of %s', ConnectorInterface::class)
            );
        }

        $this->connector = $connector;
    }

    /**
     * Set the object cache instance.
     *
     * @param  class-string<\RedisCachePro\ObjectCaches\ObjectCacheInterface>  $cache
     * @return void
     */
    public function setCache($cache)
    {
        if (! \is_string($cache) || empty($cache)) {
            throw new ConfigurationInvalidException('`cache` must be a fully qualified class name');
        }

        if (! \class_exists($cache)) {
            throw new ConfigurationInvalidException("`cache` class `{$cache}` was not found");
        }

        if (! \in_array(ObjectCacheInterface::class, (array) \class_implements($cache))) {
            throw new ConfigurationInvalidException(
                \sprintf('`cache` must be implementation of %s', ObjectCacheInterface::class)
            );
        }

        $this->cache = $cache;
    }

    /**
     * Set the tracer.
     *
     * @param  string|callable|null  $tracer
     * @return void
     */
    public function setTracer($tracer)
    {
        if (is_callable($tracer)) {
            $this->tracer = $tracer;

            return;
        }

        if (! \is_string($tracer) || empty($tracer)) {
            throw new ConfigurationInvalidException('`tracer` must be a callable or non-empty-string');
        }

        $tracer = \str_replace('new-relic', self::TRACER_NEWRELIC, \strtolower($tracer));
        $tracer = \str_replace(['open-telemetry', 'otel'], self::TRACER_OPENTELEMETRY, $tracer);

        $tracers = [
            self::TRACER_NONE,
            self::TRACER_NEWRELIC,
            self::TRACER_OPENTELEMETRY,
        ];

        if (! \in_array($tracer, $tracers, true)) {
            throw new ConfigurationInvalidException("Tracer `{$tracer}` is not supported");
        }

        if ($tracer === self::TRACER_NEWRELIC && ! \function_exists('\newrelic_record_datastore_segment')) {
            throw new ConfigurationInvalidException('The `newrelic` tracer requires New Relic’s PHP extension');
        }

        $this->tracer = $tracer;
    }

    /**
     * Set the logger instance.
     *
     * @param  callable|class-string<\RedisCachePro\Loggers\LoggerInterface>  $logger
     * @return void
     */
    public function setLogger($logger)
    {
        if (! \is_callable($logger) && (\is_string($logger) && ! \class_exists($logger))) {
            throw new ConfigurationInvalidException(
                \sprintf('`logger` must be a FQCN or `callable`, %s given', \gettype($logger))
            );
        }

        try {
            $instance = \is_callable($logger)
                ? new CallbackLogger($logger)
                : new $logger;
        } catch (Throwable $exception) {
            throw new ConfigurationInvalidException(
                \sprintf('Could not instantiate logger: %s', $exception->getMessage())
            );
        }

        if (! \in_array(LoggerInterface::class, (array) \class_implements($instance))) {
            throw new ConfigurationInvalidException(
                \sprintf('Logger must be implementation of %s', LoggerInterface::class)
            );
        }

        $this->logger = $instance;
    }

    /**
     * Set the log levels.
     *
     * @param  array<string>  $levels
     * @return void
     */
    public function setLogLevels($levels)
    {
        if (\is_null($levels)) {
            $this->log_levels = null;

            return;
        }

        if (! \is_array($levels)) {
            throw new ConfigurationInvalidException(
                \sprintf('`log_levels` must be an array, %s given', \gettype($levels))
            );
        }

        $levels = \array_filter($levels);

        if (empty($levels)) {
            throw new ConfigurationInvalidException('`log_levels` must be a non-empty array');
        }

        foreach ($levels as $level) {
            if (! \defined(\sprintf('%s::%s', Logger::class, \strtoupper($level)))) {
                throw new ConfigurationInvalidException("Invalid log level: {$level}");
            }
        }

        $this->log_levels = \array_values($levels);
    }

    /**
     * Set the instance scheme, host, port, username, password and database using URL.
     *
     * @param  string  $url
     * @return void
     */
    public function setUrl($url)
    {
        $components = static::parseUrl($url);

        $this->setHost($components['host']);

        if ($components['database']) {
            $this->setDatabase($components['database']);
        }

        if ($components['scheme']) {
            $this->setScheme($components['scheme']);
        }

        if ($components['port']) {
            $this->setPort($components['port']);
        }

        if ($components['username']) {
            $this->setUsername($components['username']);
        }

        if ($components['password']) {
            $this->setPassword($components['password']);
        }
    }

    /**
     * Set the connection protocol.
     *
     * @param  string  $scheme
     * @return void
     */
    public function setScheme($scheme)
    {
        if (! \is_string($scheme)) {
            throw new ConfigurationInvalidException('`scheme` must be a string');
        }

        $scheme = \str_replace(
            ['://', 'rediss', 'redis'],
            ['', 'tls', 'tcp'],
            \strtolower($scheme)
        );

        if (! \in_array($scheme, ['tcp', 'tls', 'unix'], true)) {
            throw new ConfigurationInvalidException("Scheme `{$scheme}` is not supported");
        }

        $this->scheme = $scheme;
    }

    /**
     * Set the instance host (and scheme if specified).
     *
     * @param  string  $host
     * @return void
     */
    public function setHost($host)
    {
        if (! \is_string($host) || empty($host)) {
            throw new ConfigurationInvalidException('`host` must be a non-empty string');
        }

        $host = \strtolower((string) $host);

        if (strpos($host, '://') !== false) {
            $this->setScheme((string) strstr($host, '://', true));
            $host = substr((string) strstr($host, '://'), 3);
        }

        if ($host[0] === '/') {
            $this->setScheme('unix');
        }

        $this->host = $host;
    }

    /**
     * Alias of `setHost()`.
     *
     * @param  string  $path
     * @return void
     */
    public function setPath($path)
    {
        if (! \is_string($path) || empty($path)) {
            throw new ConfigurationInvalidException('`path` must be a non-empty string');
        }

        $this->setHost($path);
    }

    /**
     * Alias of `setHost()`.
     *
     * @param  string  $socket
     * @return void
     */
    public function setSocket($socket)
    {
        if (! \is_string($socket) || empty($socket)) {
            throw new ConfigurationInvalidException('`socket` must be a non-empty string');
        }

        $this->setHost($socket);
    }

    /**
     * Set the instance port.
     *
     * @param  int|string  $port
     * @return void
     */
    public function setPort($port)
    {
        if (\is_string($port) && \filter_var($port, FILTER_VALIDATE_INT) !== false) {
            $port = (int) $port;
        }

        if (! \is_int($port)) {
            throw new ConfigurationInvalidException(
                \sprintf('`port` must be an integer, %s given', \gettype($port))
            );
        }

        $this->port = (int) $port;
    }

    /**
     * Set the database number.
     *
     * @param  int|string  $database
     * @return void
     */
    public function setDatabase($database)
    {
        if (\is_string($database) && \filter_var($database, FILTER_VALIDATE_INT) !== false) {
            $database = (int) $database;
        }

        if (! \is_int($database)) {
            throw new ConfigurationInvalidException(
                \sprintf('`database` must be an integer, %s given', \gettype($database))
            );
        }

        $this->database = (int) $database;
    }

    /**
     * Set the instance/cluster username (Redis 6+).
     *
     * @param  string  $username
     * @return void
     */
    public function setUsername($username)
    {
        if (\is_null($username)) {
            $this->username = null;

            return;
        }

        if (empty($username)) {
            throw new ConfigurationInvalidException('`username` must be a non-empty string');
        }

        $this->username = (string) $username;
    }

    /**
     * Set the instance/cluster password.
     *
     * @param  string  $password
     * @return void
     */
    public function setPassword($password)
    {
        if (\is_null($password)) {
            $this->password = null;

            return;
        }

        if (empty($password)) {
            throw new ConfigurationInvalidException('`password` must be a non-empty string');
        }

        $this->password = (string) $password;
    }

    /**
     * Set the prefix for all keys.
     *
     * @param  string  $prefix
     * @return void
     */
    public function setPrefix($prefix)
    {
        $prefix = (string) $prefix;

        if (\strlen($prefix) > 32) {
            throw new ConfigurationInvalidException('`prefix` must be 32 characters or less and should be human-readable, not WordPress salts');
        }

        $prefix = \preg_replace('/[^\w-]/i', '', $prefix);
        $prefix = \trim($prefix, '_-:$');
        $prefix = \strtolower($prefix);

        $this->prefix = $prefix ?: null;
    }

    /**
     * Set the  maximum time-to-live in seconds.
     *
     * @param  int|string  $seconds
     * @return void
     */
    public function setMaxttl($seconds)
    {
        if (\is_string($seconds) && \filter_var($seconds, FILTER_VALIDATE_INT) !== false) {
            $seconds = (int) $seconds;
        }

        if (! \is_int($seconds)) {
            throw new ConfigurationInvalidException(
                \sprintf('`maxttl` must be an integer, %s given', \gettype($seconds))
            );
        }

        if ($seconds < 0) {
            throw new ConfigurationInvalidException('`maxttl` must be `0` (forever) or a positive integer (seconds)');
        }

        $this->maxttl = $seconds;
    }

    /**
     * Set the connection timeout in seconds.
     *
     * @param  float|int  $seconds
     * @return void
     */
    public function setTimeout($seconds)
    {
        if (\is_int($seconds)) {
            $seconds = (float) $seconds;
        }

        if (\is_string($seconds) && $seconds == (float) $seconds) {
            $seconds = (float) $seconds;
        }

        if (! is_float($seconds)) {
            throw new ConfigurationInvalidException(
                \sprintf('`timeout` must be a float, %s given', \gettype($seconds))
            );
        }

        if ($seconds < 0) {
            throw new ConfigurationInvalidException('`timeout` must be `0.0` (infinite) or a positive float (seconds)');
        }

        $this->timeout = (float) $seconds;
    }

    /**
     * Set the read timeout in seconds.
     *
     * @param  float|int  $seconds
     * @return void
     */
    public function setReadTimeout($seconds)
    {
        if (\is_int($seconds)) {
            $seconds = (float) $seconds;
        }

        if (\is_string($seconds) && $seconds == (float) $seconds) {
            $seconds = (float) $seconds;
        }

        if (! \is_float($seconds)) {
            throw new ConfigurationInvalidException(
                \sprintf('`read_timeout` must be a float, %s given', \gettype($seconds))
            );
        }

        if ($seconds < 0) {
            throw new ConfigurationInvalidException(
                '`read_timeout` must be `0.0` (infinite) or a positive float (seconds).'
            );
        }

        $this->read_timeout = (float) $seconds;
    }

    /**
     * Set the retry interval in milliseconds.
     *
     * @param  int|string  $milliseconds
     * @return void
     */
    public function setRetryInterval($milliseconds)
    {
        if (\is_string($milliseconds) && \filter_var($milliseconds, FILTER_VALIDATE_INT) !== false) {
            $milliseconds = (int) $milliseconds;
        }

        if (! \is_int($milliseconds)) {
            throw new ConfigurationInvalidException(
                \sprintf('`retry_interval` must be an integer, %s given', \gettype($milliseconds))
            );
        }

        if ($milliseconds < 0) {
            throw new ConfigurationInvalidException(
                '`retry_interval` must be `0` (instant) or a positive float (milliseconds).'
            );
        }

        $this->retry_interval = (int) $milliseconds;
    }

    /**
     * Set whether the connection is persistent.
     *
     * @param  bool  $is_persistent
     * @return void
     */
    public function setPersistent($is_persistent)
    {
        if (! \is_bool($is_persistent)) {
            throw new ConfigurationInvalidException(
                \sprintf('`persistent` must be a boolean, %s given', \gettype($is_persistent))
            );
        }

        $this->persistent = (bool) $is_persistent;
    }

    /**
     * Set whether the Redis server/cluster is shared or dedicated.
     *
     * @param  bool  $shared
     * @return void
     */
    public function setShared($shared)
    {
        if (! \is_bool($shared)) {
            throw new ConfigurationInvalidException(
                \sprintf('`shared` must be a boolean, %s given', \gettype($shared))
            );
        }

        $this->shared = (bool) $shared;
    }

    /**
     * Set whether flushing is asynchronous.
     *
     * @param  bool  $async
     * @return void
     */
    public function setAsyncFlush($async)
    {
        if (! \is_bool($async)) {
            throw new ConfigurationInvalidException(
                \sprintf('`async_flush` must be a boolean, %s given', \gettype($async))
            );
        }

        $this->async_flush = (bool) $async;
    }

    /**
     * Set how groups and patterns should be flushed.
     *
     * @param  string  $method
     * @return void
     */
    public function setGroupFlush($method)
    {
        if (! \is_string($method)) {
            throw new ConfigurationInvalidException(
                \sprintf('`group_flush` method must be a string, %s given', \gettype($method))
            );
        }

        $constant = \sprintf(
            '%s::GROUP_FLUSH_%s',
            self::class,
            \strtoupper((string) $method)
        );

        $method = \strtolower((string) $method);

        if (! \defined($constant)) {
            throw new ConfigurationInvalidException("`group_flush` method `{$method}` is not supported");
        }

        $this->group_flush = $method;
    }

    /**
     * Set the multisite network environment cache flushing strategy.
     *
     * @param  string  $strategy
     * @return void
     */
    public function setNetworkFlush($strategy)
    {
        if (! \is_string($strategy)) {
            throw new ConfigurationInvalidException(
                \sprintf('`network_flush` strategy must be a string, %s given', \gettype($strategy))
            );
        }

        $constant = \sprintf(
            '%s::NETWORK_FLUSH_%s',
            self::class,
            \strtoupper((string) $strategy)
        );

        $strategy = \strtolower((string) $strategy);

        if (! \defined($constant)) {
            throw new ConfigurationInvalidException("`network_flush` strategy `{$strategy}` is not supported");
        }

        $this->network_flush = $strategy;
    }

    /**
     * Shim to support deprecated `flush_network` option.
     *
     * @param  string  $strategy
     * @return void
     */
    public function setFlushNetwork($strategy)
    {
        $this->setNetworkFlush($strategy);
    }

    /**
     * Set the data serializer.
     *
     * @param  string  $serializer
     * @return void
     */
    public function setSerializer($serializer)
    {
        $constant = \sprintf(
            '%s::SERIALIZER_%s',
            self::class,
            \strtoupper((string) $serializer)
        );

        $serializer = \strtolower((string) $serializer);

        if ($serializer === self::SERIALIZER_IGBINARY && ! $this->connector::supports($serializer)) {
            throw new ConfigurationInvalidException("{$this->clientName} was not compiled with igbinary support. 'For more information about enabling serializers see: https://objectcache.pro/docs/data-encoding/'");
        }

        if (! \defined("\\{$constant}")) {
            throw new ConfigurationInvalidException("Serializer `{$serializer}` is not supported");
        }

        $this->serializer = $serializer;
    }

    /**
     * Set the data compression format.
     *
     * @param  string  $compression
     * @return void
     */
    public function setCompression($compression)
    {
        $constant = \sprintf(
            '%s::COMPRESSION_%s',
            self::class,
            \strtoupper((string) $compression)
        );

        $compression = \strtolower((string) $compression);

        $linkToDocs = 'For more information about enabling compressions see: https://objectcache.pro/docs/data-encoding/';

        if ($compression === self::COMPRESSION_LZF && ! $this->connector::supports($compression)) {
            throw new ConfigurationInvalidException("{$this->clientName} was not compiled with LZF compression support, see {$linkToDocs}");
        }

        if ($compression === self::COMPRESSION_LZ4 && ! $this->connector::supports($compression)) {
            throw new ConfigurationInvalidException("{$this->clientName} was not compiled with LZ4 compression support, see {$linkToDocs}");
        }

        if ($compression === self::COMPRESSION_ZSTD && ! $this->connector::supports($compression)) {
            throw new ConfigurationInvalidException("{$this->clientName} was not compiled with Zstandard compression support, see {$linkToDocs}");
        }

        if (! \defined("\\{$constant}")) {
            throw new ConfigurationInvalidException("Compression format `{$compression}` is not supported, see {$linkToDocs}");
        }

        $this->compression = $compression;
    }

    /**
     * The list of global cache groups that are not blog-specific in a network environment.
     *
     * @param  array<string>  $groups
     * @return void
     */
    public function setGlobalGroups($groups)
    {
        if (! \is_array($groups)) {
            throw new ConfigurationInvalidException(
                \sprintf('`global_groups` must be an array, %s given', \gettype($groups))
            );
        }

        $this->global_groups = \array_unique(\array_values($groups));
    }

    /**
     * Set the non-persistent groups that will only be cached for the duration of a request.
     *
     * @param  array<string>  $groups
     * @return void
     */
    public function setNonPersistentGroups($groups)
    {
        if (! \is_array($groups)) {
            throw new ConfigurationInvalidException(
                \sprintf('`non_persistent_groups` must be an array, %s given', \gettype($groups))
            );
        }

        $this->non_persistent_groups = \array_unique(\array_values($groups));
    }

    /**
     * Set the non-prefetchable groups that will not be prefetched.
     *
     * @param  array<string>  $groups
     * @return void
     */
    public function setNonPrefetchableGroups($groups)
    {
        if (! \is_array($groups)) {
            throw new ConfigurationInvalidException(
                \sprintf('`non_prefetchable_groups` must be an array, %s given', \gettype($groups))
            );
        }

        $this->non_prefetchable_groups = \array_unique(\array_values($groups));
    }

    /**
     * Set whether debug mode is enabled.
     *
     * @param  bool  $debug
     * @return void
     */
    public function setDebug($debug)
    {
        if (\in_array($debug, self::Truthy, true)) {
            $debug = true;
        }

        if (\in_array($debug, self::Falsy, true)) {
            $debug = false;
        }

        if (! \is_bool($debug)) {
            throw new ConfigurationInvalidException(
                \sprintf('`debug` must be a boolean, %s given', \gettype($debug))
            );
        }

        $this->debug = (bool) $debug;
    }

    /**
     * Set whether to prefetch keys for requests.
     *
     * @param  bool  $prefetch
     * @return void
     */
    public function setPrefetch($prefetch)
    {
        if (\in_array($prefetch, self::Truthy, true)) {
            $prefetch = true;
        }

        if (\in_array($prefetch, self::Falsy, true)) {
            $prefetch = false;
        }

        if (! \is_bool($prefetch)) {
            throw new ConfigurationInvalidException(
                \sprintf('`prefetch` must be a boolean, %s given', \gettype($prefetch))
            );
        }

        $this->prefetch = (bool) $prefetch;
    }

    /**
     * Set whether to enable plugin updates.
     *
     * @param  bool  $updates
     * @return void
     */
    public function setUpdates($updates)
    {
        if (\in_array($updates, self::Truthy, true)) {
            $updates = true;
        }

        if (\in_array($updates, self::Falsy, true)) {
            $updates = false;
        }

        if (! \is_bool($updates)) {
            throw new ConfigurationInvalidException(
                \sprintf('`updates` must be a boolean, %s given', \gettype($updates))
            );
        }

        $this->updates = (bool) $updates;
    }

    /**
     * Set the analytics configuration.
     *
     * @param  array<mixed>|bool  $analytics
     * @return void
     */
    public function setAnalytics($analytics)
    {
        if (\in_array($analytics, self::Truthy, true)) {
            $analytics = true;
        }

        if (\in_array($analytics, self::Falsy, true)) {
            $analytics = false;
        }

        if (\is_bool($analytics)) {
            is_object($this->analytics)
                ? $this->analytics->enabled = $analytics
                : $this->analytics['enabled'] = $analytics;

            return;
        }

        if (! \is_array($analytics)) {
            throw new ConfigurationInvalidException(
                \sprintf('`analytics` must be an array, %s given', \gettype($analytics))
            );
        }

        foreach (\array_keys((array) $this->analytics) as $option) {
            if (! \array_key_exists($option, $analytics)) {
                continue;
            }

            switch ($option) {
                case 'retention':
                    $this->analytics[$option] = (int) $analytics[$option];
                    break;
                case 'sample_rate':
                    $this->analytics[$option] = (float) $analytics[$option];
                    break;
                default:
                    $this->analytics[$option] = (bool) $analytics[$option];
                    break;
            }
        }

        $this->analytics = (object) $this->analytics;
    }

    /**
     * Set the Relay configuration.
     *
     * @param  array<string, mixed>  $relay
     * @return void
     */
    public function setRelay($relay)
    {
        if (! \is_array($relay)) {
            throw new ConfigurationInvalidException(
                \sprintf('`relay` must be an array, %s given', \gettype($relay))
            );
        }

        $invalid = \array_diff_key($relay, (array) $this->relay);

        if (! empty($invalid)) {
            throw new ConfigurationInvalidException(
                \sprintf('`relay.%s` is not a valid configuration option', key(array_slice($invalid, 0, 1)))
            );
        }

        $this->relay = (object) $this->relay;

        foreach (\array_keys((array) $this->relay) as $option) {
            if (! \array_key_exists($option, $relay)) {
                continue;
            }

            if (\in_array($option, ['cache', 'listeners', 'invalidations'], true)) {
                if (\in_array($relay[$option], self::Truthy, true)) {
                    $relay[$option] = true;
                }

                if (\in_array($relay[$option], self::Falsy, true)) {
                    $relay[$option] = false;
                }

                if (! \is_bool($relay[$option])) {
                    throw new ConfigurationInvalidException(
                        \sprintf('`relay.%s` must be a boolean, %s given', $option, \gettype($relay[$option]))
                    );
                }

                $this->relay->{$option} = $relay[$option];
                continue;
            }

            if (\in_array($option, ['allowed', 'ignored'], true)) {
                if (! \is_null($relay[$option]) && ! \is_array($relay[$option])) {
                    throw new ConfigurationInvalidException(
                        \sprintf('`relay.%s` must be a array, %s given', $option, \gettype($relay[$option]))
                    );
                }

                $this->relay->{$option} = $relay[$option];
                continue;
            }
        }
    }

    /**
     * Set whether all executed commands should be logged.
     *
     * @param  bool  $save_commands
     * @return void
     */
    public function setSaveCommands($save_commands)
    {
        if (\in_array($save_commands, self::Truthy, true)) {
            $save_commands = true;
        }

        if (\in_array($save_commands, self::Falsy, true)) {
            $save_commands = false;
        }

        if (! \is_bool($save_commands)) {
            throw new ConfigurationInvalidException(
                \sprintf('`save_commands` must be a boolean, %s given', \gettype($save_commands))
            );
        }

        $this->save_commands = (bool) $save_commands;
    }

    /**
     * Set whether to store the `alloptions` key in a hash.
     *
     * @param  bool  $split_alloptions
     * @return void
     */
    public function setSplitAlloptions($split_alloptions)
    {
        if (\in_array($split_alloptions, self::Truthy, true)) {
            $split_alloptions = true;
        }

        if (\in_array($split_alloptions, self::Falsy, true)) {
            $split_alloptions = false;
        }

        if (! \is_bool($split_alloptions)) {
            throw new ConfigurationInvalidException(
                \sprintf('`split_alloptions` must be a boolean, %s given', \gettype($split_alloptions))
            );
        }

        $this->split_alloptions = (bool) $split_alloptions;
    }

    /**
     * Set the TLS context options, such as `verify_peer` and `ciphers`.
     *
     * @link https://www.php.net/manual/context.ssl.php
     *
     * @param  array<mixed>  $options
     * @return void
     */
    public function setTlsOptions($options)
    {
        if (! \is_array($options)) {
            throw new ConfigurationInvalidException(
                \sprintf('`tls_options` context must be an array, %s given', \gettype($options))
            );
        }

        if (empty($options)) {
            throw new ConfigurationInvalidException(
                '`tls_options` context must be a non-empty array'
            );
        }

        $this->tls_options = $options;
    }

    /**
     * Set the retries option.
     *
     * @param  int  $retries
     * @return void
     */
    public function setRetries($retries)
    {
        if (! \is_int($retries)) {
            throw new ConfigurationInvalidException(
                \sprintf('`retries` must be an integer, %s given', \gettype($retries))
            );
        }

        $this->retries = (int) $retries;
    }

    /**
     * Set the backoff algorithm.
     *
     * @param  string  $backoff
     * @return void
     */
    public function setBackoff($backoff)
    {
        if (! \is_string($backoff)) {
            throw new ConfigurationInvalidException(
                \sprintf('`backoff` must be a string, %s given', \gettype($backoff))
            );
        }

        if (! \in_array($backoff, [self::BACKOFF_NONE, self::BACKOFF_SMART])) {
            throw new ConfigurationInvalidException(
                \sprintf('Backoff `%s` is not supported', $backoff)
            );
        }

        $this->backoff = (string) $backoff;
    }

    /**
     * Determine the client that should be used.
     *
     * @param  mixed  $client
     * @return string
     */
    public function determineClient($client)
    {
        if ($client === false || ! empty($client)) {
            return $client;
        }

        if (
            \in_array(\getenv('OBJECTCACHE_PREFER_RELAY'), self::Truthy, true) &&
            \extension_loaded('relay')
        ) {
            return 'Relay';
        }

        return 'PhpRedis';
    }

    /**
     * Determine whether strict mode should be enabled.
     *
     * @param  array<string, mixed>  $config
     * @return bool
     */
    public function determineStrictMode($config)
    {
        $override = \getenv('OBJECTCACHE_STRICT');

        if (is_string($override) && strlen($override)) {
            var_dump($override);

            if (\in_array($override, self::Truthy, true)) {
                return true;
            }

            if (\in_array($override, self::Falsy, true)) {
                return false;
            }
        }

        if (isset($config['strict'])) {
            return $config['strict'];
        }

        if (! empty($config['prefix'])) {
            return false;
        }

        return $this->strict;
    }

    /**
     * Set whether strict mode is enabled.
     *
     * @param  bool  $strict
     * @return void
     */
    public function setStrict($strict)
    {
        if (\in_array($strict, ['true', 'on', '1', 1, true], true)) {
            $strict = true;
        }

        if (\in_array($strict, ['false', 'off', '0', 0, false], true)) {
            $strict = false;
        }

        if (! \is_bool($strict)) {
            throw new ConfigurationInvalidException(
                \sprintf('`strict` must be a boolean, %s given', \gettype($strict))
            );
        }

        $this->strict = (bool) $strict;
    }

    /**
     * Parse the given URL into Redis connection information.
     *
     * @param  string  $url
     * @return array
     * @return array<mixed>
     */
    public static function parseUrl($url)
    {
        $components = \parse_url((string) $url);

        if (! \is_array($components)) {
            throw new ConfigurationInvalidException("`url` is malformed and could not be parsed: `{$url}`");
        }

        if (! isset($components['host']) && isset($components['path'])) {
            $components['host'] = $components['path'];
            unset($components['path']);
        }

        if (empty($components['host'])) {
            throw new ConfigurationInvalidException("`url` is malformed and could not be parsed: `{$url}`");
        }

        $components = \array_map('rawurldecode', $components); // @phpstan-ignore-line

        if (! empty($components['scheme'])) {
            $components['scheme'] = \str_replace(['rediss', 'redis'], ['tls', 'tcp'], $components['scheme']);
        }

        if (\in_array($components['user'] ?? '', ['', 'h', 'default'])) {
            unset($components['user']);
        }

        $database = \trim($components['path'] ?? '', '/');
        unset($components['path']);

        if (! empty($database)) {
            $components['database'] = $database;
        }

        \parse_str($components['query'] ?? '', $query);
        unset($components['query']);

        /** @var array{database?: string, role?: string} $query */
        if (! empty($query['database'])) {
            $components['database'] = $query['database'];
        }

        if (! empty($query['role'])) {
            $components['role'] = \strtolower($query['role']);
        }

        return [
            'scheme' => \strtolower($components['scheme'] ?? 'tcp'),
            'host' => $components['host'],
            'port' => isset($components['port']) ? (int) $components['port'] : null,
            'username' => $components['user'] ?? null,
            'password' => $components['pass'] ?? null,
            'database' => (int) ($components['database'] ?? null),
            'role' => $components['role'] ?? null,
        ];
    }

    /**
     * Return configuration options.
     *
     * @param  string  $name
     * @return mixed
     */
    public function __get($name)
    {
        return $this->{$name};
    }

    /**
     * Handle calls to invalid configuration options.
     *
     * @param  string  $method
     * @param  array<mixed>  $arguments
     * @return void
     */
    public function __call(string $method, array $arguments)
    {
        if (\strpos($method, 'set') === 0 && \strlen($method) > 3) {
            $method = \strtolower(
                \preg_replace('/(?<!^)[A-Z]/', '_$0', \substr($method, 3))
            );

            \error_log("objectcache.warning: `{$method}` is not a valid config option");

            return;
        }

        throw new BadMethodCallException("Call to undefined method `Configuration::{$method}`");
    }

    /**
     * Return the configuration as array.
     *
     * @return array<string, mixed>
     */
    public function toArray()
    {
        return [
            'token' => $this->token,
            'connector' => $this->connector,
            'cache' => $this->cache,
            'logger' => $this->logger,
            'log_levels' => $this->log_levels,
            'scheme' => $this->scheme,
            'host' => $this->host,
            'port' => $this->port,
            'database' => $this->database,
            'username' => $this->username,
            'password' => $this->password,
            'prefix' => $this->prefix,
            'maxttl' => $this->maxttl,
            'timeout' => $this->timeout,
            'read_timeout' => $this->read_timeout,
            'retry_interval' => $this->retry_interval,
            'retries' => $this->retries,
            'backoff' => $this->backoff,
            'persistent' => $this->persistent,
            'shared' => $this->shared,
            'async_flush' => $this->async_flush,
            'group_flush' => $this->group_flush,
            'network_flush' => $this->network_flush,
            'cluster' => $this->cluster,
            'cluster_failover' => $this->cluster_failover,
            'servers' => $this->servers,
            'replication_strategy' => $this->replication_strategy,
            'sentinels' => $this->sentinels,
            'service' => $this->service,
            'tracer' => $this->tracer,
            'serializer' => $this->serializer,
            'compression' => $this->compression,
            'global_groups' => $this->global_groups,
            'non_persistent_groups' => $this->non_persistent_groups,
            'non_prefetchable_groups' => $this->non_prefetchable_groups,
            'prefetch' => $this->prefetch,
            'split_alloptions' => $this->split_alloptions,
            'analytics' => $this->analytics,
            'relay' => $this->relay,
            'tls_options' => $this->tls_options,
            'updates' => $this->updates,
            'debug' => $this->debug,
            'strict' => $this->strict,
            'save_commands' => $this->save_commands,
        ];
    }

    /**
     * Return the configuration as array for diagnostics.
     *
     * @return array<string, mixed>
     */
    public function diagnostics()
    {
        $config = $this->toArray();

        $encodeJson = static function ($value) {
            return \json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        };

        $formatter = static function ($name, $value) use ($encodeJson) {
            if (in_array($name, ['cluster', 'analytics', 'relay', 'tls_options'])) {
                return [$name, $encodeJson($value)];
            }

            if (in_array($name, ['maxttl', 'timeout', 'read_timeout']) && ! is_null($value)) {
                $value = $value + 0;

                $value = $value > 60
                    ? sprintf('%ss (%s)', $value, human_time_diff(time(), time() + $value))
                    : "{$value}s";

                return [$name, $value];
            }

            if (in_array($name, ['retry_interval']) && ! is_null($value)) {
                return [$name, "{$value}ms"];
            }

            if (\is_object($value)) {
                return [$name, \get_class($value)];
            }

            if (\is_array($value)) {
                return [$name, \implode(', ', $value)];
            }

            if (\is_string($value)) {
                return [$name, $value];
            }

            return [$name, $encodeJson($value)];
        };

        return array_column(array_map($formatter, array_keys($config), $config), 1, 0); // @phpstan-ignore-line
    }
}

Anon7 - 2022
AnonSec Team