php面向对象中__set __get __isset __unset用法介绍

 更新时间:2016年11月25日 16:23  点击:1891
我们经常会在php的面向对象中可以看到位__set __get __isset __unset这些东西的用法,但很不明白为什么会要用这些东西,下面我们来一一介绍一下他们哥四的用法吧。

 一般来说,总是把类的属性定义为private,这更符合现实的逻辑。但是,对属性的读取和赋值操作是非常频繁的,因此在PHP5中,预定义了两个函数“__get()”和“__set()”来获取和赋值其属性,以及检查属性的“__isset()”和删除属性的方法“__unset()”。
上一节中,我们为每个属性做了设置和获取的方法,在PHP5中给我们提供了专门为属性设置值和获取值的方法,“__set()”和“__get()”这两个方法,这两个方法不是默认存在的,而是我们手工添加到类里面去的,像构造方法(__construct())一样, 类里面添加了才会存在,可以按下面的方式来添加这两个方法,当然也可以按个人的风格来添加:

//__get()方法用来获取私有属性

 代码如下 复制代码

private function __get($property_name)


{

 


if(isset($this->$property_name))


{


return($this->$property_name);


}else


{


return(NULL);


}


}


//__set()方法用来设置私有属性


private function __set($property_name, $value)


{


$this->$property_name = $value;


}

__get()方法:这个方法用来获取私有成员属性值的,有一个参数,参数传入你要获取的成员属性的名称,返回获取的属性值,这个方法不用我们手工的去调用,因为我们也可以把这个方法做成私有的方法,是在直接获取私有属性的时候对象自动调用的。因为私有属性已经被封装上了,是不能直接获取值的(比如:“echo $p1->name”这样直接获取是错误的),但是如果你在类里面加上了这个方法,在使用“echo $p1->name”这样的语句直接获取值的时候就会自动调用__get($property_name)方法,将属性name传给参数$property_name,通过这个方法的内部执行,返回我们传入的私有属性的值。如果成员属性不封装成私有的,对象本身就不会去自动调用这个方法。
__set()方法:这个方法用来为私有成员属性设置值的,有两个参数,第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。这个方法同样不用我们手工去调用,它也可以做成私有的,是在直接设置私有属性值的时候自动调用的,同样属性私有的已经被封装上
 
了,如果没有__set()这个方法,是不允许的,比如:$this->name=‘zhangsan’, 这样会出错,但是如果你在类里面加上了__set($property_name, $value)这个方法,在直接给私有属性赋值的时候,就会自动调用它,把属性比如name传给$property_name, 把要赋的值“zhangsan”传给$value,通过这个方法的执行,达到赋值的目的。如果成员属性不封装成私有的,对象本身就不会去自动调用这个方法。为了不传入非法的值,还可以在这个方法给做一下判断。代码如下:

 代码如下 复制代码

<?php


class Person


{


//下面是人的成员属性, 都是封装的私有成员


private $name;          //人的名子


private $sex;           //人的性别


private $age;           //人的年龄


//__get()方法用来获取私有属性


private function __get($property_name)


{


echo "在直接获取私有属性值的时候,自动调用了这个__get()方法<br>";


if(isset($this->$property_name))


{


return($this->$property_name);


}


else


{


return(NULL);


}


}


//__set()方法用来设置私有属性


private function __set($property_name, $value)


{


echo "在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值<br>";

 


$this->$property_name = $value;


}


}


$p1=new Person();


//直接为私有属性赋值的操作, 会自动调用__set()方法进行赋值


$p1->name="张三";


$p1->sex="男";


$p1->age=20;


//直接获取私有属性的值, 会自动调用__get()方法,返回成员属性的值


echo "姓名:".$p1->name."<br>";


echo "性别:".$p1->sex."<br>";


echo "年龄:".$p1->age."<br>";


?>

程序执行结果:
在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值
在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值
在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值
在直接获取私有属性值的时候,自动调用了这个__get()方法
姓名:张三
在直接获取私有属性值的时候,自动调用了这个__get()方法
性别:男
在直接获取私有属性值的时候,自动调用了这个__get()方法
年龄:20
 
