Completed
Push — master ( e8d3de...59dd4d )
by Marwan
12s queued 11s
created

Serializer::assertNoJsonSerializationError()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.0119

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 12
c 2
b 0
f 0
nc 6
nop 1
dl 0
loc 19
ccs 10
cts 11
cp 0.9091
crap 4.0119
rs 9.8666
1
<?php
2
/**
3
 * @author Marwan Al-Soltany <[email protected]>
4
 * @copyright Marwan Al-Soltany 2020
5
 * For the full copyright and license information, please view
6
 * the LICENSE file that was distributed with this source code.
7
 */
8
9
10
namespace MAKS\AmqpAgent\Helper;
11
12
use Exception;
13
use Closure;
14
use MAKS\AmqpAgent\Exception\SerializerViolationException;
15
16
/**
17
 * A flexible serializer to be used in conjunction with the workers.
18
 * @since 1.0.0
19
 */
20
class Serializer
21
{
22
    /**
23
     * The JSON serialization type constant.
24
     * @var string
25
     */
26
    public const TYPE_JSON = 'JSON';
27
28
    /**
29
     * The PHP serialization type constant.
30
     * @var string
31
     */
32
    public const TYPE_PHP = 'PHP';
33
34
    /**
35
     * The default data the serializer works with if none was provided.
36
     * @var null
37
     */
38
    public const DEFAULT_DATA = null;
39
40
    /**
41
     * The default type the serializer works with if none was provided.
42
     * @var string
43
     */
44
    public const DEFAULT_TYPE = self::TYPE_JSON;
45
46
    /**
47
     * The default strict value the serializer works with if none was provided.
48
     * @var bool
49
     */
50
    public const DEFAULT_STRICT = true;
51
52
    /**
53
     * The supported serialization types.
54
     * @var array
55
     */
56
    protected const SUPPORTED_TYPES = [self::TYPE_JSON, self::TYPE_PHP];
57
58
59
    /**
60
     * The current data the serializer has.
61
     * @var mixed
62
     */
63
    protected $data;
64
65
    /**
66
     * The current type the serializer uses.
67
     * @var string
68
     */
69
    protected $type;
70
71
    /**
72
     * The current strict value the serializer works with.
73
     * @var bool
74
     */
75
    protected $strict;
76
77
    /**
78
     * The result of the last (un)serialization operation.
79
     * @var mixed
80
     */
81
    protected $result;
82
83
84
    /**
85
     * Serializer object constructor.
86
     * @param mixed $data [optional] The data to (un)serialize. Defaults to null.
87
     * @param string $type [optional] The type of (un)serialization. Defaults to JSON.
88
     * @param bool $strict [optional] Wether or not to assert that no errors have occurred while executing (un)serialization functions. Defaults to true.
89
     */
90 32
    public function __construct($data = null, ?string $type = null, ?bool $strict = null)
91
    {
92 32
        $this->setData($data ?? self::DEFAULT_DATA);
93 32
        $this->setType($type ?? self::DEFAULT_TYPE);
94 32
        $this->setStrict($strict ?? self::DEFAULT_STRICT);
95 32
    }
96
97
    /**
98
     * Executes when calling the class like a function.
99
     * @param mixed $data The data to (un)serialize.
100
     * @param string $type [optional] The type of (un)serialization. Defaults to JSON.
101
     * @param bool $strict [optional] Wether or not to assert that no errors have occurred while executing (un)serialization functions. Defaults to true.
102
     * @return mixed Serialized or unserialized data depending on the passed parameters.
103
     */
104 3
    public function __invoke($data, ?string $type = self::DEFAULT_TYPE, ?bool $strict = self::DEFAULT_STRICT)
105
    {
106 3
        $this->setData($data);
107 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

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