Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.08% covered (warning)
78.08%
57 / 73
66.67% covered (warning)
66.67%
6 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Cache
78.08% covered (warning)
78.08%
57 / 73
66.67% covered (warning)
66.67%
6 / 9
37.85
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createOrUpdate
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 lookup
53.85% covered (warning)
53.85%
7 / 13
0.00% covered (danger)
0.00%
0 / 1
5.57
 lookupBatch
66.67% covered (warning)
66.67%
14 / 21
0.00% covered (danger)
0.00%
0 / 1
5.93
 setContext
83.33% covered (warning)
83.33%
15 / 18
0.00% covered (danger)
0.00%
0 / 1
8.30
 isPrimary
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isFallback
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isCachable
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getVuFindRecord
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3/**
4 * Record Cache
5 *
6 * PHP version 8
7 *
8 * Copyright (C) University of Freiburg 2014.
9 * Copyright (C) The National Library of Finland 2015.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2,
13 * as published by the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 *
24 * @category VuFind
25 * @package  Record
26 * @author   Markus Beh <markus.beh@ub.uni-freiburg.de>
27 * @author   Ere Maijala <ere.maijala@helsinki.fi>
28 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
29 * @link     https://vufind.org Main Page
30 */
31
32namespace VuFind\Record;
33
34use Laminas\Config\Config as Config;
35use VuFind\Db\Entity\RecordEntityInterface;
36use VuFind\Db\Service\RecordServiceInterface;
37use VuFind\RecordDriver\PluginManager as RecordFactory;
38
39/**
40 * Record Cache
41 *
42 * @category VuFind
43 * @package  Record
44 * @author   Markus Beh <markus.beh@ub.uni-freiburg.de>
45 * @author   Ere Maijala <ere.maijala@helsinki.fi>
46 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
47 * @link     https://vufind.org Main Site
48 */
49class Cache implements \Laminas\Log\LoggerAwareInterface
50{
51    use \VuFind\Log\LoggerAwareTrait;
52
53    public const CONTEXT_DISABLED = '';
54    public const CONTEXT_DEFAULT = 'Default';
55    public const CONTEXT_FAVORITE = 'Favorite';
56
57    /**
58     * Record sources which may be cached.
59     *
60     * @var array
61     */
62    protected $cachableSources = [];
63
64    /**
65     * Constructor
66     *
67     * @param RecordFactory          $recordFactoryManager Record driver plugin manager
68     * @param Config                 $cacheConfig          RecordCache.ini contents
69     * @param RecordServiceInterface $recordService        Record database service
70     */
71    public function __construct(
72        protected RecordFactory $recordFactoryManager,
73        protected Config $cacheConfig,
74        protected RecordServiceInterface $recordService
75    ) {
76        $this->setContext(Cache::CONTEXT_DEFAULT);
77    }
78
79    /**
80     * Create a new or update an existing cache entry
81     *
82     * @param string $recordId Record id
83     * @param string $source   Source name
84     * @param mixed  $rawData  Raw data from source (must be serializable)
85     *
86     * @return void
87     */
88    public function createOrUpdate($recordId, $source, $rawData)
89    {
90        if (isset($this->cachableSources[$source])) {
91            $this->debug("Updating {$source}|{$recordId}");
92            $this->recordService->updateRecord($recordId, $source, $rawData);
93        }
94    }
95
96    /**
97     * Given a record ID, look up a record for that source.
98     *
99     * @param string $id     Record ID
100     * @param string $source Record source
101     *
102     * @return array Array of \VuFind\RecordDriver\AbstractBase
103     */
104    public function lookup($id, $source)
105    {
106        $this->debug("Checking {$source}|{$id}");
107        $record = $this->recordService->getRecord($id, $source);
108        $this->debug(
109            "Cached record {$source}|{$id} "
110            . ($record ? 'found' : 'not found')
111        );
112        try {
113            return $record ? [$this->getVuFindRecord($record)] : [];
114        } catch (\Exception $e) {
115            $this->logError(
116                'Could not load record {$source}|{$id} from the record cache: '
117                . $e->getMessage()
118            );
119        }
120        return [];
121    }
122
123    /**
124     * Given an array of IDs and a record source, look up a batch of records for
125     * that source.
126     *
127     * @param array  $ids    Record IDs
128     * @param string $source Record source
129     *
130     * @return array Array of \VuFind\RecordDriver\AbstractBase
131     */
132    public function lookupBatch($ids, $source)
133    {
134        if (empty($ids)) {
135            return [];
136        }
137
138        $this->debug("Checking $source batch: " . implode(', ', $ids));
139        $vufindRecords = [];
140        $cachedRecords = $this->recordService->getRecords($ids, $source);
141        foreach ($cachedRecords as $cachedRecord) {
142            try {
143                $vufindRecords[] = $this->getVuFindRecord($cachedRecord);
144            } catch (\Exception $e) {
145                $this->logError(
146                    'Could not load record ' . $cachedRecord->getSource() . '|'
147                    . $cachedRecord->getRecordId() . ' from the record cache: '
148                    . $e->getMessage()
149                );
150            }
151        }
152
153        $extractIdCallback = function ($record) {
154            return $record->getUniqueID();
155        };
156        $foundIds = array_map($extractIdCallback, $vufindRecords);
157        $this->debug(
158            "Cached records for $source "
159            . ($foundIds ? 'found: ' . implode(', ', $foundIds) : 'not found')
160        );
161
162        return $vufindRecords;
163    }
164
165    /**
166     * Set the context for controlling cache behaviour
167     *
168     * @param string $context Cache context
169     *
170     * @return void
171     */
172    public function setContext($context)
173    {
174        $this->debug("Setting context to '$context'");
175        if (empty($context)) {
176            $this->cachableSources = [];
177            return;
178        }
179        $context = ucfirst($context);
180        if (!isset($this->cacheConfig->$context)) {
181            $context = Cache::CONTEXT_DEFAULT;
182        }
183        $this->cachableSources = isset($this->cacheConfig->$context)
184            ? $this->cacheConfig->$context->toArray() : [];
185        if (
186            $context != Cache::CONTEXT_DEFAULT
187            && isset($this->cacheConfig->{Cache::CONTEXT_DEFAULT})
188        ) {
189            // Inherit settings from Default section
190            $this->cachableSources = array_merge(
191                $this->cacheConfig->{Cache::CONTEXT_DEFAULT}->toArray(),
192                $this->cachableSources
193            );
194        }
195
196        foreach ($this->cachableSources as &$cachableSource) {
197            if (!isset($cachableSource['operatingMode'])) {
198                $cachableSource['operatingMode'] = 'disabled';
199            }
200        }
201    }
202
203    /**
204     * Convenience method for checking if cache is used as primary data data source
205     *
206     * @param string $source Record source
207     *
208     * @return bool
209     */
210    public function isPrimary($source)
211    {
212        return isset($this->cachableSources[$source]['operatingMode'])
213            ? $this->cachableSources[$source]['operatingMode'] === 'primary'
214            : false;
215    }
216
217    /**
218     * Convenience method for checking if cache is used as fallback data source
219     *
220     * @param string $source Record source
221     *
222     * @return bool
223     */
224    public function isFallback($source)
225    {
226        return isset($this->cachableSources[$source]['operatingMode'])
227            ? $this->cachableSources[$source]['operatingMode'] === 'fallback'
228            : false;
229    }
230
231    /**
232     * Check whether a record source is cachable
233     *
234     * @param string $source Record source
235     *
236     * @return bool
237     */
238    public function isCachable($source)
239    {
240        return isset($this->cachableSources[$source]['operatingMode'])
241            ? $this->cachableSources[$source]['operatingMode'] !== 'disabled'
242            : false;
243    }
244
245    /**
246     * Helper function to get records from cached source-specific record data
247     *
248     * @param RecordEntityInterface $cachedRecord Record data
249     *
250     * @return \VuFind\RecordDriver\AbstractBase
251     */
252    protected function getVuFindRecord(RecordEntityInterface $cachedRecord)
253    {
254        $source = $cachedRecord->getSource();
255        $doc = unserialize($cachedRecord->getData());
256
257        // Solr records are loaded in special-case fashion:
258        if ($source === 'VuFind' || $source === 'Solr') {
259            $driver = $this->recordFactoryManager->getSolrRecord($doc);
260        } else {
261            $driver = $this->recordFactoryManager->get($source);
262            $driver->setRawData($doc);
263        }
264
265        $driver->setSourceIdentifiers($source);
266
267        return $driver;
268    }
269}