Completed
Push — master ( 9e2471...10c12e )
by Ryuichi
02:53
created

ClassLoader::loadClassList()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 1
crap 3
1
<?php
2
namespace WebStream\ClassLoader;
3
4
use WebStream\DI\Injector;
5
use WebStream\IO\File;
6
7
/**
8
 * クラスローダ
9
 * @author Ryuichi TANAKA.
10
 * @since 2013/09/02
11
 * @version 0.7
12
 */
13
class ClassLoader
14
{
15
    use Injector;
16
17
    /**
18
     * @var Psr\Log\LoggerInterface
19
     */
20
    private $logger;
21
22
    /**
23
     * @var string アプリケーションルートパス
24
     */
25
    private $applicationRoot;
26
27
    /**
28
     * @var array<string> サブディレクトリパスリスト
29
     */
30
    private $subDirectoryPathList;
31
32
    /**
33
     * constructor
34
     * @param string アプリケーションルートパス
35
     * @param array<string> サブディレクトリパスリスト
36
     */
37
    public function __construct(string $applicationRoot, $subDirectoryPathList = [])
38
    {
39
        $this->logger = new class() { function __call($name, $args) {} };
0 ignored issues
show
Documentation Bug introduced by
It seems like new class { function...e, $args) { } } of type object<WebStream\ClassLo...ous//ClassLoader.php$0> is incompatible with the declared type object<WebStream\ClassLo...sr\Log\LoggerInterface> of property $logger.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
40 15
        $this->applicationRoot = $applicationRoot;
41 15
        $this->subDirectoryPathList = $subDirectoryPathList;
42 15
    }
43
44
    /**
45
     * クラスをロードする
46
     * @param mixed クラスまたはクラスリスト
47
     * @return array<string> ロード済みクラスリスト
48
     */
49 8
    public function load($target): array
50
    {
51 8
        return is_array($target) ? $this->loadClassList($target) : $this->loadClass($target);
52
    }
53
54
    /**
55
     * ファイルをインポートする
56
     * @param string ファイルパス
57
     * @param callable フィルタリング無名関数 trueを返すとインポート
58
     * @return bool インポート結果
59
     */
60 4
    public function import($filepath, callable $filter = null): bool
61
    {
62 4
        $file = new File($this->applicationRoot . "/" . $filepath);
63 4
        if ($file->isFile()) {
64 3
            if ($file->getFileExtension() === 'php') {
65 3
                if ($filter === null || (is_callable($filter) && $filter($file->getFilePath()) === true)) {
66 2
                    include_once $file->getFilePath();
67 2
                    $this->logger->debug($file->getFilePath() . " import success.");
68
                }
69
            }
70
71 3
            return true;
72
        }
73
74 1
        return false;
75
    }
76
77
    /**
78
     * 指定ディレクトリのファイルをインポートする
79
     * @param string ディレクトリパス
80
     * @param callable フィルタリング無名関数 trueを返すとインポート
81
     * @return bool インポート結果
82
     */
83 3
    public function importAll($dirPath, callable $filter = null): bool
84
    {
85 3
        $dir = new File($this->applicationRoot . "/" . $dirPath);
86 3
        if ($dir->isDirectory()) {
87 3
            $iterator = $this->getFileSearchIterator($dir->getFilePath());
88 3
            $isSuccess = true;
89 3
            foreach ($iterator as $filepath => $fileObject) {
90 3
                if (preg_match("/(?:\/\.|\/\.\.|\.DS_Store)$/", $filepath)) {
91 3
                    continue;
92
                }
93 3
                $file = new File($filepath);
94 3
                if ($file->isFile()) {
95 3
                    if ($file->getFileExtension() === 'php') {
96 3
                        if ($filter === null || (is_callable($filter) && $filter($file->getFilePath()) === true)) {
97 2
                            include_once $file->getFilePath();
98 3
                            $this->logger->debug($file->getFilePath() . " import success.");
99
                        }
100
                    }
101
                } else {
102
                    $this->logger->warn($filepath . " import failure.");
103 3
                    $isSuccess = false;
104
                }
105
            }
106
        }
107
108 3
        return $isSuccess;
0 ignored issues
show
Bug introduced by
The variable $isSuccess does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
109
    }
