├── README.md ├── composer.json ├── lib └── Qiniu │ ├── Client.php │ ├── Mac.php │ ├── Qiniu.php │ ├── Request.php │ ├── Response.php │ ├── Result.php │ └── Util.php ├── phpunit.xml └── test ├── MacTest.php ├── RequestTest.php └── UtilTest.php /README.md: -------------------------------------------------------------------------------- 1 | # php-qiniu 2 | 3 | [七牛云存储](http://qiniu.com)非官方SDK,采用[PSR规范](https://github.com/hfcorriez/fig-standards),支持[Composer](http://getcomposer.org)安装 4 | 5 | `开发中...` 6 | 7 | # 安装 8 | 9 | 添加 `"qiniu/qiniu": "*"` 到 [`composer.json`](http://getcomposer.org). 10 | 11 | ``` 12 | composer.phar install 13 | ``` 14 | 15 | # 引导 16 | 17 | - [基本用法](#基本用法) 18 | - [上传](#上传) 19 | - [上传文件](#上传文件) 20 | - [上传字符串](#上传字符串) 21 | - [基本操作](#基本操作) 22 | - [查看文件](#查看文件) 23 | - [复制文件](#复制文件) 24 | - [移动文件](#移动文件) 25 | - [删除文件](#删除文件) 26 | - [筛选文件](#筛选文件) 27 | - [批量操作](#批量操作) 28 | - [图片查看](#图片查看) 29 | - [查看图片信息](#查看图片信息) 30 | - [查看图片Exif](#查看图片exif) 31 | - [图片生成](#图片生成) 32 | - [缩略图生成](#缩略图生成) 33 | - [高级图片处理](#高级图片处理) 34 | 35 | # 使用 36 | 37 | ## 基本用法 38 | 39 | ```php 40 | require dirname(__DIR__) . '/vendor/autoload.php'; 41 | 42 | $client = \Qiniu\Qiniu::create(array( 43 | 'access_key' => '', 44 | 'secret_key' => '', 45 | 'bucket' => '' 46 | )); 47 | 48 | // 查看文件状态 49 | $res = $client->stat('jobs.jpg'); 50 | 51 | print_r($res); 52 | ``` 53 | 54 | 输出 55 | 56 | ``` 57 | Qiniu\Result Object 58 | ( 59 | [error] => 60 | [data] => Array 61 | ( 62 | [fsize] => 2425435 63 | [hash] => Fqng6_0hUF8Q-POxmWNi8JD9VRmy 64 | [mimeType] => image/jpeg 65 | [putTime] => 13707100228731119 66 | [url] => http://php-sdk.qiniudn.com/jobs.jpg 67 | ) 68 | [debug] => Array 69 | ( 70 | [log] => BDT;BDT;LBD 71 | [id] => zxwAAMj4OP6_7yET 72 | ) 73 | ) 74 | ``` 75 | 76 | 推荐用法 77 | 78 | ```php 79 | if ($res->ok()) { 80 | // 成功上传或者操作 81 | 82 | // 获取返回的数据 83 | $data = $res->data; // Or $res->toArray() 84 | 85 | //做一些事情 86 | } else { 87 | // 失败,获取失败信息 88 | $res->error; 89 | 90 | // 七牛的Debug头信息 91 | $res->debug; 92 | } 93 | ``` 94 | 95 | ## 上传 96 | 97 | ### 上传文件 98 | 99 | ```php 100 | $res = $client->uploadFile('/home/hfcorriez/Code/jobs.jpg', 'jobs.jpg'); 101 | 102 | /* 103 | $res->data: 104 | 105 | Array 106 | ( 107 | [key] => 4276 108 | [hash] => FpLJ7yTujtJ85yU6QLA79ZaR3kKg 109 | [url] => http://php-sdk.qiniudn.com/jobs.jpg 110 | ) 111 | */ 112 | ``` 113 | 114 | ### 上传字符串 115 | 116 | ```php 117 | $res = $client->upload('I am Qiniu SDK', 'readme.txt'); 118 | 119 | /* 120 | $res->data: 121 | 122 | Array 123 | ( 124 | [key] => 4276 125 | [hash] => FpLJ7yTujtJ85yU6QLA79ZaR3kKg 126 | [url] => http://php-sdk.qiniudn.com/readme.txt 127 | ) 128 | */ 129 | ``` 130 | 131 | ## 文件操作 132 | 133 | ### 查看文件 134 | 135 | ```php 136 | // 查看文件状态 137 | $res = $client->stat('jobs.jpg'); 138 | 139 | /* 140 | $res->data: 141 | 142 | [data] => Array 143 | ( 144 | [fsize] => 2425435 145 | [hash] => Fqng6_0hUF8Q-POxmWNi8JD9VRmy 146 | [mimeType] => image/jpeg 147 | [putTime] => 13787406554413228 148 | [url] => http://php-sdk.qiniudn.com/jobs.jpg 149 | ) 150 | */ 151 | ``` 152 | 153 | ### 复制文件 154 | 155 | ```php 156 | $res = $client->copy('jobs.jpg', 'jobs.jpg.new'); 157 | 158 | /* 159 | $res->data: 160 | 161 | [data] => Array 162 | ( 163 | [url] => http://php-sdk.qiniudn.com/jobs.jpg.new 164 | ) 165 | */ 166 | ``` 167 | 168 | ### 移动文件 169 | 170 | ```php 171 | $res = $client->move('jobs.jpg.new', 'jobs.jpg'); 172 | 173 | /* 174 | $res->data: 175 | 176 | [data] => Array 177 | ( 178 | [url] => http://php-sdk.qiniudn.com/jobs.jpg 179 | ) 180 | */ 181 | ``` 182 | 183 | ### 删除文件 184 | 185 | ```php 186 | $res = $client->delete('jobs.jpg'); 187 | 188 | /* 189 | $res->data: 190 | 191 | [data] => Array 192 | ( 193 | [url] => http://php-sdk.qiniudn.com/jobs.jpg 194 | ) 195 | */ 196 | ``` 197 | 198 | ### 筛选文件 199 | 200 | ```php 201 | $res = $client->ls(); 202 | 203 | /* 204 | $res->data: 205 | 206 | Array 207 | ( 208 | [items] => Array 209 | ( 210 | [0] => Array 211 | ( 212 | [fsize] => 2425435 213 | [putTime] => 13787406554413228 214 | [key] => jobs.jpg 215 | [hash] => Fqng6_0hUF8Q-POxmWNi8JD9VRmy 216 | [mimeType] => image/jpeg 217 | ) 218 | ) 219 | ) 220 | */ 221 | ``` 222 | 223 | ## 批量操作 224 | 225 | ```php 226 | $res = $client->batch() 227 | ->stat('jobs.jpg') 228 | ->copy('jobs.jpg', 'jobs.jpg.new') 229 | ->move('jobs.jpg.new', 'jobs.jpg.old') 230 | ->delete('jobs.jpg.old') 231 | ->exec(); 232 | 233 | /* 234 | $res: 235 | 236 | Qiniu\Result Object 237 | ( 238 | [error] => 239 | [data] => Array 240 | ( 241 | [0] => Qiniu\Result Object 242 | ( 243 | [error] => 244 | [data] => Array 245 | ( 246 | [fsize] => 2425435 247 | [hash] => Fqng6_0hUF8Q-POxmWNi8JD9VRmy 248 | [mimeType] => image/jpeg 249 | [putTime] => 13787430682676023 250 | [url] => http://php-sdk.qiniudn.com/jobs.jpg 251 | ) 252 | 253 | [response] => 254 | [debug] => Array 255 | ( 256 | [log] => MQ;MC/404;RS.mcd 257 | [id] => u00AAFaxXKXYfiIT 258 | ) 259 | 260 | ) 261 | 262 | [1] => Qiniu\Result Object 263 | ( 264 | [error] => 265 | [data] => Array 266 | ( 267 | [url] => http://php-sdk.qiniudn.com/jobs.jpg 268 | ) 269 | 270 | [response] => 271 | [debug] => Array 272 | ( 273 | [log] => MQ;MC/404;RS.mcd 274 | [id] => u00AAFaxXKXYfiIT 275 | ) 276 | 277 | ) 278 | 279 | [2] => Qiniu\Result Object 280 | ( 281 | [error] => 282 | [data] => Array 283 | ( 284 | [url] => http://php-sdk.qiniudn.com/jobs.jpg.new 285 | ) 286 | 287 | [response] => 288 | [debug] => Array 289 | ( 290 | [log] => MQ;MC/404;RS.mcd 291 | [id] => u00AAFaxXKXYfiIT 292 | ) 293 | 294 | ) 295 | 296 | [3] => Qiniu\Result Object 297 | ( 298 | [error] => 299 | [data] => Array 300 | ( 301 | [url] => http://php-sdk.qiniudn.com/jobs.jpg.old 302 | ) 303 | 304 | [response] => 305 | [debug] => Array 306 | ( 307 | [log] => MQ;MC/404;RS.mcd 308 | [id] => u00AAFaxXKXYfiIT 309 | ) 310 | 311 | ) 312 | 313 | ) 314 | ... 315 | ) 316 | */ 317 | ``` 318 | 319 | ## 图片查看 320 | 321 | ### 查看图片信息 322 | 323 | 查看图片的信息,信息参数介绍参见[七牛官方文档](http://docs.qiniu.com/api/v6/image-process.html#imageInfo) 324 | 325 | ```php 326 | $res = $client->imageInfo('qn.jpeg'); 327 | 328 | /* 329 | $res->data: 330 | 331 | Array 332 | ( 333 | [format] => jpeg 334 | [width] => 640 335 | [height] => 423 336 | [colorModel] => ycbcr 337 | ) 338 | */ 339 | ``` 340 | 341 | ### 查看图片Exif 342 | 343 | EXIF (Exchangeable image file format),是专门为数码相机的照片设定的可交换图像文件格式。详情参见[EXIF](http://zh.wikipedia.org/wiki/EXIF) 344 | 345 | ```php 346 | $res = $client->exif('qn.jpeg'); 347 | 348 | /* 349 | $res->data: 350 | 351 | Array 352 | ( 353 | [ApertureValue] => Array 354 | ( 355 | [val] => 2.53 EV (f/2.4) 356 | [type] => 5 357 | ) 358 | 359 | [BrightnessValue] => Array 360 | ( 361 | [val] => 5.31 EV (135.85 cd/m^2) 362 | [type] => 10 363 | ) 364 | ... 365 | ) 366 | */ 367 | ``` 368 | 369 | ## 图片生成 370 | 371 | ### 缩略图生成 372 | 373 | 参数列表请参照[七牛官方文档](http://docs.qiniu.com/api/v6/image-process.html#imageMogr) 374 | 375 | ```php 376 | $url = $client->imageView('jobs.jpg', array("mode" => 1, "width" => 100, "height" => 100, "q" => 50, "format" => 'png')); 377 | 378 | /* 379 | $url: 380 | 381 | http://php-sdk.qiniudn.com/jobs.jpg?imageView/1/w/100/h/100/q/50/format/png 382 | */ 383 | ``` 384 | 385 | ### 高级图片处理 386 | 387 | 包括(缩略、裁剪、旋转、转化),参数列表请参照[七牛官方文档](http://docs.qiniu.com/api/v6/image-process.html#imageMogr) 388 | 389 | ```php 390 | $res = $client->imageMogr('jobs.jpg', array( 391 | "thumbnail" => '!50p', 392 | "gravity" => 'NorthWest', 393 | "quality" => 50, 394 | "rotate" => -50, 395 | "format" => 'gif' 396 | )); 397 | 398 | /* 399 | $url: 400 | 401 | http://php-sdk.qiniudn.com/jobs.jpg?imageMogr/v2/auto-orient/thumbnail/!50p/gravity/NorthWest/quality/50/rotate/-50/format/gif 402 | */ 403 | ``` 404 | 405 | # License 406 | 407 | (The MIT License) 408 | 409 | Copyright (c) 2012 hfcorriez <hfcorriez@gmail.com> 410 | 411 | Permission is hereby granted, free of charge, to any person obtaining 412 | a copy of this software and associated documentation files (the 413 | 'Software'), to deal in the Software without restriction, including 414 | without limitation the rights to use, copy, modify, merge, publish, 415 | distribute, sublicense, and/or sell copies of the Software, and to 416 | permit persons to whom the Software is furnished to do so, subject to 417 | the following conditions: 418 | 419 | The above copyright notice and this permission notice shall be 420 | included in all copies or substantial portions of the Software. 421 | 422 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 423 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 424 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 425 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 426 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 427 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 428 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qiniu/qiniu", 3 | "description": "Qiniu SDK", 4 | "keywords": ["sdk", "cloud storage", "api", "qiniu"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Corrie Zhao", 9 | "email": "hfcorriez@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.3.9" 14 | }, 15 | "autoload": { 16 | "psr-0": { 17 | "Qiniu": "lib/" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/Qiniu/Client.php: -------------------------------------------------------------------------------- 1 | null, 13 | 'secret_key' => null, 14 | 'bucket' => null, 15 | 'domain' => null, 16 | 'timeout' => '3600', 17 | 'upload_url' => 'http://up.qiniu.com', 18 | 'rs_url' => 'http://rs.qbox.me', 19 | 'rsf_url' => 'http://rsf.qbox.me', 20 | 'base_url' => null 21 | ); 22 | 23 | /** 24 | * @var array Image url options 25 | */ 26 | protected $image_options = array( 27 | 'imageView' => array( 28 | 'mode' => array('type' => 'no-key'), 29 | 'w' => array('alias' => 'width'), 30 | 'h' => array('alias' => 'height'), 31 | 'q' => array('alias' => 'quality'), 32 | 'format' => array('alias' => 'f') 33 | ), 34 | 'imageMogr' => array( 35 | '_path' => 'v2', 36 | 'auto-orient' => array('type' => 'no-value', 'default' => true), 37 | 'thumbnail' => array('alias' => array('thumb', 't')), 38 | 'gravity' => array('alias' => 'g'), 39 | 'crop' => array('alias' => 'c'), 40 | 'quality' => array('alias' => 'q'), 41 | 'rotate' => array('alias' => 'r'), 42 | 'format' => array('alias' => 'f') 43 | ) 44 | ); 45 | 46 | protected $batch = false; 47 | protected $batch_operators = array(); 48 | 49 | /** 50 | * @var Mac 51 | */ 52 | protected $mac; 53 | 54 | public function __construct(array $options) 55 | { 56 | $this->options = $options + $this->options; 57 | 58 | if (!$this->options['access_key'] || !$this->options['secret_key'] || !$this->options['bucket']) { 59 | throw new \InvalidArgumentException("Options access_key, secret_key and bucket required"); 60 | } 61 | 62 | $this->mac = new Mac($this->options['access_key'], $this->options['secret_key']); 63 | 64 | if (!$this->options['base_url']) $this->options['base_url'] = $this->options['domain'] ? $this->options['domain'] : ('http://' . $this->options['bucket'] . '.qiniudn.com'); 65 | } 66 | 67 | /** 68 | * Upload file 69 | * 70 | * @param string $path 71 | * @param string $key 72 | * @throws \InvalidArgumentException 73 | * @return bool|Result 74 | */ 75 | public function uploadFile($path, $key) 76 | { 77 | if (!is_file($path)) throw new \InvalidArgumentException("Can not upload non-exists file: $path"); 78 | return $this->uploadRequest(file_get_contents($path), $key); 79 | } 80 | 81 | /** 82 | * Upload string 83 | * 84 | * @param string $string 85 | * @param string $key 86 | * @return bool|Result 87 | */ 88 | public function upload($string, $key) 89 | { 90 | return $this->uploadRequest($string, $key); 91 | } 92 | 93 | /** 94 | * Get file stats 95 | * 96 | * @param string $key 97 | * @return bool|Result|Client 98 | */ 99 | public function stat($key) 100 | { 101 | $uri = '/stat/' . Util::uriEncode("{$this->options['bucket']}:$key"); 102 | 103 | if ($this->batch) { 104 | $this->batch_operators[] = array($uri, $key); 105 | return $this; 106 | } else { 107 | return $this->operateRequest($uri, $key); 108 | } 109 | } 110 | 111 | /** 112 | * Move file 113 | * 114 | * @param string $key 115 | * @param string $new_key 116 | * @return bool|Result|Client 117 | */ 118 | public function move($key, $new_key) 119 | { 120 | $uri = '/move/' . Util::uriEncode("{$this->options['bucket']}:$key") . '/' . Util::uriEncode("{$this->options['bucket']}:$new_key"); 121 | 122 | if ($this->batch) { 123 | $this->batch_operators[] = array($uri, $key); 124 | return $this; 125 | } else { 126 | return $this->operateRequest($uri, $new_key); 127 | } 128 | } 129 | 130 | /** 131 | * Copy file 132 | * 133 | * @param string $key 134 | * @param string $new_key 135 | * @return bool|Result|Client 136 | */ 137 | public function copy($key, $new_key) 138 | { 139 | $uri = '/copy/' . Util::uriEncode("{$this->options['bucket']}:$key") . '/' . Util::uriEncode("{$this->options['bucket']}:$new_key"); 140 | 141 | if ($this->batch) { 142 | $this->batch_operators[] = array($uri, $key); 143 | return $this; 144 | } else { 145 | return $this->operateRequest($uri, $new_key); 146 | } 147 | } 148 | 149 | /** 150 | * Delete file 151 | * 152 | * @param string $key 153 | * @return bool|Result|Client 154 | */ 155 | public function delete($key) 156 | { 157 | $uri = '/delete/' . Util::uriEncode("{$this->options['bucket']}:$key"); 158 | 159 | if ($this->batch) { 160 | $this->batch_operators[] = array($uri, $key); 161 | return $this; 162 | } else { 163 | return $this->operateRequest($uri, $key); 164 | } 165 | } 166 | 167 | /** 168 | * 169 | * @param $prefix 170 | * @return bool|Result 171 | */ 172 | public function ls($prefix = '') 173 | { 174 | $query = array('bucket' => $this->options['bucket']) + (is_array($prefix) ? $prefix : array('prefix' => $prefix)); 175 | $uri = '/list?' . http_build_query($query); 176 | return $this->operateRequest($uri, null, $this->options['rsf_url']); 177 | } 178 | 179 | /** 180 | * Start batch mode 181 | * 182 | * @return $this 183 | */ 184 | public function batch() 185 | { 186 | $this->batch = true; 187 | return $this; 188 | } 189 | 190 | /** 191 | * Exec the batch 192 | * 193 | * @return Result 194 | * @throws \RuntimeException 195 | */ 196 | public function exec() 197 | { 198 | if (!$this->batch) throw new \RuntimeException("Not in batch mode!"); 199 | 200 | $ops = array(); 201 | foreach ($this->batch_operators as $operator) { 202 | $ops[] = 'op=' . $operator[0]; 203 | } 204 | 205 | $url = $this->options['rs_url'] . '/batch'; 206 | 207 | $request = Request::create(array( 208 | 'url' => $url, 209 | 'body' => join('&', $ops), 210 | 'method' => 'POST' 211 | )); 212 | $token = $this->mac->signRequest('/batch', join('&', $ops)); 213 | $request->header('authorization', 'QBox ' . $token); 214 | 215 | $result = new Result($response = $request->send(), $request); 216 | if (!$result->ok()) { 217 | return $result; 218 | } 219 | 220 | if (is_array($result->data)) { 221 | foreach ($result->data as $i => $item) { 222 | $r = new Result($response, $request); 223 | $r->data = isset($item['data']) ? $item['data'] : array(); 224 | if ($item['code'] !== 200) { 225 | $r->error = $item['error']; 226 | } 227 | $r->response = null; 228 | $r->data['url'] = $this->options['base_url'] . '/' . $this->batch_operators[$i][1]; 229 | $result->data[$i] = $r; 230 | } 231 | } 232 | 233 | // Reset batch mode 234 | $this->batch = false; 235 | $this->batch_operators = array(); 236 | 237 | return $result; 238 | } 239 | 240 | /** 241 | * Get image info 242 | * 243 | * @param string $key 244 | * @return Result 245 | */ 246 | public function imageInfo($key) 247 | { 248 | return $this->imageRequest($key, 'imageInfo'); 249 | } 250 | 251 | /** 252 | * Get exif info 253 | * 254 | * @param string $key 255 | * @return Result 256 | */ 257 | public function exif($key) 258 | { 259 | return $this->imageRequest($key, 'exif'); 260 | } 261 | 262 | /** 263 | * Generate imageView url 264 | * 265 | * @param string $key 266 | * @param array $options 267 | * @return Result 268 | */ 269 | public function imageView($key, array $options) 270 | { 271 | return $this->imageUrl($key, 'imageView', $options); 272 | } 273 | 274 | /** 275 | * Generate imageMogr url 276 | * 277 | * @param string $key 278 | * @param array $options 279 | * @return string 280 | */ 281 | public function imageMogr($key, array $options) 282 | { 283 | return $this->imageUrl($key, 'imageMogr', $options); 284 | } 285 | 286 | /** 287 | * Generate image url 288 | * 289 | * @param string $key 290 | * @param string $type 291 | * @param array $options 292 | * @return string 293 | */ 294 | protected function imageUrl($key, $type, array $options) 295 | { 296 | $paths = array($type); 297 | foreach ($this->image_options[$type] as $field => $opt) { 298 | if ($field == '_path') { 299 | $paths[] = $opt; 300 | continue; 301 | } 302 | 303 | $look = array($field); 304 | $value = null; 305 | $type = isset($opt['type']) ? $opt['type'] : ''; 306 | 307 | if (isset($opt['alias'])) { 308 | $look = array_merge($look, (array)$opt['alias']); 309 | } 310 | 311 | foreach ($look as $f) { 312 | if (isset($options[$f])) { 313 | $value = $options[$f]; 314 | break; 315 | } 316 | } 317 | 318 | if (!$value && isset($opt['default'])) $value = $opt['default']; 319 | 320 | if ($value !== null) { 321 | switch ($type) { 322 | case 'no-key': 323 | $paths[] = $value; 324 | break; 325 | case 'no-value': 326 | if ($value) $paths[] = $field; 327 | break; 328 | default: 329 | $paths[] = $field; 330 | $paths[] = $value; 331 | } 332 | } 333 | } 334 | return $this->options['base_url'] . '/' . $key . '?' . join('/', $paths); 335 | } 336 | 337 | /** 338 | * Image info request 339 | * 340 | * @param string $key 341 | * @param string $type 342 | * @return Result 343 | */ 344 | protected function imageRequest($key, $type) 345 | { 346 | $url = $this->options['base_url'] . '/' . $key . '?' . $type; 347 | $request = Request::create($url); 348 | return new Result($request->get(), $request); 349 | } 350 | 351 | /** 352 | * Operate request 353 | * 354 | * @param string $uri 355 | * @param string $key 356 | * @param string $host 357 | * @return bool|Result 358 | */ 359 | protected function operateRequest($uri, $key, $host = null) 360 | { 361 | $url = ($host ? $host : $this->options['rs_url']) . $uri; 362 | $request = Request::create(array( 363 | 'url' => $url, 364 | 'method' => 'POST', 365 | 'content-type' => 'application/x-www-form-urlencoded' 366 | )); 367 | $token = $this->mac->signRequest($uri); 368 | $request->header('authorization', 'QBox ' . $token); 369 | $result = new Result($request->send(), $request); 370 | if ($result->ok() && $key) { 371 | $result->data['url'] = $this->options['base_url'] . '/' . $key; 372 | } 373 | return $result; 374 | } 375 | 376 | /** 377 | * Upload request 378 | * 379 | * @param string $body 380 | * @param string|array $key 381 | * @param array $policy 382 | * @return bool|Result 383 | */ 384 | public function uploadRequest($body, $key, $policy = null) 385 | { 386 | $options = (is_string($key) ? array('key' => $key) : array()) + array( 387 | 'filename' => null 388 | ); 389 | 390 | $policy = (array)$policy + array( 391 | 'scope' => $this->options['bucket'], 392 | 'deadline' => time() + 3600, 393 | 'callbackUrl' => null, 394 | 'callbackBody' => null, 395 | 'returnUrl' => null, 396 | 'returnBody' => null, 397 | 'asyncOps' => null, 398 | 'endUser' => null 399 | ); 400 | 401 | foreach ($policy as $k => $v) { 402 | if ($v === null) unset($policy[$k]); 403 | } 404 | 405 | $token = $this->mac->signWithData(json_encode($policy)); 406 | $request = Request::create(array( 407 | 'url' => $this->options['upload_url'], 408 | 'method' => 'POST', 409 | 'headers' => array( 410 | 'content-type' => 'multipart/form-data' 411 | ), 412 | 'form' => array( 413 | 'token' => $token, 414 | 'key' => $options['key'] 415 | ) 416 | ))->file($body, basename($options['filename'] ? $options['filename'] : $options['key'])); 417 | $result = new Result($request->send(), $request); 418 | if ($result->ok()) { 419 | $result->data['url'] = $this->options['base_url'] . '/' . $result->data['key']; 420 | } 421 | return $result; 422 | } 423 | } -------------------------------------------------------------------------------- /lib/Qiniu/Mac.php: -------------------------------------------------------------------------------- 1 | access_key = $access_key; 13 | $this->secret_key = $secret_key; 14 | } 15 | 16 | /** 17 | * Sign data 18 | * 19 | * @param string $data 20 | * @return string 21 | */ 22 | public function sign($data) 23 | { 24 | $sign = hash_hmac('sha1', $data, $this->secret_key, true); 25 | return $this->access_key . ':' . Util::uriEncode($sign); 26 | } 27 | 28 | /** 29 | * Sign with data 30 | * 31 | * @param string $data 32 | * @return string 33 | */ 34 | public function signWithData($data) 35 | { 36 | $data = Util::uriEncode($data); 37 | return $this->sign($data) . ':' . $data; 38 | } 39 | 40 | /** 41 | * Sign url and body for generate token 42 | * 43 | * @param string $url 44 | * @param string $body 45 | * @return string 46 | */ 47 | public function signRequest($url, $body = '') 48 | { 49 | $url = parse_url($url); 50 | $data = ''; 51 | if (isset($url['path'])) { 52 | $data = $url['path']; 53 | } 54 | if (isset($url['query'])) { 55 | $data .= '?' . $url['query']; 56 | } 57 | $data .= "\n"; 58 | 59 | if ($body) { 60 | $data .= $body; 61 | } 62 | return $this->sign($data); 63 | } 64 | } -------------------------------------------------------------------------------- /lib/Qiniu/Qiniu.php: -------------------------------------------------------------------------------- 1 | 'GET', 12 | 'url' => null, 13 | 'headers' => array(), 14 | 'body' => '', 15 | 'form' => array(), 16 | 'files' => array(), 17 | 'encoding' => 'utf-8', 18 | 'timeout' => 60 19 | ); 20 | 21 | /** 22 | * @var Response 23 | */ 24 | public $response; 25 | 26 | /** 27 | * @var string 28 | */ 29 | public $error; 30 | 31 | /** 32 | * Create request 33 | * 34 | * @param array $options 35 | * @return Request 36 | */ 37 | public static function create($options = null) 38 | { 39 | return new self($options); 40 | } 41 | 42 | /** 43 | * @param array $options 44 | */ 45 | public function __construct($options = null) 46 | { 47 | if (is_string($options)) { 48 | $options = array('url' => $options); 49 | } 50 | $this->options = (array)$options + $this->options; 51 | } 52 | 53 | /** 54 | * Set url 55 | * 56 | * @param string $url 57 | * @return Request 58 | */ 59 | public function url($url = null) 60 | { 61 | if ($url) { 62 | $this->options['url'] = $url; 63 | return $this; 64 | } else { 65 | return $this->options['url']; 66 | } 67 | } 68 | 69 | /** 70 | * Set headers 71 | * 72 | * @param string|array $key 73 | * @param string $value 74 | * @return Request 75 | */ 76 | public function header($key, $value = null) 77 | { 78 | if (is_array($key)) { 79 | foreach ($key as $k => $v) { 80 | $this->header($k, $v); 81 | } 82 | } else { 83 | $this->options['headers'][strtolower($key)] = $value; 84 | } 85 | return $this; 86 | } 87 | 88 | /** 89 | * @param string|array $key 90 | * @param string $value 91 | * @return Request 92 | */ 93 | public function form($key, $value = null) 94 | { 95 | if (is_array($key)) { 96 | foreach ($key as $k => $v) { 97 | $this->form($k, $v); 98 | } 99 | } else { 100 | $this->options['form'][$key] = $value; 101 | } 102 | return $this; 103 | } 104 | 105 | /** 106 | * Alias form 107 | * 108 | * @param string|array $key 109 | * @param string $value 110 | * @return Request 111 | */ 112 | public function data($key, $value = null) 113 | { 114 | return $this->form($key, $value); 115 | } 116 | 117 | /** 118 | * Attach file 119 | * 120 | * @param string $file 121 | * @param string $filename 122 | * @param string $name 123 | * @throws \InvalidArgumentException 124 | * @throws \Exception 125 | * @return Request 126 | */ 127 | public function file($file, $filename = null, $name = 'file') 128 | { 129 | if (is_array($file)) { 130 | foreach ($file as $f) { 131 | $this->file($f[0], isset($f[1]) ? $f[1] : null, isset($f[2]) ? $f[2] : 'file'); 132 | } 133 | } else { 134 | if ($filename === null) { 135 | if (!is_file($file)) throw new \InvalidArgumentException("Can not upload non-exists file: $file"); 136 | 137 | if (($content = file_get_contents($file)) === false) throw new \Exception("Can not read file: $file"); 138 | } else { 139 | $content = $file; 140 | } 141 | $this->options['files'][$name] = array(basename($filename), $content); 142 | } 143 | return $this; 144 | } 145 | 146 | /** 147 | * Get request 148 | * 149 | * @param string|array $url 150 | * @param array $data 151 | * @return Response 152 | */ 153 | public function get($url = null, $data = null) 154 | { 155 | return $this->send($url, $data, 'GET'); 156 | } 157 | 158 | /** 159 | * Post request 160 | * 161 | * @param string|array $url 162 | * @param array $data 163 | * @return Response 164 | */ 165 | public function post($url = null, $data = null) 166 | { 167 | return $this->send($url, $data, 'POST'); 168 | } 169 | 170 | /** 171 | * Upload files 172 | * 173 | * @param string|array $file 174 | * @param array $options 175 | * @param string $name 176 | * @return Response|bool 177 | */ 178 | public function upload($file, $name = 'file', $options = null) 179 | { 180 | if (is_array($name)) { 181 | $name = $options ? $options : 'file'; 182 | $options = $name; 183 | } 184 | $this->file($file, $name); 185 | return $this->send($options, null, 'POST'); 186 | } 187 | 188 | /** 189 | * Put request 190 | * 191 | * @param string|array $url 192 | * @param array $data 193 | * @return Response 194 | */ 195 | public function put($url = null, $data = null) 196 | { 197 | return $this->send($url, $data, 'PUT'); 198 | } 199 | 200 | /** 201 | * Delete request 202 | * 203 | * @param string|array $url 204 | * @param null $data 205 | * @return Response 206 | */ 207 | public function delete($url = null, $data = null) 208 | { 209 | return $this->send($url, $data, 'DELETE'); 210 | } 211 | 212 | /** 213 | * Send request 214 | * 215 | * @param string|array $url 216 | * @param array $data 217 | * @param string $method 218 | * @return Response|bool 219 | */ 220 | public function send($url = null, $data = null, $method = null) 221 | { 222 | $options = array(); 223 | if ($url) $options = is_array($url) ? $url : array('url' => $url); 224 | if ($data && is_array($data)) $options['form'] = $data; 225 | if ($method) $options['method'] = $method; 226 | 227 | $this->options = $options + $this->options; 228 | 229 | if ($this->parseOptions() === false) { 230 | return false; 231 | } 232 | 233 | $options = $this->options; 234 | $url = parse_url($options['url']); 235 | 236 | $ch = curl_init(); 237 | 238 | curl_setopt($ch, CURLOPT_URL, $options['url']); 239 | curl_setopt($ch, CURLOPT_HEADER, true); 240 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 241 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $options['method']); 242 | if ($url['scheme'] == 'https') { 243 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 244 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, true); 245 | } 246 | if ($options['timeout'] > 0) curl_setopt($ch, CURLOPT_TIMEOUT, $options['timeout']); 247 | if ($options['body']) { 248 | curl_setopt($ch, CURLOPT_POSTFIELDS, $options['body']); 249 | } 250 | if ($options['headers']) { 251 | $headers = array(); 252 | foreach ($options['headers'] as $key => $value) { 253 | $headers[] = "$key: $value"; 254 | } 255 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 256 | } 257 | 258 | $error = false; 259 | if (($response = curl_exec($ch)) === false) { 260 | $error = curl_error($ch); 261 | } 262 | return $this->response = new Response($response, $error); 263 | } 264 | 265 | /** 266 | * Parse the options and data 267 | */ 268 | protected function parseOptions() 269 | { 270 | $options = & $this->options; 271 | if (!$options['url'] || !$options['method']) { 272 | $this->error = "Options url, method is required"; 273 | return false; 274 | } 275 | 276 | if ($options['method'] !== 'GET') { 277 | if ($options['files'] 278 | || isset($options['headers']['content-type']) 279 | && strpos($options['headers']['content-type'], 'multipart/form-data') === 0 280 | ) { 281 | list($boundary, $body) = self::buildMultiForm($options['form'], $options['files']); 282 | $options['headers']['content-type'] = 'multipart/form-data; boundary=' . $boundary; 283 | $options['body'] = $body; 284 | } elseif ($options['form']) { 285 | $options['headers']['content-type'] = 'application/x-www-form-urlencoded'; 286 | $options['body'] = http_build_query($options['form']); 287 | } 288 | } else if ($options['form']) { 289 | $options['url'] = $options['url'] . (strpos($options['url'], '?') ? '&' : '?') . http_build_query($options['form']); 290 | } 291 | 292 | if (!empty($options['headers']['content-type']) && $options['encoding']) { 293 | $options['headers']['content-type'] .= '; charset=' . $options['encoding']; 294 | } 295 | return true; 296 | } 297 | 298 | /** 299 | * Parse multi form 300 | * 301 | * @param array $form 302 | * @param $files 303 | * @throws \InvalidArgumentException 304 | * @return array 305 | */ 306 | protected static function buildMultiForm($form, $files) 307 | { 308 | $data = array(); 309 | $boundary = md5(uniqid()); 310 | 311 | foreach ($form as $name => $val) { 312 | $data[] = '--' . $boundary; 313 | $data[] = "Content-Disposition: form-data; name=\"$name\""; 314 | $data[] = ''; 315 | $data[] = $val; 316 | } 317 | 318 | foreach ($files as $name => $file) { 319 | $data[] = '--' . $boundary; 320 | $filename = str_replace(array("\\", "\""), array("\\\\", "\\\""), $file[0]); 321 | $data[] = "Content-Disposition: form-data; name=\"$name\"; filename=\"$filename\""; 322 | $data[] = 'Content-Type: application/octet-stream'; 323 | $data[] = ''; 324 | $data[] = $file[1]; 325 | } 326 | 327 | $data[] = '--' . $boundary . '--'; 328 | $data[] = ''; 329 | 330 | $body = implode("\r\n", $data); 331 | return array($boundary, $body); 332 | } 333 | } -------------------------------------------------------------------------------- /lib/Qiniu/Response.php: -------------------------------------------------------------------------------- 1 | parse($response))) { 22 | foreach ($parsed as $key => $value) { 23 | $this->{$key} = $value; 24 | } 25 | } 26 | $this->error = $error; 27 | } 28 | 29 | /** 30 | * Parse response 31 | * 32 | * @param $response 33 | * @return array 34 | */ 35 | public static function parse($response) 36 | { 37 | $body_pos = strpos($response, "\r\n\r\n"); 38 | $header_string = substr($response, 0, $body_pos); 39 | if (strpos($header_string, 'HTTP/1.1 100 Continue') !== false) { 40 | $head_pos = $body_pos + 4; 41 | $body_pos = strpos($response, "\r\n\r\n", $head_pos); 42 | $header_string = substr($response, $head_pos, $body_pos - $head_pos); 43 | } 44 | $header_lines = explode("\r\n", $header_string); 45 | 46 | $headers = array(); 47 | $code = false; 48 | $body = false; 49 | $protocol = null; 50 | $message = null; 51 | $data = array(); 52 | 53 | foreach ($header_lines as $index => $line) { 54 | if ($index === 0) { 55 | preg_match("/^(HTTP\/\d\.\d) (\d{3}) (.*?)$/", $line, $match); 56 | list(, $protocol, $code, $message) = $match; 57 | $code = (int)$code; 58 | continue; 59 | } 60 | list($key, $value) = explode(":", $line); 61 | $headers[strtolower(trim($key))] = trim($value); 62 | } 63 | 64 | if (is_numeric($code)) { 65 | $body_string = substr($response, $body_pos + 4); 66 | if (!empty($headers['transfer-encoding']) && $headers['transfer-encoding'] == 'chunked') { 67 | $body = self::decodeChunk($body_string); 68 | } else { 69 | $body = (string)$body_string; 70 | } 71 | $result['header'] = $headers; 72 | } 73 | 74 | // 自动解析数据 75 | if (strpos($headers['content-type'], 'json')) { 76 | $data = json_decode($body, true); 77 | } 78 | 79 | return $code ? array( 80 | 'code' => $code, 81 | 'body' => $body, 82 | 'headers' => $headers, 83 | 'message' => $message, 84 | 'protocol' => $protocol, 85 | 'data' => $data 86 | ) : false; 87 | } 88 | 89 | /** 90 | * Get header 91 | * 92 | * @param string $key 93 | * @param string $default 94 | * @return string 95 | */ 96 | public function header($key, $default = null) 97 | { 98 | $key = strtolower($key); 99 | return !isset($this->headers[$key]) ? $this->headers[$key] : $default; 100 | } 101 | 102 | /** 103 | * Is error? 104 | * 105 | * @return bool 106 | */ 107 | public function error() 108 | { 109 | return !!$this->error; 110 | } 111 | 112 | 113 | /** 114 | * Is response cachable? 115 | * 116 | * @return bool 117 | */ 118 | public function cachable() 119 | { 120 | return $this->code >= 200 && $this->code < 300 || $this->code == 304; 121 | } 122 | 123 | /** 124 | * Is empty? 125 | * 126 | * @return bool 127 | */ 128 | public function bare() 129 | { 130 | return in_array($this->code, array(201, 204, 304)); 131 | } 132 | 133 | /** 134 | * Is 200 ok? 135 | * 136 | * @return bool 137 | */ 138 | public function ok() 139 | { 140 | return $this->code === 200; 141 | } 142 | 143 | /** 144 | * Is successful? 145 | * 146 | * @return bool 147 | */ 148 | public function success() 149 | { 150 | return $this->code >= 200 && $this->code < 300; 151 | } 152 | 153 | /** 154 | * Is redirect? 155 | * 156 | * @return bool 157 | */ 158 | public function redirect() 159 | { 160 | return in_array($this->code, array(301, 302, 303, 307)); 161 | } 162 | 163 | /** 164 | * Is forbidden? 165 | * 166 | * @return bool 167 | */ 168 | public function forbidden() 169 | { 170 | return $this->code === 403; 171 | } 172 | 173 | /** 174 | * Is found? 175 | * 176 | * @return bool 177 | */ 178 | public function notFound() 179 | { 180 | return $this->code === 404; 181 | } 182 | 183 | /** 184 | * Is client error? 185 | * 186 | * @return bool 187 | */ 188 | public function clientError() 189 | { 190 | return $this->code >= 400 && $this->code < 500; 191 | } 192 | 193 | /** 194 | * Is server error? 195 | * 196 | * @return bool 197 | */ 198 | public function serverError() 199 | { 200 | return $this->code >= 500 && $this->code < 600; 201 | } 202 | 203 | /** 204 | * Decode chunk 205 | * 206 | * @param $str 207 | * @return string 208 | */ 209 | protected static function decodeChunk($str) 210 | { 211 | $body = ''; 212 | while ($str) { 213 | $chunk_pos = strpos($str, "\r\n") + 2; 214 | $chunk_size = hexdec(substr($str, 0, $chunk_pos)); 215 | $str = substr($str, $chunk_pos); 216 | $body .= substr($str, 0, $chunk_size); 217 | } 218 | return $body; 219 | } 220 | } -------------------------------------------------------------------------------- /lib/Qiniu/Result.php: -------------------------------------------------------------------------------- 1 | data = $response->data; 20 | if ($response->error) { 21 | $this->error = $response->error; 22 | } else if (!empty($this->data['error'])) { 23 | $this->error = $this->data['error']; 24 | } 25 | $this->response = $response; 26 | $this->debug = array( 27 | 'log' => $response->headers['x-log'], 28 | 'id' => isset($response->headers['x-reqid']) ? $response->headers['x-reqid'] : null 29 | ); 30 | } else if ($request instanceof Request) { 31 | $this->data = false; 32 | $this->error = $request->error; 33 | } 34 | } 35 | 36 | /** 37 | * Is ok? 38 | * 39 | * @return bool 40 | */ 41 | public function ok() 42 | { 43 | return !$this->error; 44 | } 45 | 46 | /** 47 | * Alias data 48 | * 49 | * @return bool 50 | */ 51 | public function toArray() 52 | { 53 | return $this->data; 54 | } 55 | 56 | /** 57 | * (PHP 5 >= 5.0.0)
58 | * Whether a offset exists 59 | * 60 | * @link http://php.net/manual/en/arrayaccess.offsetexists.php 61 | * @param mixed $offset

