PluginProbe
JCH Optimize / trunk
JCH Optimize vtrunk
6.0.2 6.0.1 trunk 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.1.0 2.10.0 2.2.0 2.2.1 All 58 releases
jch-optimize / lib / src / Service / Provider / LaminasCache.php

LaminasCache.php in JCH Optimize trunk, at lib/src/Service/Provider/LaminasCache.php

349 lines 11.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * JCH Optimize - Performs several front-end optimizations for fast downloads
5 *
6 * @package jchoptimize/core
7 * @author Samuel Marshall <samuel@jch-optimize.net>
8 * @copyright Copyright (c) 2022 Samuel Marshall / JCH Optimize
9 * @license GNU/GPLv3, or later. See LICENSE file
10 *
11 * If LICENSE file missing, see <http://www.gnu.org/licenses/>.
12 */
13
14 namespace JchOptimize\Core\Service\Provider;
15
16 use _JchOptimizeVendor\V91\Joomla\DI\Container;
17 use _JchOptimizeVendor\V91\Joomla\DI\ServiceProviderInterface;
18 use _JchOptimizeVendor\V91\Joomla\Filesystem\File;
19 use _JchOptimizeVendor\V91\Laminas\Cache\Exception\ExceptionInterface;
20 use _JchOptimizeVendor\V91\Laminas\Cache\Pattern\CallbackCache;
21 use _JchOptimizeVendor\V91\Laminas\Cache\Pattern\CaptureCache;
22 use _JchOptimizeVendor\V91\Laminas\Cache\Pattern\PatternOptions;
23 use _JchOptimizeVendor\V91\Laminas\Cache\Service\StorageCacheAbstractServiceFactory;
24 use _JchOptimizeVendor\V91\Laminas\Cache\Storage\Adapter\Apcu;
25 use _JchOptimizeVendor\V91\Laminas\Cache\Storage\Adapter\BlackHole;
26 use _JchOptimizeVendor\V91\Laminas\Cache\Storage\Adapter\Filesystem;
27 use _JchOptimizeVendor\V91\Laminas\Cache\Storage\Adapter\Memcached;
28 use _JchOptimizeVendor\V91\Laminas\Cache\Storage\Adapter\Redis;
29 use _JchOptimizeVendor\V91\Laminas\Cache\Storage\IterableInterface;
30 use _JchOptimizeVendor\V91\Laminas\Cache\Storage\Plugin\ExceptionHandler;
31 use _JchOptimizeVendor\V91\Laminas\Cache\Storage\StorageInterface;
32 use _JchOptimizeVendor\V91\Laminas\Cache\Storage\TaggableInterface;
33 use _JchOptimizeVendor\V91\Laminas\EventManager\LazyListener;
34 use _JchOptimizeVendor\V91\Laminas\EventManager\SharedEventManager;
35 use _JchOptimizeVendor\V91\Psr\Log\LoggerInterface;
36 use JchOptimize\Core\Exception;
37 use JchOptimize\Core\Helper;
38 use JchOptimize\Core\Html\HtmlManager;
39 use JchOptimize\Core\Laminas\CacheConfigurationContainerFactory;
40 use JchOptimize\Core\Laminas\ClearExpiredByFactor;
41 use JchOptimize\Core\PageCache\PageCache;
42 use JchOptimize\Core\Platform\CacheInterface;
43 use JchOptimize\Core\Platform\PathsInterface;
44 use JchOptimize\Core\Platform\ProfilerInterface;
45 use JchOptimize\Core\Platform\UtilityInterface;
46 use JchOptimize\Core\Registry;
47 use Throwable;
48
49 use function defined;
50 use function file_exists;
51 use function max;
52 use function ucfirst;
53
54 defined('_JCH_EXEC') or die('Restricted access');
55
56 class LaminasCache implements ServiceProviderInterface
57 {
58 private array $adapterStatus = [];
59
60 private bool $connectionError = false;
61
62 private ?Container $laminasCacheContainer = null;
63
64 public function register(Container $container): void
65 {
66 $container->share(StorageInterface::class, [$this, 'getStorageInterfaceService']);
67 $container->share(CallbackCache::class, [$this, 'getCallbackCacheService']);
68 $container->share(CaptureCache::class, [$this, 'getCaptureCacheService']);
69 $container->share('page_cache', [$this, 'getPageCacheStorageService']);
70
71 $container->alias('Filesystem', Filesystem::class)
72 ->share(Filesystem::class, [$this, 'getFilesystemService']);
73 $container->alias('Redis', Redis::class)
74 ->share(Redis::class, [$this, 'getRedisService']);
75 $container->alias('Apcu', Apcu::class)
76 ->share(Apcu::class, [$this, 'getApcuService']);
77 $container->alias('Memcached', Memcached::class)
78 ->share(Memcached::class, [$this, 'getMemcachedService']);
79
80 $container->share(TaggableInterface::class, [$this, 'getTaggableInterfaceService']);
81 $container->share(ClearExpiredByFactor::class, [$this, 'getClearExpiredByFactorService']);
82
83 $sharedEvents = $container->get(SharedEventManager::class);
84 $sharedEvents->attach(
85 HtmlManager::class,
86 'preProcessHtml',
87 new LazyListener([
88 'listener' => ClearExpiredByFactor::class,
89 'method' => 'clearExpiredByFactor',
90 ], $container)
91 );
92 }
93
94 /**
95 * This will always fetch the Filesystem storage adapter
96 *
97 * @throws Exception\RuntimeException
98 */
99 public function getFilesystemService(Container $container): StorageInterface
100 {
101 $fsCache = $this->getCacheAdapter($container, 'filesystem');
102 $fsCache->getOptions()->setTtl(0);
103
104 return $fsCache;
105 }
106
107 private function getCacheAdapter(Container $container, string $adapter): StorageInterface
108 {
109 // If we already know this adapter failed, go straight to filesystem
110 if (($this->adapterStatus[$adapter] ?? null) === false && $adapter !== 'filesystem') {
111 $adapter = 'filesystem';
112 }
113
114 try {
115 $cache = $this->createAdapter($container, $adapter);
116
117 // Only test connection once per adapter
118 if ($adapter !== 'filesystem' && !isset($this->adapterStatus[$adapter])) {
119 $this->testAdapterConnection($cache);
120 $this->adapterStatus[$adapter] = true;
121 }
122
123 return $cache;
124 } catch (Throwable $e) {
125 $this->adapterStatus[$adapter] = false;
126 $this->connectionError = true;
127
128 $logger = $container->get(LoggerInterface::class);
129
130 $message = 'Error in JCH Optimize retrieving ' . ucfirst($adapter) . ' storage adapter with message: '
131 . $e->getMessage();
132
133 if ($adapter !== 'filesystem') {
134 $message .= ': Using the filesystem storage instead';
135 }
136
137 $logger->error($message);
138 $container->get(UtilityInterface::class)->publishAdminMessages($message, 'error');
139
140 if ($adapter === 'filesystem') {
141 throw new Exception\RuntimeException($message);
142 }
143
144 // fallback without recursion loop risk
145 return $this->getCacheAdapter($container, 'filesystem');
146 }
147 }
148
149 private function createAdapter(Container $container, string $adapter): StorageInterface
150 {
151 if ($adapter === 'filesystem') {
152 Helper::createCacheFolder($container);
153 }
154
155 $laminasContainer = $this->laminasCacheContainer ??= CacheConfigurationContainerFactory::create($container);
156 $factory = new StorageCacheAbstractServiceFactory();
157
158 /** @var StorageInterface $cache */
159 $cache = $factory($laminasContainer, $adapter);
160 $cache->getOptions()
161 ->setNamespace($container->get(CacheInterface::class)->getGlobalCacheNamespace());
162
163 return $cache;
164 }
165
166 /**
167 * @throws ExceptionInterface
168 */
169 private function testAdapterConnection(StorageInterface $cache): void
170 {
171 foreach ($cache->getPluginRegistry() as $plugin) {
172 if ($plugin instanceof ExceptionHandler) {
173 $options = $plugin->getOptions();
174 $throwException = $options->getThrowExceptions();
175
176 $options->setThrowExceptions(true);
177
178 try {
179 $cache->addItem('__jch_test__', '__jch_test__');
180 } finally {
181 $options->setThrowExceptions($throwException);
182 }
183 break;
184 }
185 }
186 }
187
188 /**
189 * @throws Exception\RuntimeException
190 */
191 public function getRedisService(Container $container): StorageInterface
192 {
193 $redisCache = $this->getCacheAdapter($container, 'redis');
194 $redisCache->getOptions()->setTtl(0);
195
196 return $redisCache;
197 }
198
199 /**
200 * @throws Exception\RuntimeException
201 */
202 public function getApcuService(Container $container): StorageInterface
203 {
204 $apcuCache = $this->getCacheAdapter($container, 'apcu');
205 $apcuCache->getOptions()->setTtl(0);
206
207 return $apcuCache;
208 }
209
210 /**
211 * @throws Exception\RuntimeException
212 */
213 public function getMemcachedService(Container $container): StorageInterface
214 {
215 $memcachedCache = $this->getCacheAdapter($container, 'memcached');
216 $memcachedCache->getOptions()->setTtl(0);
217
218 return $memcachedCache;
219 }
220
221 /**
222 * This will get the storage adapter that is configured in the plugin parameters
223 *
224 * @throws Exception\RuntimeException
225 */
226 public function getStorageInterfaceService(Container $container): StorageInterface
227 {
228 $params = $container->get(Registry::class);
229
230 $cache = $this->getCacheAdapter(
231 $container,
232 $params->get('pro_cache_storage_adapter', 'filesystem')
233 );
234
235 $ttl = max(
236 (int)$params->get('cache_lifetime', '900'),
237 (int)$params->get('page_cache_lifetime', '900')
238 );
239 $cache->getOptions()
240 ->setNamespace($container->get(CacheInterface::class)->getGlobalCacheNamespace())
241 ->setTtl($ttl);
242
243 return $cache;
244 }
245
246 public function getCallbackCacheService(Container $container): CallbackCache
247 {
248 return new CallbackCache(
249 $container->get(StorageInterface::class),
250 new PatternOptions(
251 ['cache_output' => false]
252 )
253 );
254 }
255
256 public function getCaptureCacheService(Container $container): CaptureCache
257 {
258 $publicDir = $container->get(PathsInterface::class)->captureCacheDir();
259
260 if (!file_exists($publicDir)) {
261 $html = <<<HTML
262 <html><head><title></title></head><body></body></html>';
263 HTML;
264 try {
265 File::write($publicDir . '/index.html', $html);
266 } catch (\Exception $e) {
267 }
268
269 $htaccess = <<<APACHECONFIG
270 <IfModule mod_autoindex.c>
271 Options -Indexes
272 </IfModule>
273 <IfModule mod_headers.c>
274 Header always unset Content-Security-Policy
275 </IfModule>
276 APACHECONFIG;
277
278 try {
279 File::write($publicDir . '/.htaccess', $htaccess);
280 } catch (\Exception $e) {
281 }
282 }
283
284 return new CaptureCache(
285 new PatternOptions(
286 [
287 'public_dir' => $publicDir,
288 'file_locking' => true,
289 'file_permission' => 0644,
290 'dir_permission' => 0755,
291 'umask' => false,
292 ]
293 )
294 );
295 }
296
297 /**
298 * @return StorageInterface&TaggableInterface&IterableInterface
299 */
300 public function getTaggableInterfaceService(Container $container)
301 {
302 $cache = $this->getCacheAdapter(
303 $container,
304 $container->get(Registry::class)->get('pro_cache_storage_adapter', 'filesystem')
305 );
306
307 if (!$cache instanceof TaggableInterface || !$cache instanceof IterableInterface) {
308 $cache = $this->getCacheAdapter($container, 'filesystem');
309 }
310
311 /** @var StorageInterface&TaggableInterface&IterableInterface $cache */
312 $cache->getOptions()
313 ->setNamespace($container->get(CacheInterface::class)->getTaggableCacheNamespace())
314 ->setTtl(0);
315
316 return $cache;
317 }
318
319 public function getPageCacheStorageService(Container $container): StorageInterface
320 {
321 $cache = $this->getCacheAdapter(
322 $container,
323 $container->get(Registry::class)->get('pro_cache_storage_adapter', 'filesystem')
324 );
325
326 $cache->getOptions()
327 ->setNamespace($container->get(CacheInterface::class)->getPageCacheNamespace())
328 ->setTtl((int)$container->get(Registry::class)->get('page_cache_lifetime', '900'));
329
330 return $cache;
331 }
332
333 public function getClearExpiredByFactorService(Container $container): ClearExpiredByFactor
334 {
335 $service = new ClearExpiredByFactor(
336 $container->get(Registry::class),
337 $container->get(ProfilerInterface::class),
338 $container->get(PageCache::class),
339 $container->get(StorageInterface::class),
340 $container->get(TaggableInterface::class),
341 $container->get(PathsInterface::class),
342 $container->get(CacheInterface::class),
343 );
344 $service->setLogger($container->get(LoggerInterface::class));
345
346 return $service;
347 }
348 }
349