110
111
    /**
112
    * ロード可能なクラスを返却する
113
    * @param string クラス名(フルパス指定の場合はクラスパス)
114
    * @return array<string> ロード可能クラス
115
     */
116 8
    private function loadClass(string $className): array
117
    {
118 8
        $rootDir = $this->applicationRoot;
119 8
        $logger = $this->logger;
120
121
        // 名前空間セパレータをパスセパレータに置換
122 8
        if (DIRECTORY_SEPARATOR === '/') {
123 8
            $className = str_replace("\\", DIRECTORY_SEPARATOR, $className);
124
        }
125
126 8
        $search = function ($dirPath, $searchFilePath) use ($logger) {
127 8
            $includeList = [];
128 8
            $dir = new File($dirPath);
129 8
            if (!$dir->isDirectory()) {
130 1
                $logger->error("Invalid search directory path: " . $dir->getFilePath());
131 1
                return $includeList;
132
            }
133 7
            $iterator = $this->getFileSearchIterator($dir->getFilePath());
134 7
            foreach ($iterator as $filepath => $fileObject) {
135 7
                if (!$fileObject->isFile()) {
136 7
                    continue;
137
                }
138 7
                if (strpos($filepath, $searchFilePath) !== false) {
139 6
                    $file = new File($filepath);
140 6
                    $absoluteFilePath = $file->getAbsoluteFilePath();
141 6
                    include_once $absoluteFilePath;
142 6
                    $includeList[] = $absoluteFilePath;
143 7
                    $logger->debug($absoluteFilePath . " load success. (search from " . $dir->getFilePath());
144
                }
145
            }
146
147 7
            return $includeList;
148 8
        };
149
150 8
        $includeList = $search("${rootDir}", DIRECTORY_SEPARATOR . "${className}.php");
151 8
        if (!empty($includeList)) {
152 6
            return $includeList;
153
        }
154
155 2
        foreach ($this->subDirectoryPathList as $searchPath) {
156
            if (preg_match("/(?:.*\/){0,}(.+)/", $className, $matches)) {
157
                $classNameWithoutNamespace = $matches[1];
158
                $includeList = $search("${rootDir}/${searchPath}", DIRECTORY_SEPARATOR . "${classNameWithoutNamespace}.php");
159
                if (!empty($includeList)) {
160
                    return $includeList;
161
                }
162
            }
163
        }
164
165 2
        return $includeList;
166
    }
167
168
    /**
169
     * ロード可能なクラスを複数返却する
170
     * @param array クラス名
171
     * @return array<string> ロード済みクラスリスト
172
     */
173
    private function loadClassList(array $classList): array
174
    {
175 1
        $includedlist = [];
176 1
        foreach ($classList as $className) {
177 1
            $result = $this->loadClass($className);
178 1
            if (is_array($result)) {
179 1
                $includedlist = array_merge($includedlist, $result);
180
            }
181
        }
182
183 1
        return $includedlist;
184
    }
185
186
    /**
187
     * ファイル検索イテレータを返却する
188
     * @param string ディレクトリパス
189
     * @return RecursiveIteratorIterator イテレータ
190
     */
191
    private function getFileSearchIterator(string $path): \RecursiveIteratorIterator
192
    {
193 10
        $iterator = [];
194 10
        $file = new File($path);
195 10
        if ($file->isDirectory()) {
196 10
            $iterator = new \RecursiveIteratorIterator(
197 10
                new \RecursiveDirectoryIterator($path),
198 10
                \RecursiveIteratorIterator::LEAVES_ONLY,
199 10
                \RecursiveIteratorIterator::CATCH_GET_CHILD // for Permission deny
200
            );
201
        }
202 10
        return $iterator;
203
    }
204
}
205