Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
Location
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
5 / 5
14
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fromThrowable
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 fromBacktrace
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 __toString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 normalizeLine
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Boiler;
6
7use Throwable;
8
9/** @api */
10final readonly class Location
11{
12    /** Covers the template helpers, whose call from the template is at most the fourth frame. */
13    private const int NEAREST_FRAMES = 5;
14
15    public function __construct(
16        public string $path,
17        public ?int $line = null,
18    ) {}
19
20    public static function fromThrowable(string $path, Throwable $throwable): self
21    {
22        if ($throwable->getFile() === $path) {
23            return new self($path, self::normalizeLine($throwable->getLine()));
24        }
25
26        foreach ($throwable->getTrace() as $frame) {
27            if (($frame['file'] ?? null) !== $path) {
28                continue;
29            }
30
31            return new self($path, self::normalizeLine($frame['line'] ?? null));
32        }
33
34        return new self($path);
35    }
36
37    public static function fromBacktrace(string $path): self
38    {
39        // A full backtrace costs more with every nested include, so the nearest
40        // frames are searched first; 0 lifts the limit.
41        foreach ([self::NEAREST_FRAMES, 0] as $limit) {
42            foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit) as $frame) {
43                if (($frame['file'] ?? null) !== $path) {
44                    continue;
45                }
46
47                return new self($path, self::normalizeLine($frame['line'] ?? null));
48            }
49        }
50
51        return new self($path);
52    }
53
54    public function __toString(): string
55    {
56        return $this->line === null
57            ? $this->path
58            : "{$this->path}:{$this->line}";
59    }
60
61    private static function normalizeLine(?int $line): ?int
62    {
63        return $line !== null && $line > 0 ? $line : null;
64    }
65}