Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
1 / 1
ObjectProxy
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
11 / 11
21
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
 is
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 in
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 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 Celema\Boiler\Proxy;
6
7use Celema\Boiler\Contract\Wrapper;
8use Celema\Boiler\Exception\RuntimeException;
9use Celema\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    #[Override]
84    public function is(mixed $other): bool
85    {
86        return $this->value === ($other instanceof Proxy ? $other->unwrap() : $other);
87    }
88
89    /** @param ArrayProxy|array<array-key, mixed> $haystack */
90    #[Override]
91    public function in(ArrayProxy|array $haystack): bool
92    {
93        if ($haystack instanceof ArrayProxy) {
94            $haystack = $haystack->unwrap();
95        }
96
97        /** @var mixed $item */
98        foreach ($haystack as $item) {
99            if ($this->is($item)) {
100                return true;
101            }
102        }
103
104        return false;
105    }
106
107    private function hasPublicProperty(string $name): bool
108    {
109        return array_key_exists($name, get_object_vars($this->value));
110    }
111
112    /**
113     * @param array<array-key, mixed> $args
114     * @return array<array-key, mixed>
115     */
116    private function unwrapArgs(array $args): array
117    {
118        $unwrapped = $this->wrapper->unwrap($args);
119        assert(is_array($unwrapped), 'Wrapper::unwrap must return an array for array input');
120
121        return $unwrapped;
122    }
123}