以上代码如果不加上__get()和__set()方法,程序就会出错,因为不能在类的外部操作私有成员,而上面的代码是通过自动调用__get()和__set()方法来帮助我们直接存取封装的私有成员的。
__isset() 方法:在看这个方法之前我们看一下“isset()”函数的应用,isset()是测定变量是否设定用的函数,传入一个变量作为参数,如果传入的变量存在则传回true,否则传回false。那么如果在一个对象外面使用“isset()”这个函数去测定对象里面的成员是否被设定可不可以用它呢?分两种情况,如果对象里面成员是公有的,我们就可以使用这个函数来测定成员属性,如果是私有的成员属性,这个函数就不起作用了,原因就是因为私有的被封装了,在外部不可见。那么我们就不可以在对象的外部使用“isset()”函数来测定私有成员属性是否被设定了呢?可以,你只要在类里面加上一个“__isset()”方法就可以了,当在类外部使用”isset()”函数来测定对象里面的私有成员是否被设定时,就会自动调用类里面的“__isset()”方法了帮我们完成这样的操作,“__isset()”方法也可以做成私有的。你可以在类里面加上下面这样的代码就可以了:

 代码如下 复制代码

private function __isset($nm)


{


echo "当在类外部使用isset()函数测定私有成员$nm时,自动调用<br>";


return isset($this->$nm);


}

__unset()方法:看这个方法之前呢,我们也先来看一下“unset()”这个函数,“unset()”这个函数的作用是删除指定的变量且传回true,参数为要删除的变量。那么如果在一个对象外部去删除对象内部的成员属性用“unset()”函数可不可以呢,也是分两种情况,如果一个对象里面的成员属性是公有的,就可以使用这个函数在对象外面删除对象的公有属性,如果对象的成员属性是私有的,我使用这个函数就没有权限去删除,但同样如果你在一个对象里面加上“__unset()”这个方法,就可以在对象的外部去删除对象的私有成员属性了。在对象里面加上了“__unset()”这个方法之后,在对象外部使用“unset()”函数删除对象内部的私有成员属性时,自动调用“__unset()”函数来帮
我们删除对象内部的私有成员属性,这个方法也可以在类的内部定义成私有的。在对象里面加上下面的代码就可以了:

 代码如下 复制代码

private function __unset($nm)


{


echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";


unset($this->$nm);


}

我们来看一个完整的实例:

<?php


class Person


{


//下面是人的成员属性


private $name;        //人的名子


private $sex;         //人的性别


private $age;         //人的年龄


//__get()方法用来获取私有属性


private function __get($property_name)


{


if(isset($this->$property_name))


{


return($this->$property_name);


}else {


return(NULL);


}


}


//__set()方法用来设置私有属性


private function __set($property_name, $value)


{

 


$this->$property_name = $value;


}


//__isset()方法


private function __isset($nm)


{


echo "isset()函数测定私有成员时,自动调用<br>";


return isset($this->$nm);


}


//__unset()方法


private function __unset($nm)


{


echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";


unset($this->$nm);


}


}


$p1=new Person();


$p1->name="this is a person name";


//在使用isset()函数测定私有成员时,自动调用__isset()方法帮我们完成,返回结果为true


echo var_dump(isset($p1->name))."<br>";


echo $p1->name."<br>";


//在使用unset()函数删除私有成员时,自动调用__unset()方法帮我们完成,删除name私有属性


unset($p1->name);


//已经被删除了, 所这行不会有输出


echo $p1->name;


?>

输出结果为:
isset()函数测定私有成员时,自动调用
bool(true)
this is a person name
当在类外部使用unset()函数来删除私有成员时自动调用的
__set()、__get()、__isset()、__unset() 这四个方法都是我们添加到对象里面的,在需要时自动调用的,来完成在对象外部对对象内部私有属性的操作

本文章来讲述一下关于在ISBN校验方法,有需要的同学可参考一下。考虑到一个严谨的图书管理程序要考虑到多方面的问题,因为10位ISBN码的图书还是有巨大的存世量的,所以要校验图书ISBN码的正确性,就必须同时考虑10位和13位的情况。

平常我们能够见到的ISBN码有10位和13位两种,其中10位的ISBN自2007年1月起已经停止使用,目前新出版的图书ISBN码都是13位。考虑到一个严谨的图书管理程序要考虑到多方面的问题,因为10位ISBN码的图书还是有巨大的存世量的,所以要校验图书ISBN码的正确性,就必须同时考虑10位和13位的情况。从维基百科可以了解到ISBN码最后一位是校验码,其实要想校验ISBN码的正确,就是通过计算ISBN的校验码,看是否与最后一位吻合。这里所说的校验也只是校验ISBN在构成上是否合法,而不会校验是否为已发行图书的ISBN。下面是维基百科提供的ISBN码校验算法:

