Code

< 40 %
40-60 %
> 60 %
1
<?php
2
3
namespace WebStream\ClassLoader;
4
5
use RecursiveIteratorIterator;
6
use WebStream\DI\Injector;
7
use WebStream\IO\File;
8
use WebStream\IO\FileInputStream;
9
10
/**
11
 * クラスローダ
12
 * @author Ryuichi TANAKA.
13
 * @since 2013/09/02
14
 * @version 0.7
15
 */
16
class ClassLoader
17
{
18
    use Injector;
19
20
    /**
21
     * @var Psr\Log\LoggerInterface
22
     */
23
    private $logger;
24
25
    /**
26
     * @var string アプリケーションルートパス
27
     */
28
    private string $applicationRoot;
29
30
    /**
31
     * constructor
32
     * @param string アプリケーションルートパス
33
     */
34 16
    public function __construct(string $applicationRoot)
35
    {
36 16
        $this->logger = new class () { public function __call($name, $args) {} };
37 16
        $this->applicationRoot = $applicationRoot;
38
    }
39
40
    /**
41
     * クラスをロードする
42
     * @param mixed クラスまたはクラスリスト
43
     * @return array<string> ロード済みクラスリスト
44
     */
45 7
    public function load($target): array
46
    {
47 7
        return is_array($target) ? $this->loadClassList($target) : $this->loadClass($target);
48
    }
49
50
    /**
51
     * ファイルをインポートする
52
     * @param string ファイルパス
53
     * @param callable|null $filter フィルタリング無名関数 trueを返すとインポート
54
     * @return bool インポート結果
55
     * @throws \WebStream\Exception\Extend\IOException
56
     */
57 3
    public function import($filepath, callable $filter = null): bool
58
    {
59 3
        $file = new File($this->applicationRoot . "/" . $filepath);
60 3
        if ($file->isFile()) {
61 2
            if ($file->getFileExtension() === 'php') {
62 2
                if ($filter === null || (is_callable($filter) && $filter($file->getFilePath()) === true)) {
63 2
                    include_once $file->getFilePath();
64 2
                    $this->logger->debug($file->getAbsoluteFilePath() . " import success.");
65
                }
66
            }
67
68 2
            return true;
69
        }
70
71 1
        return false;
72
    }
73
74
    /**
75
     * 指定ディレクトリのファイルをインポートする
76
     * @param string ディレクトリパス
77
     * @param callable|null $filter フィルタリング無名関数 trueを返すとインポート
78
     * @return bool インポート結果
79
     * @throws \WebStream\Exception\Extend\IOException
80
     */
81 3
    public function importAll($dirPath, callable $filter = null): bool
82
    {
83 3
        $dir = new File($this->applicationRoot . "/" . $dirPath);
84 3
        $isSuccess = true;
85 3
        if ($dir->isDirectory()) {
86 3
            $iterator = $this->getFileSearchIterator($dir->getAbsoluteFilePath());
87 3
            foreach ($iterator as $filepath => $fileObject) {
88 3
                if (preg_match("/(?:\/\.|\/\.\.|\.DS_Store)$/", $filepath)) {
89 3
                    continue;
90
                }
91 3
                $file = new File($filepath);
92 3
                if ($file->isFile()) {
93 3
                    if ($file->getFileExtension() === 'php') {
94 3
                        if ($filter === null || (is_callable($filter) && $filter($file->getFilePath()) === true)) {
95 1
                            include_once $file->getFilePath();
96 1
                            $this->logger->debug($file->getAbsoluteFilePath() . " import success.");
97
                        }
98
                    }
99
                } else {
100
                    $this->logger->warn($filepath . " import failure.");
101
                    $isSuccess = false;
102
                }
103
            }
104
        }
105
106 3
        return $isSuccess;
107
    }
108
109
    /**
110
     * 名前空間リストを返却する
111
     * @param string ファイル名
112
     * @return array<string> 名前空間リスト
113
     * @throws \WebStream\Exception\Extend\IOException
114
     * @throws \WebStream\Exception\Extend\InvalidArgumentException
115
     */
116 3
    public function getNamespaces($fileName): array
117
    {
118 3
        $dir = new File($this->applicationRoot);
119 3
        $namespaces = [];
120 3
        if ($dir->isDirectory()) {
121 3
            $iterator = $this->getFileSearchIterator($dir->getAbsoluteFilePath());
122 3
            foreach ($iterator as $filepath => $fileObject) {
123 3
                if (preg_match("/(?:\/\.|\/\.\.|\.DS_Store)$/", $filepath)) {
124 3
                    continue;
125
                }
126 3
                $file = new File($filepath);
127 3
                if ($file->isFile() && $file->getFileName() === $fileName) {
128 2
                    $fis = new FileInputStream($file);
129 2
                    while (($line = $fis->readLine()) !== null) {
130 2
                        if (preg_match("/^namespace\s(.*);$/", $line, $matches)) {
131 2
                            $namespaces[] = $matches[1];
132
                        }
133
                    }
134 2
                    $fis->close();
135
                }
136
            }
137
        }
138
139 3
        return $namespaces;
140
    }
141
142
    /**
143
     * ロード可能なクラスを返却する
144
     * @param string クラス名(フルパス指定の場合はクラスパス)
145
     * @return array<string> ロード可能クラス
146
     * @throws \WebStream\Exception\Extend\IOException
147
     */
148 7
    private function loadClass(string $className): array
149
    {
150 7
        $rootDir = $this->applicationRoot;
151 7
        $logger = $this->logger;
152
153
        // 名前空間セパレータをパスセパレータに置換
154 7
        if (DIRECTORY_SEPARATOR === '/') {
155 7
            $className = str_replace("\\", DIRECTORY_SEPARATOR, $className);
156
        }
157
158 7
        $search = function ($dirPath, $searchFilePath) use ($logger) {
159 7
            $includeList = [];
160 7
            $dir = new File($dirPath);
161 7
            if (!$dir->isDirectory()) {
162 1
                $logger->error("Invalid search directory path: " . $dir->getFilePath());
163 1
                return $includeList;
164
            }
165 6
            $iterator = $this->getFileSearchIterator($dir->getAbsoluteFilePath());
166 6
            foreach ($iterator as $filepath => $fileObject) {
167 6
                if (!$fileObject->isFile()) {
168 6
                    continue;
169
                }
170 6
                if (strpos($filepath, $searchFilePath) !== false) {
171 5
                    $file = new File($filepath);
172 5
                    $absoluteFilePath = $file->getAbsoluteFilePath();
173 5
                    include_once $absoluteFilePath;
174 5
                    $includeList[] = $absoluteFilePath;
175 5
                    $logger->debug($absoluteFilePath . " load success. (search from " . $dir->getAbsoluteFilePath() . ")");
176
                }
177
            }
178
179 6
            return $includeList;
180 7
        };
181
182 7
        return $search("${rootDir}", DIRECTORY_SEPARATOR . "${className}.php");
183
    }
184
185
    /**
186
     * ロード可能なクラスを複数返却する
187
     * @param array クラス名
188
     * @return array<string> ロード済みクラスリスト
189
     * @throws \WebStream\Exception\Extend\IOException
190
     */
191
    private function loadClassList(array $classList): array
192
    {
193
        $includedList = [];
194
        foreach ($classList as $className) {
195
            $result = $this->loadClass($className);
196
            if (is_array($result)) {
197
                $includedList = array_merge($includedList, $result);
198
            }
199
        }
200
201
        return $includedList;
202
    }
203
204
    /**
205
     * ファイル検索イテレータを返却する
206
     * @param string ディレクトリパス
207
     * @return RecursiveIteratorIterator イテレータ
208
     */
209 12
    private function getFileSearchIterator(string $path): \RecursiveIteratorIterator
210
    {
211 12
        $iterator = [];
212 12
        $file = new File($path);
213 12
        if ($file->isDirectory()) {
214 12
            $iterator = new \RecursiveIteratorIterator(
215 12
                new \RecursiveDirectoryIterator($path),
216 12
                \RecursiveIteratorIterator::LEAVES_ONLY,
217 12
                \RecursiveIteratorIterator::CATCH_GET_CHILD // for Permission deny
218
            );
219
        }
220 12
        return $iterator;
221
    }
222
}
223