Serializer   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 349
Duplicated Lines 0 %

Test Coverage

Coverage 98.99%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 33
eloc 96
c 6
b 0
f 0
dl 0
loc 349
ccs 98
cts 99
cp 0.9899
rs 9.76

15 Methods

Rating   Name   Duplication   Size   Complexity  
A isStrict() 0 3 1
A deserialize() 0 3 1
A setStrict() 0 5 1
A assertNoJsonSerializationError() 0 19 4
A assertNoPhpSerializationError() 0 15 3
A getData() 0 3 1
A setData() 0 5 1
A getUnserialized() 0 3 1
A getSerialized() 0 3 1
A serialize() 0 27 6
A __construct() 0 5 1
A unserialize() 0 27 6
A getType() 0 3 1
A setType() 0 17 2
A __invoke() 0 21 3
1
<?php
2
3
/**
4
 * @author Marwan Al-Soltany <[email protected]>
5
 * @copyright Marwan Al-Soltany 2020
6
 * For the full copyright and license information, please view
7
 * the LICENSE file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace MAKS\AmqpAgent\Helper;
13
14
use Exception;
15
use Closure;
16
use MAKS\AmqpAgent\Exception\SerializerViolationException;
17
18
/**
19
 * A flexible serializer to be used in conjunction with the workers.
20
 * @since 1.0.0
21
 */