校验码的计算方法(10码)
假设某国际标准书号号码前9位是:7-309-04547
计算加权和S:S = 7×10+3×9+0×8+9×7+0×6+4×5+5×4+4×3+7×2 = 226
计算S÷11的余数M:M = 226 mod 11 = 6
计算11 – M 的差N:N = 11 − 6 = 5
如果N = 10,校验码是字母"X"
如果N = 11,校验码是数字"0"
如果N为其他数字,校验码是数字N
    所以,本书的校验码是5;如果用户提供的ISBN码是7-309-04547-6,那么校验失败

校验码的计算方法(13码)
假设某国际标准书号号码前12位是:978-986-181-728
计算加权和S:S = (9×1)+(7×3)+(8×1)+(9×3)+(8×1)+(6×3)+(1×1)+(8×3)+(1×1)+(7×3)+(2×1)+(8×3) = 164
计算S÷10的余数M:M = 164 mod 10 = 4
计算10 – M 的差N:N = 10 − 4 = 6
如果N = 10,校验码是数字"0"
如果N为其他数字,校验码是数字N
    所以,本书的校验码是6。完整的国际标准书号号码为 ISBN 978-986-181-728-6

    好了,背景知识介绍到这,下面我写的ISBN码校验函数(php版),需要的话可以直接使用

 代码如下 复制代码

/**
 * 名称: PHP校验ISBN码的函数
 * 作者:露兜
 * 最后修改:2010年09月26日
 */

function isbn_sum($isbn, $len)
{
/*
 * 该函数用于计算ISBN加权和
 * 参数说明:
 *   $isbn : isbn码
 *   $len  : isbn码长度
 */
    $sum = 0;
   
    if ($len == 10)
    {
        for ($i = 0; $i < $len-1; $i++)
        {
            $sum = $sum + (int)$isbn[$i] * ($len - $i);
        }
    }
    elseif ($len == 13)
    {
        for ($i = 0; $i < $len-1; $i++)
        {
            if ($i % 2 == 0)
                $sum = $sum + (int)$isbn[$i];
            else
                $sum = $sum + (int)$isbn[$i] * 3;
        }
    }
    return $sum;
}

function isbn_compute($isbn, $len)
{
/*
* 该函数用于计算ISBN末位校验码
* 参数说明:
*   $isbn : isbn码
*   $len  : isbn码长度
*/

    if ($len == 10)
    {
        $digit = 11 - isbn_sum($isbn, $len) % 11;

        if ($digit == 10)
            $rc = 'X';
        else if ($digit == 11)
            $rc = '0';
        else
            $rc = (string)$digit;
    }
    else if($len == 13)
    {
        $digit = 10 - isbn_sum($isbn, $len) % 10;

        if ($digit == 10)
            $rc = '0';
        else
            $rc = (string)$digit;
    }

    return $rc;
}

function is_isbn($isbn)
{
/*
 * 该函数用于判断是否为ISBN号
 * 参数说明:
 *    $isbn : isbn码
 */
    $len = strlen($isbn);

    if ($len!=10 && $len!=13)
        return 0;

    $rc = isbn_compute($isbn, $len);

    if ($isbn[$len-1] != $rc)   /* ISBN尾数与计算出来的校验码不符 */
        return 0;
    else
        return 1;
}

函数写好后,就可以直接调用了,下面是调用示例:

 

 代码如下 复制代码
 <?php echo is_isbn('9787507421781') ? '校验通过' : '校验失败'; ?>

另外我写了一个在线校验ISBN的工具,使用该工具可以在线校验ISBN码的合法性

 

PHP中简单工厂模式实例讲解,简单的讲述了一下大家可参考一下。

简单工厂模式:
①抽象基类:类中定义抽象一些方法,用以在子类中实现
②继承自抽象基类的子类:实现基类中的抽象方法
③工厂类:用以实例化对象

看完文章再回头来看下这张图,效果会比较好

采用封装方式

 代码如下 复制代码
<?php
    class Calc{
        /**
         * 计算结果
         *
         * @param int|float $num1
         * @param int|float $num2
         * @param string $operator
         * @return int|float
         */
        public function calculate($num1,$num2,$operator){
            try {
                $result=0;
                switch ($operator){
                    case '+':
                        $result= $num1+$num2;
                        break;
                    case '-':
                        $result= $num1-$num2;
                        break;
                    case '*':
                        $result= $num1*$num2;
                        break;
                    case '/':
                        if ($num2==0) {
                            throw new Exception("除数不能为0");
                        }
                        $result= $num1/$num2;
                        break;
                    return $result;
                }
            }catch (Exception $e){
                echo "您输入有误:".$e->getMessage();
            }
        }
    }
    $test=new Calc();
