Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
ObjectProxy
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
9 / 9
15
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 __toString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 __get
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 __set
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 __call
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 __invoke
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 unwrap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasPublicProperty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unwrapArgs
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Boiler\Proxy;
6
7use Celemas\Boiler\Contract\Wrapper;
8use Celemas\Boiler\Exception\RuntimeException;
9use Celemas\Boiler\Exception\UnexpectedValueException;
10use Override;
11use Stringable;
12use Traversable;
13
14/**
15 * @api
16 *
17 * @implements Proxy<object>
18 */
19final class ObjectProxy implements Proxy
20{
21    public function __construct(
22        private readonly object $value,
23        private readonly Wrapper $wrapper,
24    ) {
25        if ($this->value instanceof Traversable) {
26            throw new UnexpectedValueException('Traversable objects must be wrapped as iterator proxies');
27        }
28    }
29
30    public function __toString(): string
31    {
32        if (!$this->value instanceof Stringable) {
33            throw new RuntimeException('Wrapped object is not stringable');
34        }
35
36        return $this->wrapper->escape((string) $this->value);
37    }
38
39    public function __get(string $name): mixed
40    {
41        if ($this->hasPublicProperty($name)) {
42            return $this->wrapper->wrap($this->value->{$name});
43        }
44
45        throw new RuntimeException('No such property');
46    }
47
48    public function __set(string $name, mixed $value): void
49    {
50        if ($this->hasPublicProperty($name)) {
51            $this->value->{$name} = $this->wrapper->unwrap($value);
52
53            return;
54        }
55
56        throw new RuntimeException('No such property');
57    }
58
59    public function __call(string $name, array $args): mixed
60    {
61        if (is_callable([$this->value, $name])) {
62            return $this->wrapper->wrap($this->value->{$name}(...$this->unwrapArgs($args)));
63        }
64
65        throw new RuntimeException('No such method');
66    }
67
68    public function __invoke(mixed ...$args): mixed
69    {
70        if (is_callable($this->value)) {
71            return $this->wrapper->wrap(($this->value)(...$this->unwrapArgs($args)));
72        }
73
74        throw new RuntimeException('No such method');
75    }
76
77    #[Override]
78    public function unwrap(): object
79    {
80        return $this->value;
81    }
82
83    private function hasPublicProperty(string $name): bool
84    {
85        return array_key_exists($name, get_object_vars($this->value));
86    }
87
88    /**
89     * @param array<array-key, mixed> $args
90     * @return array<array-key, mixed>
91     */
92    private function unwrapArgs(array $args): array
93    {
94        $unwrapped = $this->wrapper->unwrap($args);
95        assert(is_array($unwrapped), 'Wrapper::unwrap must return an array for array input');
96
97        return $unwrapped;
98    }
99}