62 | * An offset to check for. 63 | *

64 | * @return boolean true on success or false on failure. 65 | *

66 | *

67 | * The return value will be casted to boolean if non-boolean was returned. 68 | */ 69 | public function offsetExists($offset) 70 | { 71 | return isset($this->data[$offset]); 72 | } 73 | 74 | /** 75 | * (PHP 5 >= 5.0.0)
76 | * Offset to retrieve 77 | * 78 | * @link http://php.net/manual/en/arrayaccess.offsetget.php 79 | * @param mixed $offset

80 | * The offset to retrieve. 81 | *

82 | * @return mixed Can return all value types. 83 | */ 84 | public function offsetGet($offset) 85 | { 86 | isset($this->data[$offset]) ? $this->data[$offset] : null; 87 | } 88 | 89 | /** 90 | * (PHP 5 >= 5.0.0)
91 | * Offset to set 92 | * 93 | * @link http://php.net/manual/en/arrayaccess.offsetset.php 94 | * @param mixed $offset

95 | * The offset to assign the value to. 96 | *

97 | * @param mixed $value

98 | * The value to set. 99 | *

100 | * @return void 101 | */ 102 | public function offsetSet($offset, $value) 103 | { 104 | $this->data[$offset] = $value; 105 | } 106 | 107 | /** 108 | * (PHP 5 >= 5.0.0)
109 | * Offset to unset 110 | * 111 | * @link http://php.net/manual/en/arrayaccess.offsetunset.php 112 | * @param mixed $offset

113 | * The offset to unset. 114 | *

115 | * @return void 116 | */ 117 | public function offsetUnset($offset) 118 | { 119 | unset($this->data[$offset]); 120 | } 121 | } -------------------------------------------------------------------------------- /lib/Qiniu/Util.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | ./test/ 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/MacTest.php: -------------------------------------------------------------------------------- 1 | mac = new Mac('test', 'test'); 15 | } 16 | 17 | public function testSign() 18 | { 19 | $this->assertEquals('test:DJRRXBXlCVuKh6ULoN87847QX-Y=', $this->mac->sign('test')); 20 | } 21 | 22 | public function testSignWithData() 23 | { 24 | $this->assertEquals('test:b25MJSh2VIQPTdMvs7POrsTIvEE=:dGVzdA==', $this->mac->signWithData('test')); 25 | } 26 | 27 | public function testSignRequest() 28 | { 29 | $this->assertEquals('test:P2jjL3PIgphwFTw9AV_zeWKl_1Q=', $this->mac->signRequest('/stat')); 30 | } 31 | } -------------------------------------------------------------------------------- /test/RequestTest.php: -------------------------------------------------------------------------------- 1 | request = new Request(); 19 | } 20 | 21 | public function testDefaultOptions() 22 | { 23 | $this->assertEquals(array( 24 | 'method' => 'GET', 25 | 'url' => null, 26 | 'headers' => array(), 27 | 'body' => '', 28 | 'form' => array(), 29 | 'files' => array(), 30 | 'encoding' => 'utf-8', 31 | 'timeout' => 60 32 | ), $this->request->options); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/UtilTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('Tm8rQ29kZS90ZXN0', Util::uriEncode('No+Code/test')); 14 | } 15 | } 16 | --------------------------------------------------------------------------------