Passed
Branch master (347cf0)
by Maxime
12:48
created

ObjectEncoder::isInstantiable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Frostaly\VarExporter\Encoders;
6
7
use Frostaly\VarExporter\Contracts\EncoderInterface;
8
use Frostaly\VarExporter\Encoder;
9
use Frostaly\VarExporter\Instantiator;
10
11
class ObjectEncoder implements EncoderInterface
12
{
13
    /**
14
     * {@inheritDoc}
15
     */
16 4
    public function supports(mixed $value): bool
17
    {
18 4
        if (!is_object($value)) {
19 1
            return false;
20
        }
21 3
        $reflector = new \ReflectionClass($value);
22 3
        return !$reflector->isInternal() || !$reflector->isFinal();
23
    }
24
25
    /**
26
     * {@inheritDoc}
27
     *
28
     * @param object $object
29
     */
30 3
    public function encode(Encoder $encoder, mixed $object): array
31
    {
32 3
        [$properties, $privateProperties] = $this->extract($object);
33
34 3
        return $this->isInstantiable($object, $properties)
35 1
            ? $this->encodeConstructor($encoder, $object, $properties)
36 3
            : $this->encodeInstantiator($encoder, $object, $properties, $privateProperties);
37
    }
38
39
    /**
40
     * Check whether the object can be instantiated with the given parameters.
41
     */
42 3
    protected function isInstantiable(object $object, array $parameters): bool
43
    {
44
        try {
45 3
            $class = $object::class;
46 3
            return $object == new $class(...$parameters);
47 1
        } catch (\Throwable) {
48 1
            return false;
49
        }
50
    }
51
52
    /**
53
     * Create the object using named parameters.
54
     */
55 1
    protected function encodeConstructor(
56
        Encoder $encoder,
57
        object $object,
58
        array $parameters,
59
    ): array {
60 1
        $encoded = [];
61 1
        foreach ($parameters as $name => $value) {
62 1
            $encoded[] = [$name . ': ', ...$encoder->encode($value), ','];
63
        }
64 1
        return ['new ' . $object::class . '(', $encoded, ')'];
65
    }
66
67
    /**
68
     * Delegate the creation of the object to the Instantiator class.
69
     */
70 2
    protected function encodeInstantiator(
71
        Encoder $encoder,
72
        object $object,
73
        array $properties,
74
        array $privateProperties,
75
    ): array {
76 2
        $encoded = [Instantiator::class . '::construct(' . $object::class . '::class'];
77 2
        empty($properties)
78 2
            ?: $encoded = [...$encoded, ', ', ...$encoder->encode($properties)];
79 2
        empty($privateProperties)
80 1
            ?: $encoded = [...$encoded, ', ', ...$encoder->encode($privateProperties)];
81 2
        return [...$encoded, ')'];
82
    }
83
84
    /**
85
     * Extract the object state as an associative array.
86
     */
87 3
    protected function extract(object $object): array
88
    {
89 3
        $values = (array) $object;
90 3
        $defaultValues = (array) Instantiator::instantiate($object::class);
91
92 3
        foreach ($values as $name => $value) {
93 3
            $hasDefault = array_key_exists($name, $defaultValues);
94 3
            if (!$hasDefault || $defaultValues[$name] !== $value) {
95 3
                preg_match('/^(?:\x00(.*)\x00)?(.*)$/', (string) $name, $matches);
96 3
                in_array($matches[1], ['', '*', $object::class])
97 3
                    ? $properties[$matches[2]] = $value
98 1
                    : $privateProperties[$matches[1]][$matches[2]] = $value;
99
            }
100
        }
101 3
        return [$properties ?? [], $privateProperties ?? []];
102
    }
103
}
104