Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
32.00% covered (danger)
32.00%
8 / 25
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ConnectorCacheTrait
32.00% covered (danger)
32.00%
8 / 25
33.33% covered (danger)
33.33%
2 / 6
75.63
0.00% covered (danger)
0.00%
0 / 1
 setCache
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCacheKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCachedData
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
3.33
 putCachedData
8.33% covered (danger)
8.33%
1 / 12
0.00% covered (danger)
0.00%
0 / 1
24.26
 logCacheWarning
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 logCacheDebug
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
1<?php
2
3/**
4 * Caching support trait for connectors.
5 *
6 * Prerequisites:
7 *
8 * - Logger as $this->logger
9 *
10 * PHP version 8
11 *
12 * Copyright (C) The National Library of Finland 2022.
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License version 2,
16 * as published by the Free Software Foundation.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
26 *
27 * @category VuFind
28 * @package  Search
29 * @author   Ere Maijala <ere.maijala@helsinki.fi>
30 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
31 * @link     https://vufind.org Main Site
32 */
33
34namespace VuFindSearch\Backend\Feature;
35
36use Laminas\Cache\Storage\Adapter\Memcached;
37use Laminas\Cache\Storage\StorageInterface;
38use Laminas\Http\Client as HttpClient;
39use Laminas\Log\LoggerInterface;
40
41/**
42 * Caching support trait for connectors.
43 *
44 * @category VuFind
45 * @package  Search
46 * @author   Ere Maijala <ere.maijala@helsinki.fi>
47 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
48 * @link     https://vufind.org Main Site
49 */
50trait ConnectorCacheTrait
51{
52    /**
53     * Request cache
54     *
55     * @var StorageInterface
56     */
57    protected $cache = null;
58
59    /**
60     * Set the cache storage
61     *
62     * @param StorageInterface $cache Cache
63     *
64     * @return void
65     */
66    public function setCache(StorageInterface $cache)
67    {
68        $this->cache = $cache;
69    }
70
71    /**
72     * Create a cache key from client's request state
73     *
74     * @param HttpClient $client HTTP Client
75     *
76     * @return string
77     */
78    public function getCacheKey(HttpClient $client): string
79    {
80        return md5($client->getRequest()->toString());
81    }
82
83    /**
84     * Get a request from cache if available
85     *
86     * @param string $key Cache key
87     *
88     * @return mixed
89     */
90    public function getCachedData(string $key)
91    {
92        try {
93            if ($result = $this->cache->getItem($key)) {
94                $this->logCacheDebug('Returning cached results');
95                return $result;
96            }
97        } catch (\Exception $ex) {
98            $this->logCacheWarning('getItem failed: ' . $ex->getMessage());
99        }
100        return null;
101    }
102
103    /**
104     * Cache response data.
105     *
106     * @param string $key      Cache entry key
107     * @param mixed  $response Response to be cached
108     *
109     * @return void
110     */
111    protected function putCachedData(string $key, $response): void
112    {
113        try {
114            $this->cache->setItem($key, $response);
115        } catch (\Laminas\Cache\Exception\RuntimeException $ex) {
116            if ($this->cache->getCapabilities()->getAdapter() instanceof Memcached) {
117                // Try to determine if caching failed due to response size and log
118                // the case accordingly. Unfortunately Laminas Cache does not
119                // translate exceptions to any common error codes, so we must check
120                // the backend-specific code. Note that error code 37 is available as
121                // a constant in Memcached, but we're not using it here due to it
122                // being an optional extension.
123                if ($ex->getCode() === 37) {
124                    $this->logCacheDebug(
125                        'setItem failed: ' . $ex->getMessage() . '; Response'
126                        . ' exceeds configured maximum cacheable size in memcached'
127                    );
128                    return;
129                }
130            }
131            $this->logCacheWarning('setItem failed: ' . $ex->getMessage());
132        } catch (\Exception $ex) {
133            $this->logCacheWarning('setItem failed: ' . $ex->getMessage());
134        }
135    }
136
137    /**
138     * Log a warning message
139     *
140     * @param string $msg Message
141     *
142     * @return void
143     */
144    protected function logCacheWarning(string $msg): void
145    {
146        if (($this->logger ?? null) instanceof LoggerInterface) {
147            $this->logger->warn("Cache: $msg");
148        } else {
149            error_log("Warning: Cache: $msg");
150        }
151    }
152
153    /**
154     * Log a debug message
155     *
156     * @param string $msg Message
157     *
158     * @return void
159     */
160    protected function logCacheDebug(string $msg): void
161    {
162        if (($this->logger ?? null) instanceof LoggerInterface) {
163            $this->logger->debug("Cache: $msg");
164        }
165    }
166}