//    echo $test->calculate(2,3,'+');//打印:5
    echo $test->calculate(5,0,'/');//打印:您输入有误:除数不能为0
?>

优点:以上代码使用了面向对象的封装特性,只要有了include这个类,其他页面就可以随便使用了

缺点:无法灵活的扩展和维护
比如:想要增加一个“求余”运算,需要在switch语句块中添加一个分支语句,代码需要做如下改动
添加分支语句

 代码如下 复制代码
<?php
    class Calc{
        public function calculate($num1,$num2,$operator){
            try {
                $result=0;
                switch ($operator){
                    //......省略......
                    case '%':
                        $result= $num1%$num2;
                        break;
                    //......省略......
                }
            }catch (Exception $e){
                echo "您输入有误:".$e->getMessage();
            }
        }
    }
?>

代码分析:用以上方法实现给计算器添加新的功能运算有以下几个缺点

①需要改动原有的代码块,可能会在为了“添加新功能”而改动原有代码的时候,不小心将原有的代码改错了
②如果要添加的功能很多,比如:‘乘方’,‘开方’,‘对数’,‘三角函数’,‘统计’,或者添加一些程序员专用的计算功能,比如:And, Or, Not, Xor,这样就需要在switch语句中添加N个分支语句。想象下,一个计算功能的函数如果有二三十个case分支语句,代码将超过一屏,不仅令代码的可读性大大降低,关键是,为了添加小功能,还得让其余不相关都参与解释,这令程序的执行效率大大降低
解决途径:采用OOP的继承和多态思想
简单工厂模式的初步实现
 

 代码如下 复制代码
<?php
     /**
      * 操作类
      * 因为包含有抽象方法,所以类必须声明为抽象类
      */
     abstract class Operation{
         //抽象方法不能包含函数体
         abstract public function getValue($num1,$num2);//强烈要求子类必须实现该功能函数
     }
     /**
      * 加法类
      */
     class OperationAdd extends Operation {
         public function getValue($num1,$num2){
             return $num1+$num2;
         }
     }
     /**
      * 减法类
      */
     class OperationSub extends Operation {
         public function getValue($num1,$num2){
             return $num1-$num2;
         }
     }
     /**
      * 乘法类
      */
     class OperationMul extends Operation {
         public function getValue($num1,$num2){
             return $num1*$num2;
         }
     }
     /**
      * 除法类
      */
     class OperationDiv extends Operation {
         public function getValue($num1,$num2){
             try {
                 if ($num2==0){
                     throw new Exception("除数不能为0");
                 }else {
                     return $num1/$num2;
                 }
             }catch (Exception $e){
                 echo "错误信息:".$e->getMessage();
             }
         }
     }
 ?>

里采用了面向对象的继承特性,首先声明一个虚拟基类,在基类中指定子类务必实现的方法(getValue())

分析:通过采用面向对象的继承特性,我们可以很容易就能对原有程序进行扩展,比如:‘乘方’,‘开方’,‘对数’,‘三角函数’,‘统计’等等。

求余类

 代码如下 复制代码

<?php
    /**
     * 求余类(remainder)
     *
     */
    class OperationRem extends Operation {
        public function getValue($num1,$num2){
            return $num1%$num12;
        }
    }
?>

我们只需要另外写一个类(该类继承虚拟基类),在类中完成相应的功能(比如:求乘方的运算),而且大大的降低了耦合度,方便日后的维护及扩展

现在还有一个问题未解决,就是如何让程序根据用户输入的操作符实例化相应的对象呢?
解决办法:使用一个单独的类来实现实例化的过程,这个类就是工厂
代码如下:

工厂类

 代码如下 复制代码

<?php
    /**
     * 工程类,主要用来创建对象
     * 功能:根据输入的运算符号,工厂就能实例化出合适的对象
     *
     */
    class Factory{
        public static function createObj($operate){
            switch ($operate){
                case '+':
                    return new OperationAdd();
                    break;
                case '-':
                    return new OperationSub();
                    break;
                case '*':
                    return new OperationSub();
                    break;
                case '/':
                    return new OperationDiv();
                    break;
            }
        }
    }
    $test=Factory::createObj('/');
    $result=$test->getValue(23,0);
    echo $result;
