ClassFinder::searchDirectories()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 8
cts 8
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 3
nop 2
crap 4
1
<?php
2
3
namespace Riimu\Kit\ClassLoader;
4
5
/**
6
 * Provides method for searching class files in the file system.
7
 * @author Riikka Kalliomäki <[email protected]>
8
 * @copyright Copyright (c) 2015-2017 Riikka Kalliomäki
9
 * @license http://opensource.org/licenses/mit-license.php MIT License
10
 */
11
class ClassFinder
12
{
13
    /** @var string[] List of file extensions used to find files */
14
    private $fileExtensions;
15
16
    /**
17
     * Creates a new PathFinder instance.
18
     */
19 96
    public function __construct()
20
    {
21 96
        $this->fileExtensions = ['.php'];
22 96
    }
23
24
    /**
25
     * Sets list of dot included file extensions to use for finding files.
26
     *
27
     * If no list of extensions is provided, the extension array defaults to
28
     * just '.php'.
29
     *
30
     * @param string[] $extensions Array of dot included file extensions to use
31
     */
32 3
    public function setFileExtensions(array $extensions)
33
    {
34 3
        $this->fileExtensions = $extensions;
35 3
    }
36
37
    /**
38
     * Attempts to find a file for the given class from given paths.
39
     *
40
     * Both lists of paths must be given as arrays with keys indicating the
41
     * namespace. Empty string can be used for the paths that apply to all
42
     * Classes. Each value must be an array of paths.
43
     *
44
     * @param string $class Full name of the class
45
     * @param array $prefixPaths List of paths used for PSR-4 file search
46
     * @param array $basePaths List of paths used for PSR-0 file search
47
     * @param bool $useIncludePath Whether to use paths in include_path for PSR-0 search or not
48
     * @return string|false Path to the class file or false if not found
49
     */
50 60
    public function findFile($class, array $prefixPaths, array $basePaths = [], $useIncludePath = false)
51
    {
52 60
        if ($file = $this->searchNamespaces($prefixPaths, $class, true)) {
53 3
            return $file;
54
        }
55
56 60
        $class = preg_replace('/_(?=[^\\\\]*$)/', '\\', $class);
57
58 60
        if ($file = $this->searchNamespaces($basePaths, $class, false)) {
59 36
            return $file;
60 27
        } elseif ($useIncludePath) {
61 3
            return $this->searchDirectories(explode(PATH_SEPARATOR, get_include_path()), $class);
62
        }
63
64 27
        return false;
65
    }
66
67
    /**
68
     * Searches for the class file from the namespaces that apply to the class.
69
     * @param array $paths All the namespace specific paths
70
     * @param string $class Canonized full class name
71
     * @param bool $truncate True to remove the namespace from the path
72
     * @return string|false Path to the class file or false if not found
73
     */
74 60
    private function searchNamespaces($paths, $class, $truncate)
75
    {
76 60
        foreach ($paths as $namespace => $directories) {
77 48
            $canonized = $this->canonizeClass($namespace, $class, $truncate);
78
79 48
            if ($canonized && $file = $this->searchDirectories($directories, $canonized)) {
80 45
                return $file;
81
            }
82 20
        }
83
84 60
        return false;
85
    }
86
87
    /**
88
     * Matches the class against the namespace and canonizes the name as needed.
89
     * @param string $namespace Namespace to match against
90
     * @param string $class Full name of the class
91
     * @param bool $truncate Whether to remove the namespace from the class
92
     * @return string|false Canonized class name or false if it does not match the namespace
93
     */
94 48
    private function canonizeClass($namespace, $class, $truncate)
95
    {
96 48
        $class = ltrim($class, '\\');
97 48
        $namespace = (string) $namespace;
98
99 48
        $namespace = $namespace === '' ? '' : trim($namespace, '\\') . '\\';
100
101 48
        if (strncmp($class, $namespace, strlen($namespace)) !== 0) {
102 3
            return false;
103
        }
104
105 48
        return $truncate ? substr($class, strlen($namespace)) : $class;
106
    }
107
108
    /**
109
     * Searches for the class file in the list of directories.
110
     * @param string[] $directories List of directory paths where to look for the class
111
     * @param string $class Part of the class name that translates to the file name
112
     * @return string|false Path to the class file or false if not found
113
     */
114 51
    private function searchDirectories(array $directories, $class)
115
    {
116 51
        foreach ($directories as $directory) {
117 51
            $directory = trim($directory);
118 51
            $path = preg_replace('/[\\/\\\\]+/', DIRECTORY_SEPARATOR, $directory . '/' . $class);
119
120 51
            if ($directory && $file = $this->searchExtensions($path)) {
121 48
                return $file;
122
            }
123 5
        }
124
125 15
        return false;
126
    }
127
128
    /**
129
     * Searches for the class file using known file extensions.
130
     * @param string $path Path to the class file without the file extension
131
     * @return string|false Path to the class file or false if not found
132
     */
133 48
    private function searchExtensions($path)
134
    {
135 48
        foreach ($this->fileExtensions as $ext) {
136 48
            if (file_exists($path . $ext)) {
137 46
                return $path . $ext;
138
            }
139 4
        }
140
141 12
        return false;
142
    }
143
}
144