Completed
Push — master ( 97c539...e502a6 )
by Michael
02:16
created

CacheRetriever::getXmlFileContents()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 30
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 30
ccs 0
cts 0
cp 0
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 24
nc 4
nop 1
crap 20
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * Contains CacheRetriever class.
5
 *
6
 * PHP version 7.0+
7
 *
8
 * LICENSE:
9
 * This file is part of Yet Another Php Eve Api Library also know as Yapeal
10
 * which can be used to access the Eve Online API data and place it into a
11
 * database.
12
 * Copyright (C) 2014-2016 Michael Cummings
13
 *
14
 * This program is free software: you can redistribute it and/or modify it
15
 * under the terms of the GNU Lesser General Public License as published by the
16
 * Free Software Foundation, either version 3 of the License, or (at your
17
 * option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful, but WITHOUT
20
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
22
 * for more details.
23
 *
24
 * You should have received a copy of the GNU Lesser General Public License
25
 * along with this program. If not, see
26
 * <http://spdx.org/licenses/LGPL-3.0.html>.
27
 *
28
 * You should be able to find a copy of this license in the COPYING-LESSER.md
29
 * file. A copy of the GNU GPL should also be available in the COPYING.md file.
30
 *
31
 * @copyright 2014-2016 Michael Cummings
32
 * @license   http://www.gnu.org/copyleft/lesser.html GNU LGPL
33
 * @author    Michael Cummings <[email protected]>
34
 */
35
namespace Yapeal\FileSystem;
36
37
use Yapeal\Event\EveApiEventEmitterTrait;
38
use Yapeal\Event\EveApiEventInterface;
39
use Yapeal\Event\EveApiRetrieverInterface;
40
use Yapeal\Event\MediatorInterface;
41
use Yapeal\Log\Logger;
42
use Yapeal\Xml\EveApiReadWriteInterface;
43
use Yapeal\Xml\LibXmlChecksTrait;
44
45
/**
46
 * Class CacheRetriever
47
 */
48
class CacheRetriever implements EveApiRetrieverInterface
49
{
50
    use EveApiEventEmitterTrait;
51
    use LibXmlChecksTrait;
52
    use SafeFileHandlingTrait;
53
    /**
54
     * @param string|null $cachePath
55
     *
56
     * @throws \InvalidArgumentException
57
     */
58
    public function __construct(string $cachePath = null)
59
    {
60
        $this->setCachePath($cachePath);
61
    }
62
    /**
63
     * Method that is called for retrieve event.
64
     *
65
     * @param EveApiEventInterface $event
66
     * @param string               $eventName
67
     * @param MediatorInterface    $yem
68
     *
69
     * @return EveApiEventInterface
70
     * @throws \DomainException
71
     * @throws \InvalidArgumentException
72
     * @throws \LogicException
73
     * @throws \UnexpectedValueException
74
     */
75
    public function retrieveEveApi(EveApiEventInterface $event, string $eventName, MediatorInterface $yem)
76
    {
77
        if (!$this->shouldRetrieve()) {
78
            return $event;
79
        }
80
        $this->setYem($yem);
81
        $data = $event->getData();
82
        $yem->triggerLogEvent('Yapeal.Log.log',
83
            Logger::DEBUG,
84
            $this->getReceivedEventMessage($data, $eventName, __CLASS__));
85
        if (false === $xml = $this->getXmlFileContents($data)) {
86
            return $event;
87
        }
88
        $data->setEveApiXml($xml);
89
        $mess = 'Successfully retrieved the XML of';
90
        $yem->triggerLogEvent('Yapeal.Log.log', Logger::DEBUG, $this->createEveApiMessage($mess, $data));
91
        return $event->setData($data)
92
            ->eventHandled();
93
    }
94
    /**
95
     * Set cache path for Eve API XML.
96
     *
97
     * @param string|null $value Absolute path to cache/ directory. If null it will use cache/ directory relative to
98
     *                           Yapeal-ng's root.
99
     *
100
     * @return self Fluent interface.
101
     * @throws \InvalidArgumentException
102
     */
103
    public function setCachePath(string $value = null): self
104
    {
105
        if (null === $value) {
106
            $value = dirname(__DIR__, 2) . '/cache/';
107
        }
108
        if (!is_string($value)) {
109
            $mess = 'Cache path MUST be string, but was given ' . gettype($value);
110
            throw new \InvalidArgumentException($mess);
111
        }
112
        $this->cachePath = $this->getFpn()
113
            ->normalizePath($value);
114
        return $this;
115
    }
116
    /**
117
     * Turn on or off retrieving of Eve API data by this retriever.
118
     *
119
     * Allows class to stay registered for events but be enabled or disabled during runtime.
120
     *
121
     * @param bool $value
122
     *
123
     * @return self Fluent interface.
124
     */
125
    public function setRetrieve(bool $value = true): self
126
    {
127
        $this->retrieve = $value;
128
        return $this;
129
    }
130
    /**
131
     * Returns current cache path.
132
     *
133
     * @return string
134
     * @throws \LogicException
135
     */
136
    protected function getCachePath(): string
137
    {
138
        if ('' === $this->cachePath) {
139
            $mess = 'Tried to access $cachePath before it was set';
140
            throw new \LogicException($mess);
141
        }
142
        return $this->getFpn()
143
            ->normalizePath($this->cachePath);
144
    }
145
    /**
146
     * Enforces minimum 5 minute cache time and does some basic checks to see if XML DateTimes are valid.
147
     *
148
     * @param EveApiReadWriteInterface $data
149
     *
150
     * @return bool
151
     * @throws \DomainException
152
     * @throws \InvalidArgumentException
153
     * @throws \LogicException
154
     * @throws \UnexpectedValueException
155
     */
156
    protected function isExpired(EveApiReadWriteInterface $data): bool
157
    {
158
        libxml_clear_errors();
159
        libxml_use_internal_errors(true);
160
        try {
161
            $simple = new \SimpleXMLElement($data->getEveApiXml());
162
        } catch (\Exception $exc) {
163
            $messagePrefix = 'The XML cause SimpleXMLElement exception during the retrieval of';
164
            $this->getYem()
165
                ->triggerLogEvent('Yapeal.log.log',
166
                    Logger::WARNING,
167
                    $this->createEveApiMessage($messagePrefix, $data),
168
                    ['exception' => $exc]);
169
            $this->checkLibXmlErrors($data, $this->getYem());
170
            libxml_use_internal_errors(false);
171
            return true;
172
        }
173
        libxml_use_internal_errors(false);
174
        /** @noinspection PhpUndefinedFieldInspection */
175
        if (null === $currentTime = $simple->currentTime[0]) {
176
            $messagePrefix = 'Cached XML file missing required currentTime element during the retrieval of';
177
            $this->getYem()
178
                ->triggerLogEvent('Yapeal.Log.log', Logger::WARNING, $this->createEveApiMessage($messagePrefix, $data));
179
            return true;
180
        }
181
        /** @noinspection PhpUndefinedFieldInspection */
182
        if (null === $cachedUntil = $simple->cachedUntil[0]) {
183
            $messagePrefix = 'Cached XML file missing required cachedUntil element during the retrieval of';
184
            $this->getYem()
185
                ->triggerLogEvent('Yapeal.Log.log', Logger::WARNING, $this->createEveApiMessage($messagePrefix, $data));
186
            return true;
187
        }
188
        $eveFormat = 'Y-m-d H:i:sP';
189
        if (false === $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC'))) {
190
            $messagePrefix = 'Failed to get DateTime instance for "now" during the retrieval of';
191
            $this->getYem()
192
                ->triggerLogEvent('Yapeal.Log.log', Logger::ERROR, $this->createEveApiMessage($messagePrefix, $data));
193
            return true;
194
        }
195
        if (false === $current = \DateTimeImmutable::createFromFormat($eveFormat, $currentTime . '+00:00')) {
196
            $messagePrefix = 'Failed to get DateTime instance for currentTime during the retrieval of';
197
            $this->getYem()
198
                ->triggerLogEvent('Yapeal.Log.log', Logger::ERROR, $this->createEveApiMessage($messagePrefix, $data));
199
            return true;
200
        }
201
        if (false === $until = \DateTimeImmutable::createFromFormat($eveFormat, $cachedUntil . '+00:00')) {
202
            $messagePrefix = 'Failed to get DateTime instance for cachedUntil during the retrieval of';
203
            $this->getYem()
204
                ->triggerLogEvent('Yapeal.Log.log', Logger::ERROR, $this->createEveApiMessage($messagePrefix, $data));
205
            return true;
206
        }
207
        // At minimum use cached XML for 5 minutes.
208
        if ($now <= $current->add(new \DateInterval('PT5M'))) {
209
            return false;
210
        }
211
        return ($until <= $now);
212
    }
213
    /**
214
     * @var string $cachePath
215
     */
216
    protected $cachePath;
217
    /**
218
     * @param EveApiReadWriteInterface $data
219
     *
220
     * @return string|false
221
     * @throws \DomainException
222
     * @throws \InvalidArgumentException
223
     * @throws \LogicException
224
     * @throws \UnexpectedValueException
225
     */
226
    private function getXmlFileContents(EveApiReadWriteInterface $data)
227
    {
228
        // BaseSection/ApiHash.xml
229
        $cacheFile = sprintf('%1$s%2$s/%3$s%4$s.xml',
230
            $this->getCachePath(),
231
            ucfirst($data->getEveApiSectionName()),
232
            ucfirst($data->getEveApiName()),
233
            $data->getHash());
234
        if (false === $xml = $this->safeFileRead($cacheFile)) {
235
            $messagePrefix = sprintf('Failed to read XML file %s during the retrieval of', $cacheFile);
236
            $this->getYem()
237
                ->triggerLogEvent('Yapeal.Log.log', Logger::NOTICE, $this->createEveApiMessage($messagePrefix, $data));
238
            return false;
239
        }
240
        if ('' === $xml) {
241
            $messagePrefix = sprintf('Received an empty XML file %s during the retrieval of', $cacheFile);
242
            $this->getYem()
243
                ->triggerLogEvent('Yapeal.Log.log', Logger::NOTICE, $this->createEveApiMessage($messagePrefix, $data));
244
            return false;
245
        }
246
        $data->setEveApiXml($xml);
247
        if ($this->isExpired($data)) {
248
            $this->deleteWithRetry($cacheFile);
249
            return false;
250
        }
251
        $messagePrefix = sprintf('Using cached XML file %s during the retrieval of', $cacheFile);
252
        $this->getYem()
253
            ->triggerLogEvent('Yapeal.Log.log', Logger::DEBUG, $this->createEveApiMessage($messagePrefix, $data));
254
        return $xml;
255
    }
256
    /**
257
     * @return bool
258
     */
259
    private function shouldRetrieve(): bool
260
    {
261
        return $this->retrieve;
262
    }
263
    /**
264
     * @var bool $retrieve
265
     */
266
    private $retrieve = false;
267
}
268