ClassFile::parse()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 12
nc 3
nop 0
dl 0
loc 20
rs 9.8666
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of di
5
 *
6
 * For the full copyright and license information, please view the LICENSE.md
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Slick\Di\DefinitionLoader\AutowireDefinitionLoader;
13
14
/**
15
 * ClassFile
16
 *
17
 * @package Slick\Di\DefinitionLoader\AutowireDefinitionLoader
18
 */
19
final class ClassFile
20
{
21
    private string $content;
22
    private ?string $namespace = null;
23
    private ?string $className = null;
24
    private array $interfaces = [];
25
26
    private ?string $parentClass = null;
27
28
    /**
29
     * Creates a ClassFile object
30
     *
31
     * @param string $file The path of the file to be read.
32
     */
33
    public function __construct(string $file)
34
    {
35
        $this->content = file_get_contents($file);
36
        $this->parse();
37
    }
38
39
    /**
40
     * Is this PHP file a class
41
     *
42
     * @return bool
43
     */
44
    public function isAClass(): bool
45
    {
46
        return is_scalar($this->className);
47
    }
48
49
    /**
50
     * Class namespace
51
     *
52
     * @return string|null
53
     */
54
    public function namespace(): ?string
55
    {
56
        return $this->namespace;
57
    }
58
59
    /**
60
     * FQCN class name
61
     *
62
     * @return string|null
63
     */
64
    public function className(): ?string
65
    {
66
        return $this->className;
67
    }
68
69
    /**
70
     * List of FQCN for the implemented interfaces
71
     *
72
     * @return array
73
     */
74
    public function interfaces(): array
75
    {
76
        return $this->interfaces;
77
    }
78
79
    /**
80
     * FQCN of the parent class
81
     *
82
     * @return string|null
83
     */
84
    public function parentClass(): ?string
85
    {
86
        return $this->parentClass;
87
    }
88
89
    /**
90
     * Parses file contents
91
     *
92
     * @return void
93
     */
94
    private function parse(): void
95
    {
96
        $regex = '/class (?<name>\w*)(\sextends\s(?<parent>\w*))?(\simplements\s(?<interfaces>[\w\,\s\\\]*)\s)/i';
97
        if (preg_match($regex, $this->content, $matches) === false) {
98
            return;
99
        }
100
101
        if (!isset($matches['name'])) {
102
            return;
103
        }
104
105
        $namespaceRegEx = '/namespace(?<namespace>(.*));/i';
106
        preg_match($namespaceRegEx, $this->content, $found);
107
108
        $this->namespace = trim($found['namespace']);
109
        $this->className = $this->namespace . "\\" . trim($matches['name']) ?? null;
110
111
        $interfaces = $this->clearNames($matches['interfaces'] ? explode(',', $matches['interfaces']) : []);
112
        $parentClass = $matches['parent'] ?? null;
113
        $this->parseImports($interfaces, $parentClass);
114
    }
115
116
    /**
117
     * Parses "use" statements to complete the parent class and interfaces FQCN
118
     *
119
     * @param array $interfaceNames
120
     * @param string|null $parentClass
121
     * @return void
122
     */
123
    private function parseImports(array $interfaceNames, ?string $parentClass = null): void
124
    {
125
        $usesRegex = '/use(?<uses>(.*));/i';
126
        preg_match_all($usesRegex, $this->content, $matches);
127
        $uses = empty($matches['uses']) ? [] : $matches['uses'];
128
129
        $this->interfaces = $this->pairInterfaces($interfaceNames, $uses);
130
        $this->parentClass = $parentClass ? $this->pairParentClass($uses, $parentClass) : null;
131
    }
132
133
    /**
134
     * Clears the names in the given array
135
     *
136
     * @param array $param The array containing names to be cleared.
137
     * @return array The array with cleared names.
138
     */
139
    private function clearNames(array $param): array
140
    {
141
        $clean = [];
142
        foreach ($param as $name) {
143
            $clean[] = trim(str_replace(',', '', $name));
144
        }
145
        return $clean;
146
    }
147
148
    /**
149
     * Filter and pair interface names with their corresponding uses
150
     *
151
     * @param array $interfaceNames The array of interface names
152
     * @param array $uses The array of use statements
153
     *
154
     * @return array The array of paired interface names with their corresponding uses
155
     */
156
    private function pairInterfaces(array $interfaceNames, array $uses): array
157
    {
158
        $interfaces = [];
159
        foreach ($interfaceNames as $interfaceName) {
160
            $found = null;
161
            foreach ($uses as $use) {
162
                if (str_contains($use, $interfaceName)) {
163
                    $found = trim($use);
164
                    break;
165
                }
166
            }
167
            if ($found) {
168
                $interfaces[] = $found;
169
                continue;
170
            }
171
172
            $interfaces[] = str_starts_with($interfaceName, "\\")
173
                ? $interfaceName
174
                : $this->namespace . "\\" . $interfaceName;
175
        }
176
        return $interfaces;
177
    }
178
179
    /**
180
     * Pair the parent class with the corresponding use statement
181
     *
182
     * @param array  $uses        An array of use statements
183
     * @param string $parentClass The parent class name
184
     *
185
     * @return string The fully qualified name of the parent class with the corresponding
186
     *                use statement or null if not found
187
     */
188
    private function pairParentClass(array $uses, string $parentClass): string
189
    {
190
        foreach ($uses as $use) {
191
            if (str_contains($use, $parentClass)) {
192
                return trim($use);
193
            }
194
        }
195
        return str_starts_with(trim($parentClass), "\\")
196
            ? trim($parentClass)
197
            : $this->namespace . "\\" . trim($parentClass);
198
    }
199
200
    /**
201
     * Checks if the class is an implementation by
202
     * verifying if the parent class or interfaces
203
     * are present.
204
     *
205
     * @return bool
206
     */
207
    public function isAnImplementation(): bool
208
    {
209
        return $this->isAClass() && !is_null($this->parentClass) || !empty($this->interfaces);
210
    }
211
}
212