MessageLocator::clearInstanceCache()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * MessageLocator.php
4
 *
5
 * MIT LICENSE
6
 *
7
 * LICENSE: This source file is subject to the MIT license.
8
 * A copy of the licenses text was distributed alongside this
9
 * file (usually the repository or package root). The text can also
10
 * be obtained through one of the following sources:
11
 * * http://opensource.org/licenses/MIT
12
 * * https://github.com/suralc/pvra/blob/master/LICENSE
13
 *
14
 * @author     suralc <[email protected]>
15
 * @license    http://opensource.org/licenses/MIT  MIT
16
 */
17
namespace Pvra\Result;
18
19
/**
20
 * Class MessageLocator
21
 *
22
 * @package Pvra\Result
23
 */
24
class MessageLocator implements \ArrayAccess
25
{
26
    use CallbackChainHelperTrait;
27
28
    const CALLBACK_POSITION_PREPEND = 1,
29
        CALLBACK_POSITION_APPEND = 2;
30
    /**
31
     * @var callable[]
32
     */
33
    private $messageSearchers = [];
34
    /**
35
     * @var callable[]
36
     */
37
    private $missingMessageHandlers = [];
38
    /**
39
     * @var array
40
     */
41
    private $fetchedMessages = [];
42
43
    /**
44
     * Append a function to handle missing messages.
45
     *
46
     * The callbacks given to this method is executed when the corresponding MessageLocator
47
     * could not find a valid message for a given id.
48
     * The callback should return a string.
49
     * The callback may also prevent return false to allow subsequent callbacks to execute.
50
     * <code>
51
     * $locator->addMissingMessageHandler(function($msgId, MessageLocator $locator) {
52
     *     if(MyOwnLocator::canLocate($msgId)) {
53
     *         return $msg;
54
     *     }
55
     *     $locator->terminateCallbackChain();
56
     * });
57
     * </code>
58
     *
59
     * @param callable $locator
60
     * @param int $position
61
     * @return $this
62
     */
63 52
    public function addMissingMessageHandler(callable $locator, $position = self::CALLBACK_POSITION_APPEND)
64
    {
65 52
        if ($position === self::CALLBACK_POSITION_PREPEND) {
66 30
            array_unshift($this->missingMessageHandlers, $locator);
67 15
        } else {
68 52
            array_push($this->missingMessageHandlers, $locator);
69
        }
70
71 52
        return $this;
72
    }
73
74
    /**
75
     * @param string|int $messageId
76
     * @return bool
77
     * @throws \Exception
78
     */
79 22
    public function messageExists($messageId)
80
    {
81 22
        if (!self::validateMessageId($messageId)) {
82 2
            throw new \InvalidArgumentException('Only valid non-empty offset types are acceptable as message ids.');
83
        }
84
85 20
        if (!empty($this->fetchedMessages[ $messageId ])) {
86 6
            return true;
87
        }
88
89 20
        $msgInfo = $this->fetchMessage($messageId, false);
90
91 20
        return $msgInfo !== null && $msgInfo !== false && !empty($msgInfo['content']);
92
    }
93
94
    /**
95
     * @param string|int $mId
96
     * @return bool
97
     */
98 22
    private static function validateMessageId($mId)
99
    {
100 22
        return is_scalar($mId) && !empty($mId);
101
    }
102
103
    /**
104
     * @param string|int $messageId
105
     * @param bool $runMissingMessageHandlers
106
     * @return array|bool
107
     * @throws \Exception
108
     */
109 80
    protected function fetchMessage($messageId, $runMissingMessageHandlers = true)
110
    {
111
        $messageInfo = [
112 80
            'id' => $messageId,
113 40
            'content' => null,
114 40
            'fallbackHandler' => false,
115 40
        ];
116
117 80
        $this->inCallbackChain(true);
118
        /** @var callable $searchCallback */
119 80 View Code Duplication
        foreach ($this->messageSearchers as $searchCallback) {
120 78
            if ($this->isCallbackChainToBeTerminated()) {
121 2
                break;
122
            }
123 78
            $value = $searchCallback($messageId, $this);
124 78
            if (!empty($value) && is_string($value)) {
125 72
                $messageInfo['id'] = $messageId;
126 72
                $messageInfo['content'] = $value;
127 75
                break;
128
            }
129 40
        }
130 80
        $this->markCallbackChainTerminated();
131
132 80
        if (empty($messageInfo['content'])) {
133 28
            if ($runMissingMessageHandlers) {
134 14
                $this->inCallbackChain(true);
135
                /** @var callable $handler */
136 14 View Code Duplication
                foreach ($this->missingMessageHandlers as $handler) {
137 12
                    if ($this->isCallbackChainToBeTerminated()) {
138 2
                        break;
139
                    }
140 12
                    $value = $handler($messageId, $this);
141 12
                    if (!empty($value) && is_string($value)) {
142 10
                        $messageInfo['id'] = $messageId;
143 10
                        $messageInfo['content'] = $value;
144 10
                        $messageInfo['fallbackHandler'] = true;
145 11
                        break;
146
                    }
147 7
                }
148 21
                $this->markCallbackChainTerminated();
149 7
            }
150 14
        } else {
151 72
            $this->fetchedMessages[ $messageId ] = $messageInfo;
152
        }
153
154 80
        return $messageInfo;
155
    }
156
157
158
    /**
159
     * @param int|string $messageId
160
     * @param bool $ignoreCachedEntries
161
     * @return string|null
162
     * @throws \Exception
163
     */
164 68
    public function getMessage($messageId, $ignoreCachedEntries = false)
165
    {
166 68
        $info = $this->getMessageWithInfo($messageId, $ignoreCachedEntries);
167 68
        return is_array($info) && array_key_exists('content', $info) ? $info['content'] : $info;
168
    }
169
170
    /**
171
     * @param int|string $messageId
172
     * @param bool $ignoreCachedEntries
173
     * @return array
174
     */
175 68
    public function getMessageWithInfo($messageId, $ignoreCachedEntries = false)
176
    {
177 34
        if ($ignoreCachedEntries !== true
178 68
            && !empty($this->fetchedMessages[ $messageId ])
179 34
        ) {
180 40
            return $this->fetchedMessages[ $messageId ];
181
        }
182
183 66
        return $this->fetchMessage($messageId);
184
    }
185
186
    /**
187
     * @param string $path
188
     * @return \Pvra\Result\MessageLocator
189
     */
190 58
    public static function fromPhpFile($path)
191
    {
192 58
        if (is_file($path) && is_readable($path)) {
193 56
            return static::fromArray(require $path);
194
        }
195
196 2
        throw new \InvalidArgumentException('Invalid path or not readable.');
197
    }
198
199 6
    public static function fromJsonFile($file)
200
    {
201 6
        if (is_readable($file) && is_file($file)) {
202 4 View Code Duplication
            if (($jsonData = json_decode(file_get_contents($file), true)) !== null) {
203 2
                return static::fromArray($jsonData);
204
            } else {
205 2
                throw new \InvalidArgumentException(sprintf("%s could not be parsed as json file. Last json error message was\n%s",
206 2
                    $file, version_compare(PHP_VERSION, '5.5.0', '>=') ? json_last_error_msg() : json_last_error()));
207
            }
208
        } else {
209 2
            throw new \InvalidArgumentException(sprintf('%s is not a valid file', $file));
210
        }
211
    }
212
213
    /**
214
     * @param $array
215
     * @return static
216
     */
217 80
    public static function fromArray($array)
218
    {
219 80
        $locator = new static();
220 80
        $locator->addMessageSearcher(function ($msgId) use ($array) {
221 68
            if (isset($array[ $msgId ])) {
222 64
                return $array[ $msgId ];
223
            }
224
225 16
            return false;
226 80
        }, self::CALLBACK_POSITION_PREPEND);
227
228 80
        return $locator;
229
    }
230
231
    /**
232
     * @param callable $searcher
233
     * @param int $position
234
     * @return $this
235
     */
236 90
    public function addMessageSearcher(callable $searcher, $position = self::CALLBACK_POSITION_APPEND)
237
    {
238 90
        if ($position === self::CALLBACK_POSITION_PREPEND) {
239 80
            array_unshift($this->messageSearchers, $searcher);
240 40
        } else {
241 10
            array_push($this->messageSearchers, $searcher);
242
        }
243
244 90
        return $this;
245
    }
246
247
    /**
248
     * @codeCoverageIgnore
249
     */
250
    public function clearInstanceCache()
251
    {
252
        $this->fetchedMessages = [];
253
    }
254
255
256
    /**
257
     * OffsetExists
258
     * Whether a message with the given id can be found without invoking missing
259
     * message handlers
260
     *
261
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
262
     * @param string|int $id message id
263
     * @return boolean true on success or false on failure.
264
     */
265 2
    public function offsetExists($id)
266
    {
267 2
        return $this->messageExists($id);
268
    }
269
270
    /**
271
     * OffsetGet
272
     * This method proxies ::getMessage($offset)
273
     *
274
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
275
     * @param string|int $id the id to retrieve
276
     * @return string the message template
277
     */
278 4
    public function offsetGet($id)
279
    {
280 4
        return $this->getMessage($id);
281
    }
282
283
    /**
284
     * Appends a new message searcher
285
     * Only the following syntax is valid: `$locator[] = function($id, $locator) {};
286
     *
287
     * @param null $offset
288
     * @param callable $value
289
     * @throws \InvalidArgumentException Exception is thrown if an offset is specified
290
     * or the value is not callable
291
     */
292 4
    public function offsetSet($offset, $value)
293
    {
294 4
        if ($offset === null && is_callable($value)) {
295 2
            $this->addMessageSearcher($value);
296 1
        } else {
297 2
            throw new \InvalidArgumentException('This is  an obscure syntax that might be removed later.');
298
        }
299 2
    }
300
301
    /**
302
     * @param mixed $offset
303
     * @throws \RuntimeException Is always thrown as this operation is not supported
304
     */
305 2
    public function offsetUnset($offset)
306
    {
307 2
        throw new \RuntimeException('This operation is  unsupported.');
308
    }
309
}
310