Issues (3)

src/Encoders/ObjectEncoder.php (1 issue)

Labels
Severity
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
    public function supports(mixed $value): bool
17
    {
18
        if (!is_object($value)) {
19
            return false;
20
        }
21
        $reflector = new \ReflectionClass($value);
22
        return !$reflector->isInternal() || !$reflector->isFinal();
23
    }
24
25
    /**
26
     * {@inheritDoc}
27
     *
28
     * @param object $object
29
     */
30
    public function encode(Encoder $encoder, mixed $object): array
31
    {
32
        [$properties, $privateProperties] = $this->extract($object);
33
34
        return $this->isInstantiable($object, $properties)
35
            ? $this->encodeConstructor($encoder, $object, $properties)
36
            : $this->encodeInstantiator($encoder, $object, $properties, $privateProperties);
37
    }
38
39
    /**
40
     * Check whether the object can be instantiated with the given parameters.
41
     */
42
    protected function isInstantiable(object $object, array $parameters): bool
43
    {
44
        try {
45
            return $object == new ($object::class)(...$parameters);
0 ignored issues
show
A parse error occurred: Syntax error, unexpected '(' on line 45 at column 34
Loading history...
46
        } catch (\Throwable) {
47
            return false;
48
        }
49
    }
50
51
    /**
52
     * Create the object using named parameters.
53
     */
54
    protected function encodeConstructor(
55
        Encoder $encoder,
56
        object $object,
57
        array $parameters,
58
    ): array {
59
        $encoded = [];
60
        foreach ($parameters as $name => $value) {
61
            $encoded[] = [$name . ': ', ...$encoder->encode($value), ','];
62
        }
63
        return ['new ' . $object::class . '(', $encoded, ')'];
64
    }
65
66
    /**
67
     * Delegate the creation of the object to the Instantiator class.
68
     */
69
    protected function encodeInstantiator(
70
        Encoder $encoder,
71
        object $object,
72
        array $properties,
73
        array $privateProperties,
74
    ): array {
75
        $encoded = [Instantiator::class . '::construct(' . $object::class . '::class'];
76
        empty($properties)
77
            ?: $encoded = [...$encoded, ', ', ...$encoder->encode($properties)];
78
        empty($privateProperties)
79
            ?: $encoded = [...$encoded, ', ', ...$encoder->encode($privateProperties)];
80
        return [...$encoded, ')'];
81
    }
82
83
    /**
84
     * Extract the object state as an associative array.
85
     */
86
    protected function extract(object $object): array
87
    {
88
        $values = (array) $object;
89
        $defaultValues = (array) Instantiator::instantiate($object::class);
90
91
        foreach ($values as $name => $value) {
92
            $hasDefault = array_key_exists($name, $defaultValues);
93
            if (!$hasDefault || $defaultValues[$name] !== $value) {
94
                preg_match('/^(?:\x00(.*)\x00)?(.*)$/', (string) $name, $matches);
95
                in_array($matches[1], ['', '*', $object::class])
96
                    ? $properties[$matches[2]] = $value
97
                    : $privateProperties[$matches[1]][$matches[2]] = $value;
98
            }
99
        }
100
        return [$properties ?? [], $privateProperties ?? []];
101
    }
102
}
103