├── Filesystem.php ├── FilesystemInterface.php ├── Manager.php ├── README.md ├── adapters ├── AliYun.php ├── Local.php ├── QiNiu.php └── UpYun.php └── composer.json /Filesystem.php: -------------------------------------------------------------------------------- 1 | parseVisibility($visibility)); 59 | } 60 | 61 | /** 62 | * Prepend to a file. 63 | * 64 | * @param string $path 65 | * @param string $data 66 | * @return int 67 | */ 68 | public function prepend($path, $data) 69 | { 70 | if ($this->has($path)) { 71 | return $this->put($path, $data . PHP_EOL . $this->get($path)); 72 | } 73 | 74 | return $this->put($path, $data); 75 | } 76 | 77 | /** 78 | * Append to a file. 79 | * 80 | * @param string $path 81 | * @param string $data 82 | * @return int 83 | */ 84 | public function append($path, $data) 85 | { 86 | if ($this->has($path)) { 87 | return $this->put($path, $this->get($path) . PHP_EOL . $data); 88 | } 89 | 90 | return $this->put($path, $data); 91 | } 92 | 93 | /** 94 | * Delete the file at a given path. 95 | * 96 | * @param string|array $paths 97 | * @return bool 98 | */ 99 | public function delete($paths) 100 | { 101 | $paths = is_array($paths) ? $paths : func_get_args(); 102 | 103 | foreach ($paths as $path) { 104 | parent::delete($path); 105 | } 106 | 107 | return true; 108 | } 109 | 110 | /** 111 | * Move a file to a new location. 112 | * 113 | * @param string $from 114 | * @param string $to 115 | * @return bool 116 | */ 117 | public function move($from, $to) 118 | { 119 | return parent::rename($from, $to); 120 | } 121 | 122 | /** 123 | * Get the file size of a given file. 124 | * 125 | * @param string $path 126 | * @return int 127 | */ 128 | public function size($path) 129 | { 130 | return parent::getSize($path); 131 | } 132 | 133 | /** 134 | * Get the mime-type of a given file. 135 | * 136 | * @param string $path 137 | * @return string|false 138 | */ 139 | public function mimeType($path) 140 | { 141 | return parent::getMimetype($path); 142 | } 143 | 144 | /** 145 | * Get the file's last modification time. 146 | * 147 | * @param string $path 148 | * @return int 149 | */ 150 | public function lastModified($path) 151 | { 152 | return parent::getTimestamp($path); 153 | } 154 | 155 | /** 156 | * Get an array of all files in a directory. 157 | * 158 | * @param string|null $directory 159 | * @param bool $recursive 160 | * @return array 161 | */ 162 | public function files($directory = null, $recursive = false) 163 | { 164 | $contents = parent::listContents($directory, $recursive); 165 | 166 | return $this->filterContentsByType($contents, 'file'); 167 | } 168 | 169 | /** 170 | * Get all of the files from the given directory (recursive). 171 | * 172 | * @param string|null $directory 173 | * @return array 174 | */ 175 | public function allFiles($directory = null) 176 | { 177 | return $this->files($directory, true); 178 | } 179 | 180 | /** 181 | * Get all of the directories within a given directory. 182 | * 183 | * @param string|null $directory 184 | * @param bool $recursive 185 | * @return array 186 | */ 187 | public function directories($directory = null, $recursive = false) 188 | { 189 | $contents = parent::listContents($directory, $recursive); 190 | 191 | return $this->filterContentsByType($contents, 'dir'); 192 | } 193 | 194 | /** 195 | * Get all (recursive) of the directories within a given directory. 196 | * 197 | * @param string|null $directory 198 | * @return array 199 | */ 200 | public function allDirectories($directory = null) 201 | { 202 | return $this->directories($directory, true); 203 | } 204 | 205 | /** 206 | * Filter directory contents by type. 207 | * 208 | * @param array $contents 209 | * @param string $type 210 | * @return array 211 | */ 212 | protected function filterContentsByType($contents, $type) 213 | { 214 | return Collection::make($contents) 215 | ->where('type', $type) 216 | ->pluck('path') 217 | ->values() 218 | ->all(); 219 | } 220 | 221 | /** 222 | * Parse the given visibility value. 223 | * 224 | * @param string|null $visibility 225 | * @return string|null 226 | * @throws \yii\web\InvalidParamException 227 | */ 228 | protected function parseVisibility($visibility) 229 | { 230 | if (is_null($visibility)) { 231 | return; 232 | } 233 | 234 | switch ($visibility) { 235 | case FilesystemInterface::VISIBILITY_PUBLIC: 236 | return AdapterInterface::VISIBILITY_PUBLIC; 237 | 238 | case FilesystemInterface::VISIBILITY_PRIVATE: 239 | return AdapterInterface::VISIBILITY_PRIVATE; 240 | } 241 | 242 | throw new InvalidParamException('Unknown visibility: ' . $visibility); 243 | } 244 | } -------------------------------------------------------------------------------- /FilesystemInterface.php: -------------------------------------------------------------------------------- 1 | default === null) { 35 | throw new InvalidConfigException('The "default" property must be set.'); 36 | } 37 | } 38 | 39 | /** 40 | * 获取Disk集合 41 | * 42 | * @param bool|true $returnDefinitions 43 | * @return array 44 | */ 45 | public function getDisks($returnDefinitions = true) 46 | { 47 | return $returnDefinitions ? $this->_definitions : $this->_disks; 48 | } 49 | 50 | /** 51 | * 设置Disk集合 52 | * 53 | * @param array $disks 54 | */ 55 | public function setDisks(array $disks) 56 | { 57 | foreach ($disks as $id => $component) { 58 | $this->setDisk($id, $component); 59 | } 60 | } 61 | 62 | /** 63 | * 获取Disk 64 | * 65 | * @param string|null $id 为空则取默认的Disk ID 66 | * @param bool|true $throwException 67 | * @return null|\weyii\filesystem\FilesystemInterface 68 | * @throws \yii\base\InvalidConfigException 69 | */ 70 | public function getDisk($id = null, $throwException = true) 71 | { 72 | if ($id === null) { 73 | $id = $this->default; 74 | } 75 | 76 | if (isset($this->_disks[$id])) { 77 | return $this->_disks[$id]; 78 | } 79 | 80 | if (isset($this->_definitions[$id])) { 81 | $definition = $this->_definitions[$id]; 82 | if (is_object($definition) && !$definition instanceof Closure) { 83 | $adapter = $definition; 84 | } else { 85 | $adapter = Yii::createObject($definition); 86 | } 87 | return $this->_disks[$id] = $this->createFilesystem($adapter); 88 | } elseif ($throwException) { 89 | throw new InvalidConfigException("Unknown disk ID: $id"); 90 | } else { 91 | return null; 92 | } 93 | } 94 | 95 | /** 96 | * 设置Disk 97 | * 98 | * @param $id 99 | * @param $definition 100 | * @throws \yii\base\InvalidConfigException 101 | */ 102 | public function setDisk($id, $definition) 103 | { 104 | if ($definition === null) { 105 | unset($this->_disks[$id], $this->_definitions[$id]); 106 | return; 107 | } 108 | 109 | unset($this->_disks[$id]); 110 | 111 | if (is_object($definition) || is_callable($definition, true)) { 112 | // an object, a class name, or a PHP callable 113 | $this->_definitions[$id] = $definition; 114 | } elseif (is_array($definition)) { 115 | // a configuration array 116 | if (isset($definition['class'])) { 117 | $this->_definitions[$id] = $definition; 118 | } else { 119 | throw new InvalidConfigException("The disk configuration for the \"$id\" component must contain a \"class\" element."); 120 | } 121 | } else { 122 | throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition)); 123 | } 124 | } 125 | 126 | /** 127 | * 创建文件系统驱动 128 | * 129 | * @param \League\Flysystem\AdapterInterface $adapter 130 | * @param array|null $config 131 | * @return \weyii\filesystem\Filesystem 132 | */ 133 | protected function createFilesystem(AdapterInterface $adapter, array $config = null) 134 | { 135 | return new Filesystem($adapter, $config); 136 | } 137 | 138 | /** 139 | * @inheritdoc 140 | */ 141 | public function __call($name, $params) 142 | { 143 | foreach ($this->getBehaviors() as $object) { 144 | if ($object->hasMethod($name)) { 145 | return call_user_func_array([$object, $name], $params); 146 | } 147 | } 148 | 149 | return call_user_func_array([$this->getDisk(), $name], $params); 150 | } 151 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | yii2-filesystem 3 | ================= 4 | Yii2-filesystem是 [Flysystem](https://github.com/thephpleague/flysystem)基础上基于 [Yii2](https://github.com/yiisoft/yii2) 框架的实现的扩展。 **任何存储,统一的方法调用** 5 | 6 | ### 已支持扩展存储 7 | - 阿里云OSS存储 8 | - 又拍云存储 9 | - 七牛与存储 10 | - 本地存储 11 | - FTP存储 12 | - SFtp存储 13 | 14 | ####墙外世界产品(去Flysystem上找) 15 | - Amazon S3/S2 16 | - Dropbox 17 | ... 18 | 19 | ### 将实现的功能 (欢迎PR) 20 | 21 | - 百度云存储 22 | - 新浪云存储 23 | - UFile(UCloud)云存储 24 | 25 | 使用要求 26 | ======== 27 | - php >= 5.4 28 | - [Flysystem](https://github.com/thephpleague/flysystem) 29 | 30 | 使用教程 31 | ======== 32 | ###使用`Componser`安装 (以下2种方式) 33 | - 命令行执行 `composer require weyii/yii2-filesystem` 34 | - 编辑`composer.json` 35 | 36 | ```php 37 | "require": { 38 | ... 39 | "weyii/yii2-filesystem": "*" 40 | }, 41 | ``` 42 | ### 编辑配置文件 43 | - 编辑`config/web.php` 44 | 45 | ```php 46 | 'components' => [ 47 | ... 48 | 'storage' => [ 49 | 'class' => 'weyii\filesystem\Manager', 50 | 'default' => 'local', 51 | 'disks' => [ 52 | 'local' => [ 53 | 'class' => 'weyii\filesystem\adapters\Local', 54 | 'root' => '@webroot/storage' // 本地存储路径 55 | ], 56 | 'qiniu' => [ 57 | 'class' => 'weyii\filesystem\adapters\QiNiu', 58 | 'accessKey' => '七牛AccessKey', 59 | 'accessSecret' => '七牛accessSecret', 60 | 'bucket' => '七牛bucket空间', 61 | 'baseUrl' => '七牛基本访问地址, 如:http://72g7lu.com1.z0.glb.clouddn.com' 62 | ], 63 | 'upyun' => [ 64 | 'class' => 'weyii\filesystem\adapters\UpYun', 65 | 'operatorName' => '又拍云授权操作员账号', 66 | 'operatorPassword' => '又拍云授权操作员密码', 67 | 'bucket' => '又拍云的bucket空间', 68 | ], 69 | 'aliyun' => [ 70 | 'class' => 'weyii\filesystem\adapters\AliYun', 71 | 'accessKeyId' => '阿里云OSS AccessKeyID', 72 | 'accessKeySecret' => '阿里云OSS AccessKeySecret', 73 | 'bucket' => '阿里云的bucket空间', 74 | // lanUrl和wanUrl样只需填写一个. 如果填写lanUrl 将优先使用lanUrl作为传输地址 75 | // 外网和内网的使用参考: https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/endpoint.html?spm=5176.2020520105.0.0.tpQOiL 76 | 'lanDomain' => 'OSS内网地址, 如:oss-cn-hangzhou-internal.aliyuncs.com', // 默认不填. 在生产环境下保证OSS和服务器同属一个区域机房部署即可, 切记不能带上bucket前缀 77 | 'wanDomain' => 'OSS外网地址, 如:oss-cn-hangzhou.aliyuncs.com' // 默认为杭州机房domain, 其他机房请自行替换, 切记不能带上bucket前缀 78 | ], 79 | ... // 其他如FTP, 墙外世界产品请参考Flysystem 80 | ] 81 | ], 82 | ... 83 | ] 84 | ``` 85 | - 使用例子 86 | 87 | ```php 88 | 89 | // 如果注册一个全局函数, 将会更简便 90 | 91 | if (!function_exists('storage')) { 92 | /** 93 | * Storage组件或Storage组件Disk实例 94 | * 95 | * @param null $disk 96 | * @return \weyii\filesystem\Manager|\weyii\filesystem\FilesystemInstance 97 | */ 98 | function storage($disk = null) 99 | { 100 | if ($disk === null) { 101 | return Yii::$app->get('storage'); 102 | } 103 | 104 | return Yii::$app->get('storage')->getDisk($disk); 105 | } 106 | } 107 | 108 | // 默认使用方法 109 | $storage = Yii::$app->get('storage'); // $storage = Yii::$app->storage; 110 | $storage->has('test.txt'); 111 | 112 | $storage = storage(); 113 | $defaultDisk = $storage->getDisk(); 114 | $disk = $storage->getDisk('local'); 115 | 116 | $storage->has('test.txt'); 117 | $defaultDisk->has('test.txt'); 118 | $disk = $disk->has('tes.txt'); 119 | 120 | $disk = storage('local'); 121 | $disk = $disk->has('tes.txt'); 122 | 123 | $disks = $storage->disks; 124 | $testFileAlias = '@webroot/assets/test.txt'; 125 | $testFile = Yii::getAlias($testFileAlias); 126 | file_put_contents($testFile, 'test.txt'); // 测试文件 127 | foreach ($disks as $name => $disk) { 128 | $disk = $storage->getDisk($name); // $disk = storage($name) 129 | 130 | $files = $disk->listContents(); 131 | 132 | $disk->has('test.txt'); 133 | $disk->get('test.txt'); 134 | $disk->size('test.txt'); 135 | $disk->lastModified('test.txt'); 136 | 137 | $disk->delete('test.txt'); 138 | $disk->copy('test.txt', 'test1.txt'); 139 | $disk->move('test1.txt', 'test2.txt'); 140 | 141 | $disk->prepend('test.txt', 'Prepended Text'); 142 | $disk->prepend('test.txt', 'Appended Text'); 143 | 144 | $disk->delete('test2.txt'); 145 | $disk->delete(['test1.txt', 'test2.txt']); 146 | 147 | $disk->files('/path'); 148 | $disk->allFiles('/path'); 149 | 150 | $disk->directories('/path'); 151 | $disk->allDirectories('/path'); 152 | 153 | $has = $disk->has('test.txt'); 154 | $has && $disk->delete('test.txt'); 155 | $disk->put('test.txt', 'hello world!'); // storage($name)->put('test.txt', 'hello world!'); //下面的都可以这样操作 156 | $disk->delete('test.txt'); 157 | $disk->putStream('test.txt', fopen($testFile, 'r')); // 流操作 158 | $disk->delete('test.txt'); 159 | $disk->putFile('test.txt', $testFileAlias); // 文件路径操作 160 | $has && $disk->delete('test.txt'); 161 | $disk->write('test.txt', 'Hello World!'); 162 | $data = $disk->read('test.txt'); 163 | $data = $disk->readStream('test.txt', fopen($testFile, 'r')); 164 | $disk->update('test.txt', 'Hello World!'); 165 | $disk->updateStream('test.txt', fopen($testFile, 'r')); 166 | $disk->delete('test.txt'); 167 | $disk->writeStream('test.txt', fopen($testFile, 'r')); 168 | $disk->has('test2.txt') && $disk->delete('test2.txt'); 169 | $disk->rename('test.txt', 'test2.txt'); 170 | $disk->copy('test2.txt', 'test.txt'); 171 | $disk->createDir('dir'); 172 | !$disk->has('dir/test2.txt') && $disk->write('dir/test2.txt', 'Hello World!'); 173 | $disk->deleteDir('dir'); 174 | $files = $disk->listContents(); 175 | $size = $disk->getSize('test.txt'); 176 | $mimeType = $disk->getMimetype('test.txt'); 177 | $timestamp = $disk->getTimestamp('test.txt'); 178 | $visibility = $disk->getVisibility('test.txt'); 179 | $disk->setVisibility('test.txt', 'public'); 180 | $metadata = $disk->getMetadata('test.txt'); 181 | ... // 更多用法参考Flysystem 182 | } 183 | ``` 184 | -------------------------------------------------------------------------------- /adapters/AliYun.php: -------------------------------------------------------------------------------- 1 | accessKeyId === null) { 62 | throw new InvalidConfigException('The "accessKeyId" property must be set.'); 63 | } elseif ($this->accessKeySecret === null) { 64 | throw new InvalidConfigException('The "accessKeySecret" property must be set.'); 65 | } elseif ($this->bucket === null) { 66 | throw new InvalidConfigException('The "bucket" property must be set.'); 67 | } 68 | 69 | if ($this->lanDomain !== null) { 70 | $this->baseUrl = $this->lanDomain; 71 | } elseif ($this->wanDomain !== null) { 72 | $this->baseUrl = $this->wanDomain; 73 | } else { 74 | throw new InvalidConfigException('The "lanDomain" or "wanDomain" property must be set.'); 75 | } 76 | } 77 | 78 | private $_client; 79 | 80 | /** 81 | * @return \OSS\OssClient 82 | */ 83 | public function getClient() 84 | { 85 | if ($this->_client === null) { 86 | $this->setClient(new OssClient($this->accessKeyId, $this->accessKeySecret, $this->baseUrl)); 87 | } 88 | return $this->_client; 89 | } 90 | 91 | /** 92 | * @param \OSS\OssClient $client 93 | */ 94 | public function setClient(OssClient $client) 95 | { 96 | $this->_client = $client; 97 | } 98 | 99 | /** 100 | * @param $object 101 | * @param $uploadFile 102 | * @param int $size 103 | * @return null 104 | * @throws \OSS\Core\OssException 105 | */ 106 | protected function streamUpload($object, $uploadFile, $size = null) 107 | { 108 | $client = $this->getClient(); 109 | $bucket = $this->bucket; 110 | $upload_position = 0; 111 | $upload_file_size = $size ?: Util::getStreamSize($uploadFile); 112 | $extension = pathinfo($object, PATHINFO_EXTENSION); 113 | $options = [ 114 | OssClient::OSS_CONTENT_TYPE => MimeType::detectByFileExtension($extension) ?: OssClient::DEFAULT_CONTENT_TYPE, 115 | OssClient::OSS_PART_SIZE => OssClient::OSS_MID_PART_SIZE 116 | ]; 117 | 118 | $is_check_md5 = false; 119 | 120 | $uploadId = $client->initiateMultipartUpload($bucket, $object, $options); 121 | 122 | // 获取的分片 123 | $pieces = $client->generateMultiuploadParts($upload_file_size, (integer)$options[OssClient::OSS_PART_SIZE]); 124 | $response_upload_part = array(); 125 | foreach ($pieces as $i => $piece) { 126 | $from_pos = $upload_position + (integer)$piece[OssClient::OSS_SEEK_TO]; 127 | $to_pos = (integer)$piece[OssClient::OSS_LENGTH] + $from_pos - 1; 128 | $up_options = array( 129 | OssClient::OSS_FILE_UPLOAD => $uploadFile, 130 | OssClient::OSS_PART_NUM => ($i + 1), 131 | OssClient::OSS_SEEK_TO => $from_pos, 132 | OssClient::OSS_LENGTH => $to_pos - $from_pos + 1, 133 | OssClient::OSS_CHECK_MD5 => $is_check_md5, 134 | ); 135 | if ($is_check_md5) { 136 | $content_md5 = OssUtil::getMd5SumForFile($uploadFile, $from_pos, $to_pos); 137 | $up_options[OssClient::OSS_CONTENT_MD5] = $content_md5; 138 | } 139 | $response_upload_part[] = $client->uploadPart($bucket, $object, $uploadId, $up_options); 140 | } 141 | 142 | $uploadParts = array(); 143 | foreach ($response_upload_part as $i => $etag) { 144 | $uploadParts[] = array( 145 | 'PartNumber' => ($i + 1), 146 | 'ETag' => $etag, 147 | ); 148 | } 149 | return $client->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts); 150 | } 151 | 152 | 153 | /** 154 | * @param \OSS\Model\ObjectInfo|array $file 155 | * @return array 156 | */ 157 | protected function normalizeData($file) 158 | { 159 | if (is_array($file) && isset($file[0])) { // listObject 160 | $data = []; 161 | foreach ($file as $k => $v) { 162 | $data[$k] = $this->normalizeData($v); 163 | } 164 | return $data; 165 | } elseif ($file instanceof ObjectInfo) { // listObject -> ObjectInfo 166 | $key = $file->getKey(); 167 | return [ 168 | 'type' => substr($key, -1) == '/' ? 'dir' : 'file', 169 | 'path' => $key, 170 | 'size' => $file->getSize(), 171 | 'timestamp' => strtotime($file->getLastModified()) 172 | ]; 173 | } else { // Metadata 174 | return [ 175 | 'type' => 'file', 176 | 'path' => $file['key'], 177 | 'size' => $file['content-length'], 178 | 'mimetype' => $file['content-type'], 179 | 'timestamp' => strtotime($file['last-modified']) 180 | ]; 181 | } 182 | } 183 | 184 | /** 185 | * @param $directory 186 | * @param null $start 187 | * @return array 188 | */ 189 | protected function listDirContents($directory, $start = null) 190 | { 191 | $listInfo = $this->getClient()->listObjects($this->bucket, [ 192 | 'prefix' => $directory ? trim($directory, '/') . '/' : $directory, 193 | 'marker' => $start 194 | ]); 195 | $start = $listInfo->getNextMarker(); 196 | $item = $this->normalizeData($listInfo->getObjectList()); 197 | if (!empty($start)) { 198 | $item = array_merge($item, $this->listDirContents($directory, $start)); 199 | } 200 | return $item; 201 | } 202 | 203 | /** 204 | * @inheritdoc 205 | */ 206 | public function has($path) 207 | { 208 | return $this->getClient()->doesObjectExist($this->bucket, $path); 209 | } 210 | 211 | /** 212 | * @inheritdoc 213 | */ 214 | public function read($path) 215 | { 216 | if (!($resource = $this->readStream($path))) { 217 | return false; 218 | } 219 | $resource['contents'] = stream_get_contents($resource['stream']); 220 | fclose($resource['stream']); 221 | unset($resource['stream']); 222 | return $resource; 223 | } 224 | 225 | /** 226 | * @inheritdoc 227 | */ 228 | public function readStream($path) 229 | { 230 | $url = $this->getClient()->signUrl($this->bucket, $path, 3600); 231 | $stream = fopen($url, 'r'); 232 | if (!$stream) { 233 | return false; 234 | } 235 | return compact('stream', 'path'); 236 | } 237 | 238 | /** 239 | * @inheritdoc 240 | */ 241 | public function listContents($directory = '', $recursive = false) 242 | { 243 | return $this->listDirContents($directory); 244 | } 245 | 246 | /** 247 | * @inheritdoc 248 | */ 249 | public function getMetadata($path) 250 | { 251 | try { 252 | $meta = $this->getClient()->getObjectMeta($this->bucket, $path); 253 | } catch (\Exception $e) { 254 | return false; 255 | } 256 | 257 | $meta['key'] = $path; 258 | return $this->normalizeData($meta); 259 | } 260 | 261 | /** 262 | * @inheritdoc 263 | */ 264 | public function getSize($path) 265 | { 266 | return $this->getMetadata($path); 267 | } 268 | 269 | /** 270 | * @inheritdoc 271 | */ 272 | public function getMimetype($path) 273 | { 274 | return $this->getMetadata($path); 275 | } 276 | 277 | /** 278 | * @inheritdoc 279 | */ 280 | public function getTimestamp($path) 281 | { 282 | return $this->getMetadata($path); 283 | } 284 | 285 | /** 286 | * @inheritdoc 287 | */ 288 | public function getVisibility($path) 289 | { 290 | return [ 291 | 'visibility' => $this->isPrivate ? AdapterInterface::VISIBILITY_PRIVATE : AdapterInterface::VISIBILITY_PUBLIC 292 | ]; 293 | } 294 | 295 | /** 296 | * @inheritdoc 297 | */ 298 | public function write($path, $contents, Config $config) 299 | { 300 | $result = $this->getClient()->putObject($this->bucket, $path, $contents); 301 | if ($result !== null) { 302 | return false; 303 | } 304 | $mimetype = Util::guessMimeType($path, $contents); 305 | return compact('mimetype', 'path'); 306 | } 307 | 308 | /** 309 | * @inheritdoc 310 | */ 311 | public function writeStream($path, $resource, Config $config) 312 | { 313 | $size = Util::getStreamSize($resource); 314 | list(, $err) = $this->streamUpload($path, $resource, $size); 315 | if ($err !== null) { 316 | return false; 317 | } 318 | return compact('size', 'path'); 319 | } 320 | 321 | /** 322 | * @inheritdoc 323 | */ 324 | public function update($path, $contents, Config $config) 325 | { 326 | return $this->write($path, $contents, $config); 327 | } 328 | 329 | /** 330 | * @inheritdoc 331 | */ 332 | public function updateStream($path, $resource, Config $config) 333 | { 334 | return $this->writeStream($path, $resource, $config); 335 | } 336 | 337 | /** 338 | * @inheritdoc 339 | */ 340 | public function rename($path, $newpath) 341 | { 342 | $return = $this->getClient()->copyObject($this->bucket, $path, $this->bucket, $newpath) === null; 343 | if ($return) { // 阿里云不能移动 只能先拷贝成功后删除旧object 344 | $this->getClient()->deleteObject($this->bucket, $path); 345 | } 346 | return $return; 347 | } 348 | 349 | /** 350 | * @inheritdoc 351 | */ 352 | public function copy($path, $newpath) 353 | { 354 | return $this->getClient()->copyObject($this->bucket, $path, $this->bucket, $newpath) === null; 355 | } 356 | 357 | /** 358 | * @inheritdoc 359 | */ 360 | public function delete($path) 361 | { 362 | return $this->getClient()->deleteObject($this->bucket, $path) === null; 363 | } 364 | 365 | /** 366 | * @inheritdoc 367 | */ 368 | public function deleteDir($dirname) 369 | { 370 | // 阿里云无目录概念. 目前实现方案是.列举指定目录资源.批量删除 371 | $keys = array_map(function($file) { 372 | return $file['path']; 373 | }, $this->listDirContents($dirname)); 374 | return $this->getClient()->deleteObjects($this->bucket, $keys) === null; 375 | } 376 | 377 | /** 378 | * @inheritdoc 379 | */ 380 | public function createDir($dirname, Config $config) 381 | { 382 | $result = $this->getClient()->createObjectDir($this->bucket, rtrim($dirname, '/')); 383 | if ($result !== null) { 384 | return false; 385 | } 386 | return ['path' => $dirname]; 387 | } 388 | 389 | /** 390 | * @inheritdoc 391 | */ 392 | public function setVisibility($path, $visibility) 393 | { 394 | if ($this->isPrivate) { 395 | return false; 396 | } 397 | return compact('visibility'); 398 | } 399 | } -------------------------------------------------------------------------------- /adapters/Local.php: -------------------------------------------------------------------------------- 1 | null, 20 | 'writeFlags' => LOCK_EX, 21 | 'linkHandling' => self::DISALLOW_LINKS, 22 | 'permissions' => [] 23 | ], $config); 24 | 25 | call_user_func_array([$this, 'parent::__construct'], $config); 26 | } 27 | 28 | /** 29 | * @inheritdoc 30 | */ 31 | protected function ensureDirectory($root) 32 | { 33 | return parent::ensureDirectory(Yii::getAlias($root)); 34 | } 35 | } -------------------------------------------------------------------------------- /adapters/QiNiu.php: -------------------------------------------------------------------------------- 1 | accessKey === null) { 52 | throw new InvalidConfigException('The "accessKey" property must be set.'); 53 | } elseif ($this->accessSecret === null) { 54 | throw new InvalidConfigException('The "accessSecret" property must be set.'); 55 | } elseif ($this->bucket === null) { 56 | throw new InvalidConfigException('The "bucket" property must be set.'); 57 | } elseif ($this->baseUrl === null) { 58 | throw new InvalidConfigException('The "baseUrl" property must be set.'); 59 | } 60 | } 61 | 62 | private $_auth; 63 | 64 | /** 65 | * @return \Qiniu\Auth 66 | */ 67 | public function getAuth() 68 | { 69 | if ($this->_auth === null) { 70 | $this->setAuth(new Auth($this->accessKey, $this->accessSecret)); 71 | } 72 | return $this->_auth; 73 | } 74 | 75 | /** 76 | * @param \Qiniu\Auth $auth 77 | */ 78 | public function setAuth(Auth $auth) 79 | { 80 | $this->_auth = $auth; 81 | } 82 | 83 | private $_bucketManager; 84 | 85 | /** 86 | * @return \Qiniu\Storage\BucketManager 87 | */ 88 | public function getBucketManager() 89 | { 90 | if ($this->_bucketManager === null) { 91 | $this->setBucketManager(new BucketManager($this->getAuth())); 92 | } 93 | return $this->_bucketManager; 94 | } 95 | 96 | /** 97 | * @param \Qiniu\Storage\BucketManager $bucketManager 98 | */ 99 | public function setBucketManager(BucketManager $bucketManager) 100 | { 101 | $this->_bucketManager = $bucketManager; 102 | } 103 | 104 | private $_uploadManager; 105 | 106 | /** 107 | * @return \Qiniu\Storage\UploadManager 108 | */ 109 | public function getUploadManager() 110 | { 111 | if ($this->_uploadManager === null) { 112 | $this->setUploadManager(new UploadManager()); 113 | } 114 | return $this->_uploadManager; 115 | } 116 | 117 | /** 118 | * @param \Qiniu\Storage\UploadManager $uploadManager 119 | */ 120 | public function setUploadManager(UploadManager $uploadManager) 121 | { 122 | $this->_uploadManager = $uploadManager; 123 | } 124 | 125 | /** 126 | * @param null $key 127 | * @param int $expires 128 | * @param null $policy 129 | * @param bool|true $strictPolicy 130 | * @return string 131 | */ 132 | public function getUploadToken($key = null, $expires = 3600, $policy = null, $strictPolicy = true) 133 | { 134 | return $this->getAuth()->uploadToken($this->bucket, $key, $expires, $policy, $strictPolicy); 135 | } 136 | 137 | /** 138 | * @param $path 139 | * @param $resource 140 | * @param $size 141 | * @param string $type 142 | * @return array 143 | */ 144 | protected function streamUpload($path, $resource, $size, $type = 'application/octet-stream', \Qiniu\Config $config = null) 145 | { 146 | if ($config === null) { 147 | $config = new \Qiniu\Config(); 148 | } 149 | $resumeUploader = new ResumeUploader($this->getUploadToken(), $path, $resource, $size, null, $type, $config); 150 | return $resumeUploader->upload(); 151 | } 152 | 153 | /** 154 | * @param $path 155 | * @return string 156 | */ 157 | public function getUrl($path) 158 | { 159 | $keyEsc = str_replace("%2F", "/", rawurlencode($path)); 160 | return $this->baseUrl . '/' . $keyEsc; 161 | } 162 | 163 | /** 164 | * @param array $file 165 | * @return array 166 | */ 167 | protected function normalizeData(array $file) 168 | { 169 | return [ 170 | 'type' => 'file', 171 | 'path' => $file['key'], 172 | 'size' => $file['fsize'], 173 | 'mimetype' => $file['mimeType'], 174 | 'visibility' => $this->isPrivate ? AdapterInterface::VISIBILITY_PRIVATE : AdapterInterface::VISIBILITY_PUBLIC, 175 | 'timestamp' => (int)($file['putTime'] / 10000000) //Epoch 时间戳 176 | ]; 177 | } 178 | 179 | /** 180 | * @param $directory 181 | * @param null $start 182 | * @return array 183 | */ 184 | protected function listDirContents($directory, $start = null) 185 | { 186 | list($item, $start, $err) = $this->getBucketManager()->listFiles($this->bucket, $directory, $start); 187 | if ($err !== null) { 188 | return []; 189 | } elseif (!empty($start)) { 190 | $item = array_merge($item, $this->listDirContents($directory, $start)); 191 | } 192 | return $item; 193 | } 194 | 195 | /** 196 | * @inheritdoc 197 | */ 198 | public function has($path) 199 | { 200 | return $this->getMetadata($path); 201 | } 202 | 203 | /** 204 | * @inheritdoc 205 | */ 206 | public function read($path) 207 | { 208 | if (!($resource = $this->readStream($path))) { 209 | return false; 210 | } 211 | $resource['contents'] = stream_get_contents($resource['stream']); 212 | fclose($resource['stream']); 213 | unset($resource['stream']); 214 | return $resource; 215 | } 216 | 217 | /** 218 | * @inheritdoc 219 | */ 220 | public function readStream($path) 221 | { 222 | $url = $this->getAuth()->privateDownloadUrl($this->getUrl($path)); 223 | $stream = fopen($url, 'r'); 224 | if (!$stream) { 225 | return false; 226 | } 227 | return compact('stream', 'path'); 228 | } 229 | 230 | /** 231 | * @inheritdoc 232 | */ 233 | public function listContents($directory = '', $recursive = false) 234 | { 235 | $files = []; 236 | foreach($this->listDirContents($directory) as $k => $file) { 237 | $pathInfo = pathinfo($file['key']); 238 | $files[] = array_merge($pathInfo, $this->normalizeData($file), [ 239 | 'type' => isset($pathInfo['extension']) ? 'file' : 'dir', 240 | ]); 241 | } 242 | return $files; 243 | } 244 | 245 | /** 246 | * @inheritdoc 247 | */ 248 | public function getMetadata($path) 249 | { 250 | list($ret, $err) = $this->getBucketManager()->stat($this->bucket, $path); 251 | if ($err !== null) { 252 | return false; 253 | } 254 | $ret['key'] = $path; 255 | return $this->normalizeData($ret); 256 | } 257 | 258 | /** 259 | * @inheritdoc 260 | */ 261 | public function getSize($path) 262 | { 263 | return $this->getMetadata($path); 264 | } 265 | 266 | /** 267 | * @inheritdoc 268 | */ 269 | public function getMimetype($path) 270 | { 271 | return $this->getMetadata($path); 272 | } 273 | 274 | /** 275 | * @inheritdoc 276 | */ 277 | public function getTimestamp($path) 278 | { 279 | return $this->getMetadata($path); 280 | } 281 | 282 | /** 283 | * @inheritdoc 284 | */ 285 | public function getVisibility($path) 286 | { 287 | return [ 288 | 'visibility' => $this->isPrivate ? AdapterInterface::VISIBILITY_PRIVATE : AdapterInterface::VISIBILITY_PUBLIC 289 | ]; 290 | } 291 | 292 | /** 293 | * @inheritdoc 294 | */ 295 | public function write($path, $contents, Config $config) 296 | { 297 | $stream = fopen('php://temp', 'w+b'); 298 | fwrite($stream, $contents); 299 | rewind($stream); 300 | $result = $this->writeStream($path, $stream, $config); 301 | is_resource($stream) && fclose($stream); // TODO 去掉is_resource判断 302 | 303 | $result['contents'] = $contents; 304 | $result['mimetype'] = Util::guessMimeType($path, $contents); 305 | 306 | return $result; 307 | } 308 | 309 | /** 310 | * @inheritdoc 311 | */ 312 | public function writeStream($path, $resource, Config $config) 313 | { 314 | $size = Util::getStreamSize($resource); 315 | list(, $err) = $this->streamUpload($path, $resource, $size); 316 | if ($err !== null) { 317 | return false; 318 | } 319 | return compact('size', 'path'); 320 | } 321 | 322 | /** 323 | * @inheritdoc 324 | */ 325 | public function update($path, $contents, Config $config) 326 | { 327 | return $this->write($path, $contents, $config); 328 | } 329 | 330 | /** 331 | * @inheritdoc 332 | */ 333 | public function updateStream($path, $resource, Config $config) 334 | { 335 | return $this->writeStream($path, $resource, $config); 336 | } 337 | 338 | /** 339 | * @inheritdoc 340 | */ 341 | public function rename($path, $newpath) 342 | { 343 | list(, $err) = $this->getBucketManager()->rename($this->bucket, $path, $newpath); 344 | return $err === null; 345 | } 346 | 347 | /** 348 | * @inheritdoc 349 | */ 350 | public function copy($path, $newpath) 351 | { 352 | list(, $err) = $this->getBucketManager()->move($this->bucket, $path, $this->bucket, $newpath); 353 | return $err === null; 354 | } 355 | 356 | /** 357 | * @inheritdoc 358 | */ 359 | public function delete($path) 360 | { 361 | list(, $err) = $this->getBucketManager()->delete($this->bucket, $path); 362 | return $err === null; 363 | } 364 | 365 | /** 366 | * @inheritdoc 367 | */ 368 | public function deleteDir($dirname) 369 | { 370 | // 七牛无目录概念. 目前实现方案是.列举指定目录资源.批量删除 371 | $keys = array_map(function($file) { 372 | return $file['key']; 373 | }, $this->listDirContents($dirname)); 374 | if (empty($keys)) { 375 | return true; 376 | } 377 | list(, $err) = $this->getBucketManager()->batch(BucketManager::buildBatchDelete($keys)); 378 | return $err === null; 379 | } 380 | 381 | /** 382 | * @inheritdoc 383 | */ 384 | public function createDir($dirname, Config $config) 385 | { 386 | return ['path' => $dirname]; 387 | } 388 | 389 | /** 390 | * @inheritdoc 391 | */ 392 | public function setVisibility($path, $visibility) 393 | { 394 | if ($this->isPrivate) { 395 | return false; 396 | } 397 | return compact('visibility'); 398 | } 399 | } -------------------------------------------------------------------------------- /adapters/UpYun.php: -------------------------------------------------------------------------------- 1 | operatorName === null || $this->username === null) { 46 | throw new InvalidConfigException('The "operatorName" property must be set.'); 47 | } elseif ($this->operatorPassword === null || $this->password === null) { 48 | throw new InvalidConfigException('The "operatorPassword" property must be set.'); 49 | } elseif ($this->bucket === null) { 50 | throw new InvalidConfigException('The "bucket" property must be set.'); 51 | } 52 | } 53 | 54 | /** 55 | * @var string 又拍云存储授权账号 56 | */ 57 | protected $operatorName; 58 | 59 | /** 60 | * @return string 61 | */ 62 | public function getOperatorName() 63 | { 64 | return $this->operatorName; 65 | } 66 | 67 | /** 68 | * @param $operatorName 69 | * @return $this 70 | */ 71 | public function setOperatorName($operatorName) 72 | { 73 | $this->operatorName = $operatorName; 74 | $this->setUsername($operatorName . '/' . $this->getBucket()); 75 | 76 | return $this; 77 | } 78 | 79 | /** 80 | * @var string 又拍云存储授权密码 81 | */ 82 | protected $operatorPassword; 83 | 84 | /** 85 | * @return string 86 | */ 87 | public function getOperatorPassword() 88 | { 89 | return $this->operatorPassword; 90 | } 91 | 92 | /** 93 | * @param $operatorPassword 94 | * @return $this 95 | */ 96 | public function setOperatorPassword($operatorPassword) 97 | { 98 | $this->operatorPassword = $operatorPassword; 99 | $this->setPassword($operatorPassword); 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * @var string 又拍云存储Bucket 106 | */ 107 | protected $bucket; 108 | 109 | /** 110 | * @return string 111 | */ 112 | public function getBucket() 113 | { 114 | return $this->bucket; 115 | } 116 | 117 | /** 118 | * @param $bucket 119 | * @return $this 120 | */ 121 | public function setBucket($bucket) 122 | { 123 | $this->bucket = $bucket; 124 | 125 | return $this; 126 | } 127 | 128 | /** 129 | * @var string 又拍云节点 130 | */ 131 | protected $endpoinet = 'v0.api.upyun.com'; 132 | /** 133 | * @inheritdoc 134 | */ 135 | protected $host = 'v0.api.upyun.com'; 136 | 137 | /** 138 | * @return string 139 | */ 140 | public function getEndpoinet() 141 | { 142 | return $this->endpoinet; 143 | } 144 | 145 | /** 146 | * @param $endpoinet 147 | * @return $this 148 | */ 149 | public function setEndpoinet($endpoinet) 150 | { 151 | $this->endpoinet = $endpoinet; 152 | $this->setHost($endpoinet); 153 | 154 | return $this; 155 | } 156 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weyii/yii2-filesystem", 3 | "type": "yii2-extension", 4 | "description": "This extension provides a filesystem solution for Yii 2.", 5 | "keywords": ["weyii", "yii2", "filesystem", "flysystem", "file", "storage", "qiniu"], 6 | "license": "MIT", 7 | "support": { 8 | "issues": "https://github.com/weyii/yii2-filesystem/issues", 9 | "wiki": "https://github.com/weyii/yii2-filesystem/wiki", 10 | "source": "https://github.com/weyii/yii2-filesystem" 11 | }, 12 | "authors": [ 13 | { 14 | "name": "Long Zhang", 15 | "email": "callme-z@qq.com" 16 | } 17 | ], 18 | "require": { 19 | "yiisoft/yii2": "*", 20 | "weyii/yii2-base": "*", 21 | 22 | "league/flysystem": "~1.0" 23 | }, 24 | "conflict": { 25 | "qiniu/php-sdk": "<7.0", 26 | "aliyuncs/oss-sdk-php": "<2.0" 27 | }, 28 | "suggest": { 29 | "qiniu/php-sdk": "七牛云存储SDK, 请使用'~7.0'以上版本.", 30 | "aliyuncs/oss-sdk-php": "阿里云OSS存储SDK, 请使用'~2.0'以上版本" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "weyii\\filesystem\\": "" 35 | } 36 | } 37 | } 38 | --------------------------------------------------------------------------------