?>

文章从最简单的cookie操作(增加,删除,修改)到我们的cookie队列操作类的操作,有需要了解的同学可以参考本实例。

1、设置Cookie

1. PHP 的COOKIE

    cookie 是一种在远程浏览器端储存数据并以此来跟踪和识别用户的机制。
    PHP 在http 协议的头信息里发送cookie,因此  setcookie()     函数必须在其它信息被输出到浏览器
前调用,这和对  header()    函数的限制类似。

1.1 设置cookie:

   可以用 setcookie()或 setrawcookie()函数来设置 cookie。也可以通过向客户端直接发送http  头来
设置。
    1.1.1  使用 setcookie()函数设置cookie:
bool setcookie ( string name [, string value [, int expire [, string path [, string domain [, bool secure [, bool
httponly]]]]]] )
   name: cookie 变量名
   value: cookie 变量的值
   expire: 有效期结束的时间
   path: 有效目录
   domain: 有效域名,顶级域唯一
   secure: 如果值为 1,则cookie 只能在https 连接上有效,如果为默认值 0,则http 和 https 都可
以。

来看几个例子:

简单的:

SetCookie("MyCookie", "Value of MyCookie");

 

带失效时间的:

 代码如下 复制代码

SetCookie("WithExpire", "Expire in 1 hour", time()+3600);//3600秒=1小时

什么都有的:

 代码如下 复制代码

SetCookie("FullCookie", "Full cookie value", time()+3600, "/forum", ".phpuser.com", 1);

我们需要用到队列。

 代码如下 复制代码

class QueueSvc
{/*{{{*/
    private $length; // 队列的长度
    private $server_arr;
   
    public function __construct($length,$server_arr)
    {
        $this->length = $length;
        $this->server_arr = $server_arr;
    }
   
    public function getServerArr()
    {
        return $this->server_arr;
    }
   
    public function set($server_name)
    {
        self::push($server_name);
    }
   
    private function push($server_name)
    {
        //有重复的记录,把重复的删掉
        if(self::isServerExist($server_name)){
            self::removeRepeat($server_name);
        }else{
            if(self::isFull()){
                //如果已经满了,要把队列最后一个记录删掉
                array_pop($this->server_arr);
            }
        }
        //如果队列为空,先置为空数组
        if(empty($this->server_arr))
            $this->server_arr = array();
        //向队列头添加数据
        array_unshift($this->server_arr,$server_name); 
    }
   
    private function isFull()
    {
        if(is_array($this->server_arr) && (count($this->server_arr) >= $this->length))
            return true;
        return false;
    }
   
    private function isServerExist($server_name)
    {
        if(is_array($this->server_arr) && in_array($server_name,$this->server_arr))
            return true;
        return false;
    }
   
    private function removeRepeat($server_name)
    {
        if(is_array($this->server_arr) && in_array($server_name,$this->server_arr))
        {
            foreach($this->server_arr as $key=>$value)
            {
                if($server_name == $value)
                {
                    $this->array_remove($this->server_arr,$key);
                }
            }  
        }
    }
   
    private function array_remove(&$arr, $offset) {    
        array_splice ( $arr, $offset, 1 );
    }
}/*}}}*/require_once('queue_svc.php');
class CookieSvc
{/*{{{*/
    const   COOKIE_KEY = "GAME_SERVER";
   
    const   SEPARATE   = "|";

    const   COOKIE_LENGTH = "2";
   
    public function getCookieArr()
    {/*{{{*/
        $server_str =  $_COOKIE[self::COOKIE_KEY];
        $server_str =  $_COOKIE['GAME_SERVER'];
        if($server_str == ''){
            $result =  array();
        }else{
            $result = explode(self::SEPARATE,$server_str);
        }
        return $result;
    }/*}}}*/
   
    public function set($cookie_id)
    {/*{{{*/
        $server_arr = self::getCookieArr();
        if($cookie_id != false)
        {
            $que = new QueueSvc(self::COOKIE_LENGTH,$server_arr);
            $que->set($cookie_id);
            $server_new = $que->getServerArr();
            if(is_array($server_new))
            {
                $cookie_str = implode(self::SEPARATE,$server_new);
                setcookie(self::COOKIE_KEY,$cookie_str,time()+3600,'/');
            }
        }
    }/*}}}*/
}/*}}}*/

不多解释了,这个别人用的不多,昨天因为需要写的,留一下吧,也许以后还用得到。。
调用的代码很简单:

 代码如下 复制代码

