Passed
Push — master ( 904fca...2ba06e )
by butschster
07:38
created

Builder::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 0
c 0
b 0
f 0
dl 0
loc 5
ccs 1
cts 1
cp 1
rs 10
cc 1
nc 1
nop 3
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Stempler;
6
7
use Spiral\Stempler\Compiler\Result;
8
use Spiral\Stempler\Exception\CompilerException;
9
use Spiral\Stempler\Exception\ContextExceptionInterface;
10
use Spiral\Stempler\Exception\LoaderException;
11
use Spiral\Stempler\Exception\ParserException;
12
use Spiral\Stempler\Lexer\StringStream;
13
use Spiral\Stempler\Lexer\Token;
14
use Spiral\Stempler\Loader\LoaderInterface;
15
use Spiral\Stempler\Loader\Source;
16
use Spiral\Stempler\Node\Template;
17
use Spiral\Stempler\Parser\Context;
18
19
/**
20
 * Builds and compiles templates using set given compiler. Template is passed thought the set of
21
 * visitors each within specific group.
22
 */
23
final class Builder
24
{
25
    // node visiting stages
26
    public const STAGE_PREPARE   = 0;
27
    public const STAGE_TRANSFORM = 1;
28
    public const STAGE_FINALIZE  = 2;
29
    public const STAGE_COMPILE   = 3;
30
31
    /** @var VisitorInterface[][] */
32
    private array $visitors = [];
33
34 113
    public function __construct(
35
        private readonly LoaderInterface $loader,
36
        private readonly Parser $parser = new Parser(),
37
        private readonly Compiler $compiler = new Compiler()
38
    ) {
39 113
    }
40
41 2
    public function getLoader(): LoaderInterface
42
    {
43 2
        return $this->loader;
44
    }
45
46 113
    public function getParser(): Parser
47
    {
48 113
        return $this->parser;
49
    }
50
51 113
    public function getCompiler(): Compiler
52
    {
53 113
        return $this->compiler;
54
    }
55
56
    /**
57
     * Add visitor to specific builder stage.
58
     */
59 111
    public function addVisitor(VisitorInterface $visitor, int $stage = self::STAGE_PREPARE): void
60
    {
61 111
        $this->visitors[$stage][] = $visitor;
62
    }
63
64
    /**
65
     * Compile template.
66
     *
67
     * @throws CompilerException
68
     * @throws \Throwable
69
     */
70 74
    public function compile(string $path): Result
71
    {
72 74
        $tpl = $this->load($path);
73
74 63
        return $this->compileTemplate($tpl);
75
    }
76
77
    /**
78
     * @throws ContextExceptionInterface
79
     * @throws \Throwable
80
     */
81 63
    public function compileTemplate(Template $tpl): Result
82
    {
83
        try {
84 63
            if (isset($this->visitors[self::STAGE_COMPILE])) {
85
                $traverser = new Traverser($this->visitors[self::STAGE_COMPILE]);
86
                $tpl = $traverser->traverse([$tpl])[0];
87
            }
88
89 63
            return $this->compiler->compile($tpl);
90
        } catch (CompilerException $e) {
91
            throw $this->mapException($e);
92
        }
93
    }
94
95
    /**
96
     * @throws \Throwable
97
     */
98 93
    public function load(string $path): Template
99
    {
100 93
        $source = $this->loader->load($path);
101 92
        $stream = new StringStream($source->getContent());
102
103
        try {
104 92
            $tpl = $this->parser->withPath($path)->parse($stream);
105 91
            $tpl->setContext(new Context(
106 91
                new Token(Token::TYPE_RAW, 0, ''),
107 91
                $path
108 91
            ));
109 3
        } catch (ParserException $e) {
110 3
            throw $this->mapException($e);
111
        }
112
113
        try {
114 91
            return $this->process($tpl);
115 9
        } catch (ContextExceptionInterface $e) {
116 9
            throw $this->mapException($e);
117
        } catch (\Throwable $e) {
118
            throw $e;
119
        }
120
    }
121
122
    /**
123
     * @throws \Throwable
124
     */
125 91
    private function process(Template $template): Template
126
    {
127 91
        if (isset($this->visitors[self::STAGE_PREPARE])) {
128 90
            $traverser = new Traverser($this->visitors[self::STAGE_PREPARE]);
129 90
            $template = $traverser->traverse([$template])[0];
130
        }
131
132 91
        if (isset($this->visitors[self::STAGE_TRANSFORM])) {
133 63
            $traverser = new Traverser($this->visitors[self::STAGE_TRANSFORM]);
134 63
            $template = $traverser->traverse([$template])[0];
135
        }
136
137 83
        if (isset($this->visitors[self::STAGE_FINALIZE])) {
138 59
            $traverser = new Traverser($this->visitors[self::STAGE_FINALIZE]);
139 59
            $template = $traverser->traverse([$template])[0];
140
        }
141
142 82
        return $template;
143
    }
144
145
    /**
146
     * Set exception path and line.
147
     */
148 10
    private function mapException(ContextExceptionInterface $e): ContextExceptionInterface
149
    {
150 10
        if ($e->getContext()->getPath() === null) {
151
            return $e;
152
        }
153
154
        try {
155 10
            $source = $this->loader->load($e->getContext()->getPath());
156
        } catch (LoaderException) {
157
            return $e;
158
        }
159
160 10
        if ($source->getFilename() === null) {
161
            return $e;
162
        }
163
164 10
        $e->setLocation(
165 10
            $source->getFilename(),
166 10
            Source::resolveLine($source->getContent(), $e->getContext()->getToken()->offset)
0 ignored issues
show
Bug introduced by
It seems like $e->getContext()->getToken()->offset can also be of type null; however, parameter $offset of Spiral\Stempler\Loader\Source::resolveLine() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

166
            Source::resolveLine($source->getContent(), /** @scrutinizer ignore-type */ $e->getContext()->getToken()->offset)
Loading history...
167 10
        );
168
169 10
        return $e;
170
    }
171
}
172