1<?php
2
3namespace Elastica;
4
5use Elastica\Exception\InvalidException;
6use Nyholm\Dsn\Configuration\Url;
7use Nyholm\Dsn\DsnParser;
8use Nyholm\Dsn\Exception\ExceptionInterface as DsnException;
9use Nyholm\Dsn\Exception\FunctionNotSupportedException;
10
11/**
12 * Elastica client configuration.
13 *
14 * @author Antoine Lamirault <lamiraultantoine@gmail.com>
15 */
16class ClientConfiguration
17{
18    /**
19     * Config with defaults.
20     *
21     * retryOnConflict: Use in \Elastica\Client::updateDocument
22     * bigintConversion: Set to true to enable the JSON bigint to string conversion option (see issue #717)
23     *
24     * @var array
25     */
26    protected $configuration = [
27        'host' => null,
28        'port' => null,
29        'path' => null,
30        'url' => null,
31        'proxy' => null,
32        'transport' => null,
33        'persistent' => true,
34        'timeout' => null,
35        'connections' => [], // host, port, path, transport, compression, persistent, timeout, username, password, auth_type, config -> (curl, headers, url)
36        'roundRobin' => false,
37        'retryOnConflict' => 0,
38        'bigintConversion' => false,
39        'username' => null,
40        'password' => null,
41        'auth_type' => null, // basic, digest, gssnegotiate, ntlm
42    ];
43
44    /**
45     * Create configuration.
46     *
47     * @param array $config Additional config
48     *
49     * @return ClientConfiguration
50     */
51    public static function fromArray(array $config): self
52    {
53        $clientConfiguration = new static();
54        foreach ($config as $key => $value) {
55            $clientConfiguration->set($key, $value);
56        }
57
58        return $clientConfiguration;
59    }
60
61    /**
62     * Create configuration from Dsn string. Example of valid DSN strings:
63     * - http://localhost
64     * - http://foo:bar@localhost:1234?timeout=4&persistant=false
65     * - pool(http://127.0.0.1 http://127.0.0.2/bar?timeout=4).
66     *
67     * @return ClientConfiguration
68     */
69    public static function fromDsn(string $dsnString): self
70    {
71        try {
72            $func = DsnParser::parseFunc($dsnString);
73        } catch (DsnException $e) {
74            throw new InvalidException(\sprintf('DSN "%s" is invalid.', $dsnString), 0, $e);
75        }
76
77        if ('dsn' === $func->getName()) {
78            /** @var Url $dsn */
79            $dsn = $func->first();
80            $clientConfiguration = self::fromArray(self::parseDsn($dsn));
81        } elseif ('pool' === $func->getName()) {
82            $connections = [];
83            $clientConfiguration = new static();
84            /** @var Url $arg */
85            foreach ($func->getArguments() as $arg) {
86                $connections[] = self::parseDsn($arg);
87            }
88            $clientConfiguration->set('connections', $connections);
89        } else {
90            throw new FunctionNotSupportedException($dsnString, $func->getName());
91        }
92
93        foreach ($func->getParameters() as $optionName => $optionValue) {
94            if ('false' === $optionValue) {
95                $optionValue = false;
96            } elseif ('true' === $optionValue) {
97                $optionValue = true;
98            } elseif (\is_numeric($optionValue)) {
99                $optionValue = (int) $optionValue;
100            }
101
102            $clientConfiguration->set($optionName, $optionValue);
103        }
104
105        return $clientConfiguration;
106    }
107
108    /**
109     * Returns a specific config key or the whole config array if not set.
110     *
111     * @throws InvalidException if the given key is not found in the configuration
112     *
113     * @return mixed Config value
114     */
115    public function get(string $key)
116    {
117        if ('' === $key) {
118            return $this->configuration;
119        }
120
121        if (!$this->has($key)) {
122            throw new InvalidException('Config key is not set: '.$key);
123        }
124
125        return $this->configuration[$key];
126    }
127
128    /**
129     * Returns boolean indicates if configuration has key.
130     */
131    public function has(string $key): bool
132    {
133        return \array_key_exists($key, $this->configuration);
134    }
135
136    /**
137     * Return all configuration.
138     */
139    public function getAll(): array
140    {
141        return $this->configuration;
142    }
143
144    /**
145     * @param string $key   Key to set
146     * @param mixed  $value Value
147     */
148    public function set(string $key, $value): void
149    {
150        $this->configuration[$key] = $value;
151    }
152
153    /**
154     * Add value to a key. If original value is not an array, value is wrapped.
155     *
156     * @param string $key   Key to add
157     * @param mixed  $value Value
158     */
159    public function add(string $key, $value): void
160    {
161        if (!\array_key_exists($key, $this->configuration)) {
162            $this->configuration[$key] = [$value];
163        } else {
164            if (\is_array($this->configuration[$key])) {
165                $this->configuration[$key][] = $value;
166            } else {
167                $this->configuration[$key] = [$this->configuration[$key], $value];
168            }
169        }
170    }
171
172    private static function parseDsn(Url $dsn): array
173    {
174        $data = ['host' => $dsn->getHost()];
175
176        if (null !== $dsn->getScheme()) {
177            $data['transport'] = $dsn->getScheme();
178        }
179
180        if (null !== $dsn->getUser()) {
181            $data['username'] = $dsn->getUser();
182        }
183
184        if (null !== $dsn->getPassword()) {
185            $data['password'] = $dsn->getPassword();
186        }
187
188        if (null !== $dsn->getUser() && null !== $dsn->getPassword()) {
189            $data['auth_type'] = 'basic';
190        }
191
192        if (null !== $dsn->getPort()) {
193            $data['port'] = $dsn->getPort();
194        }
195
196        if (null !== $dsn->getPath()) {
197            $data['path'] = $dsn->getPath();
198        }
199
200        foreach ($dsn->getParameters() as $optionName => $optionValue) {
201            if ('false' === $optionValue) {
202                $optionValue = false;
203            } elseif ('true' === $optionValue) {
204                $optionValue = true;
205            } elseif (\is_numeric($optionValue)) {
206                $optionValue = (int) $optionValue;
207            }
208
209            $data[$optionName] = $optionValue;
210        }
211
212        return $data;
213    }
214}
215