Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
14 / 14
CRAP
100.00% covered (success)
100.00%
1 / 1
Engine
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
14 / 14
17
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unescaped
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 method
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 wrapper
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 escape
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 filter
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 template
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 render
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 renderEscaped
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 renderUnescaped
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 renderTemplate
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 resolve
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 exists
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Boiler;
6
7use Celemas\Boiler\Exception\LookupException;
8use Celemas\Boiler\Exception\UnexpectedValueException;
9
10/**
11 * @api
12 * @psalm-type DirsInput = non-empty-string|list<non-empty-string>|array<non-empty-string, non-empty-string>
13 */
14final class Engine
15{
16    private readonly Contract\Environment $environment;
17    private readonly Contract\Resolver $resolver;
18    private Methods $methods;
19    private ?Contract\Wrapper $wrapper = null;
20
21    /** @param list<class-string> $trusted */
22    public function __construct(
23        Contract\Resolver $resolver,
24        Contract\Environment $environment,
25        public readonly bool $autoescape,
26        private readonly array $defaults = [],
27        private readonly array $trusted = [],
28    ) {
29        $this->resolver = $resolver;
30        $this->environment = $environment;
31        $this->methods = new Methods();
32    }
33
34    /**
35     * @psalm-param DirsInput $dirs
36     * @param list<class-string> $trusted
37     */
38    public static function create(
39        array|string $dirs,
40        array $defaults = [],
41        array $trusted = [],
42    ): self {
43        return new self(new Resolver($dirs), new Environment(), true, $defaults, $trusted);
44    }
45
46    /**
47     * @psalm-param DirsInput $dirs
48     * @param list<class-string> $trusted
49     */
50    public static function unescaped(
51        array|string $dirs,
52        array $defaults = [],
53        array $trusted = [],
54    ): self {
55        return new self(new Resolver($dirs), new Environment(), false, $defaults, $trusted);
56    }
57
58    /** @param non-empty-string $name */
59    public function method(string $name, callable $callable, bool $safe = false): static
60    {
61        $this->methods->add($name, $callable, $safe);
62
63        return $this;
64    }
65
66    public function wrapper(): Contract\Wrapper
67    {
68        return $this->wrapper ??= $this->environment->wrapper();
69    }
70
71    public function escape(string $name, Contract\Escaper $with): static
72    {
73        $this->environment->registerEscaper($name, $with);
74
75        return $this;
76    }
77
78    public function filter(string $name, Contract\Filter $with): static
79    {
80        $this->environment->registerFilter($name, $with);
81
82        return $this;
83    }
84
85    /** @param non-empty-string $path */
86    public function template(string $path): Template
87    {
88        $file = $this->resolve($path);
89        $template = new Template($file, engine: $this);
90        $template->setMethods($this->methods);
91
92        return $template;
93    }
94
95    /** @param non-empty-string $path */
96    public function render(
97        string $path,
98        array $context = [],
99    ): string {
100        return $this->renderTemplate($path, $context, $this->autoescape);
101    }
102
103    /** @param non-empty-string $path */
104    public function renderEscaped(
105        string $path,
106        array $context = [],
107    ): string {
108        return $this->renderTemplate($path, $context, true);
109    }
110
111    /** @param non-empty-string $path */
112    public function renderUnescaped(
113        string $path,
114        array $context = [],
115    ): string {
116        return $this->renderTemplate($path, $context, false);
117    }
118
119    /** @param non-empty-string $path */
120    private function renderTemplate(
121        string $path,
122        array $context,
123        bool $autoescape,
124    ): string {
125        $template = $this->template($path);
126        $context = $this->defaults === []
127            ? $context
128            : array_merge($this->defaults, $context);
129
130        return $autoescape
131            ? $template->renderEscaped($context, $this->trusted)
132            : $template->renderUnescaped($context, $this->trusted);
133    }
134
135    /**
136     * @param non-empty-string $path
137     *
138     * @return non-empty-string
139     */
140    public function resolve(string $path): string
141    {
142        return $this->resolver->resolve($path);
143    }
144
145    /** @param non-empty-string $path */
146    public function exists(string $path): bool
147    {
148        try {
149            $this->resolve($path);
150
151            return true;
152        } catch (LookupException|UnexpectedValueException) {
153            return false;
154        }
155    }
156}