require_once("queue_svc.php");

require_once("cookie_svc.php");

$cookie_id = '4';

CookieSvc::set($cookie_id);

这样就可以了。呼。。大家可以每次把$cookie_id换做不同的值,来检验此操作。
检验的代码可以用如下代码:

 代码如下 复制代码
var_dump($_COOKIE);

常见问题解决:

    1) 用 setcookie()时有错误提示,可能是因为调用setcookie()前面有输出或空格。也可能你的文
       档是从其他字符集转换过来,文档后面可能带有 BOM 签名(就是在文件内容添加一些隐藏
       的BOM 字符)。解决的办法就是使你的文档不出现这种情况。还有通过使用ob_start()函数
       也能处理一点。
    2) $_COOKIE 受magic_quotes_gpc 影响,可能自动转义。
    3) 使用的时候,有必要测试用户是否支持cookie

文章简单的介绍了关于PHP中ZipArchive压缩文件并下载打包好的文件介绍 ,有需要了解php中文件打包的朋友可参考五下。

分析下技术要点:

将文件打包成zip格式
下载文件的功能
要点解析:

这里我采用的是php自带的ZipArchive类
    a) 我们只需要new一个ZipArchive对象,然后使用open方法创建一个zip文件,接着使用addFile方法,将要打包的文件写入刚刚创建的zip文件中,最好还得记得关闭该对象。

    b) 注意点:使用open方法的时候,第二个参数$flags是可选的,$flags用来指定对打开的zip文件的处理方式,共有四种情况

                    i.     ZIPARCHIVE::OVERWRITE 总是创建一个新的文件,如果指定的zip文件存在,则会覆盖掉

                      ii.    ZIPARCHIVE::CREATE     如果指定的zip文件不存在,则新建一个

              iii.  ZIPARCHIVE::EXCL      如果指定的zip文件存在,则会报错   

               iv.  ZIPARCHIVE::CHECKCONS

 


--------------------------------------------------------------------------------

 下载文件的流程:

服务器端的工作:
-------------------------------------------
客户端的浏览器发送一个请求到处理下载的php文件。
注意:任何一个操作都首先需要写入到内存当中,不管是视频、音频还是文本文件,都需要先写入到内存当中。
换句话说,将“服务器”上的文件读入到“服务器”的内存当中的这个操作时必不可少的(注意:这里我将服务器三个字加上双引号,主要是说明这一系类的操作时在服务器上完成的)。<br>
既然要将文件写入到内存当中,就必然要先将文件打开
所以这里就需要三个文件操作的函数了:
一:fopen($filename ,$mode)
二:fread ( int $handle , int $length )
三:fclose ( resource $handle )

---------------------------------------
客户端端的工作:
---------------------------------------
那么,如何将已经存在于服务器端内存当中的文件信息流,传给客户端呢?
答案是通过header()函数,客户端就知道该如何处理文件,是保存还是打开等等

最终的效果如下图所示:

 代码如下 复制代码

<?php
 require'./download.php';
 /**
  * 遍历目录,打包成zip格式
  */
     class traverseDir{
         public $currentdir;//当前目录
         public $filename;//文件名
         public $fileinfo;//用于保存当前目录下的所有文件名和目录名以及文件大小
         public function __construct(){
             $this->currentdir=getcwd();//返回当前目录
         }       
         //遍历目录
         public function scandir($filepath){
             if (is_dir($filepath)){
                     $arr=scandir($filepath);
                     foreach ($arr as $k=>$v){
                         $this->fileinfo[$v][]=$this->getfilesize($v);
                     }
                 }else {
                     echo "<script>alert('当前目录不是有效目录');</script>";
                 }
         }
         /**
          * 返回文件的大小
          *
          * @param string $filename 文件名
          * @return 文件大小(KB)
          */
         public function getfilesize($fname){
             return filesize($fname)/1024;
         }
        
         /**
          * 压缩文件(zip格式)
          */
         public function tozip($items){
             $zip=new ZipArchive();
             $zipname=date('YmdHis',time());
             if (!file_exists($zipname)){
                 $zip->open($zipname.'.zip',ZipArchive::OVERWRITE);//创建一个空的zip文件
                 for ($i=0;$i<count($items);$i++){
                     $zip->addFile($this->currentdir.'/'.$items[$i],$items[$i]);
                 }
                 $zip->close();
                 $dw=new download($zipname.'.zip'); //下载文件
                 $dw->getfiles();
                 unlink($zipname.'.zip'); //下载完成后要进行删除   
             }
         }
     }
 ?>

 代码如下 复制代码

