Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
20.00% covered (danger)
20.00%
15 / 75
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ChangeEnumerator
20.00% covered (danger)
20.00%
15 / 75
0.00% covered (danger)
0.00%
0 / 3
269.81
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 determineReplacements
21.13% covered (danger)
21.13%
15 / 71
0.00% covered (danger)
0.00%
0 / 1
216.27
 determineNamespaceReplacement
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Determine the replacements to be made to the discovered symbols.
4 *
5 * Typically, this will just be a prefix, but more complex rules allow for replacements specific to individual symbols/namespaces.
6 */
7
8namespace BrianHenryIE\Strauss\Pipeline;
9
10use BrianHenryIE\Strauss\Config\ChangeEnumeratorConfigInterface;
11use BrianHenryIE\Strauss\Types\ClassSymbol;
12use BrianHenryIE\Strauss\Types\DiscoveredSymbols;
13use BrianHenryIE\Strauss\Types\NamespaceSymbol;
14use Psr\Log\LoggerAwareTrait;
15use Psr\Log\LoggerInterface;
16
17class ChangeEnumerator
18{
19    use LoggerAwareTrait;
20
21    protected ChangeEnumeratorConfigInterface $config;
22
23    public function __construct(
24        ChangeEnumeratorConfigInterface $config,
25        LoggerInterface $logger
26    ) {
27        $this->config = $config;
28        $this->setLogger($logger);
29    }
30
31    public function determineReplacements(DiscoveredSymbols $discoveredSymbols): void
32    {
33        $discoveredNamespaces = $discoveredSymbols->getDiscoveredNamespaces();
34
35        foreach ($discoveredNamespaces as $symbol) {
36            // This line seems redundant.
37            if ($symbol instanceof NamespaceSymbol) {
38                $namespaceReplacementPatterns = $this->config->getNamespaceReplacementPatterns();
39
40                if (in_array(
41                    $symbol->getOriginalSymbol(),
42                    $this->config->getExcludeNamespacesFromPrefixing(),
43                    true
44                )) {
45                    $symbol->setDoRename(false);
46                }
47
48                // `namespace_prefix` is just a shorthand for a replacement pattern that applies to all namespaces.
49
50                // TODO: Maybe need to preg_quote and add regex delimiters to the patterns here.
51                foreach ($namespaceReplacementPatterns as $pattern => $replacement) {
52                    if (substr($pattern, 0, 1) !== substr($pattern, - 1, 1)) {
53                        unset($namespaceReplacementPatterns[ $pattern ]);
54                        $pattern                                  = '~' . preg_quote($pattern, '~') . '~';
55                        $namespaceReplacementPatterns[ $pattern ] = $replacement;
56                    }
57                    unset($pattern, $replacement);
58                }
59
60                if (! is_null($this->config->getNamespacePrefix())) {
61                    $stripPattern   = '~^(' . preg_quote($this->config->getNamespacePrefix(), '~') . '\\\\*)*(.*)~';
62                    $strippedSymbol = preg_replace(
63                        $stripPattern,
64                        '$2',
65                        $symbol->getOriginalSymbol()
66                    );
67                    $namespaceReplacementPatterns[ "~(" . preg_quote($this->config->getNamespacePrefix(), '~') . '\\\\*)*' . preg_quote($strippedSymbol, '~') . '~' ]
68                                    = "{$this->config->getNamespacePrefix()}\\{$strippedSymbol}";
69                    unset($stripPattern, $strippedSymbol);
70                }
71
72                // `namespace_replacement_patterns` should be ordered by priority.
73                foreach ($namespaceReplacementPatterns as $namespaceReplacementPattern => $replacement) {
74                    $prefixed = preg_replace(
75                        $namespaceReplacementPattern,
76                        $replacement,
77                        $symbol->getOriginalSymbol()
78                    );
79
80                    if ($prefixed !== $symbol->getOriginalSymbol()) {
81                        $symbol->setReplacement($prefixed);
82                        continue 2;
83                    }
84                }
85                $this->logger->debug("Namespace {$symbol->getOriginalSymbol()} not changed.");
86            }
87        }
88
89        $classmapPrefix = $this->config->getClassmapPrefix();
90
91
92        $classesTraitsInterfaces = array_merge(
93            $discoveredSymbols->getDiscoveredTraits(),
94            $discoveredSymbols->getDiscoveredInterfaces(),
95            $discoveredSymbols->getAllClasses()
96        );
97
98        foreach ($classesTraitsInterfaces as $symbol) {
99            if (str_starts_with($symbol->getOriginalSymbol(), $classmapPrefix)) {
100                // Already prefixed / second scan.
101                continue;
102            }
103
104            if ($symbol->getNamespace() === '\\') {
105                if ($symbol instanceof ClassSymbol) {
106                    // Don't double-prefix classnames.
107                    if (str_starts_with($symbol->getOriginalSymbol(), $this->config->getClassmapPrefix())) {
108                        continue;
109                    }
110
111                    $symbol->setReplacement($this->config->getClassmapPrefix() . $symbol->getOriginalSymbol());
112                }
113            }
114
115            // If we're a namespaced class, apply the fqdnchange.
116            if ($symbol->getNamespace() !== '\\') {
117                if (isset($discoveredNamespaces[$symbol->getNamespace()])) {
118                    $newNamespace = $discoveredNamespaces[$symbol->getNamespace()];
119                    $replacement = $this->determineNamespaceReplacement(
120                        $newNamespace->getOriginalSymbol(),
121                        $newNamespace->getReplacement(),
122                        $symbol->getOriginalSymbol()
123                    );
124
125                    $symbol->setReplacement($replacement);
126
127                    unset($newNamespace, $replacement);
128                }
129                continue;
130            } else {
131                // Global class.
132                $replacement = $classmapPrefix . $symbol->getOriginalSymbol();
133                $symbol->setReplacement($replacement);
134            }
135        }
136
137        $functionsSymbols = $discoveredSymbols->getDiscoveredFunctions();
138
139        foreach ($functionsSymbols as $symbol) {
140            // Don't prefix functions in a namespace – that will be addressed by the namespace prefix.
141            if ($symbol->getNamespace() !== '\\') {
142                continue;
143            }
144            $functionPrefix = $this->config->getFunctionsPrefix();
145            if (empty($functionPrefix) || str_starts_with($symbol->getOriginalSymbol(), $functionPrefix)) {
146                continue;
147            }
148
149            $symbol->setReplacement($functionPrefix . $symbol->getOriginalSymbol());
150        }
151    }
152
153    /**
154     *`str_replace` was replacing multiple. This stops after one. Maybe should be tied to start of string.
155     */
156    protected function determineNamespaceReplacement(string $originalNamespace, string $newNamespace, string $fqdnClassname): string
157    {
158        $search = '/' . preg_quote($originalNamespace, '/') . '/';
159
160        return preg_replace($search, $newNamespace, $fqdnClassname, 1);
161    }
162}