Passed
Push — v9 ( e84ae2...80c47f )
by Georges
03:02
created

ClassNamespaceResolverTrait::createClassMap()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 13
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 22
rs 9.2222
1
<?php
2
3
/**
4
 *
5
 * This file is part of Phpfastcache.
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For full copyright and license information, please see the docs/CREDITS.txt and LICENCE files.
10
 *
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 * @author Contributors  https://github.com/PHPSocialNetwork/phpfastcache/graphs/contributors
13
 */
14
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Util;
18
19
use Iterator;
20
use RecursiveDirectoryIterator;
21
use RecursiveIteratorIterator;
22
23
trait ClassNamespaceResolverTrait
24
{
25
    /**
26
     * @var array<string, string>
27
     */
28
    protected static array $namespaces = [];
29
30
    /**
31
     * Iterate over all files in the given directory searching for classes.
32
     *
33
     * NOTICE: This method has been borrowed from Symfony ClassLoader 3.4 since they
34
     * deprecated the whole component as of SF4. Our thanks to them.
35
     *
36
     * @param string $dir The directory to search in or an iterator
37
     *
38
     * @return array<string, string> A class map array
39
     */
40
    protected static function createClassMap(string $dir): array
41
    {
42
        $dirIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
43
44
        $map = [];
45
46
        foreach ($dirIterator as $file) {
47
            if (!$file->isFile()) {
48
                continue;
49
            }
50
            $path = $file->getRealPath() ?: $file->getPathname();
51
            if ('php' !== pathinfo($path, PATHINFO_EXTENSION)) {
52
                continue;
53
            }
54
            $classes = self::findClasses($path);
55
            gc_mem_caches();
56
            foreach ($classes as $class) {
57
                $map[$class] = $path;
58
            }
59
        }
60
61
        return $map;
62
    }
63
64
    /**
65
     * Extract the classes in the given file.
66
     *
67
     * NOTICE: This method has been borrowed from Symfony ClassLoader 3.4 since they
68
     * deprecated the whole component as of SF4. Our thanks to them.
69
     *
70
     * @param string $path The file to check
71
     *
72
     * @return string[] The found classes
73
     *
74
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
75
     */
76
    protected static function findClasses(string $path): array
77
    {
78
        $contents = file_get_contents($path);
79
        $tokens = token_get_all($contents);
80
        $classes = [];
81
        $namespace = '';
82
        for ($i = 0; isset($tokens[$i]); ++$i) {
83
            $token = $tokens[$i];
84
            if (!isset($token[1])) {
85
                continue;
86
            }
87
            $class = '';
88
            switch ($token[0]) {
89
                case T_NAMESPACE:
90
                    $namespace = '';
91
                    // If there is a namespace, extract it (PHP 8 test)
92
                    if (\defined('T_NAME_QUALIFIED')) {
93
                        while (isset($tokens[++$i][1])) {
94
                            if ($tokens[$i][0] === T_NAME_QUALIFIED) {
95
                                $namespace = $tokens[$i][1];
96
                                break;
97
                            }
98
                        }
99
                    } else {
100
                        while (isset($tokens[++$i][1])) {
101
                            if (\in_array($tokens[$i][0], [T_STRING, T_NS_SEPARATOR], true)) {
102
                                $namespace .= $tokens[$i][1];
103
                            }
104
                        }
105
                    }
106
                    $namespace .= '\\';
107
                    break;
108
                case T_CLASS:
109
                case T_INTERFACE:
110
                case T_TRAIT:
111
                    // Skip usage of ::class constant
112
                    $isClassConstant = false;
113
                    for ($j = $i - 1; $j > 0; --$j) {
114
                        if (!isset($tokens[$j][1])) {
115
                            break;
116
                        }
117
                        if (T_DOUBLE_COLON === $tokens[$j][0]) {
118
                            $isClassConstant = true;
119
                            break;
120
                        } elseif (!\in_array($tokens[$j][0], [T_WHITESPACE, T_DOC_COMMENT, T_COMMENT], false)) {
121
                            break;
122
                        }
123
                    }
124
                    if ($isClassConstant) {
125
                        break;
126
                    }
127
                    // Find the classname
128
                    while (isset($tokens[++$i][1])) {
129
                        $t = $tokens[$i];
130
                        if (T_STRING === $t[0]) {
131
                            $class .= $t[1];
132
                        } elseif ('' !== $class && T_WHITESPACE === $t[0]) {
133
                            break;
134
                        }
135
                    }
136
                    $classes[] = ltrim($namespace . $class, '\\');
137
                    break;
138
                default:
139
                    break;
140
            }
141
        }
142
143
        return $classes;
144
    }
145
146
    /**
147
     * @return string
148
     */
149
    public static function getClassNamespace(): string
150
    {
151
        if (!isset(self::$namespaces[static::class])) {
152
            self::$namespaces[static::class] = substr(static::class, 0, strrpos(static::class, '\\'));
153
        }
154
155
        return self::$namespaces[static::class];
156
    }
157
158
    /**
159
     * @return string
160
     */
161
    public function getClassName(): string
162
    {
163
        return static::class;
164
    }
165
}
166