├── .codeclimate.yml ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── Yii.php ├── composer.json ├── phpunit.xml ├── src ├── Component.php ├── Filesystem.php ├── Pfop.php ├── Policy.php └── QiniuAdapter.php └── tests ├── ComponentTest.php ├── FilesystemTest.php ├── PfopTest.php ├── PolicyTest.php ├── QiniuAdapter2Test.php ├── QiniuAdapterTest.php ├── bootstrap.php ├── main.php └── yii2-logo.png /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | fixme: 4 | enabled: true 5 | phpcodesniffer: 6 | enabled: true 7 | checks: 8 | PSR2 Classes PropertyDeclaration Underscore: 9 | enabled: false 10 | CleanCode/StaticAccess: 11 | enabled: false 12 | Naming/ShortVariable: 13 | enabled: false 14 | phpmd: 15 | enabled: true 16 | ratings: 17 | paths: 18 | - "**.php" 19 | exclude_paths: 20 | - tests/**/* 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Unix-style newlines with a newline ending every file 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | charset = utf-8 8 | 9 | [*.php] 10 | indent_style = space 11 | indent_size = 4 12 | trim_trailing_whitespace = true 13 | 14 | [*.{js,twig,css,scss,html,json}] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /composer.lock 3 | *.swp 4 | /.idea 5 | /runtime 6 | /tests/testmp4.mp4 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | 6 | install: 7 | - composer global require "fxp/composer-asset-plugin:~1.1.1" 8 | - composer require codeclimate/php-test-reporter --dev 9 | - composer install --no-progress --no-interaction 10 | 11 | script: 12 | - ./vendor/bin/phpunit --coverage-clover build/logs/clover.xml 13 | 14 | addons: 15 | code_climate: 16 | repo_token: 7bf1b66f75b8de587bf4b0df6e11591b34b0dae27f67b9b0fe88c92e7a9e840f 17 | 18 | after_script: 19 | - vendor/bin/test-reporter 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Qiniu Extension for Yii2 Change Log 2 | ================= 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Qiniu Extension for Yii2 2 | ================= 3 | 4 | The Qiniu integration for the Yii framework 5 | 6 | [![Build Status](https://travis-ci.org/dcb9/yii2-qiniu.svg)](https://travis-ci.org/dcb9/yii2-qiniu) 7 | [![Code Climate](https://codeclimate.com/github/dcb9/yii2-qiniu/badges/gpa.svg)](https://codeclimate.com/github/dcb9/yii2-qiniu) 8 | [![Issue Count](https://codeclimate.com/github/dcb9/yii2-qiniu/badges/issue_count.svg)](https://codeclimate.com/github/dcb9/yii2-qiniu) 9 | [![Latest Stable Version](https://poser.pugx.org/dcb9/yii2-qiniu/version)](https://packagist.org/packages/dcb9/yii2-qiniu) 10 | [![Total Downloads](https://poser.pugx.org/dcb9/yii2-qiniu/downloads)](https://packagist.org/packages/dcb9/yii2-qiniu) 11 | [![License](https://poser.pugx.org/dcb9/yii2-qiniu/license)](https://packagist.org/packages/dcb9/yii2-qiniu) 12 | 13 | [CHANGE LOG](CHANGELOG.md) 14 | 15 | Installation 16 | -------------------- 17 | 18 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 19 | 20 | Either run 21 | 22 | ``` 23 | composer require --prefer-dist dcb9/yii2-qiniu 24 | ``` 25 | 26 | or add 27 | 28 | ```json 29 | "dcb9/yii2-qiniu": "*" 30 | ``` 31 | 32 | to the `require` section of your composer.json. 33 | 34 | 35 | Configuration 36 | -------------------- 37 | 38 | To use this extension, simply add the following code in your application configuration: 39 | 40 | ```php 41 | return [ 42 | //.... 43 | 'components' => [ 44 | 'qiniu' => [ 45 | 'class' => 'dcb9\qiniu\Component', 46 | 'accessKey' => 'YOUR_ACCESS_KEY', 47 | 'secretKey' => 'YOUR_SECRET_KEY', 48 | 'disks' => [ 49 | 'testBucket' => [ 50 | 'bucket' => 'bucketOnQiniu', 51 | 'baseUrl' => 'ACCESS_QINIU_URL', 52 | 'isPrivate' => true, 53 | 'zone' => 'zone0', // 可设置为 zone0, zone1 @see \Qiniu\Zone 54 | ], 55 | ], 56 | ], 57 | ], 58 | ]; 59 | ``` 60 | 61 | [资源操作](http://developer.qiniu.com/code/v6/api/kodo-api/index.html#资源操作) 62 | -------------------- 63 | 64 | 资源操作就 [Flysystem](https://github.com/thephpleague/flysystem) 的一个扩展, 所以所有的调用方法与 [Flysystem](https://github.com/thephpleague/flysystem) 调用方法一致. 65 | 66 | ```php 67 | // 获取 Disk 68 | $filesystem = Yii::$app->qiniu->getDisk('testBucket'); 69 | 70 | $filesystem->has('hello.txt'); 71 | 72 | // 七牛独有 API 73 | $filesystem->getUrl('hello.txt'); // 获取访问地址 74 | ``` 75 | 76 | **[其它所有可调用的 API](http://flysystem.thephpleague.com/api/)** 77 | 78 | #### [上传策略](http://developer.qiniu.com/article/developer/security/put-policy.html) 79 | 80 | 默认设置 Policy 是使用 Array 的方式来设置的, 但是这种方式对程序员不是很友好,于是创建了一个 Policy 的类,但所有的操作还是跟操作数组一样. 81 | 82 | ```php 83 | $policy = new \dcb9\qiniu\Policy(); 84 | $policy->callbackUrl = ''; 85 | $policy->callbackBody = ''; 86 | ``` 87 | 88 | #### [获取 UploadToken](http://developer.qiniu.com/article/developer/security/upload-token.html) 89 | 90 | ```php 91 | $qiniu = Yii::$app->qiniu; 92 | 93 | $diskName = 'testBucket'; 94 | $token1 = $qiniu->getUploadToken($diskName); 95 | 96 | $key = null; 97 | $expires = 3600; 98 | $policy = new \dcb9\qiniu\Policy(); 99 | $policy->callbackUrl = ''; 100 | $policy->callbackBody = ''; 101 | 102 | // Fop @see src/Pfop.php 103 | $policy->persistentOps = \dcb9\qiniu\Pfop::instance() 104 | ->avthumb('mp4') 105 | ->wmImage('http://o82pobmde.bkt.clouddn.com/yii2-logo.png') 106 | ->saveas('testbucket', 'after-ops' . date('Y-m-d H:i:s') . '.mp4') 107 | ->__toString(); 108 | $policy->persistentNotifyUrl = 'http://blog.phpor.me'; 109 | 110 | $diskName = 'testBucket'; 111 | $token2 = $qiniu->getUploadToken($diskName, $key, $expires, $policy); 112 | ``` 113 | 114 | #### 使用 Token 上传文件 115 | 116 | ```php 117 | $token = ''; // @see 获取 UploadToken 118 | $config = ['token' => $token]; 119 | $filesystem->writeStream($path, $stream, $config); 120 | 121 | $filesystem->write($path, $content, $config); 122 | 123 | $filesystem->put($path, $content, $config); 124 | ``` 125 | 126 | Tricks 127 | -------------------- 128 | 129 | * 给配置的组件加 IDE 自动补全 [IDE autocompletion for custom components](https://github.com/samdark/yii2-cookbook/blob/master/book/ide-autocompletion.md) 130 | -------------------------------------------------------------------------------- /Yii.php: -------------------------------------------------------------------------------- 1 | =5.4.0", 21 | "qiniu/php-sdk": "v7.0.7", 22 | "league/flysystem": "~1.0" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "*" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "dcb9\\qiniu\\": "./src" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | ./tests/ 18 | 19 | 20 | 21 | 22 | ./vendor 23 | ./tests 24 | 25 | 26 | ./src 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Component.php: -------------------------------------------------------------------------------- 1 | accessKey) { 33 | throw new InvalidConfigException('accessKey can not be blank'); 34 | } 35 | 36 | if (!$this->secretKey) { 37 | throw new InvalidConfigException('secretKey can not be blank'); 38 | } 39 | } 40 | 41 | protected function getAuth() 42 | { 43 | if (!$this->_auth) { 44 | $this->_auth = new Auth($this->accessKey, $this->secretKey); 45 | } 46 | 47 | return $this->_auth; 48 | } 49 | 50 | /** 51 | * @param array $disks 52 | */ 53 | public function setDisks(array $disks) 54 | { 55 | foreach ($disks as $id => $component) { 56 | $this->setDisk($id, $component); 57 | } 58 | } 59 | 60 | public function setDisk($id, array $definition) 61 | { 62 | if (!isset($definition['class'])) { 63 | $definition['class'] = QiniuAdapter::className(); 64 | } 65 | /* @var $qiniuAdapter QiniuAdapter */ 66 | $qiniuAdapter = Yii::createObject($definition); 67 | $qiniuAdapter->setAuth($this->getAuth()); 68 | 69 | $this->_disks[$id] = $this->createFilesystem($qiniuAdapter); 70 | } 71 | 72 | /** 73 | * @param $id 74 | * @return Filesystem 75 | */ 76 | public function getDisk($id) 77 | { 78 | if (!isset($this->_disks[$id])) { 79 | throw new \BadMethodCallException('Unknown disk id: ' . $id); 80 | } 81 | 82 | return $this->_disks[$id]; 83 | } 84 | 85 | protected function createFilesystem(AdapterInterface $adapter, array $config = null) 86 | { 87 | return new Filesystem($adapter, $config); 88 | } 89 | 90 | /** 91 | * @param string $disk 92 | * @param null $key 93 | * @param int $expires 94 | * @param null $policy 95 | * @param bool $strictPolicy 96 | * @return string 97 | */ 98 | public function getUploadToken($disk, $key = null, $expires = 3600, $policy = null, $strictPolicy = true) 99 | { 100 | /* @var $qiniuAdapter QiniuAdapter */ 101 | $qiniuAdapter = $this->getDisk($disk)->getAdapter(); 102 | 103 | return $this->getAuth() 104 | ->uploadToken($qiniuAdapter->bucket, $key, $expires, $policy, $strictPolicy); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Filesystem.php: -------------------------------------------------------------------------------- 1 | getAdapter()->getUrl($path, $expires); 37 | } 38 | 39 | public function writeWithoutKey($contents, array $config = []) 40 | { 41 | $config = $this->prepareConfig($config); 42 | 43 | return $this->getAdapter()->writeWithoutKey($contents, $config); 44 | } 45 | 46 | public function writeStreamWithoutKey($resource, array $config = []) 47 | { 48 | $config = $this->prepareConfig($config); 49 | 50 | return $this->getAdapter()->writeStreamWithoutKey($resource, $config); 51 | } 52 | 53 | public function getBucket() 54 | { 55 | return $this->getAdapter()->bucket; 56 | } 57 | 58 | public function __get($name) 59 | { 60 | if ($name === 'bucket') { 61 | return $this->getBucket(); 62 | } 63 | } 64 | 65 | /** 66 | * 获取持久化处理类 67 | * 68 | * 该类用于主动触发异步持久化操作 69 | * 70 | * @param null|string $pipeline 71 | * @param null|string $notifyUrl 72 | * @param bool $force 73 | * 74 | * @return PersistentFop 75 | */ 76 | public function getPersistentFop($pipeline = null, $notifyUrl = null, $force = false) 77 | { 78 | return new PersistentFop($this->getAdapter()->getAuth(), $this->getBucket(), $pipeline, $notifyUrl, $force); 79 | } 80 | 81 | /** 82 | * 第三方资源抓取 83 | * 84 | * @see http://developer.qiniu.com/code/v6/api/kodo-api/rs/fetch.html 85 | * @see BucketManager::fetch() 86 | * @param $url 87 | * @param null $key 88 | * 89 | * @return array 90 | */ 91 | public function fetchUrl($url, $key = null) 92 | { 93 | return $this->getAdapter()->fetchUrl($url, $key); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Pfop.php: -------------------------------------------------------------------------------- 1 | $value) { 47 | if (!property_exists($obj, $name)) { 48 | throw new \InvalidArgumentException('Property ' . $name . ' does not exists'); 49 | } 50 | $obj->$name = $obj->handleValue($name, $value); 51 | } 52 | 53 | return $obj; 54 | } 55 | 56 | protected $avthumb; 57 | protected $segtime; 58 | protected $ab; 59 | protected $aq; 60 | protected $ar; 61 | protected $r; 62 | protected $vb; 63 | protected $vcodec; 64 | protected $acodec; 65 | protected $scodec; 66 | protected $subtitle; 67 | protected $ss; 68 | protected $t; 69 | protected $autoscale; 70 | protected $aspect; 71 | protected $stripmeta; 72 | protected $rotate; 73 | protected $wmImage; 74 | protected $wmGravity; 75 | protected $wmText; 76 | protected $wmGravityText; 77 | protected $wmFont; 78 | protected $wmFontColor; 79 | protected $wmFontSize; 80 | protected $writeXing; 81 | protected $an; 82 | protected $vn; 83 | protected $s; 84 | protected $saveas; 85 | 86 | /** 87 | * @param $w 88 | * @param $h 89 | * @return $this 90 | */ 91 | public function aspect($w, $h) 92 | { 93 | $this->aspect = $w . ':' . $h; 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * @param string $bucket 100 | * @param string $key 101 | * @return $this 102 | */ 103 | public function saveas($bucket, $key) 104 | { 105 | $this->saveas = \Qiniu\base64_urlSafeEncode($bucket . ':' . $key); 106 | 107 | return $this; 108 | } 109 | 110 | protected function handleValue($name, $value) 111 | { 112 | if (in_array($name, ['an', 'vn', 'stripmeta'])) { 113 | $value = $value ? 1 : 0; 114 | } elseif (in_array($name, ['wmImage', 'wmText', 'wmFont', 'wmFontColor'])) { 115 | $value = \Qiniu\base64_urlSafeEncode($value); 116 | } elseif ($name === 'rotate' && !in_array($value, ['90', '180', '270', 'auto'])) { 117 | throw new \InvalidArgumentException('Rotate can not be ' . $value); 118 | } 119 | 120 | return $value; 121 | } 122 | 123 | /** 124 | * @param $name 125 | * @param $arguments 126 | * @return $this 127 | */ 128 | public function __call($name, $arguments) 129 | { 130 | if (!property_exists($this, $name)) { 131 | throw new \BadMethodCallException(); 132 | } 133 | $this->$name = $this->handleValue($name, array_shift($arguments)); 134 | 135 | return $this; 136 | } 137 | 138 | public function __toString() 139 | { 140 | $keys = array_filter(array_keys(get_object_vars($this)), function ($property) { 141 | return !in_array($property, ['saveas']) && $this->$property !== null; 142 | }); 143 | 144 | return call_user_func_array([$this, 'getValue'], $keys) 145 | . ($this->saveas === null ? '' : '|saveas/' . $this->saveas); 146 | } 147 | 148 | /** 149 | * @return string 150 | */ 151 | public function getValue() 152 | { 153 | $values = array_map(function ($key) { 154 | return $key . '/' . $this->$key; 155 | }, func_get_args()); 156 | 157 | return implode('/', $values); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Policy.php: -------------------------------------------------------------------------------- 1 | ,表示允许用户上传文件到指定的 bucket。 23 | * 在这种格式下文件只能“新增”,若已存在同名资源上传则会失败。 24 | * ● :,表示只允许用户上传指定key的文件。 25 | * 在这种格式下文件默认允许“修改”,若已存在同名资源则会被覆盖。 26 | * 如果只希望上传指定key的文件,并且不允许修改,那么可以将下面的 insertOnly 属性值设为 1。 27 | * 28 | * @property int $deadline UnixTimestamp 上传凭证有效截止时间。 29 | * Unix时间戳,单位:秒。该截止时间为上传完成后,在七牛空间生成文件的校验时间, 30 | * 而非上传的开始时间,一般建议设置为“上传开始时间+3600s”, 31 | * 用户可根据具体的业务场景对凭证截止时间进行调整。 32 | * 33 | * @property int $insertOnly AllowFileUpdating 限定为“新增”语意。 34 | * 如果设置为非0值,则无论scope设置为什么形式,仅能以“新增”模式上传文件。 35 | * 36 | * @property string $endUser EndUserId 唯一属主标识。 37 | * 特殊场景下非常有用,比如根据"App-Client"标识给图片或视频打水印。 38 | * 39 | * @property string $returnUrl RedirectURL Web端文件上传成功后,浏览器执行303跳转的URL。 40 | * 通常用于HTML Form上传。 41 | * 文件上传成功后会跳转到?upload_ret=, 包含returnBody内容。 42 | * 如不设置returnUrl,则直接将returnBody的内容返回给客户端。 43 | * 44 | * @property string $returnBody ResponseBodyForAppClient 上传成功后,自定义七牛云最终返回給上传端(在指定returnUrl时是携带在跳转路径参数中)的数据。 45 | * 支持魔法变量和自定义变量。 46 | * returnBody 要求是合法的 JSON 文本。 47 | * 如:{"key": $(key), "hash": $(etag), "w": $(imageInfo.width), "h": $(imageInfo.height)}。 48 | * @see http://developer.qiniu.com/article/kodo/kodo-developer/up/vars.html#magicvar 49 | * @see http://developer.qiniu.com/article/kodo/kodo-developer/up/vars.html#xvar 50 | * 51 | * @property string $callbackUrl RequestUrlForAppServer 上传成功后,七牛云向"App-Server"发送POST请求的URL。 52 | * 必须是公网上可以正常进行POST请求并能响应"HTTP/1.1 200 OK"的有效URL。 53 | * 另外,为了给客户端有一致的体验,我们要求callbackUrl 返回包 Content-Type 为 "application/json",即返回的内容必须是合法的 JSON 文本。 54 | * 出于高可用的考虑,本字段允许设置多个 callbackUrl(用 ; 分隔),在前一个 callbackUrl 请求失败的时候会依次重试下一个callbackUrl。 55 | * 一个典型例子是 http:///callback;http:///callback,并同时指定下面的 callbackHost 字段。 56 | * 在 callbackUrl 中使用 ip 的好处是减少了对 dns 解析的依赖,可改善回调的性能和稳定性。 57 | * 58 | * @property string $callbackHost RequestHostForAppServer 上传成功后,七牛云向"App-Server"发送回调通知时的 Host 值。 59 | * 与callbackUrl配合使用,仅当设置了 callbackUrl 时才有效。 60 | * 61 | * @property string $callbackBody RequestBodyForAppServer 上传成功后,七牛云向"App-Server"发送Content-Type: application/x-www-form-urlencoded 的POST请求。 62 | * 该字段"App-Server"可以通过直接读取请求的query来获得,支持魔法变量和自定义变量。 63 | * callbackBody 要求是合法的 url query string。如:key=$(key)&hash=$(etag)&w=$(imageInfo.width)&h=$(imageInfo.height)。 64 | * 65 | * @property string $callbackBodyType RequestBodyTypeForAppServer 上传成功后,七牛云向"App-Server"发送回调通知callbackBody的Content-Type。 66 | * 默认为application/x-www-form-urlencoded,也可设置为application/json。 67 | * 68 | * @property int $callbackFetchKey RequestKeyForApp 是否启用fetchKey上传模式。 69 | * 0为关闭,1为启用。具体见callbackFetchKey详解。 70 | * @see http://developer.qiniu.com/article/developer/security/put-policy.html#fetchkey 71 | * 72 | * @property string $persistentOps persistentOpsCmds 资源上传成功后触发执行的预转持久化处理指令列表。 73 | * 每个指令是一个API规格字符串,多个指令用“;”分隔。 请参看persistenOps详解与示例。 74 | * 75 | * @property string $persistentNotifyUrl persistentNotifyUrl 接收预转持久化结果通知的URL。 76 | * 必须是公网上可以正常进行POST请求并能响应"HTTP/1.1 200 OK"的有效URL。 77 | * 该URL获取的内容和持久化处理状态查询(prefop)的处理结果一致。 78 | * 发送body格式为Content-Type为application/json的POST请求,需要按照读取流的形式读取请求的body才能获取。 79 | * 80 | * @property string $persistentPipeline persistentPipeline 转码队列名。 81 | * 资源上传成功后,触发转码时指定独立的队列进行转码。为空则表示使用公用队列,处理速度比较慢。建议使用专用队列。 82 | * 83 | * @property string $saveKey SaveKey 自定义资源名。 84 | * 支持魔法变量及自定义变量。这个字段仅当用户上传的时候没有主动指定key的时候起作用。 85 | * 86 | * @property int $fsizeMin FileSizeMin 限定上传文件大小最小值,单位:字节(Byte)。 87 | * @property int $fsizeLimit FileSizeLimit 限定上传文件大小最大值,单位:字节(Byte)。 88 | * 超过限制上传文件大小的最大值会被判为上传失败,返回413状态码。 89 | * 90 | * @property int $detectMime AutoDetectMimeType 开启MimeType侦测功能。 91 | * 设为非0值,则忽略上传端传递的文件MimeType信息,使用七牛服务器侦测内容后的判断结果。 92 | * 默认设为0值,如上传端指定了MimeType则直接使用该值,否则按如下顺序侦测MimeType值: 93 | * 1. 检查文件扩展名; 94 | * 2. 检查Key扩展名; 95 | * 3. 侦测内容。 96 | * 如不能侦测出正确的值,会默认使用 application/octet-stream 。 97 | * 98 | * @property string $mimeLimit MimeLimit 限定用户上传的文件类型。 99 | * 指定本字段值,七牛服务器会侦测文件内容以判断MimeType,再用判断值跟指定值进行匹配,匹配成功则允许上传,匹配失败则返回403状态码。 100 | * 示例: 101 | * ● image/*表示只允许上传图片类型 102 | * ● image/jpeg;image/png表示只允许上传jpg和png类型的图片 103 | * ● !application/json;text/plain表示禁止上传json文本和纯文本。注意最前面的感叹号! 104 | * 105 | * @property int $deleteAfterDays deleteAfterDays 文件在多少天后被删除 106 | * 七牛将文件上传时间与指定的deleteAfterDays天数相加,得到的时间入到后一天的午夜(CST,中国标准时间),从而得到文件删除开始时间。 107 | * 例如文件在2015年1月1日上午10:00 CST上传,指定deleteAfterDays为3天,那么会在2015年1月5日00:00 CST之后当天内删除文件。 108 | */ 109 | class Policy implements IteratorAggregate, ArrayAccess 110 | { 111 | use ArrayAccessTrait; 112 | 113 | protected $data; 114 | 115 | public function __set($name, $value) 116 | { 117 | $this->data[$name] = $value; 118 | } 119 | 120 | public function __construct(array $policy = []) 121 | { 122 | $this->data = $policy; 123 | } 124 | 125 | public function __toArray() 126 | { 127 | return $this->data; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/QiniuAdapter.php: -------------------------------------------------------------------------------- 1 | bucket === null) { 39 | throw new InvalidConfigException('The "bucket" property must be set.'); 40 | } elseif ($this->baseUrl === null) { 41 | throw new InvalidConfigException('The "baseUrl" property must be set.'); 42 | } 43 | $zone = isset($config['zone']) ? $config['zone'] : 'zone0'; 44 | $this->setZone($zone); 45 | } 46 | 47 | public function getZone() 48 | { 49 | if (!$this->_zone) { 50 | $this->setZone('zone0'); 51 | } 52 | 53 | return $this->_zone; 54 | } 55 | 56 | public function setZone($zone) 57 | { 58 | if (!in_array($zone, ['zone1', 'zone0'])) { 59 | throw new InvalidConfigException('The "zone" property must be "zone1" OR "zone0"'); 60 | } 61 | $this->_zone = call_user_func(['Qiniu\Zone', $zone]); 62 | } 63 | 64 | /** 65 | * @var string 七牛存储Bucket 66 | */ 67 | public $bucket; 68 | /** 69 | * @var string 基本访问域名 70 | */ 71 | public $baseUrl; 72 | /** 73 | * @var bool 是否私有空间, 默认公开空间 74 | */ 75 | public $isPrivate = false; 76 | 77 | private $_auth; 78 | 79 | /** 80 | * @return \Qiniu\Auth 81 | */ 82 | public function getAuth() 83 | { 84 | return $this->_auth; 85 | } 86 | 87 | /** 88 | * @param \Qiniu\Auth $auth 89 | */ 90 | public function setAuth(Auth $auth) 91 | { 92 | $this->_auth = $auth; 93 | } 94 | 95 | private $_bucketManager; 96 | 97 | /** 98 | * @return \Qiniu\Storage\BucketManager 99 | */ 100 | public function getBucketManager() 101 | { 102 | if ($this->_bucketManager === null) { 103 | $this->setBucketManager(new BucketManager($this->getAuth())); 104 | } 105 | 106 | return $this->_bucketManager; 107 | } 108 | 109 | /** 110 | * @param \Qiniu\Storage\BucketManager $bucketManager 111 | */ 112 | public function setBucketManager(BucketManager $bucketManager) 113 | { 114 | $this->_bucketManager = $bucketManager; 115 | } 116 | 117 | private $_uploadManager; 118 | 119 | /** 120 | * @return \Qiniu\Storage\UploadManager 121 | */ 122 | public function getUploadManager() 123 | { 124 | if ($this->_uploadManager === null) { 125 | $this->setUploadManager(new UploadManager()); 126 | } 127 | 128 | return $this->_uploadManager; 129 | } 130 | 131 | /** 132 | * @param \Qiniu\Storage\UploadManager $uploadManager 133 | */ 134 | public function setUploadManager(UploadManager $uploadManager) 135 | { 136 | $this->_uploadManager = $uploadManager; 137 | } 138 | 139 | /** 140 | * @param null $key 141 | * @param int $expires 142 | * @param null $policy 143 | * @param bool|true $strictPolicy 144 | * @return string 145 | */ 146 | public function getUploadToken($key = null, $expires = 3600, $policy = null, $strictPolicy = true) 147 | { 148 | return $this->getAuth()->uploadToken($this->bucket, $key, $expires, $policy, $strictPolicy); 149 | } 150 | 151 | /** 152 | * @param $path 153 | * @param $resource 154 | * @param $size 155 | * @param string $type 156 | * @return array 157 | */ 158 | protected function streamUpload( 159 | $path, 160 | $resource, 161 | $size, 162 | $type = 'application/octet-stream', 163 | QiniuConfig $config = null, 164 | $token = null 165 | ) { 166 | if ($config === null) { 167 | $config = new \Qiniu\Config($this->_zone); 168 | } 169 | if ($token === null) { 170 | $token = $this->getUploadToken(); 171 | } 172 | $resumeUploader = new ResumeUploader($token, $path, $resource, $size, null, $type, $config); 173 | 174 | return $resumeUploader->upload(); 175 | } 176 | 177 | /** 178 | * @param string $path 179 | * @param integer $expires 只有 Bucket 为 private 时候该值才有效 180 | * @return string 181 | */ 182 | public function getUrl($path, $expires = 3600) 183 | { 184 | $keyEsc = str_replace("%2F", "/", rawurlencode($path)); 185 | 186 | $baseUrl = rtrim($this->baseUrl, '/') . '/' . $keyEsc; 187 | if ($this->isPrivate) { 188 | return $this->getAuth()->privateDownloadUrl($baseUrl, $expires); 189 | } 190 | 191 | return $baseUrl; 192 | } 193 | 194 | /** 195 | * @param array $file 196 | * @return array 197 | */ 198 | protected function normalizeData(array $file) 199 | { 200 | return [ 201 | 'type' => 'file', 202 | 'path' => $file['key'], 203 | 'size' => $file['fsize'], 204 | 'mimetype' => $file['mimeType'], 205 | 'visibility' => $this->isPrivate ? AdapterInterface::VISIBILITY_PRIVATE : AdapterInterface::VISIBILITY_PUBLIC, 206 | 'timestamp' => (int)($file['putTime'] / 10000000) //Epoch 时间戳 207 | ]; 208 | } 209 | 210 | /** 211 | * @param $directory 212 | * @param null $start 213 | * @return array 214 | */ 215 | protected function listDirContents($directory, $start = null) 216 | { 217 | list($item, $start, $err) = $this->getBucketManager()->listFiles($this->bucket, $directory, $start); 218 | if ($err !== null) { 219 | return []; 220 | } elseif (!empty($start)) { 221 | $item = array_merge($item, $this->listDirContents($directory, $start)); 222 | } 223 | 224 | return $item; 225 | } 226 | 227 | /** 228 | * @inheritdoc 229 | */ 230 | public function has($path) 231 | { 232 | return $this->getMetadata($path); 233 | } 234 | 235 | /** 236 | * @inheritdoc 237 | */ 238 | public function read($path) 239 | { 240 | $resource = $this->readStream($path); 241 | $resource['contents'] = stream_get_contents($resource['stream']); 242 | fclose($resource['stream']); 243 | unset($resource['stream']); 244 | 245 | return $resource; 246 | } 247 | 248 | /** 249 | * @inheritdoc 250 | */ 251 | public function readStream($path) 252 | { 253 | $url = $this->getAuth()->privateDownloadUrl($this->getUrl($path)); 254 | $stream = fopen($url, 'r'); 255 | if (!$stream) { 256 | return false; 257 | } 258 | 259 | return compact('stream', 'path'); 260 | } 261 | 262 | /** 263 | * @inheritdoc 264 | */ 265 | public function listContents($directory = '', $recursive = false) 266 | { 267 | $files = []; 268 | foreach ($this->listDirContents($directory) as $k => $file) { 269 | $pathInfo = pathinfo($file['key']); 270 | $files[] = array_merge($pathInfo, $this->normalizeData($file), [ 271 | 'type' => isset($pathInfo['extension']) ? 'file' : 'dir', 272 | ]); 273 | } 274 | 275 | return $files; 276 | } 277 | 278 | /** 279 | * @inheritdoc 280 | */ 281 | public function getMetadata($path) 282 | { 283 | list($ret, $err) = $this->getBucketManager()->stat($this->bucket, $path); 284 | if ($err !== null) { 285 | return false; 286 | } 287 | $ret['key'] = $path; 288 | 289 | return $this->normalizeData($ret); 290 | } 291 | 292 | /** 293 | * @inheritdoc 294 | */ 295 | public function getSize($path) 296 | { 297 | return $this->getMetadata($path); 298 | } 299 | 300 | /** 301 | * @inheritdoc 302 | */ 303 | public function getMimetype($path) 304 | { 305 | return $this->getMetadata($path); 306 | } 307 | 308 | /** 309 | * @inheritdoc 310 | */ 311 | public function getTimestamp($path) 312 | { 313 | return $this->getMetadata($path); 314 | } 315 | 316 | /** 317 | * @inheritdoc 318 | */ 319 | public function getVisibility($path) 320 | { 321 | return [ 322 | 'visibility' => $this->isPrivate ? AdapterInterface::VISIBILITY_PRIVATE : AdapterInterface::VISIBILITY_PUBLIC 323 | ]; 324 | } 325 | 326 | /** 327 | * @inheritdoc 328 | */ 329 | public function write($path, $contents, Config $config) 330 | { 331 | $stream = fopen('php://temp', 'w+b'); 332 | fwrite($stream, $contents); 333 | rewind($stream); 334 | $result = $this->writeStream($path, $stream, $config); 335 | is_resource($stream) && fclose($stream); 336 | 337 | if (is_array($result)) { 338 | $result['contents'] = $contents; 339 | $result['mimetype'] = Util::guessMimeType($path, $contents); 340 | } 341 | 342 | return $result; 343 | } 344 | 345 | private $_lastError; 346 | 347 | public function getLastError() 348 | { 349 | $lastError = $this->_lastError; 350 | $this->_lastError = null; 351 | 352 | return $lastError; 353 | } 354 | 355 | /** 356 | * @inheritdoc 357 | */ 358 | public function writeStream($path, $resource, Config $config) 359 | { 360 | $size = Util::getStreamSize($resource); 361 | $token = $config->get('token'); 362 | list(, $err) = $this->streamUpload($path, $resource, $size, 'application/octet-stream', null, $token); 363 | if ($err !== null) { 364 | $this->_lastError = $err; 365 | 366 | return false; 367 | } 368 | 369 | return compact('size', 'path'); 370 | } 371 | 372 | /** 373 | * @inheritdoc 374 | */ 375 | public function update($path, $contents, Config $config) 376 | { 377 | return $this->delete($path) && $this->write($path, $contents, $config); 378 | } 379 | 380 | /** 381 | * @inheritdoc 382 | */ 383 | public function updateStream($path, $resource, Config $config) 384 | { 385 | return $this->writeStream($path, $resource, $config); 386 | } 387 | 388 | /** 389 | * @inheritdoc 390 | */ 391 | public function rename($path, $newpath) 392 | { 393 | list(, $err) = $this->getBucketManager()->rename($this->bucket, $path, $newpath); 394 | 395 | return $err === null; 396 | } 397 | 398 | /** 399 | * @inheritdoc 400 | */ 401 | public function copy($path, $newpath) 402 | { 403 | list(, $err) = $this->getBucketManager() 404 | ->copy($this->bucket, $path, $this->bucket, $newpath); 405 | 406 | return $err === null; 407 | } 408 | 409 | /** 410 | * @inheritdoc 411 | */ 412 | public function delete($path) 413 | { 414 | $error = $this->getBucketManager() 415 | ->delete($this->bucket, $path); 416 | 417 | return $error === null; 418 | } 419 | 420 | /** 421 | * @inheritdoc 422 | */ 423 | public function deleteDir($dirname) 424 | { 425 | // 七牛无目录概念. 目前实现方案是.列举指定目录资源.批量删除 426 | $keys = array_map(function ($file) { 427 | return $file['key']; 428 | }, $this->listDirContents($dirname)); 429 | if (empty($keys)) { 430 | return true; 431 | } 432 | $batchDelete = BucketManager::buildBatchDelete($this->bucket, $keys); 433 | list(, $err) = $this->getBucketManager() 434 | ->batch($batchDelete); 435 | 436 | return $err === null; 437 | } 438 | 439 | /** 440 | * @inheritdoc 441 | */ 442 | public function createDir($dirname, Config $config) 443 | { 444 | return ['path' => $dirname]; 445 | } 446 | 447 | /** 448 | * @inheritdoc 449 | */ 450 | public function setVisibility($path, $visibility) 451 | { 452 | if ($this->isPrivate) { 453 | $visibility = AdapterInterface::VISIBILITY_PRIVATE; 454 | } else { 455 | $visibility = AdapterInterface::VISIBILITY_PUBLIC; 456 | } 457 | 458 | return compact('visibility'); 459 | } 460 | 461 | public function writeWithoutKey($contents, Config $config) 462 | { 463 | $uploadManager = new UploadManager(); 464 | $token = $this->getUploadToken(); 465 | 466 | return $uploadManager->put($token, null, $contents); 467 | } 468 | 469 | public function writeStreamWithoutKey($resource, Config $config) 470 | { 471 | $size = Util::getStreamSize($resource); 472 | $token = $config->get('token'); 473 | 474 | return $this->streamUpload(null, $resource, $size, 'application/octet-stream', null, $token); 475 | } 476 | 477 | public function fetchUrl($url, $key = null) 478 | { 479 | return $this->getBucketManager() 480 | ->fetch($url, $this->bucket, $key); 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /tests/ComponentTest.php: -------------------------------------------------------------------------------- 1 | Component::className(), 17 | 'accessKey' => '', 18 | ]); 19 | } 20 | 21 | /** 22 | * @expectedException \yii\base\InvalidConfigException 23 | */ 24 | public function testEmptySecretKey() 25 | { 26 | Yii::createObject([ 27 | 'class' => Component::className(), 28 | 'accessKey' => '111', 29 | 'secretKey' => '', 30 | ]); 31 | } 32 | 33 | public function testCreateComponent() 34 | { 35 | $component = Yii::createObject([ 36 | 'class' => Component::className(), 37 | 'accessKey' => '111', 38 | 'secretKey' => '222', 39 | ]); 40 | 41 | $this->assertInstanceOf(Component::className(), $component); 42 | } 43 | 44 | public function testCreateComponentWithDisk() 45 | { 46 | /* @var $component Component */ 47 | $component = Yii::createObject([ 48 | 'class' => Component::className(), 49 | 'accessKey' => '111', 50 | 'secretKey' => '222', 51 | 'disks' => [ 52 | 'testBucket' => [ 53 | 'bucket' => 'bucketOnQiniu', 54 | 'baseUrl' => 'http://xxx.xx.clouddn.com', 55 | ] 56 | ], 57 | ]); 58 | 59 | $this->assertInstanceOf(Component::className(), $component); 60 | $this->assertInstanceOf(Filesystem::className(), $component->getDisk('testBucket')); 61 | } 62 | 63 | /** 64 | * @expectedException \BadMethodCallException 65 | */ 66 | public function testGetInvalidDisk() 67 | { 68 | Yii::$app->qiniu->getDisk('invalidDistId'); 69 | } 70 | 71 | public function testGetToken() 72 | { 73 | $token = Yii::$app->qiniu->getUploadToken('testBucket'); 74 | $this->assertTrue(is_string($token)); 75 | } 76 | 77 | public function testGetTokenByPolicy() 78 | { 79 | $policy = new Policy(); 80 | $policy->callbackUrl = 'http://www.baidu.com'; 81 | $policy->callbackBody = 82 | $policy->persistentOps = Pfop::instance()->avthumb('mp4')->saveas('testbucket', 'test.mp4'); 83 | $token = Yii::$app->qiniu->getUploadToken('testBucket', null, 3600, $policy); 84 | $this->assertTrue(is_string($token)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/FilesystemTest.php: -------------------------------------------------------------------------------- 1 | Component::className(), 15 | 'accessKey' => '111', 16 | 'secretKey' => '222', 17 | 'disks' => [ 18 | 'testBucket' => [ 19 | 'bucket' => 'bucketOnQiniu', 20 | 'baseUrl' => 'http://xxx.xx.clouddn.com', 21 | ] 22 | ], 23 | ]); 24 | $component->setDisk('testBucket', []); 25 | } 26 | 27 | /** 28 | * @dataProvider urlDataProvider 29 | */ 30 | public function testGetUrl($baseUrl, $path, $expect) 31 | { 32 | /* @var $component Component */ 33 | $component = Yii::createObject([ 34 | 'class' => Component::className(), 35 | 'accessKey' => '111', 36 | 'secretKey' => '222', 37 | 'disks' => [ 38 | 'testBucket' => [ 39 | 'bucket' => 'bucketOnQiniu', 40 | 'baseUrl' => $baseUrl, 41 | ] 42 | ], 43 | ]); 44 | $this->assertEquals($expect, $component->getDisk('testBucket')->getUrl($path)); 45 | } 46 | 47 | public function urlDataProvider() 48 | { 49 | return [ 50 | [ 51 | 'http://xxx.xx.clouddn.com/', 52 | 'test.txt', 53 | 'http://xxx.xx.clouddn.com/test.txt' 54 | ], 55 | [ 56 | 'http://xxx.xx.clouddn.com', 57 | 'test.txt', 58 | 'http://xxx.xx.clouddn.com/test.txt' 59 | ], 60 | ]; 61 | } 62 | 63 | public function testGetBucket() 64 | { 65 | /* @var $component Component */ 66 | $component = Yii::createObject([ 67 | 'class' => Component::className(), 68 | 'accessKey' => '111', 69 | 'secretKey' => '222', 70 | 'disks' => [ 71 | 'testBucket' => [ 72 | 'bucket' => 'bucketOnQiniu', 73 | 'baseUrl' => 'http://xxx.xx.xx', 74 | ] 75 | ], 76 | ]); 77 | $this->assertEquals('bucketOnQiniu', $component->getDisk('testBucket')->bucket); 78 | $this->assertEquals('bucketOnQiniu', $component->getDisk('testBucket')->getBucket()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/PfopTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expect, Pfop::instance($config)->__toString()); 13 | } 14 | 15 | /** 16 | * @dataProvider builderDataProvider 17 | */ 18 | public function testBuilder($actual, $expected) 19 | { 20 | $this->assertEquals($actual, $expected); 21 | } 22 | 23 | /** 24 | * @dataProvider builderBoolDataProvider 25 | */ 26 | public function testBuilderBool($actual, $expected) 27 | { 28 | $this->assertEquals($actual, $expected); 29 | } 30 | 31 | /** 32 | * @dataProvider builderBase64DataProvider 33 | */ 34 | public function testBuilderBase64($actual, $expected) 35 | { 36 | $this->assertEquals($actual, $expected); 37 | } 38 | 39 | public function builderBase64DataProvider() 40 | { 41 | return [ 42 | [ 43 | Pfop::instance() 44 | ->wmImage('http://developer.qiniu.com/img/developer-logo@2x.png'), 45 | 'wmImage/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vaW1nL2RldmVsb3Blci1sb2dvQDJ4LnBuZw==', 46 | ], 47 | [ 48 | Pfop::instance() 49 | ->wmFontColor('#FFFFFF'), 50 | 'wmFontColor/I0ZGRkZGRg==', 51 | ], 52 | [ 53 | Pfop::instance() 54 | ->wmText('七牛视频处理'), 55 | 'wmText/5LiD54mb6KeG6aKR5aSE55CG', 56 | ], 57 | ]; 58 | } 59 | 60 | public function builderBoolDataProvider() 61 | { 62 | return [ 63 | [ 64 | Pfop::instance() 65 | ->avthumb('mp4') 66 | ->stripmeta(false) 67 | ->an(false) 68 | ->vn(true), 69 | 'avthumb/mp4/stripmeta/0/an/0/vn/1', 70 | ], 71 | [ 72 | Pfop::instance() 73 | ->avthumb('mp4') 74 | ->vn(false), 75 | 'avthumb/mp4/vn/0', 76 | ], 77 | [ 78 | Pfop::instance() 79 | ->avthumb('mp4') 80 | ->an('asdfasdf'), 81 | 'avthumb/mp4/an/1', 82 | ], 83 | ]; 84 | } 85 | 86 | public function builderDataProvider() 87 | { 88 | return [ 89 | [ 90 | Pfop::instance() 91 | ->avthumb('m3u8') 92 | ->segtime(10) 93 | ->vcodec('libx264') 94 | ->s('320x240') 95 | ->__toString(), 96 | 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240', 97 | ], 98 | [ 99 | Pfop::instance() 100 | ->avthumb('mp4'), 101 | 'avthumb/mp4', 102 | ], 103 | [ 104 | Pfop::instance() 105 | ->avthumb('mp4') 106 | ->wmImage('http://developer.qiniu.com/img/developer-logo@2x.png'), 107 | 'avthumb/mp4/wmImage/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vaW1nL2RldmVsb3Blci1sb2dvQDJ4LnBuZw==' 108 | ], 109 | [ 110 | Pfop::instance() 111 | ->avthumb('mp4') 112 | ->wmImage('http://developer.qiniu.com/img/developer-logo@2x.png') 113 | ->saveas('testBucket', 'testBucket.mp4'), 114 | 'avthumb/mp4/wmImage/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vaW1nL2RldmVsb3Blci1sb2dvQDJ4LnBuZw==|saveas/dGVzdEJ1Y2tldDp0ZXN0QnVja2V0Lm1wNA==', 115 | ], 116 | ]; 117 | } 118 | 119 | public function inInstanceProvider() 120 | { 121 | return [ 122 | [ 123 | [ 124 | 'avthumb' => 'm3u8', 125 | 'segtime' => 10, 126 | 'vcodec' => 'libx264', 127 | 's' => '320x240', 128 | ], 129 | 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240', 130 | ], 131 | [ 132 | ['avthumb' => 'mp4'], 133 | 'avthumb/mp4', 134 | ], 135 | ]; 136 | } 137 | 138 | /** 139 | * @expectedException \InvalidArgumentException 140 | */ 141 | public function testInvalidArgumentException() 142 | { 143 | Pfop::instance(['notExistProperty' => 1]); 144 | } 145 | 146 | /** 147 | * @expectedException \BadMethodCallException 148 | */ 149 | public function testBadMethodCallException() 150 | { 151 | Pfop::instance()->notExistFunc(); 152 | } 153 | 154 | public function testSingleFunc() 155 | { 156 | $this->assertEquals('aspect/10:20', Pfop::instance()->aspect(10, 20)); 157 | $this->assertEquals('|saveas/dGVzdEJ1Y2tldDp0ZXN0a2V5', Pfop::instance()->saveas('testBucket', 'testkey')); 158 | 159 | $this->assertEquals('an/1', Pfop::instance()->an(true)); 160 | $this->assertEquals( 161 | 'wmImage/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vaW1nL2RldmVsb3Blci1sb2dvQDJ4LnBuZw==', 162 | Pfop::instance()->wmImage('http://developer.qiniu.com/img/developer-logo@2x.png') 163 | ); 164 | $this->assertEquals('rotate/90', Pfop::instance()->rotate(90)); 165 | } 166 | 167 | /** 168 | * @expectedException \InvalidArgumentException 169 | */ 170 | public function testInvalidRotate() 171 | { 172 | Pfop::instance()->rotate(30); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /tests/PolicyTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([], $policy->__toArray()); 19 | 20 | $policy->callbackUrl = 'http://www.baidu.com'; 21 | $this->assertEquals(['callbackUrl' => 'http://www.baidu.com'], $policy->__toArray()); 22 | 23 | $policy->persistentOps = Pfop::instance()->avthumb('mp4')->saveas('testbucket', 'test.mp4'); 24 | $this->assertEquals('avthumb/mp4|saveas/dGVzdGJ1Y2tldDp0ZXN0Lm1wNA==', (string)$policy['persistentOps']); 25 | } 26 | 27 | public function testWriteStreamAndPersistentFop() 28 | { 29 | $qiniu = Yii::$app->qiniu; 30 | $policy = new Policy(); 31 | $policy->persistentOps = Pfop::instance() 32 | ->avthumb('mp4') 33 | ->wmImage('http://o82pobmde.bkt.clouddn.com/yii2-logo.png') 34 | ->saveas('testbucket', 'after-ops' . date('Y-m-d H:i:s') . '.mp4') 35 | ->__toString(); 36 | 37 | $policy->persistentNotifyUrl = 'http://blog.phpor.me'; 38 | 39 | $token = $qiniu->getUploadToken('testBucket', null, 3600, $policy); 40 | $disk = $qiniu->getDisk('testBucket'); 41 | 42 | $filePath = __DIR__ . '/testmp4.mp4'; 43 | $file = 'file://' . $filePath; 44 | if (!file_exists($filePath)) { 45 | $url = 'http://o82pobmde.bkt.clouddn.com/Son%20of%20Kama%20-%20The%20Surfing%20Piglet.mp4'; 46 | $fp = fopen($file, 'w+'); 47 | $ch = curl_init(str_replace(" ", "%20", $url)); 48 | curl_setopt($ch, CURLOPT_TIMEOUT, 300); 49 | curl_setopt($ch, CURLOPT_FILE, $fp); 50 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 51 | curl_exec($ch); 52 | curl_close($ch); 53 | fclose($fp); 54 | } 55 | $stream = fopen($file, 'rb'); 56 | $config = ['token' => $token]; 57 | $disk->writeStream(static::$path, $stream, $config); 58 | } 59 | 60 | public function testWriteWithToken() 61 | { 62 | $qiniu = Yii::$app->qiniu; 63 | $token = $qiniu->getUploadToken('testBucket'); 64 | $disk = $qiniu->getDisk('testBucket'); 65 | $this->assertTrue($disk->put('hello6.txt', 'hello6', ['token' => $token])); 66 | $this->assertEquals('hello6', $disk->read('hello6.txt')); 67 | $this->assertTrue($disk->delete('hello6.txt')); 68 | } 69 | 70 | /** 71 | * @depends testWriteStreamAndPersistentFop 72 | */ 73 | public function testPfopAfterUpload() 74 | { 75 | $qiniu = Yii::$app->qiniu; 76 | $disk = $qiniu->getDisk('testBucket'); 77 | $pfop = $disk->getPersistentFop(); 78 | $fops = Pfop::instance() 79 | ->avthumb('flv') 80 | ->s('640x360') 81 | ->vb('1.25m') 82 | ->saveas($disk->getBucket(), 'testflv' . date('Y-m-d H:i:s') . '.flv') 83 | ->__toString(); 84 | list(, $err) = $pfop->execute(static::$path, $fops); 85 | $this->assertTrue($err === null); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/QiniuAdapter2Test.php: -------------------------------------------------------------------------------- 1 | qiniu->getDisk('testBucket'); 8 | $avatar = 'https://wx.qlogo.cn/mmopen/VZDGic1IfDsiakiaQlgMvKzXPiaZafN2Z9TFI9HsFk8fLHd1NBnSs7DaibXP4nEIgM0y5Xo4frAD1N5Krm8Izrlpcdk4FndIt1TWY/0'; 9 | list($ret, $err) = $disk->fetchUrl($avatar); 10 | $this->assertEquals(null, $err); 11 | $this->assertArrayHasKey('key', $ret); 12 | 13 | $avatar = 'http://error_url'; 14 | list($ret, $err) = $disk->fetchUrl($avatar); 15 | $this->assertEquals(null, $ret); 16 | $this->assertInstanceOf('Qiniu\Http\Error', $err); 17 | 18 | 19 | $avatar = 'https://wx.qlogo.cn/mmopen/VZDGic1IfDsiakiaQlgMvKzXPiaZafN2Z9TFI9HsFk8fLHd1NBnSs7DaibXP4nEIgM0y5Xo4frAD1N5Krm8Izrlpcdk4FndIt1TWY/0'; 20 | $key = 'test-avatar-certain-key'; 21 | list($ret, $err) = $disk->fetchUrl($avatar, $key); 22 | $this->assertEquals(null, $err); 23 | $this->assertEquals($key, $ret['key']); 24 | } 25 | 26 | public function testWriteWithoutKey() 27 | { 28 | $disk = Yii::$app->qiniu->getDisk('testBucket'); 29 | list($ret, $err) = $disk->writeWithoutKey('hello from write without key'); 30 | $this->assertTrue($err === null); 31 | $this->assertArrayHasKey('hash', $ret); 32 | $this->assertArrayHasKey('key', $ret); 33 | $this->checkUrlExists($disk->getUrl($ret['key'])); 34 | } 35 | 36 | public function testWriteStreamWithoutKey() 37 | { 38 | $disk = Yii::$app->qiniu->getDisk('privateBucket'); 39 | list($ret, $err) = $disk->writeStreamWithoutKey(fopen(__DIR__ . '/yii2-logo.png', 'rb')); 40 | $this->assertTrue($err === null); 41 | $this->assertArrayHasKey('hash', $ret); 42 | $this->assertArrayHasKey('key', $ret); 43 | $this->checkUrlExists($disk->getUrl($ret['key'])); 44 | } 45 | 46 | protected function checkUrlExists($url) 47 | { 48 | $headers = get_headers($url, 1); 49 | $this->assertTrue((bool)preg_match('/[2|3]0\d/', $headers[0])); 50 | } 51 | 52 | public function testGetPersistentFop() 53 | { 54 | $filesystem = Yii::$app->qiniu->getDisk('testBucket'); 55 | $pfop = $filesystem->getPersistentFop(); 56 | $this->assertInstanceOf('Qiniu\Processing\PersistentFop', $pfop); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/QiniuAdapterTest.php: -------------------------------------------------------------------------------- 1 | qiniu 11 | ->getDisk('testBucket'); 12 | if ($disk->has('hello.txt')) { 13 | $disk->delete('hello.txt'); 14 | } 15 | if ($disk->has('hello3.txt')) { 16 | $disk->delete('hello3.txt'); 17 | } 18 | if ($disk->has('hello4.txt')) { 19 | $disk->delete('hello4.txt'); 20 | } 21 | if ($disk->has('/testdir/hello.txt')) { 22 | $disk->delete('/testdir/hello.txt'); 23 | } 24 | } 25 | 26 | public static function tearDownAfterClass() 27 | { 28 | $disk = Yii::$app->qiniu 29 | ->getDisk('testBucket'); 30 | 31 | if ($disk->has('hello.txt')) { 32 | $disk->delete('hello.txt'); 33 | } 34 | } 35 | 36 | /** 37 | * @expectedException \yii\base\InvalidConfigException 38 | */ 39 | public function testEmptyBucket() 40 | { 41 | /* @var $component Component */ 42 | Yii::createObject([ 43 | 'class' => Component::className(), 44 | 'accessKey' => '111', 45 | 'secretKey' => '222', 46 | 'disks' => [ 47 | 'testBucket' => [ 48 | 'baseUrl' => 'http://xxx.xx.clouddn.com', 49 | ] 50 | ], 51 | ]); 52 | } 53 | 54 | /** 55 | * @expectedException \yii\base\InvalidConfigException 56 | */ 57 | public function testEmptyBaseUrl() 58 | { 59 | /* @var $component Component */ 60 | Yii::createObject([ 61 | 'class' => Component::className(), 62 | 'accessKey' => '111', 63 | 'secretKey' => '222', 64 | 'disks' => [ 65 | 'testBucket' => [ 66 | 'bucket' => 'testBucket', 67 | ] 68 | ], 69 | ]); 70 | } 71 | 72 | public function testWrite() 73 | { 74 | $disk = Yii::$app->qiniu->getDisk('testBucket'); 75 | 76 | $this->assertTrue($disk->write('hello.txt', 'hello world')); 77 | $this->assertTrue($disk->write('/testdir/hello.txt', 'hello world')); 78 | } 79 | 80 | /** 81 | * @depends testWrite 82 | */ 83 | public function testGetSize() 84 | { 85 | $this->assertEquals(11, Yii::$app->qiniu->getDisk('testBucket')->getSize('/hello.txt')); 86 | } 87 | 88 | /** 89 | * @depends testWrite 90 | */ 91 | public function testListContents() 92 | { 93 | $list = Yii::$app->qiniu->getDisk('testBucket')->listContents('/', true); 94 | $list = array_map(function ($item) { 95 | return $item['path']; 96 | }, $list); 97 | 98 | $this->assertTrue(in_array('hello.txt', $list)); 99 | 100 | $this->assertEquals([], Yii::$app->qiniu->getDisk('testBucket')->listContents('/not-exist-dir', true)); 101 | } 102 | 103 | /** 104 | * @depends testWrite 105 | */ 106 | public function testRead() 107 | { 108 | $content = Yii::$app->qiniu->getDisk('testBucket') 109 | ->read('hello.txt'); 110 | $this->assertEquals('hello world', $content); 111 | } 112 | 113 | /** 114 | * @depends testWrite 115 | * @expectedException \League\Flysystem\FileNotFoundException 116 | */ 117 | public function testReadNotExistFile() 118 | { 119 | $this->assertFalse(Yii::$app->qiniu->getDisk('testBucket') 120 | ->read('not-exist-file.txt')); 121 | } 122 | 123 | /** 124 | * @depends testWrite 125 | */ 126 | public function testUpdate() 127 | { 128 | $this->assertTrue(Yii::$app->qiniu->getDisk('testBucket') 129 | ->update('hello.txt', 'hello everyone')); 130 | } 131 | 132 | /** 133 | * @depends testUpdate 134 | */ 135 | public function testGetSizeAfterUpdate() 136 | { 137 | $this->assertEquals(14, Yii::$app->qiniu->getDisk('testBucket')->getSize('/hello.txt')); 138 | } 139 | 140 | /** 141 | * @depends testWrite 142 | */ 143 | public function testPut() 144 | { 145 | $disk = Yii::$app->qiniu 146 | ->getDisk('testBucket'); 147 | 148 | $this->assertTrue($disk->put('hello.txt', 'update content')); 149 | $this->assertTrue($disk->put('hello2.txt', 'add new file')); 150 | $this->assertTrue($disk->has('hello2.txt')); 151 | } 152 | 153 | /** 154 | * @depends testPut 155 | */ 156 | public function testRename() 157 | { 158 | $disk = Yii::$app->qiniu 159 | ->getDisk('testBucket'); 160 | 161 | $this->assertTrue($disk->rename('hello2.txt', 'hello3.txt')); 162 | $this->assertFalse($disk->has('hello2.txt')); 163 | $this->assertTrue($disk->has('hello3.txt')); 164 | } 165 | 166 | /** 167 | * @depends testRename 168 | */ 169 | public function testCopy() 170 | { 171 | $disk = Yii::$app->qiniu 172 | ->getDisk('testBucket'); 173 | $disk->copy('hello3.txt', 'hello4.txt'); 174 | $this->assertTrue($disk->has('hello3.txt') && $disk->has('hello4.txt')); 175 | $this->assertEquals($disk->read('hello3.txt'), $disk->read('hello4.txt')); 176 | } 177 | 178 | /** 179 | * @depends testCopy 180 | */ 181 | public function testReadAndDelete() 182 | { 183 | $disk = Yii::$app->qiniu 184 | ->getDisk('testBucket'); 185 | 186 | $content = $disk->readAndDelete('hello3.txt'); 187 | $content2 = $disk->readAndDelete('hello4.txt'); 188 | 189 | $this->assertEquals($content, $content2); 190 | $this->assertFalse($disk->has('hello3.txt') && $disk->has('hello4.txt')); 191 | } 192 | 193 | public function testDeleteDir() 194 | { 195 | $disk = Yii::$app->qiniu 196 | ->getDisk('testBucket'); 197 | $this->assertTrue($disk->deleteDir('/testdir')); 198 | $this->assertTrue($disk->deleteDir('/testdir2')); 199 | } 200 | 201 | public function testCreateDir() 202 | { 203 | $disk = Yii::$app->qiniu 204 | ->getDisk('testBucket'); 205 | $disk->createDir('/testdir'); 206 | } 207 | 208 | /** 209 | * @dataProvider setVisibilityDataProvider 210 | */ 211 | public function testSetVisibility($isPrivate) 212 | { 213 | $v = Yii::createObject([ 214 | 'class' => Component::className(), 215 | 'accessKey' => '111', 216 | 'secretKey' => '222', 217 | 'disks' => [ 218 | 'testBucket' => [ 219 | 'isPrivate' => $isPrivate, 220 | 'bucket' => 'testBucket', 221 | 'baseUrl' => 'http://xxx.xx.clouddn.com', 222 | ] 223 | ], 224 | ])->getDisk('testBucket')->setVisibility('/', AdapterInterface::VISIBILITY_PUBLIC); 225 | 226 | $this->assertTrue($v); 227 | } 228 | 229 | public function setVisibilityDataProvider() 230 | { 231 | return [[false], [true]]; 232 | } 233 | 234 | public function testGetMimetype() 235 | { 236 | $mimetype = Yii::$app->qiniu 237 | ->getDisk('testBucket') 238 | ->getMimetype('/hello.txt'); 239 | 240 | $this->assertEquals('text/plain', $mimetype); 241 | } 242 | 243 | public function testTimestamp() 244 | { 245 | $val = Yii::$app->qiniu 246 | ->getDisk('testBucket') 247 | ->getTimestamp('/hello.txt'); 248 | 249 | $this->assertTrue(is_int($val)); 250 | $this->assertTrue($val > 0); 251 | } 252 | 253 | public function testGetVisibility() 254 | { 255 | $val = Yii::$app->qiniu 256 | ->getDisk('testBucket') 257 | ->getVisibility('/hello.txt'); 258 | 259 | $this->assertTrue(in_array($val, [ 260 | AdapterInterface::VISIBILITY_PRIVATE, 261 | AdapterInterface::VISIBILITY_PUBLIC 262 | ])); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 'tests', 5 | 'basePath' => dirname(__DIR__), 6 | 'components' => [ 7 | 'qiniu' => [ 8 | 'class' => 'dcb9\qiniu\Component', 9 | 'accessKey' => '0nOV0Qe99aHk3BjI6Eak-1y2IAYfGMyG6756X2BB', 10 | 'secretKey' => 'SSQpx6V7TDUnZBobJQiYdsjgGWH8WHOJP4M6gZtX', 11 | 'disks' => [ 12 | 'testBucket' => [ 13 | 'bucket' => 'testbucket', 14 | 'baseUrl' => 'http://o82y2yum4.bkt.clouddn.com', 15 | 'isPrivate' => false, 16 | 'zone' => 'zone0', 17 | ], 18 | 'privateBucket' => [ 19 | 'bucket' => 'testprivatebucket', 20 | 'baseUrl' => 'http://o84kf9um3.bkt.clouddn.com', 21 | 'isPrivate' => true, 22 | 'zone' => 'zone0', 23 | ], 24 | ], 25 | ], 26 | ], 27 | ]; 28 | -------------------------------------------------------------------------------- /tests/yii2-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcb9/yii2-qiniu/ccf4ac1d8ffe5818f934111d6ec42c3b002a4260/tests/yii2-logo.png --------------------------------------------------------------------------------