<?php
 /**
  * 下载文件
  *
  */
     class download{
         protected $_filename;
         protected $_filepath;
         protected $_filesize;//文件大小
         public function __construct($filename){
             $this->_filename=$filename;
             $this->_filepath=dirname(__FILE__).'/'.$filename;
         }
         //获取文件名
         public function getfilename(){
             return $this->_filename;
         }
        
         //获取文件路径(包含文件名)
         public function getfilepath(){
             return $this->_filepath;
         }
        
         //获取文件大小
         public function getfilesize(){
             return $this->_filesize=number_format(filesize($this->_filepath)/(1024*1024),2);//去小数点后两位
         }
         //下载文件的功能
         public function getfiles(){
             //检查文件是否存在
             if (file_exists($this->_filepath)){
                 //打开文件
                 $file = fopen($this->_filepath,"r");
                 //返回的文件类型
                 Header("Content-type: application/octet-stream");
                 //按照字节大小返回
                 Header("Accept-Ranges: bytes");
                 //返回文件的大小
                 Header("Accept-Length: ".filesize($this->_filepath));
                 //这里对客户端的弹出对话框,对应的文件名
                 Header("Content-Disposition: attachment; filename=".$this->_filename);
                 //修改之前,一次性将数据传输给客户端
                 echo fread($file, filesize($this->_filepath));
                 //修改之后,一次只传输1024个字节的数据给客户端
                 //向客户端回送数据
                 $buffer=1024;//
                 //判断文件是否读完
                 while (!feof($file)) {
                     //将文件读入内存
                     $file_data=fread($file,$buffer);
                     //每次向客户端回送1024个字节的数据
                     echo $file_data;
                 }
                
                 fclose($file);
             }else {
                 echo "<script>alert('对不起,您要下载的文件不存在');</script>";
             }
         }
     }
 ?>

页面中的显示代码如下

 代码如下 复制代码

<script type="text/javascript" src="jquery-1.7.2.js"></script>
 <script type="text/javascript" src="ajax.js"></script>
 <?php
     header("Content-type:text/html;charset=utf8");
     require('./getfile.php');
     $scandir=new traverseDir();
     $scandir->scandir($scandir->currentdir);
     $scandir->currentdir;
    
     if (isset($_POST['down_load'])){
         $items=$_POST['items'];
         $scandir->tozip($items);//将文件压缩成zip格式
     }
     echo "当前的工作目录:".$scandir->currentdir;
     echo "<br>当前目录下的所有文件";
 ?>
 
 <form action="list.php" method="POST">
 <table>
     <tr>
         <td></td>
         <td>名称</td>
         <td>大小(KB)</td>
     </tr>
 <?php
     $res=$scandir->fileinfo;
     foreach ($res as $k=>$v){
         if (!($k=='.' || $k=='..'))    {//过滤掉.和..
 ?>
     <tr>
         <td><input type="checkbox" name="items[]" class="filename" value="<?php echo $k;?>"></td>
         <td><?php echo $k; ?></td>
         <td><?php echo number_format($v[0],0); ?></td>
     </tr>
 <?php
         }
     }
 ?>
     <tr>
         <td><input type="checkbox" id="selall"><label for="selall">全选</label></td>
         <td><input type="submit" name="down_load" value="打包并下载" id="tozip_tetttt"></td>
     </tr>
 </table>
 </form>

总结:


--------------------------------------------------------------------------------

使用PHP下载文件的操作需要给出四个header(),可以参考我的另一篇博文:PHP如何实现下载功能超详细流程分析
计算文件的大小的时候,并不需要先打开文件,通过filesize($filename)就可以看出,如果需要先打开文件的话,filesize可能就会是这样的形式了filesize($filehandle)
向客户端回送数据的是,记得要设置一个buffer,用来指定每次向客户端输出多少数据,如:$buffer=1023。如果不指定的话,就会将整个文件全部写入内存当中,再一次性的讲数据传送给客户端
通过feof()函数,可以判断要读取的文件是否读完,如果还没读完,继续读取文件($file_data=fread()),并将数据回送给客户端(echo $file_data)
每次下载完成后,在客户端都会刷新下,说明了,其实每次都将数据写入到一个临时文件中,等全部下载完成后,再将所有的数据重新整合在一起
这里我使用的是绝对路径,绝对路径有个好处,就是适应性比较强,而且相对于相对路径,效率更高(免去了查找文件的过程)

