├── vendor
├── composer
│ ├── installed.json
│ ├── autoload_classmap.php
│ ├── autoload_namespaces.php
│ ├── autoload_psr4.php
│ ├── autoload_static.php
│ ├── LICENSE
│ ├── autoload_real.php
│ └── ClassLoader.php
└── autoload.php
├── composer.json
├── composer.lock
├── README.md
└── src
└── Upload
└── Upload.php
/vendor/composer/installed.json:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/vendor/autoload.php:
--------------------------------------------------------------------------------
1 | array($baseDir . '/src/Upload'),
10 | );
11 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aileshe/upload",
3 | "description": "file upload",
4 | "type": "library",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Dejan",
9 | "email": "673008865@qq.com"
10 | }
11 | ],
12 | "require": {
13 | "php":">=5.3"
14 | },
15 | "autoload": {
16 | "psr-4":{
17 | "Dj\\":"src/Upload"
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "2b7a39f3f408ed27ab08a9b6987caee5",
8 | "packages": [],
9 | "packages-dev": [],
10 | "aliases": [],
11 | "minimum-stability": "stable",
12 | "stability-flags": [],
13 | "prefer-stable": false,
14 | "prefer-lowest": false,
15 | "platform": {
16 | "php": ">=5.3"
17 | },
18 | "platform-dev": []
19 | }
20 |
--------------------------------------------------------------------------------
/vendor/composer/autoload_static.php:
--------------------------------------------------------------------------------
1 |
11 | array (
12 | 'Dj\\' => 3,
13 | ),
14 | );
15 |
16 | public static $prefixDirsPsr4 = array (
17 | 'Dj\\' =>
18 | array (
19 | 0 => __DIR__ . '/../..' . '/src/Upload',
20 | ),
21 | );
22 |
23 | public static function getInitializer(ClassLoader $loader)
24 | {
25 | return \Closure::bind(function () use ($loader) {
26 | $loader->prefixLengthsPsr4 = ComposerStaticInitded0c41947aaf8cf6e725424ec40b9ee::$prefixLengthsPsr4;
27 | $loader->prefixDirsPsr4 = ComposerStaticInitded0c41947aaf8cf6e725424ec40b9ee::$prefixDirsPsr4;
28 |
29 | }, null, ClassLoader::class);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/vendor/composer/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Copyright (c) Nils Adermann, Jordi Boggiano
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is furnished
9 | to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
22 |
--------------------------------------------------------------------------------
/vendor/composer/autoload_real.php:
--------------------------------------------------------------------------------
1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27 | if ($useStaticLoader) {
28 | require_once __DIR__ . '/autoload_static.php';
29 |
30 | call_user_func(\Composer\Autoload\ComposerStaticInitded0c41947aaf8cf6e725424ec40b9ee::getInitializer($loader));
31 | } else {
32 | $map = require __DIR__ . '/autoload_namespaces.php';
33 | foreach ($map as $namespace => $path) {
34 | $loader->set($namespace, $path);
35 | }
36 |
37 | $map = require __DIR__ . '/autoload_psr4.php';
38 | foreach ($map as $namespace => $path) {
39 | $loader->setPsr4($namespace, $path);
40 | }
41 |
42 | $classMap = require __DIR__ . '/autoload_classmap.php';
43 | if ($classMap) {
44 | $loader->addClassMap($classMap);
45 | }
46 | }
47 |
48 | $loader->register(true);
49 |
50 | return $loader;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Upload是什么?
2 | 一个PHP文件上传组件,该组件简化了上传文件的验证使用会更简单优雅!
3 |
4 | # 安装
5 | 通过composer,这是推荐的方式,可以使用composer.json 声明依赖,或者直接运行下面的命令。
6 | ```
7 | composer require aileshe/upload:*
8 | ```
9 | 放入composer.json文件中
10 | ```
11 | "require": {
12 | "aileshe/upload": "*"
13 | }
14 | ```
15 | 然后运行
16 | ```
17 | composer update
18 | ```
19 |
20 | # 基本用法
21 | 以下是一个模拟 "单文件上传" 和 "多文件上传" 的Demo表单HTML代码
22 | ```
23 |
24 |
25 | 本地单文件上传
26 |
30 |
31 |
32 |
33 |
34 |
35 | 本地多文件上传
36 |
42 |
43 |
44 | ```
45 | upload.php 代码如下
46 | ```
47 | $upload = new \Dj\Upload();
48 | $filelist = $upload->save('./upload');
49 | if(is_array($filelist)){
50 | # 返回数组,文件就上传成功了
51 | print_r($filelist);
52 | }else{
53 | # 如果返回负整数(int)就是发生错误了
54 | $error_msg = [-1=>'上传失败',-2=>'文件存储路径不合法',-3=>'上传非法格式文件',-4=>'文件大小不合符规定',-5=>'token验证错误'];
55 | echo $error_msg[$filelist];
56 | }
57 | ```
58 | 上传成功返回打印结果
59 | ```
60 | # 单文件上传
61 | Array
62 | (
63 | [name] => 919ff6986614ada.jpg // 上传时的原文件名
64 | [ext] => jpg // 文件后缀名
65 | [mime] => image/jpeg // 文件MIME
66 | [size] => 171635 // 文件大小(单位:字节)
67 | [savename] => 1524626782VGdnXS50.jpg // 文件上传后在服务器上存储的名称
68 | [savepath] => E:/WWW/composer/upload_dev/upload/1524626782VGdnXS50.jpg // 上传到服务器的存储绝对路径
69 | [url] => http://upload.a.com/upload/1524626782VGdnXS50.jpg // 文件访问URL地址
70 | [uri] => /upload/1524626782VGdnXS50.jpg // 文件访问URI地址
71 | [md5] => 6308045756c126c8b823f4ade0bad77d // 文件MD5
72 | )
73 |
74 | # 多文件上传
75 | Array
76 | (
77 | [0] => Array
78 | (
79 | [name] => dejan.jpg
80 | [ext] => jpg
81 | [mime] => image/jpeg
82 | [size] => 1964
83 | [savename] => 1524627074JQLQuKXs.jpg
84 | [savepath] => E:/WWW/composer/upload_dev/upload/1524627074JQLQuKXs.jpg
85 | [url] => http://upload.a.com/upload/1524627074JQLQuKXs.jpg
86 | [uri] => /upload/1524627074JQLQuKXs.jpg
87 | [md5] => 9382a7b44ea865519c82d077cd6346b0
88 | )
89 |
90 | [1] => Array
91 | (
92 | [name] => 《系统安装手册》.docx
93 | [ext] => docx
94 | [mime] => application/vnd.openxmlformats-officedocument.wordprocessingml.document
95 | [size] => 1041956
96 | [savename] => 1524627074oQVSkUO2.docx
97 | [savepath] => E:/WWW/composer/upload_dev/upload/1524627074oQVSkUO2.docx
98 | [url] => http://upload.a.com/upload/1524627074oQVSkUO2.docx
99 | [uri] => /upload/1524627074oQVSkUO2.docx
100 | [md5] => 9f1c186790769c09a9318eb352deb114
101 | )
102 |
103 | [2] => Array
104 | (
105 | [name] => 测试导入用户.xlsx
106 | [ext] => xlsx
107 | [mime] => application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
108 | [size] => 9249
109 | [savename] => 1524627074LUkMlrPQ.xlsx
110 | [savepath] => E:/WWW/composer/upload_dev/upload/1524627074LUkMlrPQ.xlsx
111 | [url] => http://upload.a.com/upload/1524627074LUkMlrPQ.xlsx
112 | [uri] => /upload/1524627074LUkMlrPQ.xlsx
113 | [md5] => b1f1c10005fcf0a2b59326a3aa3af032
114 | )
115 |
116 | )
117 |
118 | ```
119 | ### $upload->save($storage, $allow, $host)
120 |
121 | > \- storage [string]
122 |
123 | 上传文件到哪的存储路径
124 |
125 | > \- allow (optional) [array]
126 |
127 | 文件上传过滤允许规则定义
128 |
129 | > \- host (optional) [string]
130 |
131 | 上传到服务器后文件的URL访问域名
132 |
133 | ### $upload->save($storage, $host)
134 |
135 | > \- storage [string]
136 |
137 | 上传文件到哪的存储路径
138 |
139 | > \- host (optional) [string]
140 |
141 | 上传到服务器后文件的URL访问域名
142 |
143 |
144 |
145 | #### 返回值参照:
146 |
147 | 返回值 | 说明
148 | :--------:|:----------:
149 | Array() | 上传文件成功
150 | -1 | 上传失败
151 | -2 | 指定上传文件的存储路径不合法
152 | -3 | 上传非法格式文件
153 | -4 | 文件大小不合符规定
154 | -5 | token验证错误
155 |
156 | # 高级用法
157 | #### 1) 设置上传文件表单name, 默认是'file'
158 | ```
159 | new \Dj\Upload('form-file-name'); #
160 | ```
161 |
162 | #### 2) 开启 token验证
163 | ```
164 | $upload = new \Dj\Upload();
165 | $upload->token('FFFX123456'); # 设置 token
166 | $filelist = $upload->save('./upload');
167 |
168 | # 或
169 |
170 | $upload = new \Dj\Upload();
171 | $filelist = $upload->token('FFFX123456')->save('./upload');
172 | ```
173 | 同时在上传文件时要也要POST提交正确的token
174 | ```
175 |
180 | ```
181 |
182 | #### 3) 上传指定格式文件(通过后缀名方式限制)
183 | ```
184 | $upload = new \Dj\Upload();
185 | $filelist = $upload->save('./upload', [
186 | 'ext'=>'jpg,jpeg,png,gif'
187 | ]);
188 |
189 | # 或
190 |
191 | $upload = new \Dj\Upload();
192 | $filelist = $upload->save('./upload', [
193 | 'ext'=>['jpg','jpeg','png','gif']
194 | ]);
195 | ```
196 |
197 | #### 4) 上传指定格式文件(通过MIME方式限制)
198 | ```
199 | $upload = new \Dj\Upload();
200 | $filelist = $upload->save('./upload', [
201 | 'mime'=>'image/jpeg,image/gif,image/bmp'
202 | ]);
203 |
204 | # 或
205 |
206 | $upload = new \Dj\Upload();
207 | $filelist = $upload->save('./upload', [
208 | 'mime'=>['image/jpeg','image/gif','image/bmp']
209 | ]);
210 | ```
211 |
212 | #### 5) 上传文件许可的大小限制
213 | ```
214 | $upload = new \Dj\Upload();
215 | $filelist = $upload->save('./upload', [
216 | 'size'=>5242880
217 | ]);
218 | ```
219 | 同时"过滤参数"是可以混用的, 如 只想限制文件大小和文件类型、可以上传xx后缀的同时要匹配MIME等.. 都可以的大胆相信无所不能!
220 | ```
221 | $upload = new \Dj\Upload();
222 | $filelist = $upload->save('./upload', [
223 | 'ext'=>'jpg,jpeg,png,gif',
224 | 'mime'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
225 | 'size'=>5242880
226 | ]);
227 |
228 | # 或
229 |
230 | $upload = new \Dj\Upload();
231 | $filelist = $upload->save('./upload', [
232 | 'ext'=>'jpg,jpeg,png,gif',
233 | 'size'=>5242880
234 | ]);
235 |
236 | # 或
237 |
238 | $upload = new \Dj\Upload();
239 | $filelist = $upload->save('./upload', [
240 | 'ext'=>'jpg,jpeg,png,gif',
241 | 'mime'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
242 | ]);
243 | ```
244 |
245 | #### 6) 设置上传后返回文件URL的域名
246 | ```
247 | $upload = new \Dj\Upload();
248 | $filelist = $upload->save('./upload', 'img.sop6.com');
249 |
250 | # 如果有过滤参数还可以这样定义
251 |
252 | $upload = new \Dj\Upload();
253 | $filelist = $upload->save('./upload', [
254 | 'ext'=>'jpg,jpeg,png,gif',
255 | 'mime'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
256 | 'size'=>5242880
257 | ], 'img.sop6.com');
258 | ```
259 | 设置域名img.sop6.com后返回的上传结果如下(带 * 号那行)
260 | ```
261 | Array
262 | (
263 | [name] => 919ff6986614ada.jpg // 上传时的原文件名
264 | [ext] => jpg // 文件后缀名
265 | [mime] => image/jpeg // 文件MIME
266 | [size] => 171635 // 文件大小(单位:字节)
267 | [savename] => 1524626782VGdnXS50.jpg // 文件上传后在服务器上存储的名称
268 | [savepath] => E:/WWW/composer/upload_dev/upload/1524626782VGdnXS50.jpg // 上传到服务器的存储绝对路径
269 | [url] => http://img.sop6.com/upload/1524626782VGdnXS50.jpg // * 文件访问URL地址
270 | [uri] => /upload/1524626782VGdnXS50.jpg // 文件访问URI地址
271 | [md5] => 6308045756c126c8b823f4ade0bad77d // 文件MD5
272 | )
273 | ```
274 |
275 | # 联系方式
276 | Author: Dejan
277 |
278 | QQ: 673008865
279 |
--------------------------------------------------------------------------------
/src/Upload/Upload.php:
--------------------------------------------------------------------------------
1 | frm_name = $frm_name;
23 | }
24 |
25 | /**
26 | * 设置上传口令 token
27 | * @param String $token 验证口令
28 | * @return ClassObject
29 | */
30 | public function token($token = NULL){
31 | $this->token = $token;
32 | return $this;
33 | }
34 |
35 | /**
36 | * 过滤允许设置
37 | * @param Array $allow 过滤允许规则 ['ext'=>'后缀名限制','size'=>193038] = ['ext'=>'png,jpg,gif','size'=>193038]
38 | * @return ClassObject
39 | */
40 | private function allow($allow){
41 | if($allow != NULL){
42 | # 文件格式过滤 - 文件后缀
43 | if(isset($allow['ext']) && !empty($allow['ext'])){
44 | if(is_array($allow['ext'])){
45 | $this->ext = $allow['ext'];
46 | }else{
47 | $this->ext = explode(',', $allow['ext']);
48 | }
49 | }
50 |
51 | # 文件格式过滤 - MIME
52 | if(isset($allow['mime']) && !empty($allow['mime'])){
53 | if(is_array($allow['mime'])){
54 | $this->mime = $allow['mime'];
55 | }else{
56 | $this->mime = explode(',', $allow['mime']);
57 | }
58 | }
59 |
60 | # 文件大小过滤
61 | if(isset($allow['size']) && !empty($allow['size'])){
62 | $this->size = (int)$allow['size'];
63 | }
64 | }
65 | return $this;
66 | }
67 |
68 | /**
69 | * 文件接收入口 - 单文件、多文件上传
70 | * @param String $storage 文件存储路径
71 | * @param Array $allow 允许上传文件规则 ['ext'=>'后缀名限制','size'=>193038] = ['ext'=>'png,jpg,gif','size'=>193038]
72 | * @param String $host 文件访问域名
73 | * @return Int [0上传提交空文件 -1上传失败 -2文件存储路径不合法 -5验证token为空]
74 | */
75 | public function save($storage = NULL, $allow = NULL, $host = NULL){
76 | # token 验证
77 | if($this->token != NULL){
78 | if(!isset($_POST['token']) || $_POST['token'] != $this->token){
79 | return -5;
80 | }
81 | }
82 |
83 | # 存储路径合法判断
84 | if(empty($storage) || !is_string($storage)){
85 | return -2;
86 | }
87 |
88 | # 初始化过滤设置, 如果$allow为字符串型时自动设置 $host = $allow
89 | if(is_string($allow)){
90 | $host = $allow;
91 | $allow = NULL;
92 | }else{
93 | $this->allow($allow);
94 | }
95 |
96 | # 初始化文件访问域名
97 | if($host == NULL){
98 | $this->host = 'http://'.$_SERVER['HTTP_HOST'];
99 | }else{
100 | $this->host = preg_match('/^http[s]?\:\/\//', $host)? $host : 'http://'.$host;
101 | }
102 |
103 | if(!empty($_FILES) && isset($_FILES[$this->frm_name])){
104 | # 判断存储目录是否存在,无则自动创建
105 | if(!is_dir($storage)){
106 | mkdir($storage,'0777',true);
107 | chmod($storage,0777);
108 | }
109 |
110 | $save_path = realpath($storage); # 上传文件存储目录的绝对路径
111 | $filelist = array(); # 文件数组
112 |
113 | $files = $_FILES[$this->frm_name]; # 简化数组
114 | if(is_string($files['name'])){
115 | # 单文件上传
116 | $check_res = $this->file_check($files);
117 | if($check_res === 0){ # 0 校检没问题,
118 | $filelist[] = $files;
119 | }else{
120 | return $check_res;
121 | }
122 | }else{
123 | # 多文件上传
124 | $file = array();
125 | foreach($files['name'] as $k=>$v){
126 | $file = array(
127 | 'name' => $v,
128 | 'type' => $files['type'][$k],
129 | 'tmp_name'=> $files['tmp_name'][$k],
130 | 'error' => $files['error'][$k],
131 | 'size' => $files['size'][$k]
132 | );
133 |
134 | $check_res = $this->file_check($file);
135 | if($check_res === 0){ # 0 校检没问题
136 | $filelist[] = $file;
137 | }else{
138 | return $check_res;
139 | }
140 | }
141 | }
142 |
143 | # 从临时空间里提取出文件到真实路径、文件信息补全
144 | $new_arr = array();
145 | if(count($filelist) === 1){
146 | # 单文件上传
147 | $ext = $this->get_file_ext($filelist[0]['name']); # 文件后缀名
148 | $fileName = $ext == '' ? $this->uuid() : $this->uuid().'.'.$ext;
149 | move_uploaded_file($filelist[0]['tmp_name'], $save_path.'/'.$fileName);
150 |
151 | # - 上传文件回调数据信息
152 | $new_arr['name'] = $filelist[0]['name']; # 文件上传时的原名称
153 | $new_arr['ext'] = $ext; # 文件后缀名
154 | $new_arr['mime'] = $filelist[0]['type']; # 文件MIME
155 | $new_arr['size'] = $filelist[0]['size']; # 文件大小(单位:字节)
156 | $new_arr['savename'] = $fileName; # 文件保存在服务器上名称
157 | $new_arr['savepath'] = str_replace('\\', '/', $save_path.'/'.$fileName); # 文件存储绝对路径(包含文件名)
158 | $new_arr['url'] = str_replace($_SERVER['DOCUMENT_ROOT'], $this->host, $new_arr['savepath']); # 文件访问URL地址
159 | $new_arr['uri'] = str_replace($_SERVER['DOCUMENT_ROOT'], '', $new_arr['savepath']); # 文件访问URI相对地址
160 | $new_arr['md5'] = md5_file($new_arr['savepath']); # 文件MD5
161 | }else{
162 | # 多文件上传
163 | foreach($filelist as $v){
164 | $ext = $this->get_file_ext($v['name']); # 文件后缀名
165 | $fileName = $ext == '' ? $this->uuid() : $this->uuid().'.'.$ext;
166 | move_uploaded_file($v['tmp_name'], $save_path.'/'.$fileName);
167 |
168 | # - 上传文件回调数据信息
169 | $savepath = str_replace('\\', '/', $save_path.'/'.$fileName);
170 | $new_arr[] = array(
171 | 'name' => $v['name'] , # 文件上传时的原名称
172 | 'ext' => $ext , # 文件后缀名
173 | 'mime' => $v['type'] , # 文件MIME
174 | 'size' => $v['size'] , # 文件大小(单位:字节)
175 | 'savename' => $fileName , # 文件保存在服务器上名称
176 | 'savepath' => $savepath , # 文件存储绝对路径(包含文件名)
177 | 'url' => str_replace($_SERVER['DOCUMENT_ROOT'], $this->host, $savepath) , # 文件访问URL地址
178 | 'uri' => str_replace($_SERVER['DOCUMENT_ROOT'], '', $savepath) , # 文件访问URI地址
179 | 'md5' => md5_file($savepath) # 文件MD5
180 | );
181 | }
182 | }
183 |
184 | return $new_arr;
185 | }else{
186 | return 0; # 提交上传的文件为空
187 | }
188 | }
189 |
190 | /**
191 | * 获取文件后缀名
192 | * @param String $file 文件名
193 | * @return String
194 | */
195 | private function get_file_ext($file){
196 | $r_offset = strrpos($file, '.');
197 | if($r_offset){
198 | $ext = substr($file, $r_offset + 1);
199 | }else{
200 | $ext = '';
201 | }
202 |
203 | return $ext;
204 | }
205 |
206 | /**
207 | * 文件唯一名称生成
208 | * @param Int $size 随机字符长度, 默认8
209 | * @return String
210 | */
211 | private function uuid($size=8){
212 | $chars='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
213 | $rand_str=null;
214 | for($i=0;$i<$size;$i++){
215 | $rand_str.=$chars[mt_rand(0,61)];
216 | }
217 | return time().$rand_str;
218 | }
219 |
220 | /**
221 | * 文件校检
222 | * @param Array $file 单个文件数组
223 | * @return Int [0校检通过 -1上传文件失败 -3非法格式文件 -4文件大小不合符]
224 | */
225 | private function file_check($file){
226 | # 上传文件是否存在失败
227 | if($file['error'] !== 0){
228 | return -1;
229 | }
230 |
231 | # 上传文件是否存在不合法的格式文件
232 | if($this->ext != NULL || $this->mime != NULL){
233 | if(@!(in_array($this->get_file_ext($file['name']), $this->ext) || in_array($file['type'], $this->mime))){
234 | return -3;
235 | }
236 | }
237 |
238 | # 上传文件的大小不符合规定的大小
239 | if($this->size != NULL){
240 | if($file['size'] > $this->size){
241 | return -4;
242 | }
243 | }
244 |
245 | return 0;
246 | }
247 | }
--------------------------------------------------------------------------------
/vendor/composer/ClassLoader.php:
--------------------------------------------------------------------------------
1 |
7 | * Jordi Boggiano
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | namespace Composer\Autoload;
14 |
15 | /**
16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17 | *
18 | * $loader = new \Composer\Autoload\ClassLoader();
19 | *
20 | * // register classes with namespaces
21 | * $loader->add('Symfony\Component', __DIR__.'/component');
22 | * $loader->add('Symfony', __DIR__.'/framework');
23 | *
24 | * // activate the autoloader
25 | * $loader->register();
26 | *
27 | * // to enable searching the include path (eg. for PEAR packages)
28 | * $loader->setUseIncludePath(true);
29 | *
30 | * In this example, if you try to use a class in the Symfony\Component
31 | * namespace or one of its children (Symfony\Component\Console for instance),
32 | * the autoloader will first look for the class under the component/
33 | * directory, and it will then fallback to the framework/ directory if not
34 | * found before giving up.
35 | *
36 | * This class is loosely based on the Symfony UniversalClassLoader.
37 | *
38 | * @author Fabien Potencier
39 | * @author Jordi Boggiano
40 | * @see http://www.php-fig.org/psr/psr-0/
41 | * @see http://www.php-fig.org/psr/psr-4/
42 | */
43 | class ClassLoader
44 | {
45 | // PSR-4
46 | private $prefixLengthsPsr4 = array();
47 | private $prefixDirsPsr4 = array();
48 | private $fallbackDirsPsr4 = array();
49 |
50 | // PSR-0
51 | private $prefixesPsr0 = array();
52 | private $fallbackDirsPsr0 = array();
53 |
54 | private $useIncludePath = false;
55 | private $classMap = array();
56 | private $classMapAuthoritative = false;
57 | private $missingClasses = array();
58 | private $apcuPrefix;
59 |
60 | public function getPrefixes()
61 | {
62 | if (!empty($this->prefixesPsr0)) {
63 | return call_user_func_array('array_merge', $this->prefixesPsr0);
64 | }
65 |
66 | return array();
67 | }
68 |
69 | public function getPrefixesPsr4()
70 | {
71 | return $this->prefixDirsPsr4;
72 | }
73 |
74 | public function getFallbackDirs()
75 | {
76 | return $this->fallbackDirsPsr0;
77 | }
78 |
79 | public function getFallbackDirsPsr4()
80 | {
81 | return $this->fallbackDirsPsr4;
82 | }
83 |
84 | public function getClassMap()
85 | {
86 | return $this->classMap;
87 | }
88 |
89 | /**
90 | * @param array $classMap Class to filename map
91 | */
92 | public function addClassMap(array $classMap)
93 | {
94 | if ($this->classMap) {
95 | $this->classMap = array_merge($this->classMap, $classMap);
96 | } else {
97 | $this->classMap = $classMap;
98 | }
99 | }
100 |
101 | /**
102 | * Registers a set of PSR-0 directories for a given prefix, either
103 | * appending or prepending to the ones previously set for this prefix.
104 | *
105 | * @param string $prefix The prefix
106 | * @param array|string $paths The PSR-0 root directories
107 | * @param bool $prepend Whether to prepend the directories
108 | */
109 | public function add($prefix, $paths, $prepend = false)
110 | {
111 | if (!$prefix) {
112 | if ($prepend) {
113 | $this->fallbackDirsPsr0 = array_merge(
114 | (array) $paths,
115 | $this->fallbackDirsPsr0
116 | );
117 | } else {
118 | $this->fallbackDirsPsr0 = array_merge(
119 | $this->fallbackDirsPsr0,
120 | (array) $paths
121 | );
122 | }
123 |
124 | return;
125 | }
126 |
127 | $first = $prefix[0];
128 | if (!isset($this->prefixesPsr0[$first][$prefix])) {
129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths;
130 |
131 | return;
132 | }
133 | if ($prepend) {
134 | $this->prefixesPsr0[$first][$prefix] = array_merge(
135 | (array) $paths,
136 | $this->prefixesPsr0[$first][$prefix]
137 | );
138 | } else {
139 | $this->prefixesPsr0[$first][$prefix] = array_merge(
140 | $this->prefixesPsr0[$first][$prefix],
141 | (array) $paths
142 | );
143 | }
144 | }
145 |
146 | /**
147 | * Registers a set of PSR-4 directories for a given namespace, either
148 | * appending or prepending to the ones previously set for this namespace.
149 | *
150 | * @param string $prefix The prefix/namespace, with trailing '\\'
151 | * @param array|string $paths The PSR-4 base directories
152 | * @param bool $prepend Whether to prepend the directories
153 | *
154 | * @throws \InvalidArgumentException
155 | */
156 | public function addPsr4($prefix, $paths, $prepend = false)
157 | {
158 | if (!$prefix) {
159 | // Register directories for the root namespace.
160 | if ($prepend) {
161 | $this->fallbackDirsPsr4 = array_merge(
162 | (array) $paths,
163 | $this->fallbackDirsPsr4
164 | );
165 | } else {
166 | $this->fallbackDirsPsr4 = array_merge(
167 | $this->fallbackDirsPsr4,
168 | (array) $paths
169 | );
170 | }
171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
172 | // Register directories for a new namespace.
173 | $length = strlen($prefix);
174 | if ('\\' !== $prefix[$length - 1]) {
175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
176 | }
177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
178 | $this->prefixDirsPsr4[$prefix] = (array) $paths;
179 | } elseif ($prepend) {
180 | // Prepend directories for an already registered namespace.
181 | $this->prefixDirsPsr4[$prefix] = array_merge(
182 | (array) $paths,
183 | $this->prefixDirsPsr4[$prefix]
184 | );
185 | } else {
186 | // Append directories for an already registered namespace.
187 | $this->prefixDirsPsr4[$prefix] = array_merge(
188 | $this->prefixDirsPsr4[$prefix],
189 | (array) $paths
190 | );
191 | }
192 | }
193 |
194 | /**
195 | * Registers a set of PSR-0 directories for a given prefix,
196 | * replacing any others previously set for this prefix.
197 | *
198 | * @param string $prefix The prefix
199 | * @param array|string $paths The PSR-0 base directories
200 | */
201 | public function set($prefix, $paths)
202 | {
203 | if (!$prefix) {
204 | $this->fallbackDirsPsr0 = (array) $paths;
205 | } else {
206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
207 | }
208 | }
209 |
210 | /**
211 | * Registers a set of PSR-4 directories for a given namespace,
212 | * replacing any others previously set for this namespace.
213 | *
214 | * @param string $prefix The prefix/namespace, with trailing '\\'
215 | * @param array|string $paths The PSR-4 base directories
216 | *
217 | * @throws \InvalidArgumentException
218 | */
219 | public function setPsr4($prefix, $paths)
220 | {
221 | if (!$prefix) {
222 | $this->fallbackDirsPsr4 = (array) $paths;
223 | } else {
224 | $length = strlen($prefix);
225 | if ('\\' !== $prefix[$length - 1]) {
226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
227 | }
228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
229 | $this->prefixDirsPsr4[$prefix] = (array) $paths;
230 | }
231 | }
232 |
233 | /**
234 | * Turns on searching the include path for class files.
235 | *
236 | * @param bool $useIncludePath
237 | */
238 | public function setUseIncludePath($useIncludePath)
239 | {
240 | $this->useIncludePath = $useIncludePath;
241 | }
242 |
243 | /**
244 | * Can be used to check if the autoloader uses the include path to check
245 | * for classes.
246 | *
247 | * @return bool
248 | */
249 | public function getUseIncludePath()
250 | {
251 | return $this->useIncludePath;
252 | }
253 |
254 | /**
255 | * Turns off searching the prefix and fallback directories for classes
256 | * that have not been registered with the class map.
257 | *
258 | * @param bool $classMapAuthoritative
259 | */
260 | public function setClassMapAuthoritative($classMapAuthoritative)
261 | {
262 | $this->classMapAuthoritative = $classMapAuthoritative;
263 | }
264 |
265 | /**
266 | * Should class lookup fail if not found in the current class map?
267 | *
268 | * @return bool
269 | */
270 | public function isClassMapAuthoritative()
271 | {
272 | return $this->classMapAuthoritative;
273 | }
274 |
275 | /**
276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
277 | *
278 | * @param string|null $apcuPrefix
279 | */
280 | public function setApcuPrefix($apcuPrefix)
281 | {
282 | $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
283 | }
284 |
285 | /**
286 | * The APCu prefix in use, or null if APCu caching is not enabled.
287 | *
288 | * @return string|null
289 | */
290 | public function getApcuPrefix()
291 | {
292 | return $this->apcuPrefix;
293 | }
294 |
295 | /**
296 | * Registers this instance as an autoloader.
297 | *
298 | * @param bool $prepend Whether to prepend the autoloader or not
299 | */
300 | public function register($prepend = false)
301 | {
302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend);
303 | }
304 |
305 | /**
306 | * Unregisters this instance as an autoloader.
307 | */
308 | public function unregister()
309 | {
310 | spl_autoload_unregister(array($this, 'loadClass'));
311 | }
312 |
313 | /**
314 | * Loads the given class or interface.
315 | *
316 | * @param string $class The name of the class
317 | * @return bool|null True if loaded, null otherwise
318 | */
319 | public function loadClass($class)
320 | {
321 | if ($file = $this->findFile($class)) {
322 | includeFile($file);
323 |
324 | return true;
325 | }
326 | }
327 |
328 | /**
329 | * Finds the path to the file where the class is defined.
330 | *
331 | * @param string $class The name of the class
332 | *
333 | * @return string|false The path if found, false otherwise
334 | */
335 | public function findFile($class)
336 | {
337 | // class map lookup
338 | if (isset($this->classMap[$class])) {
339 | return $this->classMap[$class];
340 | }
341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
342 | return false;
343 | }
344 | if (null !== $this->apcuPrefix) {
345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit);
346 | if ($hit) {
347 | return $file;
348 | }
349 | }
350 |
351 | $file = $this->findFileWithExtension($class, '.php');
352 |
353 | // Search for Hack files if we are running on HHVM
354 | if (false === $file && defined('HHVM_VERSION')) {
355 | $file = $this->findFileWithExtension($class, '.hh');
356 | }
357 |
358 | if (null !== $this->apcuPrefix) {
359 | apcu_add($this->apcuPrefix.$class, $file);
360 | }
361 |
362 | if (false === $file) {
363 | // Remember that this class does not exist.
364 | $this->missingClasses[$class] = true;
365 | }
366 |
367 | return $file;
368 | }
369 |
370 | private function findFileWithExtension($class, $ext)
371 | {
372 | // PSR-4 lookup
373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
374 |
375 | $first = $class[0];
376 | if (isset($this->prefixLengthsPsr4[$first])) {
377 | $subPath = $class;
378 | while (false !== $lastPos = strrpos($subPath, '\\')) {
379 | $subPath = substr($subPath, 0, $lastPos);
380 | $search = $subPath.'\\';
381 | if (isset($this->prefixDirsPsr4[$search])) {
382 | foreach ($this->prefixDirsPsr4[$search] as $dir) {
383 | $length = $this->prefixLengthsPsr4[$first][$search];
384 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
385 | return $file;
386 | }
387 | }
388 | }
389 | }
390 | }
391 |
392 | // PSR-4 fallback dirs
393 | foreach ($this->fallbackDirsPsr4 as $dir) {
394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
395 | return $file;
396 | }
397 | }
398 |
399 | // PSR-0 lookup
400 | if (false !== $pos = strrpos($class, '\\')) {
401 | // namespaced class name
402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
404 | } else {
405 | // PEAR-like class name
406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
407 | }
408 |
409 | if (isset($this->prefixesPsr0[$first])) {
410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
411 | if (0 === strpos($class, $prefix)) {
412 | foreach ($dirs as $dir) {
413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
414 | return $file;
415 | }
416 | }
417 | }
418 | }
419 | }
420 |
421 | // PSR-0 fallback dirs
422 | foreach ($this->fallbackDirsPsr0 as $dir) {
423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
424 | return $file;
425 | }
426 | }
427 |
428 | // PSR-0 include paths.
429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
430 | return $file;
431 | }
432 |
433 | return false;
434 | }
435 | }
436 |
437 | /**
438 | * Scope isolated include.
439 | *
440 | * Prevents access to $this/self from included files.
441 | */
442 | function includeFile($file)
443 | {
444 | include $file;
445 | }
446 |
--------------------------------------------------------------------------------