TP图片上传类的理解

在做自己项目上传图片的时候一直都有用到TP的上传图片类,所以要进入源码探索一下。

文件目录:./THinkPHP/Library/Think/Upload.class.php

namespace Think; //声明命名空间

class Upload //声明类名

/**
 * 默认上传配置
 * @var array
 */
private $config = array(
    'mimes'        => array(), //允许上传的文件MiMe类型
    'maxSize'      => 0, //上传的文件大小限制 (0-不做限制)
    'exts'         => array(), //允许上传的文件后缀
    'autoSub'      => true, //自动子目录保存文件
    'subName'      => array('date', 'Y-m-d'), //子目录创建方式,[0]-函数名,[1]-参数,多个参数使用数组
    'rootPath'     => './Uploads/', //保存根路径
    'savePath'     => '', //保存路径
    'saveName'     => array('uniqid', ''), //上传文件命名规则,[0]-函数名,[1]-参数,多个参数使用数组
    'saveExt'      => '', //文件保存后缀,空则使用原后缀
    'replace'      => false, //存在同名是否覆盖
    'hash'         => true, //是否生成hash编码
    'callback'     => false, //检测文件是否存在回调,如果存在返回文件信息数组
    'driver'       => '', // 文件上传驱动
    'driverConfig' => array(), // 上传驱动配置
);

/**

* 上传错误信息

* @var string

*/

private $error = ''; //上传错误信息

/**

* 上传驱动实例

* @var Object

*/

private $uploader;

public function __construct($config = array(), $driver = '', $driverConfig = null)
{
    /* 获取配置 */
    $this->config = array_merge($this->config, $config);

    /* 设置上传驱动 */
    $this->setDriver($driver, $driverConfig);

    /* 调整配置,把字符串配置参数转换为数组 */
    if (!empty($this->config['mimes'])) {
        if (is_string($this->mimes)) {
            $this->config['mimes'] = explode(',', $this->mimes);
        }
        $this->config['mimes'] = array_map('strtolower', $this->mimes);
    }
    if (!empty($this->config['exts'])) {
        if (is_string($this->exts)) {
            $this->config['exts'] = explode(',', $this->exts);
        }
        $this->config['exts'] = array_map('strtolower', $this->exts);
    }
}

这里的构造方法的参数就是实例化对象传入的$config,实例化对象时传入的参数在构造方法里进行处理。!!!。

比如:

$config = array(

'maxSize'    =>    3145728,

'savePath'   =>    './Public/Uploads/',

'saveName'   =>    array('uniqid',''),

'exts'       =>    array('jpg', 'gif', 'png', 'jpeg'),

'autoSub'    =>    true,

'subName'    =>    array('date','Ymd'),

);

$upload = new \Think\Upload($config);//这个$config参数是给构造方法处理的。

驱动和驱动配置是默认为空的

/* 获取配置 */

$this->config = array_merge($this->config, $config);

如果实例化对象的时候传入参数,则对类中$config属性进行替换。

/* 设置上传驱动 */

$this->setDriver($driver, $driverConfig);

调用私有方法setDriver();

/**
 * 设置上传驱动
 * @param string $driver 驱动名称
 * @param array $config 驱动配置
 */
private function setDriver($driver = null, $config = null)
{
    $driver         = $driver ?: ($this->driver ?: C('FILE_UPLOAD_TYPE'));
    $config         = $config ?: ($this->driverConfig ?: C('UPLOAD_TYPE_CONFIG'));
    $class          = strpos($driver, '\\') ? $driver : 'Think\\Upload\\Driver\\' . ucfirst(strtolower($driver));
    $this->uploader = new $class($config);
    if (!$this->uploader) {
        E("不存在上传驱动:{$name}");
    }
}

$driver 和$config默认为空,

$driver = $driver ?: ($this->driver ?: C('FILE_UPLOAD_TYPE'));

如果$driver存在,即有上传驱动参数,则使用上传驱动,否则使用C()调用基础配置

下面是C方法的内容

/**
 * 获取和设置配置参数 支持批量定义
 * @param string|array $name 配置变量
 * @param mixed $value 配置值
 * @param mixed $default 默认值
 * @return mixed
 */
function C($name = null, $value = null, $default = null)
{
    static $_config = array();
    // 无参数时获取所有
    if (empty($name)) {
        return $_config;
    }
    // 优先执行设置获取或赋值
    if (is_string($name)) {
        if (!strpos($name, '.')) {
            $name = strtoupper($name);
            if (is_null($value)) {
                return isset($_config[$name]) ? $_config[$name] : $default;
            }

            $_config[$name] = $value;
            return null;
        }
        // 二维数组设置和获取支持
        $name    = explode('.', $name);
        $name[0] = strtoupper($name[0]);
        if (is_null($value)) {
            return isset($_config[$name[0]][$name[1]]) ? $_config[$name[0]][$name[1]] : $default;
        }

        $_config[$name[0]][$name[1]] = $value;
        return null;
    }
    // 批量设置
    if (is_array($name)) {
        $_config = array_merge($_config, array_change_key_case($name, CASE_UPPER));
        return null;
    }
    return null; // 避免非法参数
}

static $_config = array();

设置一个静态数组,用于存储信息,为什么是静态,因为爱情,百度一下

// 无参数时获取所有
if (empty($name)) {
    return $_config;
}

如果使用C方法没有传入配置名称,则返回所有配置

