Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.89% covered (warning)
68.89%
31 / 45
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
CachingDownloader
68.89% covered (warning)
68.89%
31 / 45
40.00% covered (danger)
40.00%
2 / 5
18.09
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getDownloaderCache
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 setUpCache
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
4.12
 download
69.23% covered (warning)
69.23%
18 / 26
0.00% covered (danger)
0.00%
0 / 1
5.73
 downloadJson
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3/**
4 * Caching downloader.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2022.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  Http
25 * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org Main Page
28 */
29
30namespace VuFind\Http;
31
32use Laminas\Cache\Storage\StorageInterface;
33use VuFind\Cache\Manager as CacheManager;
34use VuFind\Config\PluginManager as ConfigManager;
35use VuFind\Exception\HttpDownloadException;
36
37/**
38 * Caching downloader.
39 *
40 * @category VuFind
41 * @package  Http
42 * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
43 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
44 * @link     https://vufind.org/wiki/development Wiki
45 */
46class CachingDownloader implements \VuFindHttp\HttpServiceAwareInterface
47{
48    use \VuFindHttp\HttpServiceAwareTrait;
49
50    /**
51     * CacheManager to update caches if necessary.
52     *
53     * @var CacheManager
54     */
55    protected $cacheManager;
56
57    /**
58     * ConfigManager to get additional settings if necessary.
59     *
60     * @var ConfigManager
61     */
62    protected $configManager;
63
64    /**
65     * Cache to use for downloads
66     *
67     * @var StorageInterface
68     */
69    protected $cache;
70
71    /**
72     * Cache ID to use for downloads
73     *
74     * @var string
75     */
76    protected $cacheId;
77
78    /**
79     * Stored client options for cache key generation.
80     *
81     * @var array
82     */
83    protected $cacheOptions = [];
84
85    /**
86     * Constructor
87     *
88     * @param CacheManager  $cacheManager  VuFind Cache Manager
89     * @param ConfigManager $configManager VuFind Config Manager
90     */
91    public function __construct(CacheManager $cacheManager, ConfigManager $configManager)
92    {
93        $this->cacheManager = $cacheManager;
94        $this->configManager = $configManager;
95        $this->setUpCache('default');
96    }
97
98    /**
99     * Get cache and initialize it, if necessary.
100     *
101     * @return StorageInterface
102     */
103    protected function getDownloaderCache()
104    {
105        if ($this->cache == null) {
106            $cacheName = $this->cacheManager->addDownloaderCache(
107                $this->cacheId,
108                $this->cacheOptions
109            );
110            $this->cache = $this->cacheManager->getCache($cacheName);
111        }
112        return $this->cache;
113    }
114
115    /**
116     * Set up a different cache.
117     *
118     * @param string $cacheId             Cache ID
119     * @param string $cacheOptionsSection Cache Options Section
120     *
121     * @return void
122     */
123    public function setUpCache(string $cacheId, string $cacheOptionsSection = null)
124    {
125        $this->cache = null;
126        $this->cacheId = $cacheId;
127
128        if (!empty($cacheOptionsSection)) {
129            $fullCacheOptionsSection = 'Cache_' . $cacheOptionsSection;
130            $section = $this->configManager->get('config')->$fullCacheOptionsSection;
131            $this->cacheOptions = !empty($section) ? $section->toArray() : [];
132        }
133    }
134
135    /**
136     * Download a resource using the cache in the background.
137     *
138     * @param string    $url            URL
139     * @param array     $params         Request parameters
140     *                                  (e.g. additional headers)
141     * @param ?callable $decodeCallback Callback for decoding
142     *
143     * @return mixed
144     */
145    public function download(
146        $url,
147        $params = [],
148        callable $decodeCallback = null
149    ) {
150        $cache = $this->getDownloaderCache();
151        $cacheItemKey = md5($url . http_build_query($params));
152
153        if ($cache->hasItem($cacheItemKey)) {
154            return $cache->getItem($cacheItemKey);
155        }
156
157        // Add new item to cache if not exists
158        try {
159            $response = $this->httpService->get($url, $params);
160        } catch (\Exception $e) {
161            throw new HttpDownloadException(
162                'HttpService download failed (error)',
163                $url,
164                null,
165                null,
166                null,
167                $e
168            );
169        }
170        if (!$response->isOk()) {
171            throw new HttpDownloadException(
172                'HttpService download failed (not ok)',
173                $url,
174                $response->getStatusCode(),
175                $response->getHeaders(),
176                $response->getBody()
177            );
178        }
179
180        $finalValue = $decodeCallback !== null
181            ? $decodeCallback($response, $url) : $response->getBody();
182        $cache->addItem($cacheItemKey, $finalValue);
183        return $finalValue;
184    }
185
186    /**
187     * Download a resource using the cache in the background,
188     * including decoding for JSON.
189     *
190     * @param string $url    URL
191     * @param array  $params Request parameters (e.g. additional headers)
192     *
193     * @return stdClass
194     */
195    public function downloadJson($url, $params = [])
196    {
197        $decodeJson = function (\Laminas\Http\Response $response, $url) {
198            $decodedJson = json_decode($response->getBody());
199            if ($decodedJson === null) {
200                throw new HttpDownloadException(
201                    'Invalid response body',
202                    $url,
203                    $response->getStatusCode(),
204                    $response->getHeaders(),
205                    $response->getBody()
206                );
207            } else {
208                return $decodedJson;
209            }
210        };
211
212        return $this->download($url, $params, $decodeJson);
213    }
214}