1<?php
2
3declare(strict_types=1);
4
5namespace voku\cache;
6
7use voku\cache\Exception\InvalidArgumentException;
8
9class CacheAdapterAutoManager
10{
11    /**
12     * @var string[]
13     */
14    private $adapter = [];
15
16    /**
17     * @var callable[]|null[]
18     */
19    private $callableFunctions = [];
20
21    /**
22     * @param string        $adapter
23     * @param callable|null $callableFunction
24     *
25     * @throws InvalidArgumentException
26     *
27     * @return $this
28     */
29    public function addAdapter(
30        string $adapter,
31        callable $callableFunction = null
32    ): self
33    {
34        $this->validateAdapter($adapter);
35        $this->validateCallable($callableFunction);
36
37        $this->adapter[] = $adapter;
38        $this->callableFunctions[] = $callableFunction;
39
40        return $this;
41    }
42
43    /**
44     * @return \Generator|\Generator<string, callable>
45     */
46    public function getAdapters(): \Generator
47    {
48        foreach ($this->adapter as $key => $value) {
49            yield $this->adapter[$key] => $this->callableFunctions[$key];
50        }
51    }
52
53    /**
54     * @param self $adapterManager
55     *
56     * @throws InvalidArgumentException
57     *
58     * @return CacheAdapterAutoManager
59     */
60    public function merge(self $adapterManager): self
61    {
62        foreach ($adapterManager->getAdapters() as $adapterTmp => $callableFunctionTmp) {
63            $this->validateAdapter($adapterTmp);
64            $this->validateCallable($callableFunctionTmp);
65
66            $key = \array_search($adapterTmp, $this->adapter, true);
67
68            if ($key) {
69                $this->adapter[$key] = $adapterTmp;
70                $this->callableFunctions[$key] = $callableFunctionTmp;
71            } else {
72                $this->adapter[] = $adapterTmp;
73                $this->callableFunctions[] = $callableFunctionTmp;
74            }
75        }
76
77        return $this;
78    }
79
80    /**
81     * @param string $replaceAdapter
82     *
83     * @throws InvalidArgumentException
84     */
85    private function validateAdapter(string $replaceAdapter)
86    {
87        /** @noinspection PhpUnhandledExceptionInspection */
88        $interfaces = (new \ReflectionClass($replaceAdapter))->getInterfaces();
89        if (!\array_key_exists(iAdapter::class, $interfaces)) {
90            throw new InvalidArgumentException('"' . $replaceAdapter . '" did not implement the "iAdapter"-interface [' . \print_r($interfaces, true) . ']');
91        }
92    }
93
94    /**
95     * @param callable $callableFunction
96     *
97     * @throws InvalidArgumentException
98     */
99    private function validateCallable(callable $callableFunction = null)
100    {
101        if (
102            $callableFunction !== null
103            &&
104            !\is_callable($callableFunction)
105        ) {
106            throw new InvalidArgumentException('$callableFunction is not callable');
107        }
108    }
109
110    /**
111     * @return CacheAdapterAutoManager
112     */
113    public static function getDefaultsForAutoInit(): self
114    {
115        $cacheAdapterManager = new self();
116
117        /** @noinspection PhpUnhandledExceptionInspection */
118        $cacheAdapterManager->addAdapter(
119            AdapterMemcached::class,
120            static function () {
121                $memcached = null;
122                $isMemcachedAvailable = false;
123                if (\extension_loaded('memcached')) {
124                    /** @noinspection PhpComposerExtensionStubsInspection */
125                    $memcached = new \Memcached();
126                    /** @noinspection PhpUsageOfSilenceOperatorInspection */
127                    $isMemcachedAvailable = @$memcached->addServer('127.0.0.1', 11211);
128                }
129
130                if (!$isMemcachedAvailable) {
131                    $memcached = null;
132                }
133
134                return $memcached;
135            }
136        );
137
138        /** @noinspection PhpUnhandledExceptionInspection */
139        $cacheAdapterManager->addAdapter(
140            AdapterMemcache::class,
141            static function () {
142                $memcache = null;
143                $isMemcacheAvailable = false;
144                /** @noinspection ClassConstantCanBeUsedInspection */
145                if (\class_exists('\Memcache')) {
146                    /** @noinspection PhpComposerExtensionStubsInspection */
147                    $memcache = new \Memcache();
148                    /** @noinspection PhpUsageOfSilenceOperatorInspection */
149                    $isMemcacheAvailable = @$memcache->connect('127.0.0.1', 11211);
150                }
151
152                if (!$isMemcacheAvailable) {
153                    $memcache = null;
154                }
155
156                return $memcache;
157            }
158        );
159
160        /** @noinspection PhpUnhandledExceptionInspection */
161        $cacheAdapterManager->addAdapter(
162            AdapterPredis::class,
163            static function () {
164                $redis = null;
165                $isRedisAvailable = false;
166                if (
167                    \extension_loaded('redis')
168                    &&
169                    \class_exists('\Predis\Client')
170                ) {
171                    /** @noinspection PhpUndefinedNamespaceInspection */
172                    /** @noinspection PhpUndefinedClassInspection */
173                    $redis = new \Predis\Client(
174                        [
175                            'scheme'  => 'tcp',
176                            'host'    => '127.0.0.1',
177                            'port'    => 6379,
178                            'timeout' => '2.0',
179                        ]
180                    );
181
182                    try {
183                        /** @noinspection PhpUndefinedMethodInspection */
184                        $redis->connect();
185                        /** @noinspection PhpUndefinedMethodInspection */
186                        $isRedisAvailable = $redis->getConnection()->isConnected();
187                    } catch (\Exception $e) {
188                        // nothing
189                    }
190                }
191
192                if ($isRedisAvailable === false) {
193                    $redis = null;
194                }
195
196                return $redis;
197            }
198        );
199
200        /** @noinspection PhpUnhandledExceptionInspection */
201        $cacheAdapterManager->addAdapter(
202            AdapterXcache::class
203        );
204
205        /** @noinspection PhpUnhandledExceptionInspection */
206        $cacheAdapterManager->addAdapter(
207            AdapterApcu::class
208        );
209
210        /** @noinspection PhpUnhandledExceptionInspection */
211        $cacheAdapterManager->addAdapter(
212            AdapterApc::class
213        );
214
215        /** @noinspection PhpUnhandledExceptionInspection */
216        $cacheAdapterManager->addAdapter(
217            AdapterOpCache::class,
218            static function () {
219                return \realpath(\sys_get_temp_dir()) . '/simple_php_cache';
220            }
221        );
222
223        /** @noinspection PhpUnhandledExceptionInspection */
224        $cacheAdapterManager->addAdapter(
225            AdapterArray::class
226        );
227
228        return $cacheAdapterManager;
229    }
230}
231