Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
CRAP
100.00% covered (success)
100.00%
1 / 1
NamespaceImportResolver
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
1 / 1
 resolve
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Code\Reflection\Support;
16
17/**
18 * Namespace import resolver class
19 *
20 * Tracks which short class names have already been claimed by a `use`-import within one
21 * *Reflection::parse() call, and decides -- for each new fully-qualified class name a parent
22 * class, interface, or attribute needs to reference -- whether it can be imported (and rendered
23 * by its bare short name) or must be referenced by its fully-qualified name instead, because
24 * another class with the same short name from a *different* namespace already claimed it. Two
25 * `use` statements for different classes sharing one short name is a PHP fatal error ("Cannot use
26 * X as Y because the name is already in use"), so the second one must fall back to its FQCN
27 * instead of colliding.
28 *
29 * One instance is meant to live for the duration of a single *Reflection::parse() call, shared
30 * across every parent/interface/attribute reference that call detects, so a collision between
31 * (for example) the parent class's short name and an attribute's short name is caught too, not
32 * just collisions among attributes alone.
33 *
34 * @category   Pop
35 * @package    Pop\Code
36 * @author     Nick Sagona, III <dev@noladev.com>
37 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
38 * @license    https://www.popphp.org/license     New BSD License
39 * @version    6.0.0
40 */
41class NamespaceImportResolver
42{
43
44    /**
45     * Short names already claimed by this resolver, each mapped to the FQCN that claimed it
46     * @var array
47     */
48    protected array $claimed = [];
49
50    /**
51     * Resolve a fully-qualified class name into [reference, needsImport]
52     *
53     * `$reference` is what should be rendered in place of the class name (a bare short name, or
54     * a fully-qualified name with a leading backslash). `$needsImport` is whether the caller
55     * should add a `use $fqcn;` import for it.
56     *
57     * @param  string $fqcn
58     * @param  string $ownNamespace  the namespace of the construct being reflected
59     * @return array
60     */
61    public function resolve(string $fqcn, string $ownNamespace): array
62    {
63        $lastSlash = strrpos($fqcn, '\\');
64
65        if ($lastSlash === false) {
66            // A genuinely root-namespace class (no backslash at all) is always unambiguous with
67            // a leading backslash, regardless of where it's referenced from -- it never needs an
68            // import and can never collide with one, unlike a namespaced class whose bare short
69            // name could shadow another import.
70            return ['\\' . $fqcn, false];
71        }
72
73        $namespacePart = substr($fqcn, 0, $lastSlash);
74        $short         = substr($fqcn, $lastSlash + 1);
75
76        if ($namespacePart === $ownNamespace) {
77            return [$short, false];
78        }
79
80        if (isset($this->claimed[$short])) {
81            return ($this->claimed[$short] === $fqcn) ? [$short, false] : ['\\' . $fqcn, false];
82        }
83
84        $this->claimed[$short] = $fqcn;
85
86        return [$short, true];
87    }
88
89}