Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
Wrapper
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
6 / 6
20
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 wrap
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
11
 unwrap
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 escape
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 filter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 filters
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Boiler;
6
7use Celemas\Boiler\Escaper\Html;
8use Celemas\Boiler\Exception\UnexpectedValueException;
9use Celemas\Boiler\Proxy\ArrayProxy;
10use Celemas\Boiler\Proxy\IteratorProxy;
11use Celemas\Boiler\Proxy\ObjectProxy;
12use Celemas\Boiler\Proxy\Proxy;
13use Celemas\Boiler\Proxy\StringProxy;
14use Override;
15use Traversable;
16
17/** @api */
18final class Wrapper implements Contract\Wrapper
19{
20    private readonly Contract\Escapers $escapers;
21    private ?Contract\Filters $filters;
22    private readonly Contract\Escaper $defaultEscaper;
23    private readonly bool $isBuiltinEscaper;
24
25    public function __construct(
26        ?Contract\Escapers $escapers = null,
27        ?Contract\Filters $filters = null,
28    ) {
29        $this->escapers = $escapers ?? new Escapers();
30        $this->defaultEscaper = $this->escapers->get($this->escapers->default);
31        $this->isBuiltinEscaper = $this->defaultEscaper instanceof Html;
32        $this->filters = $filters;
33    }
34
35    #[Override]
36    public function wrap(mixed $value): mixed
37    {
38        if (is_string($value)) {
39            return new StringProxy($value, $this);
40        }
41
42        if (is_array($value)) {
43            return new ArrayProxy($value, $this);
44        }
45
46        if (
47            $value === null
48            || is_int($value)
49            || is_float($value)
50            || is_bool($value)
51            || is_resource($value)
52        ) {
53            return $value;
54        }
55
56        if ($value instanceof Proxy) {
57            return $value;
58        }
59
60        if ($value instanceof Traversable) {
61            return new IteratorProxy($value, $this);
62        }
63
64        if (is_object($value)) {
65            return new ObjectProxy($value, $this);
66        }
67
68        throw new UnexpectedValueException('Unsupported template value type');
69    }
70
71    #[Override]
72    public function unwrap(mixed $value): mixed
73    {
74        if ($value instanceof Proxy) {
75            return $value->unwrap();
76        }
77
78        if (!is_array($value)) {
79            return $value;
80        }
81
82        return array_map($this->unwrap(...), $value);
83    }
84
85    #[Override]
86    public function escape(
87        string $value,
88        ?string $escaper = null,
89    ): string {
90        if ($escaper === null) {
91            return $this->isBuiltinEscaper
92                ? htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
93                : $this->defaultEscaper->escape($value);
94        }
95
96        return $this->escapers->get($escaper)->escape($value);
97    }
98
99    #[Override]
100    public function filter(string $name): Contract\Filter
101    {
102        return $this->filters()->get($name);
103    }
104
105    private function filters(): Contract\Filters
106    {
107        return $this->filters ??= new Filters();
108    }
109}