// 优先执行设置获取或赋值
if (is_string($name)) {
    if (!strpos($name, '.')) {
        $name = strtoupper($name);
        if (is_null($value)) {
            return isset($_config[$name]) ? $_config[$name] : $default;
        }

        $_config[$name] = $value;
        return null;
    }
    // 二维数组设置和获取支持
    $name    = explode('.', $name);
    $name[0] = strtoupper($name[0]);
    if (is_null($value)) {
        return isset($_config[$name[0]][$name[1]]) ? $_config[$name[0]][$name[1]] : $default;
    }

    $_config[$name[0]][$name[1]] = $value;
    return null;
}

if (is_string($name)) {

如果是传入的参数是字符串,继续往下执行

if (!strpos($name, '.')) {

strpos() 函数查找字符串在另一字符串中第一次出现的位置。

注释:strpos() 函数对大小写敏感

返回值:

返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。

即判断传递的参数中是否含有‘.’,没有才继续往下执行

$name = strtoupper($name);

将参数全部变成大写。

is_null($value)

如果$value为null,即没有没有传入参数值。

return isset($_config[$name]) ? $_config[$name] : $default;

如果设置了$_config[$name]就返回这个值,否则返回默认值

如果$value存在,则进行设置

$_config[$name] = $value;

也就是说

C('FILE_UPLOAD_TYPE')

是读取FILE_UPLOAD_TYPE这个配置的

$class          = strpos($driver, '\\') ? $driver : 'Think\\Upload\\Driver\\' . ucfirst(strtolower($driver));

Strpos($driver,’\\’)  如果存在就\就使用,否则到Thinkphp\Upload\Driver\Xxxxx驱动

Strtolower全部转换为小写,  ucfirst首字母大写

$this->uploader = new $class($config);

对驱动进行实例化对象并传入参数,

if (!$this->uploader) {
    E("不存在上传驱动:{$name}");
}

检查是否实例化成功

E方法是报错,去看一下

/**
 * 抛出异常处理
 * @param string $msg 异常消息
 * @param integer $code 异常代码 默认为0
 * @throws Think\Exception
 * @return void
 */
function E($msg, $code = 0)
{
    throw new Think\Exception($msg, $code);
}

抛出一个自定义异常,参数源于$msg,捕捉错误以后调用Exception进行处理,并传入参数

/**
 * ThinkPHP系统异常基类
 */
class Exception extends \Exception
{
}

这个类继承基类 Exception

class Exception implements Throwable {

接口  Throwable

然后看不到了~~~

/* 调整配置,把字符串配置参数转换为数组 */
if (!empty($this->config['mimes'])) {
    if (is_string($this->mimes)) {
        $this->config['mimes'] = explode(',', $this->mimes);
    }
    $this->config['mimes'] = array_map('strtolower', $this->mimes);
}
if (!empty($this->config['exts'])) {
    if (is_string($this->exts)) {
        $this->config['exts'] = explode(',', $this->exts);
    }
    $this->config['exts'] = array_map('strtolower', $this->exts);
}

这里的$this->config()读取都是动态读取,是根据__GET  __SET魔术方法进行读取和设置的。

如果config[‘mimes’]不为空,继续执行。

当$this->mimes是字符串的时候,将$this->mimes根据‘,’将字符串分割成数组,

array_map(‘strtolower’,$this->mimes);表达的意思是   对$this->mimes这个数组里的所有元素都执行,strtolower这个函数。

array_map() 函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。

回调函数接受的参数数目应该和传递给 array_map() 函数的数组数目一致。

提示:您可以向函数输入一个或者多个数组。

即将配置都变成小写,下面对exts的操作和上面是一样的

总的来说,构造方法执行的功能:

1.将在创建对象时候,对传入的参数进行处理,先将传入的数组和类原先的config属性进行合并

public function __get($name)
{
    return $this->config[$name];
}

当方法中对属性进行读取的时候,比如$this->maxSize,执行这个语句的时候,如果maxSize属性不存在,就执行__get(),在这个类中,$this->maxSize   相当于  $this->config[‘maxSize’]

同样,

public function __set($name, $value)
{
    if (isset($this->config[$name])) {
        $this->config[$name] = $value;
        if ('driverConfig' == $name) {
            //改变驱动配置后重置上传驱动
            //注意:必须选改变驱动然后再改变驱动配置
            $this->setDriver();
        }
    }
}

如果执行$this->maxSize = 10086,此类中并没有maxSize这个属性,就会寻找 __set()魔术方法来执行,

if (isset($this->config[$name])) {

调用__isset()魔术方法,即

public function __isset($name)
{
    return isset($this->config[$name]);
}

道理和__set一样,如果config数组存在这个元素,

$this->config[$name] = $value;

对这个元素进行赋值,如果是驱动配置,进行驱动配置

if ('driverConfig' == $name) {
    //改变驱动配置后重置上传驱动
    //注意:必须选改变驱动然后再改变驱动配置
    $this->setDriver();
}

public function getError()
{
    return $this->error;
}

这里是getError方法,用于返回错误信息,

private $error = ''; //上传错误信息

私有属性$error就是用于存放错误信息的字符串。

看重头戏,public function upload();

/**
 * 上传文件
 * @param 文件信息数组 $files ,通常是 $_FILES数组
 */
public function upload($files = '')
{
    if ('' === $files) {
        $files = $_FILES;
    }
    if (empty($files)) {
        $this->error = '没有上传的文件!';
        return false;
    }

    /* 检测上传根目录 */
    if (!$this->uploader->checkRootPath($this->rootPath)) {
        $this->error = $this->uploader->getError();
        return false;
    }

    /* 检查上传目录 */
    if (!$this->uploader->checkSavePath($this->savePath)) {
        $this->error = $this->uploader->getError();
        return false;
    }

    /* 逐个检测并上传文件 */
    $info = array();
    if (function_exists('finfo_open')) {
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
    }
    // 对上传文件数组信息处理
    $files = $this->dealFiles($files);
    foreach ($files as $key => $file) {
        $file['name'] = strip_tags($file['name']);
        if (!isset($file['key'])) {
            $file['key'] = $key;
        }

        /* 通过扩展获取文件类型,可解决FLASH上传$FILES数组返回文件类型错误的问题 */
        if (isset($finfo)) {
            $file['type'] = finfo_file($finfo, $file['tmp_name']);
        }

        /* 获取上传文件后缀,允许上传无后缀文件 */
        $file['ext'] = pathinfo($file['name'], PATHINFO_EXTENSION);

        /* 文件上传检测 */
        if (!$this->check($file)) {
            continue;
        }

        /* 获取文件hash */
        if ($this->hash) {
            $file['md5']  = md5_file($file['tmp_name']);
            $file['sha1'] = sha1_file($file['tmp_name']);
        }

        /* 调用回调函数检测文件是否存在 */
        $data = call_user_func($this->callback, $file);
        if ($this->callback && $data) {
            if (file_exists('.' . $data['path'])) {
                $info[$key] = $data;
                continue;
            } elseif ($this->removeTrash) {
                call_user_func($this->removeTrash, $data); //删除垃圾据
            }
        }

        /* 生成保存文件名 */
        $savename = $this->getSaveName($file);
        if (false == $savename) {
            continue;
        } else {
            $file['savename'] = $savename;
        }

        /* 检测并创建子目录 */
        $subpath = $this->getSubPath($file['name']);
        if (false === $subpath) {
            continue;
        } else {
            $file['savepath'] = $this->savePath . $subpath;
        }

        /* 对图像文件进行严格检测 */
        $ext = strtolower($file['ext']);
        if (in_array($ext, array('gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'))) {
            $imginfo = getimagesize($file['tmp_name']);
            if (empty($imginfo) || ('gif' == $ext && empty($imginfo['bits']))) {
                $this->error = '非法图像文件!';
                continue;
            }
        }

        /* 保存文件 并记录保存成功的文件 */
        if ($this->uploader->save($file, $this->replace)) {
            unset($file['error'], $file['tmp_name']);
            $info[$key] = $file;
        } else {
            $this->error = $this->uploader->getError();
        }
    }
    if (isset($finfo)) {
        finfo_close($finfo);
    }
    return empty($info) ? false : $info;
}

public function upload($files = '')

upload函数,$files默认为空

if ('' === $files) {
    $files = $_FILES;
}

如果$files === ‘’  ,则把上传的文件信息赋值给$files;

if (empty($files)) {
    $this->error = '没有上传的文件!';
    return false;
}

如果上传文件为空,将错误信息赋值给私有属性。并跳出方法。

/* 检测上传根目录 */
if (!$this->uploader->checkRootPath($this->rootPath)) {
    $this->error = $this->uploader->getError();
    return false;
}

这里的$this->uploader是实例化驱动的时候得到的。

$this->uploader = new $class($config);
if (!$this->uploader) {
    E("不存在上传驱动:{$name}");
}

但是现在这种用法已经过时了,

class  Demo1 {

private   Person p=new  Person();

}

这样的话在new Demo1 这个类的时候默认就把Person这个类也装载进内存了,除非这两个是必然的联系,用到Demo1类的时候必然会用到Person这个类,否则这样写纯属浪费。

有一个新的东西叫做IOC,Inversion of Control 控制翻转。稍后学习。

具体的我也不知道是哪一个对象,随便找一个驱动哪来解释,因为感觉是差不多的。

./Thinkphp/Library/Think/Upload/Driver/Local.class.php

public function checkRootPath($rootpath)
{
    if (!(is_dir($rootpath) && is_writable($rootpath))) {
        $this->error = '上传根目录不存在!请尝试手动创建:' . $rootpath;
        return false;
    }
    $this->rootPath = $rootpath;
    return true;
}

1.首先判断这是一个目录,并且是可写的。如果两者出现一个错误,就像错误信息赋值给Local类的私有属性$error,

2.上面条件都通过,则把路径赋值给rootPath这个属性

如果Local类有错误,在通过getError()将错误信息返回给Upload类的error属性

/* 检查上传目录 */
if (!$this->uploader->checkSavePath($this->savePath)) {
    $this->error = $this->uploader->getError();
    return false;
}

Local.class.php

public function checkSavePath($savepath)
{
    /* 检测并创建目录 */
    if (!$this->mkdir($savepath)) {
        return false;
    } else {
        /* 检测目录是否可写 */
        if (!is_writable($this->rootPath . $savepath)) {
            $this->error = '上传目录 ' . $savepath . ' 不可写!';
            return false;
        } else {
            return true;
        }
    }
}

传入的参数是$savepath,首先是创建文件保存目录,然后是检测目录文件是否可写。知识点和上面一样。

/* 逐个检测并上传文件 */
$info = array();
if (function_exists('finfo_open')) {
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
}

这个不知道。

// 对上传文件数组信息处理
$files = $this->dealFiles($files);
foreach ($files as $key => $file) {
    $file['name'] = strip_tags($file['name']);
    if (!isset($file['key'])) {
        $file['key'] = $key;
    }

    /* 通过扩展获取文件类型,可解决FLASH上传$FILES数组返回文件类型错误的问题 */
    if (isset($finfo)) {
        $file['type'] = finfo_file($finfo, $file['tmp_name']);
    }

    /* 获取上传文件后缀,允许上传无后缀文件 */
    $file['ext'] = pathinfo($file['name'], PATHINFO_EXTENSION);

    /* 文件上传检测 */
    if (!$this->check($file)) {
        continue;
    }

    /* 获取文件hash */
    if ($this->hash) {
        $file['md5']  = md5_file($file['tmp_name']);
        $file['sha1'] = sha1_file($file['tmp_name']);
    }

    /* 调用回调函数检测文件是否存在 */
    $data = call_user_func($this->callback, $file);
    if ($this->callback && $data) {
        if (file_exists('.' . $data['path'])) {
            $info[$key] = $data;
            continue;
        } elseif ($this->removeTrash) {
            call_user_func($this->removeTrash, $data); //删除垃圾据
        }
    }

    /* 生成保存文件名 */
    $savename = $this->getSaveName($file);
    if (false == $savename) {
        continue;
    } else {
        $file['savename'] = $savename;
    }

    /* 检测并创建子目录 */
    $subpath = $this->getSubPath($file['name']);
    if (false === $subpath) {
        continue;
    } else {
        $file['savepath'] = $this->savePath . $subpath;
    }

    /* 对图像文件进行严格检测 */
    $ext = strtolower($file['ext']);
    if (in_array($ext, array('gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'))) {
        $imginfo = getimagesize($file['tmp_name']);
        if (empty($imginfo) || ('gif' == $ext && empty($imginfo['bits']))) {
            $this->error = '非法图像文件!';
            continue;
        }
    }

    /* 保存文件 并记录保存成功的文件 */
    if ($this->uploader->save($file, $this->replace)) {
        unset($file['error'], $file['tmp_name']);
        $info[$key] = $file;
    } else {
        $this->error = $this->uploader->getError();
    }
}

首先要注意的是,当我们只用上传文件的时候,所以的信息是放在一个二维数组里,我们处理文件只要处理这个数组,边处理边echo & die。

比如两个文件上传的时候 var_dump($_FILES)的值:

array(1) {
    ["upload"]=> array(5) {
        ["name"]=> array(2) {
            [0]=> string(15) "mj0103de03s.jpg"
            [1]=> string(26) "2416984_105902003029_2.jpg"
            }
    ["type"]=> array(2) {
            [0]=> string(10) "image/jpeg"
            [1]=> string(10) "image/jpeg"
            }
    ["tmp_name"]=> array(2) {
            [0]=> string(27) "C:\Windows\Temp\phpAFF9.tmp"
            [1]=> string(27) "C:\Windows\Temp\phpAFFA.tmp"
    }
    ["error"]=> array(2) {
            [0]=> int(0)
            [1]=> int(0)
            }
    ["size"]=> array(2) {
            [0]=> int(43146)
            [1]=> int(284406)
            }
  }
}

$files = $this->dealFiles($files);

在获取$files的时候,先调用私有方法 dealFiles来处理

/**
 * 转换上传文件数组变量为正确的方式
 * @access private
 * @param array $files  上传的文件变量
 * @return array
 */
function dealFiles($files)
{
    $fileArray = array();
    $n         = 0;
    foreach ($files as $key => $file) {
        if (is_array($file['name'])) {                      //上传文件名为数组
            //array_keys() 函数返回包含数组中所有键名的一个新数组。如果提供了第二个参数,则只返回键值为该值的键名。
            //print_r($keys);   Array ( [0] => name [1] => type [2] => tmp_name [3] => error [4] => size )
            $keys  = array_keys($file);

            $count = count($file['name']);          //查看共有几个上传文件
            for ($i = 0; $i < $count; $i++) {       //对每个文件进行处理
                $fileArray[$n]['key'] = $key;       //$key为上传文件的name值
                foreach ($keys as $_key) {          //$keys是数组所有下标
                    //这里就把上传文数组的索引拿出来,$file[$_key][$i]对应的是每个文件的每个数组里的信息
                    $fileArray[$n][$_key] = $file[$_key][$i];
                }
                $n++;
            }
        } else {
            $fileArray = $files;
            break;
        }
    }
    return $fileArray;
}

本来上传的数组是多维的,这个函数的目的就转变文件的形式,经过这个函数,上面的数组形式为:

Array
(
    [0] => Array
    (      
        [key] => upload
        [name] => mj0103de03s.jpg
        [type] => image/jpeg
        [tmp_name] => C:\Windows\Temp\phpC4A7.tmp
        [error] => 0
        [size] => 43146
     )

    [1] => Array
(
    [key] => upload
    [name] => 2416984_105902003029_2.jpg
    [type] => image/jpeg
    [tmp_name] => C:\Windows\Temp\phpC4A8.tmp
    [error] => 0
    [size] => 284406
        )

)

这个函数的意思就是,对原数组进行改编,使其符合一定的格式。

现在$files就是上面形式的一条一条数组

foreach ($files as $key => $file) {

对这个数组进行循环,每一个数组都是一个文件信息。

$file['name'] = strip_tags($file['name']);

strip_tags() 函数剥去字符串中的 HTML、XML 以及 PHP 的标签。

strip_tags(string,allow)

参数

描述

string

必需。规定要检查的字符串。

allow

可选。规定允许的标签。这些标签不会被删除。

foreach ($files as $key => $file) {
    $file['name'] = strip_tags($file['name']);
    if (!isset($file['key'])) {
        $file['key'] = $key;
    }

循环遍历每一条文件信息,如果下标没有设置,就把表单的name值赋值给他

/* 通过扩展获取文件类型,可解决FLASH上传$FILES数组返回文件类型错误的问题 */
if (isset($finfo)) {
    $file['type'] = finfo_file($finfo, $file['tmp_name']);
}

这个不管

/* 获取上传文件后缀,允许上传无后缀文件 */
$file['ext'] = pathinfo($file['name'], PATHINFO_EXTENSION);

pathinfo() 函数以数组或字符串的形式返回关于文件路径的信息。

返回的数组元素如下:

  • [dirname]:返回文件路径中的目录部分
  • [basename]:返回文件路径中文件名的部分
  • [extension]:返回文件路径中文件的类型的部分

pathinfo(path,options)

path

必需。规定要检查的路径。

options

可选。规定要返回的数组元素。默认是 all。

可能的值:

  • PATHINFO_DIRNAME - 只返回 dirname
  • PATHINFO_BASENAME - 只返回 basename
  • PATHINFO_EXTENSION - 只返回 extension

$file[‘ext’]  获取上传文件的后缀名

/* 文件上传检测 */
if (!$this->check($file)) {
    continue;
}

调用私有方法检测文件,如果不符合要求,就跳过去

/**
 * 检查上传的文件
 * @param array $file 文件信息
 */
private function check($file)
{
    /* 文件上传失败,捕获错误代码 */
    if ($file['error']) {
        $this->error($file['error']);
        return false;
    }

    /* 无效上传 */
    if (empty($file['name'])) {
        $this->error = '未知上传错误!';
    }

    /* 检查是否合法上传 */
    if (!is_uploaded_file($file['tmp_name'])) {
        $this->error = '非法上传文件!';
        return false;
    }

    /* 检查文件大小 */
    if (!$this->checkSize($file['size'])) {
        $this->error = '上传文件大小不符!';
        return false;
    }

    /* 检查文件Mime类型 */
    //TODO:FLASH上传的文件获取到的mime类型都为application/octet-stream
    if (!$this->checkMime($file['type'])) {
        $this->error = '上传文件MIME类型不允许!';
        return false;
    }

    /* 检查文件后缀 */
    if (!$this->checkExt($file['ext'])) {
        $this->error = '上传文件后缀不允许';
        return false;
    }

    /* 通过检测 */
    return true;
}

/* 文件上传失败,捕获错误代码 */
if ($file['error']) {
    $this->error($file['error']);
    return false;
}

如果上传文件的error不为空,把错误代码发给 error这个私有方法。

/**
 * 获取错误代码信息
 * @param string $errorNo  错误号
 */
private function error($errorNo)
{
    switch ($errorNo) {
        case 1:
            $this->error = '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值!';
            break;
        case 2:
            $this->error = '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值!';
            break;
        case 3:
            $this->error = '文件只有部分被上传!';
            break;
        case 4:
            $this->error = '没有文件被上传!';
            break;
        case 6:
            $this->error = '找不到临时文件夹!';
            break;
        case 7:
            $this->error = '文件写入失败!';
            break;
        default:
            $this->error = '未知上传错误!';
    }
}

根据不同的错误号,给error这个私有属性赋值。

/* 无效上传 */
if (empty($file['name'])) {
    $this->error = '未知上传错误!';
}

如果$file[‘name’]也就是说文件没有正常上传

/* 检查是否合法上传 */
if (!is_uploaded_file($file['tmp_name'])) {
    $this->error = '非法上传文件!';
    return false;
}

is_uploaded_file() 函数判断指定的文件是否是通过 HTTP POST 上传的。

如果不是通过http post上传的文件

/* 检查文件大小 */
if (!$this->checkSize($file['size'])) {
    $this->error = '上传文件大小不符!';
    return false;
}

调用私有方法检查文件大小

private function checkSize($size)
{
    return !($size > $this->maxSize) || (0 == $this->maxSize);
}

当$size > 设置的上限值,或者最大值为0  返回boolean值 false

/* 检查文件Mime类型 */
//TODO:FLASH上传的文件获取到的mime类型都为application/octet-stream
if (!$this->checkMime($file['type'])) {
    $this->error = '上传文件MIME类型不允许!';
    return false;
}

调用私有方法 checkMime

/**
 * 检查上传的文件MIME类型是否合法
 * @param string $mime 数据
 */
private function checkMime($mime)
{
    return empty($this->config['mimes']) ? true : in_array(strtolower($mime), $this->mimes);
}

in_array(search,array,type)

参数

描述

search

必需。规定要在数组搜索的值。

array

必需。规定要搜索的数组。

type

可选。如果设置该参数为 true,则检查搜索的数据与数组的值的类型是否相同。

如果给定的值 search 存在于数组 array 中则返回 true。如果第三个参数设置为 true,函数只有在元素存在于数组中且数据类型与给定值相同时才返回 true。

如果没有在数组中找到参数,函数返回 false。

注释:如果 search 参数是字符串,且 type 参数设置为 true,则搜索区分大小写。

/* 检查文件后缀 */
if (!$this->checkExt($file['ext'])) {
    $this->error = '上传文件后缀不允许';
    return false;
}

private function checkExt($ext)
{
    return empty($this->config['exts']) ? true : in_array(strtolower($ext), $this->exts);
}

/* 获取文件hash */
if ($this->hash) {
    $file['md5']  = md5_file($file['tmp_name']);
    $file['sha1'] = sha1_file($file['tmp_name']);
}

如果hash值为真,则调用,分别使用md5_file和sha1_file 计算文件的散列

md5_file() 函数计算文件的 MD5 散列。

sha1_file() 函数计算文件的 SHA-1 散列。

/* 调用回调函数检测文件是否存在 */
$data = call_user_func($this->callback, $file);
if ($this->callback && $data) {
    if (file_exists('.' . $data['path'])) {
        $info[$key] = $data;
        continue;
    } elseif ($this->removeTrash) {
        call_user_func($this->removeTrash, $data); //删除垃圾据
    }
}

call_user_func — 把第一个参数作为回调函数调用

也就是说如果config中配置了callback,则会使用这个函数对$file进行处理,$file是每一个的文件信息形成的数组。

如果存在回调函数并且存在返回数据的花,将文件信息的$key换为 $data,

file_exists() 函数检查文件或目录是否存在。

如果指定的文件或目录存在则返回 true,否则返回 false。

file_exists(path)

/**
 * 根据上传文件命名规则取得保存文件名
 * @param string $file 文件信息
 */
private function getSaveName($file)
{
    $rule = $this->saveName;
    if (empty($rule)) {
        //保持文件名不变
        /* 解决pathinfo中文文件名BUG */
        $filename = substr(pathinfo("_{$file['name']}", PATHINFO_FILENAME), 1);
        $savename = $filename;
    } else {
        $savename = $this->getName($rule, $file['name']);
        if (empty($savename)) {
            $this->error = '文件命名规则错误!';
            return false;
        }
    }

    /* 文件保存后缀,支持强制更改文件后缀 */
    $ext = empty($this->config['saveExt']) ? $file['ext'] : $this->saveExt;

    return $savename . '.' . $ext;
}

$rule = $this->saveName;

先将配置文件里的配置名称赋值给$rule

if (empty($rule)) {
    //保持文件名不变
    /* 解决pathinfo中文文件名BUG */
    $filename = substr(pathinfo("_{$file['name']}", PATHINFO_FILENAME), 1);
    $savename = $filename;
} else {
    $savename = $this->getName($rule, $file['name']);
    if (empty($savename)) {
        $this->error = '文件命名规则错误!';
        return false;
    }
}

如果实例化对象时候没有传入saveName,则执行上面

pathinfo("_{$file['name']}", PATHINFO_FILENAME)

pathinfo函数

PATHINFO_DIRNAME - 目录

PATHINFO_BASENAME - 文件名(含扩展名)

PATHINFO_EXTENSION - 扩展名

PATHINFO_FILENAME - 文件名(不含扩展名

_{$file['name']}没有找到它的出处,但是直观的意思就是上传的文件夹的名称

意思就是如果没有配置文件名称,就用文件上传时候的名称,否则

$savename = $this->getName($rule, $file['name']);
if (empty($savename)) {
    $this->error = '文件命名规则错误!';
    return false;
}

getName是私有方法,用来使文件名唯一的函数。然后将文件名设置为处理过后的文件名

/**
 * 根据指定的规则获取文件或目录名称
 * @param  array  $rule     规则
 * @param  string $filename 原文件名
 * @return string           文件或目录名称
 */
private function getName($rule, $filename)
{
    $name = '';
    if (is_array($rule)) {
        //数组规则
        $func  = $rule[0];
        $param = (array) $rule[1];
        foreach ($param as &$value) {
            $value = str_replace('__FILE__', $filename, $value);
        }
        $name = call_user_func_array($func, $param);
    } elseif (is_string($rule)) {
        //字符串规则
        if (function_exists($rule)) {
            $name = call_user_func($rule);
        } else {
            $name = $rule;
        }
    }
    return $name;
}

private function getName($rule, $filename)

传入的两个参数一个是 $rule, $rule=$this->saveName一个是$filename,是$file[‘name’]

if (is_array($rule)) {
    //数组规则
    $func  = $rule[0];
    $param = (array) $rule[1];
    foreach ($param as &$value) {
        $value = str_replace('__FILE__', $filename, $value);
    }
    $name = call_user_func_array($func, $param);
}

如果$rule是数组,将第一个对name的处理规则提取出来

$param = (array) $rule[1];

强制将文件名称赋值给$param,

foreach ($param as &$value) {
    $value = str_replace('__FILE__', $filename, $value);
}

当得到文件名以后(可能为数组),对数组本身,因为$value前面多了一个&,这样处理的时候就对元素本身进行操作了,

str_replace(find,replace,string,count)

参数

描述

find

必需。规定要查找的值。

replace

必需。规定替换 find 中的值的值。

string

必需。规定被搜索的字符串。

count

可选。对替换数进行计数的变量。

<?php

$arr = array("blue","red","green","yellow");

print_r(str_replace("red","pink",$arr,$i));

echo "替换数:$i";

?>

Array ( [0] => blue [1] => pink [2] => green [3] => yellow ) 替换数:1

也就是说,将原文件名变成现在的路径文件名

$name = call_user_func_array($func, $param);

再调用saveName里的处理函数来对其进行处理

elseif (is_string($rule)) {
        //字符串规则
        if (function_exists($rule)) {
            $name = call_user_func($rule);
        } else {
            $name = $rule;
        }
    }
    return $name;
}

如果是字符串,直接判断是否存在方法$rule 存在就处理,否则原样输出。最终返回$name

$savename = $this->getName($rule, $file['name']);
if (empty($savename)) {
    $this->error = '文件命名规则错误!';
    return false;
}

/* 文件保存后缀,支持强制更改文件后缀 */
$ext = empty($this->config['saveExt']) ? $file['ext'] : $this->saveExt;

return $savename . '.' . $ext;

如果有错误,文件名不存在,报错。如果config没有后缀名则使用原来的文件后缀,否则使用配置文件的后缀名。这样就获取了上传文件的文件名。

这样  动态生成的文件名就被保存到 $savename中了

if (false == $savename) {
    continue;
} else {
    $file['savename'] = $savename;
}

跳过false的文件名,将新的文件名保存在$file数组的savename 中

/* 检测并创建子目录 */
$subpath = $this->getSubPath($file['name']);
if (false === $subpath) {
    continue;
} else {
    $file['savepath'] = $this->savePath . $subpath;
}

调用子目录私有方法

/**
 * 获取子目录的名称
 * @param array $file  上传的文件信息
 */
private function getSubPath($filename)
{
    $subpath = '';
    $rule    = $this->subName;
    if ($this->autoSub && !empty($rule)) {
        $subpath = $this->getName($rule, $filename) . '/';

        if (!empty($subpath) && !$this->uploader->mkdir($this->savePath . $subpath)) {
            $this->error = $this->uploader->getError();
            return false;
        }
    }
    return $subpath;
}

如果autoSub 为true,并且子文件夹创建方式不为空的话,就开始开始创建子目录

$subpath = $this->getName($rule, $filename) . '/';

$subpath = $this->getName($rule, $filename) . '/';

如果存在子目录就先将文件名的路径形式文件名取到,

if (!empty($subpath) && !$this->uploader->mkdir($this->savePath . $subpath)) {
    $this->error = $this->uploader->getError();
    return false;
}

然后使用驱动mkdir方法创建文件,将文件根路径和子路径都传给他

public function mkdir($savepath)
{
    $dir = $this->rootPath . $savepath;
    if (is_dir($dir)) {
        return true;
    }

    if (mkdir($dir, 0777, true)) {
        return true;
    } else {
        $this->error = "目录 {$savepath} 创建失败!";
        return false;
    }
}

这个就是,根据路径已经存在,直接返回,否则创建777文件夹,否则就失败。

如果创建目录失败就报错,最终返回子目录

如果$subpath为真,就将savepath保存下来

/* 对图像文件进行严格检测 */
$ext = strtolower($file['ext']);
if (in_array($ext, array('gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'))) {
    $imginfo = getimagesize($file['tmp_name']);
    if (empty($imginfo) || ('gif' == $ext && empty($imginfo['bits']))) {
        $this->error = '非法图像文件!';
        continue;
    }
}

$ext = strtolower($file['ext']);

将上传的文件后缀名变成小写,看是否在符合的后缀中,和其他检测。

/* 保存文件 并记录保存成功的文件 */
if ($this->uploader->save($file, $this->replace)) {
    unset($file['error'], $file['tmp_name']);
    $info[$key] = $file;
} else {
    $this->error = $this->uploader->getError();
}

调用驱动类的方法save去保存文件

/**
 * 保存指定文件
 * @param  array   $file    保存的文件信息
 * @param  boolean $replace 同名文件是否覆盖
 * @return boolean          保存状态,true-成功,false-失败
 */
public function save($file, $replace = true)
{
    $filename = $this->rootPath . $file['savepath'] . $file['savename'];

    /* 不覆盖同名文件 */
    if (!$replace && is_file($filename)) {
        $this->error = '存在同名文件' . $file['savename'];
        return false;
    }

    /* 移动文件 */
    if (!move_uploaded_file($file['tmp_name'], $filename)) {
        $this->error = '文件上传保存错误!';
        return false;
    }

    return true;
}

传入两个参数,一个是当前上传的文件信息,一个是是否设置同名覆盖。

$filename = $this->rootPath . $file['savepath'] . $file['savename'];

将文件的根路径+保存路径+文件保存名称合并给$filename

/* 不覆盖同名文件 */
if (!$replace && is_file($filename)) {
    $this->error = '存在同名文件' . $file['savename'];
    return false;
}

如果没有设置同名覆盖,但是有同名文件名存在,报错。

/* 移动文件 */
if (!move_uploaded_file($file['tmp_name'], $filename)) {
    $this->error = '文件上传保存错误!';
    return false;
}

bool move_uploaded_file ( string $filename , string $destination )、

move_uploaded_file() 函数将上传的文件移动到新位置。

move_uploaded_file(file,newloc)

参数

描述

file

必需。规定要移动的文件。

newloc

必需。规定文件的新位置。

然后回到upload.class.php

unset($file['error'], $file['tmp_name']);

unset掉error和tmp_name信息

$info[$key] = $file;

把文件信息复制给相应数组下的$key

if (isset($finfo)) {
    finfo_close($finfo);
}

return empty($info) ? false : $info;

关闭资源连接,返回文件信息。

然后是多个文件循环上传。

总结:

$upload = new \Think\Upload();// 实例化上传类

$upload->maxSize   =     3145728 ;// 设置附件上传大小

$upload->exts      =     array('jpg', 'gif', 'png', 'jpeg');// 设置附件上传类型

$upload->savePath  =      './Public/Uploads/'; // 设置附件上传目录

// 上传文件

$info   =   $upload->upload();

if(!$info) {// 上传错误提示错误信息

$this->error($upload->getError());

}else{// 上传成功 获取上传文件信息

foreach($info as $file){

echo $file['savepath'].$file['savename'];

}

}

1先是实例化upload类,然后可以动态设计config参数,也可以通过在实例化对象的时候动态传入数组,给对象里的构造方法进行处理。

2如果是动态设置就是调用__set方法和__get方法,如果是实例化对象的时候传入参数,则将config数组与类的默认config属性进行合并

3设置上传驱动,默认是local驱动,实例化驱动类成为upload.class.php的属性uploader

4 将mimes和ext属性都是用array_map(‘strtolower’,$var)进行处理,将其变成小写

5调用类的主要方法   upload(),在处理文件之前会对文件有判断,调用方法的时候是否传入文件,如果没有就设置为$_FILE 是否为空、检测上传到的根目录和上传目录(必须,因为if判断的结果是return false)、通过function_exists(‘finfo_open’)获取MIME类型的文件

6使用dealFiles()方法对上传的文件进行处理,这个方法的主要功能就是将文件(可以为多个)的数组形式进行处理,分别以一个文件信息为一个数组的形式赋值给$files

7.然后使用foreach对每一条文件信息进行处理。

1.先使用strip_tags对文件名进行处理,去掉所有的html、xml、php标签

2.再次检测这个文件的key并设置为文件本来的下标

3.将上传文件的后缀获取到,运用到的函数是 pathinfo($name,PATHINFO_EXTENSION)

4.调用check()对文件信息进行检查,分别检查是是:文件名、是否合法、大小、MIME类型、后缀

5.然后根据config设置判断是否使用hash

6.根据config设置的callback对文件进行处理

7.生成 保存文件名,调用getSaveName()

8.根据subPath的设置对子目录进行时设置

9.检测图片后缀

10.保存图片到指定地方

8.所有的报错都通过getError()返回error属性。驱动类也是

9.最终返回的所有信息都放在$info这个信息数组里

THINKPHP源码学习--------文件上传类的更多相关文章

  1. SpringMVC源码分析--文件上传

    SpringMVC提供了文件上传的功能,接下来我们就简单了解一下SpringMVC文件上传的开发及大致过程. 首先需要在springMVC的配置文件中配置文件上传解析器 <bean id=&qu ...

  2. 源码解读-文件上传angularFileUpload1

    angular-file-upload 1.文件上传模块的引入就不赘述,简单准备 2.初始化组件并绑定change事件 3.监听用户选择文件FileItem(change事件),push进文件数组qu ...

  3. PHP 大文件上传,支持断点续传,求具体方案、源码或者文件上传插件

    文件夹数据库处理逻辑 publicclass DbFolder { JSONObject root; public DbFolder() { this.root = new JSONObject(); ...

  4. ThinkPHP文件上传类

    TP框架自带文件上传类使用: 类文件在ThinkPHP/Library/Think/默认在目录下 public function upload(){ $upload = new \Think\Uplo ...

  5. php 文件上传类 实例分享

    最近在研究php上传的内容,找到一个不错的php上传类,分享下. <?php /** * 文件上传类 * class: uploadFile * edit: www.jbxue.com */ c ...

  6. [上传下载] C#FileUp文件上传类 (转载)

    点击下载 FileUp.zip 主要功能如下 .把上传的文件转换为字节数组 .流转化为字节数组 .上传文件根据FileUpload控件上传 .把Byte流上传到指定目录并保存为文件 看下面代码吧 // ...

  7. php 文件上传类,功能相当齐全,留作开发中备用吧。

    收藏一个经典好用的php 文件上传类,功能相当齐全,留作开发中备用吧. 好东西,大家都喜欢,才是真的好,哈哈!!! <?php  /**   * 文件上传类   */  class upload ...

  8. ASP.NET 文件上传类 简单好用

    调用: UploadFile uf = new UploadFile(); /*可选参数*/ uf.SetIsUseOldFileName(true);//是否使用原始文件名作为新文件的文件名(默认: ...

  9. PHP 文件上传类

    FileUpload.;                $];                $_newname = date(,). :                             To ...

随机推荐

  1. C# 设置Excel打印选项及打印excel文档

    C# 设置Excel打印选项及打印excel文档 打印Excel文档是一个很常见的操作,但有时候我们会碰到各种不同的打印需求,例如只打印一个Excel工作表的其中一部分,或打印时每页都有表头,或把工作 ...

  2. 使用OAuth、Identity创建WebApi认证接口供客户端调用

    前言 现在的web app基本上都是前后端分离,之前接触的大部分应用场景最终产品都是部署在同一个站点下,那么随着WebApi(Restful api)的发展前后端实现的完全分离,前端不在后端框架的页面 ...

  3. Linux 解决数量庞大wildfly容器启动与停止的脚本

    一.问题 因公司业务的发展,后台架构的变更,导致测试环境(Linux)部署与管理困难成倍增长,duang的一下,增加N倍.进入正题说问题: 问题1.  测试环境包含普通用户环境.开发者用户环境,原来只 ...

  4. OWIN与Katana详解

    前言 我胡汉三又回来了,!!!!, 最近忙成狗,实在没空写博文,实在对不起自己,博客园上逛了逛发现 我大微软还是很给力的 asp.net core 1.0 .net core 1.0 即将发布,虽然. ...

  5. php内核分析(五)-zval

    这里阅读的php版本为PHP-7.1.0 RC3,阅读代码的平台为linux 实际上,从这个函数开始,就已经进入到了zend引擎的范围了. zend_eval_string_ex(exec_direc ...

  6. 【转】SQL Server -- 已成功与服务器建立连接,但是在登录过程中发生错误

    SQL Server -- 已成功与服务器建立连接,但是在登录过程中发生错误 最近在VS2013上连接远程数据库时,突然连接不上,在跑MSTest下跑的时候,QTAgent32 crash.换成IIS ...

  7. Entity Framework 教程——什么是Entity Framework

    什么是Entity Framework 编写和管理ADO.NET是一个繁琐而又无聊的工作.微软为你的应用提供了一个名为"Entity Framework"的ORM框架来自动化管理你 ...

  8. 8 种提升 ASP.NET Web API 性能的方法

    ASP.NET Web API 是非常棒的技术.编写 Web API 十分容易,以致于很多开发者没有在应用程序结构设计上花时间来获得很好的执行性能. 在本文中,我将介绍8项提高 ASP.NET Web ...

  9. asp.net MVC4——省市三级联动数据库

    数据库设计

  10. Css3新特性总结之边框与背景(二)

    一.条纹背景 利用background为linear-gradient函数实现,linear-gradient取值如下: <angle>:角度,渐变的方向 to left right to ...