[!--infotagslink--]

相关文章

  • java8如何用Stream查List对象某属性是否有重复

    这篇文章主要介绍了java8如何用Stream查List对象某属性是否有重复的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-11
  • R语言 如何删除指定变量或对象

    这篇文章主要介绍了R语言删除指定变量或对象的操作方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-05-06
  • C#中using的三种用法

    using 指令有两个用途: 允许在命名空间中使用类型,以便您不必限定在该命名空间中使用的类型。 为命名空间创建别名。 using 关键字还用来创建 using 语句 定义一个范围,将在此...2020-06-25
  • 如何在Spring WebFlux的任何地方获取Request对象

    这篇文章主要介绍了如何在Spring WebFlux的任何地方获取Request对象,帮助大家更好的理解和使用springboot框架,感兴趣的朋友可以了解下...2021-01-26
  • JavaScript预解析,对象详解

    这篇文章主要介绍了JavaScript预解析,对象的的相关资料,小编觉得这篇文章写的还不错,需要的朋友可以参考下,希望能够给你带来帮助...2021-11-10
  • 牛叉的Jquery――Jquery与DOM对象的互相转换及DOM的三种操作

    只有jQuery对象才能调用jQuery类库的各种函数,同样有些dom对象的属性和方法在jQuery上也是无法调用的,不过基本上jQuery类库提供的函数包含了所有的dom操作。这就需要我们知道如何将jQuery对象和DOM的相互转换的方法。1...2015-10-30
  • js如何打印object对象

    js调试中经常会碰到输出的内容是对象而无法打印的时候,光靠alert只能打印出object标示,却不能打印出来里面的内容,甚是不方便,于是各方面整理总结了如下一个函数,能够将数组或者对象这类的结果一一打印出来,具体代码如下: fu...2015-10-21
  • iscroll.js 用法介绍

    最新版下载: http://www.csdn123.com/uploadfile/2015/0428/20150428062734485.zip 概要 iScroll 4 这个版本完全重写了iScroll这个框架的原始代码。这个项目的产生...2016-05-19
  • C++中cin的用法详细

    这篇文章主要介绍了C++中cin的用法详细,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C#中的try catch finally用法分析

    这篇文章主要介绍了C#中的try catch finally用法,以实例形式分析了try catch finally针对错误处理时的不同用法,具有一定的参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • javascript self对象使用详解

    这篇文章主要介绍了javascript self对象使用详解的相关资料,需要的朋友可以参考下...2016-10-20
  • 替换json对象中的key最佳方案

    本文给大家介绍如何替换json对象中的key,通过实例代码给大家介绍key的替换方法,代码也很简单,需要的朋友参考下吧...2021-06-02
  • ActiveX部件不能创建对象:dm.dmsoft代码:800A01AD

    vbs调用插件报:ActiveX部件不能创建对象,代码:800A01AD,一般是因为病毒导致dll文件丢失或者64系统问题导致,需要的朋友可以参考下...2020-06-30
  • 示例详解react中useState的用法

    useState 通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state,接下来通过一个示例来看看怎么使用 useState吧...2021-06-04
  • Xml中使用foreach遍历对象实现代码

    这篇文章主要介绍了Xml中使用foreach遍历对象实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-12-04
  • 理解JavaScript事件对象

    这篇文章主要为大家介绍了JavaScript事件对象,了解JavaScript事件...2016-01-26
  • JS实现简单面向对象的颜色选择器实例

    这篇文章主要介绍了JS实现简单面向对象的颜色选择器,以完整实例形式分析了JavaScript基于面向对象实现颜色选择器的具体步骤与实现技巧,需要的朋友可以参考下...2016-04-23
  • 解决SpringCloud Feign传对象参数调用失败的问题

    这篇文章主要介绍了解决SpringCloud Feign传对象参数调用失败的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-24
  • PHP中print_r、var_export、var_dump用法介绍

    文章详细的介绍了关于PHP中print_r、var_export、var_dump区别比较以及这几个在php不同的应用中的用法,有需要的朋友可以参考一下 可以看出print_r跟var_export都...2016-11-25
  • 对象题目的一个坑 理解Javascript对象

    这篇文章主要介绍了Javascript对象,特别为大家分享了对象题目的一个坑,提供了解题思路,感兴趣的小伙伴们可以参考一下...2015-12-24