22
class Serializer
23
{
24
    /**
25
     * The JSON serialization type constant.
26
     * @var string
27
     */
28
    public const TYPE_JSON = 'JSON';
29
30
    /**
31
     * The PHP serialization type constant.
32
     * @var string
33
     */
34
    public const TYPE_PHP = 'PHP';
35
36
    /**
37
     * The default data the serializer works with if none was provided.
38
     * @var null
39
     */
40
    public const DEFAULT_DATA = null;
41
42
    /**
43
     * The default type the serializer works with if none was provided.
44
     * @var string
45
     */
46
    public const DEFAULT_TYPE = self::TYPE_JSON;
47
48
    /**
49
     * The default strict value the serializer works with if none was provided.
50
     * @var bool
51
     */
52
    public const DEFAULT_STRICT = true;
53
54
    /**
55
     * The supported serialization types.
56
     * @var array
57
     */
58
    protected const SUPPORTED_TYPES = [self::TYPE_JSON, self::TYPE_PHP];
59
60
61
    /**
62
     * The current data the serializer has.
63
     * @var mixed
64
     */
65
    protected $data;
66
67
    /**
68
     * The current type the serializer uses.
69
     * @var string
70
     */
71
    protected $type;
72
73
    /**
74
     * The current strict value the serializer works with.
75
     * @var bool
76
     */
77
    protected $strict;
78
79
    /**
80
     * The result of the last (un)serialization operation.
81
     * @var mixed
82
     */
83
    protected $result;
84
85
86
    /**
87
     * Serializer object constructor.
88
     * @param mixed $data [optional] The data to (un)serialize. Defaults to null.
89
     * @param string|null $type [optional] The type of (un)serialization. Defaults to JSON.
90
     * @param bool $strict [optional] Whether or not to assert that no errors have occurred while executing (un)serialization functions. Defaults to true.
91
     * @throws SerializerViolationException
92
     */
93 34
    public function __construct($data = null, ?string $type = null, ?bool $strict = null)
94
    {
95 34
        $this->setData($data ?? self::DEFAULT_DATA);
96 34
        $this->setType($type ?? self::DEFAULT_TYPE);
97 34
        $this->setStrict($strict ?? self::DEFAULT_STRICT);
98 34
    }
99
100
    /**
101
     * Executes when calling the class like a function.
102
     * @param mixed $data The data to (un)serialize.
103
     * @param string|null $type [optional] The type of (un)serialization. Defaults to JSON.
104
     * @param bool $strict [optional] Whether or not to assert that no errors have occurred while executing (un)serialization functions. Defaults to true.
105
     * @return mixed Serialized or unserialized data depending on the passed parameters.
106
     * @throws SerializerViolationException
107
     */
108 3
    public function __invoke($data, ?string $type = self::DEFAULT_TYPE, ?bool $strict = self::DEFAULT_STRICT)
109
    {
110 3
        $this->setData($data);
111 3
        $this->setType($type ?? self::DEFAULT_TYPE);
0 ignored issues
show
Bug introduced by
It seems like $type ?? self::DEFAULT_TYPE can also be of type null; however, parameter $type of MAKS\AmqpAgent\Helper\Serializer::setType() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
        $this->setType(/** @scrutinizer ignore-type */ $type ?? self::DEFAULT_TYPE);
Loading history...
112 2
        $this->setStrict($strict ?? self::DEFAULT_STRICT);
113
114
        try {
115 2
            $this->result = is_string($data) ? $this->unserialize() : $this->serialize();
116 1
        } catch (Exception $error) {
117 1
            $dataType = gettype($data);
118 1
            throw new SerializerViolationException(
119 1
                sprintf(
120 1
                    'The data passed to the serializer (data-type: %s) could not be processed!',
121 1
                    $dataType
122
                ),
123 1
                (int)$error->getCode(),
124 1
                $error
125
            );
126
        }
127
128 1
        return $this->result;
129
    }
130
131
132
    /**
133
     * Serializes the passed or registered data. When no parameters are passed, it uses the registered ones.
134
     * @param mixed $data [optional] The data to serialize.
135
     * @param string|null $type [optional] The type of serialization.
136
     * @param bool $strict [optional] Whether or not to assert that no errors have occurred while executing serialization functions.
137
     * @return string|null A serialized representation of the passed or registered data or null on failure.
138
     * @throws SerializerViolationException
139
     */
140 7
    public function serialize($data = null, ?string $type = null, ?bool $strict = null): string
141
    {
142 7
        if (null !== $data) {
143 5
            $this->setData($data);
144
        }
145
146 7
        if (null !== $type) {
147 5
            $this->setType($type);
148
        }
149
150 6
        if (null !== $strict) {
151 1
            $this->setStrict($strict);
152
        }
153
154 6
        if (self::TYPE_PHP === $this->type) {
155
            $this->assertNoPhpSerializationError(function () {
156 2
                $this->result = serialize($this->data);
157 2
            });
158
        }
159
160 6
        if (self::TYPE_JSON === $this->type) {
161
            $this->assertNoJsonSerializationError(function () {
162 6
                $this->result = json_encode($this->data);
163 6
            });
164
        }
165
166 6
        return $this->result;
167
    }
168
169
    /**
170
     * Unserializes the passed or registered data. When no parameters are passed, it uses the registered ones.
171
     * @param string|null $data [optional] The data to unserialize.
172
     * @param string|null $type [optional] The type of unserialization.
173
     * @param bool $strict [optional] Whether or not to assert that no errors have occurred while executing unserialization functions.
174
     * @return mixed A PHP type on success or false or null on failure.
175
     * @throws SerializerViolationException
176
     */
177 10
    public function unserialize(?string $data = null, ?string $type = null, ?bool $strict = null)
178
    {
179 10
        if (null !== $data) {
180 7
            $this->setData($data);
181
        }
182
183 10
        if (null !== $type) {
184 7
            $this->setType($type);
185
        }
186
187 9
        if (null !== $strict) {
188 5
            $this->setStrict($strict);
189
        }
190
191 9
        if (self::TYPE_PHP === $this->type) {
192
            $this->assertNoPhpSerializationError(function () {
193 5
                $this->result = unserialize($this->data);
194 5
            });
195
        }
196
197 8
        if (self::TYPE_JSON === $this->type) {
198
            $this->assertNoJsonSerializationError(function () {
199 7
                $this->result = json_decode($this->data, true);
200 7
            });
201
        }
202
203 6
        return $this->result;
204
    }
205
206
    /**
207
     * Deserializes the passed or registered data. When no parameters are passed, it uses the registered ones.
208
     * @since 1.2.2 Alias for `self::unserialize()`.
209
     * @param string|null $data [optional] The data to unserialize.
210
     * @param string|null $type [optional] The type of unserialization.
211
     * @param bool $strict [optional] Whether or not to assert that no errors have occurred while executing unserialization functions.
212
     * @return mixed A PHP type on success or false or null on failure.
213
     * @throws SerializerViolationException
214
     */
215 1
    public function deserialize(?string $data = null, ?string $type = null, ?bool $strict = null)
216
    {
217 1
        return $this->unserialize($data, $type, $strict);
218
    }
219
220
    /**
221
     * Registers the passed data in the object.
222
     * @param mixed $data The data wished to be registered.
223
     * @return self
224
     */
225 34
    public function setData($data)
226
    {
227 34
        $this->data = $data;
228
229 34
        return $this;
230
    }
231
232
    /**
233
     * Returns the currently registered data.
234
     * @return mixed
235
     */
236 1
    public function getData()
237
    {
238 1
        return $this->data;
239
    }
240
241
    /**
242
     * Registers the passed type in the object.
243
     * @param string $type The type wished to be registered.
244
     * @return self
245
     * @throws SerializerViolationException
246
     */
247 34
    public function setType(string $type)
248
    {
249 34
        $type = strtoupper($type);
250
251 34
        if (!in_array($type, static::SUPPORTED_TYPES)) {
252 3
            throw new SerializerViolationException(
253 3
                sprintf(
254 3
                    '"%s" is unsupported (un)serialization type. Supported types are: [%s]!',
255 3
                    $type,
256 3
                    implode(', ', static::SUPPORTED_TYPES)
257
                )
258
            );
259
        }
260
261 34
        $this->type = $type;
262
263 34
        return $this;
264
    }
265
266
    /**
267
     * Returns the currently registered type.
268
     * @return string
269
     */
270 1
    public function getType(): string
271
    {
272 1
        return $this->type;
273
    }
274
275
    /**
276
     * Registers the passed strict value in the object.
277
     * @since 1.2.2
278
     * @param bool $strict The strict value wished to be registered.
279
     * @return self
280
     */
281 34
    public function setStrict(bool $strict)
282
    {
283 34
        $this->strict = $strict;
284
285 34
        return $this;
286
    }
287
288
    /**
289
     * Returns the currently registered strict value.
290
     * @since 1.2.2
291
     * @return bool
292
     */
293 1
    public function isStrict()
294
    {
295 1
        return $this->strict;
296
    }
297
298
    /**
299
     * Alias for `self::serialize()` that does not accept any parameters (works with currently registered parameters).
300
     * @return string The serialized data.
301
     * @throws SerializerViolationException
302
     */
303 1
    public function getSerialized(): string
304
    {
305 1
        return $this->serialize();
306
    }
307
308
    /**
309
     * Alias for `self::unserialize()` that does not accept any parameters (works with currently registered parameters).
310
     * @return mixed The unserialized data.
311
     * @throws SerializerViolationException
312
     */
313 1
    public function getUnserialized()
314
    {
315 1
        return $this->unserialize();
316
    }
317
318
    /**
319
     * Asserts that `serialize()` and/or `unserialize()` was executed successfully depending on strictness of the Serializer.
320
     * @since 1.2.2
321
     * @param Closure $callback The (un)serialization callback to execute.
322
     * @return void
323
     * @throws SerializerViolationException
324
     */
325 6
    protected function assertNoPhpSerializationError(Closure $callback): void
326
    {
327 6
        $this->result = null;
328
329
        try {
330 6
            $callback();
331 2
        } catch (Exception $error) {
332 2
            if ($this->strict) {
333 2
                throw new SerializerViolationException(
334 2
                    sprintf(
335 2
                        'An error occurred while executing serialize() or unserialize(): %s',
336 2
                        (string)$error->getMessage()
337
                    ),
338 2
                    (int)$error->getCode(),
339 2
                    $error
340
                );
341
            }
342
        }
343 4
    }
344
345
    /**
346
     * Asserts that `json_encode()` and/or `json_decode()` was executed successfully depending on strictness of the Serializer.
347
     * @since 1.2.2
348
     * @param Closure $callback The (un)serialization callback to execute.
349
     * @return void
350
     * @throws SerializerViolationException
351
     */
352 12
    protected function assertNoJsonSerializationError(Closure $callback): void
353
    {
354 12
        $this->result = null;
355
356
        try {
357 12
            $callback();
358
        } catch (Exception $error) {
359
            // JSON functions do not throw exceptions on PHP < v7.3.0
360
            // The code down below takes care of throwing the exception.
361
        }
362
363 12
        if ($this->strict) {
364 9
            $errorCode = json_last_error();
365 9
            if ($errorCode !== JSON_ERROR_NONE) {
366 2
                $errorMessage = json_last_error_msg();
367 2
                throw new SerializerViolationException(
368 2
                    sprintf(
369 2
                        'An error occurred while executing json_encode() or json_decode(): %s',
370 2
                        $errorMessage
371
                    )
372
                );
373
            }
374
        }
375 10
    }
376
}
377