PHP的资源操作

一、正则表达式

1、什么是正则表达式?

正则表达式是对字符串进行操作的一种逻辑公式,就是用一些特定的字符组合成一个规则字符串,称之为正则匹配模式。

$p = '/apple/';
$str = "apple banna";
if (preg_match($p, $str)) {
    echo 'matched';
}

其中字符串'/apple/'就是一个正则表达式,他用来匹配源字符串中是否存在apple字符串。
PHP中使用PCRE库函数进行正则匹配,比如上例中的preg_match用于执行一个正则匹配,常用来判断一类字符模式是否存在。

2、正则表达式的基本语法

PCRE库函数中,正则匹配模式使用分隔符与元字符组成,分隔符可以是非数字、非反斜线、非空格的任意字符。经常使用的分隔符是正斜线/、hash符号# 以及取反符号~,例如:

/foo bar/
#^[^0-9]$#
~php~

如果模式中包含分隔符,则分隔符需要使用反斜杠\进行转义。

/http:\/\//

如果模式中包含较多的分割字符,建议更换其他的字符作为分隔符,也可以采用preg_quote进行转义。

$p = 'http://';
$p = '/'.preg_quote($p, '/').'/';
echo $p;

分隔符后面可以使用模式修饰符,模式修饰符包括:i,m,s,x等,例如使用i修饰符可以忽略大小写匹配:

$str = "Http://www.imooc.com/";
if (preg_match('/http/i', $str)) {
    echo '匹配成功';
}

3、元字符与转义

正则表达式中具有特殊含义的字符称之为元字符,常用的元字符有:
\一般用于转义字符
^断言目标的开始位置(或在多行模式下是行首)
$ 断言目标的结束位置(或在多行模式下是行尾)
.匹配除换行符外的任何字符(默认)
[开始字符类定义
]结束字符类定义
|开始一个可选分支
(子组的开始标记
)子组的结束标记
?作为量词,表示 0 次或 1 次匹配。位于量词后面用于改变量词的贪婪特性。 (查阅量词)
*量词,0 次或多次匹配
+量词,1 次或多次匹配
{自定义量词开始标记
}自定义量词结束标记

//下面的\s匹配任意的空白符,包括空格,制表符,换行符。[^\s]代表非空白符。[^\s]+表示一次或多次匹配非空白符。
$p = '/^我[^\s]+(苹果|香蕉)$/';
$str = "我喜欢吃苹果";
if (preg_match($p, $str)) {
    echo '匹配成功';
}

元字符具有两种使用场景,一种是可以在任何地方都能使用,另一种是只能在方括号内使用,在方括号内使用的有:
\转义字符
^仅在作为第一个字符(方括号内)时,表明字符类取反
-标记字符范围
其中^在反括号外面,表示断言目标的开始位置,但在方括号内部则代表字符类取反,方括号内的减号-可以标记字符范围,例如0-9表示0到9之间的所有数字。

//下面的\w匹配字母或数字或下划线。
$p = '/[\w\.\-]+@[a-z0-9\-]+\.(com|cn)/';
$str = "我的邮箱是Spark.eric@imooc.com";
preg_match($p, $str, $match);
echo $match[0];

4、贪婪模式与懒惰模式

正则表达式中每个元字符匹配一个字符,当使用+之后将会变的贪婪,它将匹配尽可能多的字符,但使用问号?字符时,它将尽可能少的匹配字符,既是懒惰模式。

贪婪模式:在可匹配与可不匹配的时候,优先匹配

//下面的\d表示匹配数字
$p = '/\d+\-\d+/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0]; //结果为:010-12345678

懒惰模式:在可匹配与可不匹配的时候,优先不匹配

$p = '/\d?\-\d?/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0];  //结果为:0-1

当我们确切的知道所匹配的字符长度的时候,可以使用{}指定匹配字符数

$p = '/\d{3}\-\d{8}/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0]; //结果为:010-12345678

5、使用正则表达式进行匹配

使用正则表达式的目的是为了实现比字符串处理函数更加灵活的处理方式,因此跟字符串处理函数一样,其主要用来判断子字符串是否存在、字符串替换、分割字符串、获取模式子串等。

PHP使用PCRE库函数来进行正则处理,通过设定好模式,然后调用相关的处理函数来取得匹配结果。

preg_match用来执行一个匹配,可以简单的用来判断模式是否匹配成功,或者取得一个匹配结果,他的返回值是匹配成功的次数0或者1,在匹配到1次以后就会停止搜索。

$subject = "abcdef";
$pattern = '/def/';
preg_match($pattern, $subject, $matches);
print_r($matches); //结果为:Array ( [0] => def )

上面的代码简单的执行了一个匹配,简单的判断def是否能匹配成功,但是正则表达式的强大的地方是进行模式匹配,因此更多的时候,会使用模式:

$subject = "abcdef";
$pattern = '/a(.*?)d/';
preg_match($pattern, $subject, $matches);
print_r($matches); //结果为:Array ( [0] => abcd [1] => bc )

通过正则表达式可以匹配一个模式,得到更多的有用的数据。

6、查找所有匹配结果

preg_match只能匹配一次结果,但很多时候我们需要匹配所有的结果,preg_match_all可以循环获取一个列表的匹配结果数组。

$p = "|<[^>]+>(.*?)</[^>]+>|i";
$str = "<b>example: </b><div align=left>this is a test</div>";
preg_match_all($p, $str, $matches);
print_r($matches);

可以使用preg_match_all匹配一个表格中的数据:

$p = "/<tr><td>(.*?)<\/td>\s*<td>(.*?)<\/td>\s*<\/tr>/i";
$str = "<table> <tr><td>Eric</td><td>25</td></tr> <tr><td>John</td><td>26</td></tr> </table>";
preg_match_all($p, $str, $matches);
print_r($matches);

$matches结果排序为$matches[0]保存完整模式的所有匹配, $matches[1] 保存第一个子组的所有匹配,以此类推。

7、正则表达式的搜索和替换

正则表达式的搜索与替换在某些方面具有重要用途,比如调整目标字符串的格式,改变目标字符串中匹配字符串的顺序等。

例如我们可以简单的调整字符串的日期格式:

$string = 'April 15, 2014';
$pattern = '/(\w+) (\d+), (\d+)/i';
$replacement = '$3, ${1} $2';
echo preg_replace($pattern, $replacement, $string);//结果为:2014, April 15

其中${1}$1的写法是等效的,表示第一个匹配的字串,$2代表第二个匹配的。

通过复杂的模式,我们可以更加精确的替换目标字符串的内容。

$patterns = array ('/(19|20)(\d{2})-(\d{1,2})-(\d{1,2})/',
                   '/^\s*{(\w+)}\s*=/');
$replace = array ('\3/\4/\1\2', '$\1 =');//\3等效于$3,\4等效于$4,依次类推
echo preg_replace($patterns, $replace, '{startDate} = 1999-5-27'); //结果为:$startDate = 5/27/1999
/*详细解释下结果:(19|20)表示取19或者20中任意一个数字,
(\d{2})表示两个数字,
(\d{1,2})表示1个或2个数字,
(\d{1,2})表示1个或2个数字。
^\s*{(\w+)}\s*=表示以任意空格开头的,
并且包含在{}中的字符,
并且以任意空格结尾的,
最后有个=号的。*/

用正则替换来去掉多余的空格与字符:

$str = 'one     two';
$str = preg_replace('/\s+/', ' ', $str);
echo $str; // 结果改变为'one two'

8、正则匹配常用案例

正则匹配常用在表单验证上,一些字段会有一定的格式要求,比如用户名一般都要求必须是字母、数字或下划线组成,邮箱、电话等也都有自己的规则,因此使用正则表达式可以很好的对这些字段进行验证。

我们通过案例来看一下一般的用户注册页,都怎样对字段进行验证。

<?php
$user = array(
    'name' => 'spark1985',
    'email' => 'spark@imooc.com',
    'mobile' => '13312345678'
);
//进行一般性验证
if (empty($user)) {
    die('用户信息不能为空');
}
if (strlen($user['name']) < 6) {
    die('用户名长度最少为6位');
}
//用户名必须为字母、数字与下划线
if (!preg_match('/^\w+$/i', $user['name'])) {
    die('用户名不合法');
}
//验证邮箱格式是否正确
if (!preg_match('/^[\w\.]+@\w+\.\w+$/i', $user['email'])) {
    die('邮箱不合法');
}
//手机号必须为11位数字,且为1开头
if (!preg_match('/^1\d{10}$/i', $user['mobile'])) {
    die('手机号不合法');
}
echo '用户信息验证成功';

二、会话操作

1、http浅析

HTTP的全称是超文本传输协议。它的特点是“无连接”和“无状态”,所谓无连接就是一次请求一次连接,不过这是早期的情况,现在浏览器都是默认开启keep-alive(持久连接)的。所谓无状态就是每一个请求都是独立的,由于http无状态的特性,要实现购物车和用户识别等功能,需要会话控制技术,而会话控制使用cookie和session技术实现。

2、cookie简介

Cookie是存储在客户端浏览器中的数据,分为内存cookie硬盘cookie。内存cookie由浏览器保存在内存中,关闭浏览器之后就消失了,存在时间短暂。 硬盘cookie保存在硬盘里,有一个过期时间,除非用户手动清除或者到了过期时间,否则不会被删除,其存在时间是长期的。

我们通过Cookie来跟踪与存储用户数据。一般情况下,Cookie通过HTTP headers从服务端返回到客户端。多数web程序都支持Cookie的操作,因为Cookie是存在于HTTP的标头之中,所以必须在其他信息输出以前进行设置,类似于header函数的使用限制。

PHP通过setcookie函数进行Cookie的设置,任何从浏览器发回的Cookie,PHP都会自动的将他存储在$_COOKIE的全局变量之中,因此我们可以通过$_COOKIE['key']的形式来读取某个Cookie值。

PHP中的Cookie具有非常广泛的使用,经常用来存储用户的登录信息,购物车等,且在使用会话Session时通常使用Cookie来存储会话id来识别用户,Cookie具备有效期,当有效期结束之后,Cookie会自动的从客户端删除。同时为了进行安全控制,Cookie还可以设置域跟路径,我们会在稍后的章节中详细的讲解他们。
将一下代码放到服务器中并访问此文件,刷新浏览器,即可看到cookie记录的时间有所改变:

<?php
setcookie('test', time());
ob_start();
print_r($_COOKIE); 
$content = ob_get_contents();
$content = str_replace(" ", '&nbsp;', $content);
ob_clean();
header("content-type:text/html; charset=utf-8");
echo '当前的Cookie为:<br>';
echo nl2br($content);

3、设置cookie

PHP设置Cookie最常用的方法就是使用setcookie函数,setcookie($name,$value,$expire,$path,$domain,$secure,$httponly);具有7个可选参数:

name(Cookie名)可以通过$_COOKIE['name'] 进行访问
value(Cookie的值)
expire(过期时间)Unix时间戳格式,默认为0,表示浏览器关闭即失效
path(有效路径)如果路径设置为'/',则整个网站都有效
domain(有效域)默认整个域名都有效,如果设置了'www.imooc.com',则只在www子域中有效
secure默认值是false,当设置为true时只能通过HTTPs传输cookie。
httponly默认值false,当为true时只能使用http访问cookie。

$value = 'test';
setcookie("TestCookie", $value);
setcookie("TestCookie", $value, time()+3600);  //有效期一小时
setcookie("TestCookie", $value, time()+3600, "/path/", "imooc.com"); //设置路径与域

cookie还可以保存数组,例如如下代码:

setcookie('userInfo[username]', 'phpuser', time()+3600);
setcookie('userInfo[age]', 20, time()+3600);
setcookie('userInfo[phone]', '18610293658', time()+3600);

PHP中还有一个设置Cookie的函数setrawcookie,setrawcookie跟setcookie基本一样,唯一的不同就是value值不会自动的进行urlencode,因此在需要的时候要手动的进行urlencode。

setrawcookie('cookie_name', rawurlencode($value), time()+60*60*24*365);

因为Cookie是通过HTTP标头进行设置的,所以也可以直接使用header方法进行设置。

header("Set-Cookie:cookie_name=value");

cookie的修改和赋值一样,都是setcookie函数,只要对相同的键进行新的赋值即可,但注意的是path和domain要和之前的相同,否则就是在新的域里建了相同键名的cookie

4、cookie的删除与过期时间

通过前面的章节,我们了解了设置cookie的函数,但是我们却发现php中没有删除Cookie的函数,在PHP中删除cookie也是采用setcookie函数来实现。

setcookie('test', '', time()-1);

可以看到将cookie的过期时间设置到当前时间之前,则该cookie会自动失效,也就达到了删除cookie的目的。之所以这么设计是因为cookie是通过HTTP的标头来传递的,客户端根据服务端返回的Set-Cookie段来进行cookie的设置,如果删除cookie需要使用新的Del-Cookie来实现,则HTTP头就会变得复杂,实际上仅通过Set-Cookie就可以简单明了的实现Cookie的设置、更新与删除。
cookie的删除同更新一样要注意path和domain。

了解原理以后,我们也可以直接通过header来删除cookie。

header("Set-Cookie:test=1393832059; expires=".gmdate('D, d M Y H:i:s \G\M\T', time()-1));

这里用到了gmdate,用来生成格林威治标准时间,以便排除时差的影响。

5、cookie的有效路径

cookie中的路径用来控制设置的cookie在哪个路径下有效,默认为'/',在所有路径下都有,当设定了其他路径之后,则只在设定的路径以及子路径下有效,例如:

setcookie('test', time(), 0, '/path');

上面的设置会使test在/path以及子路径/path/abc下都有效,但是在根目录下就读取不到test的cookie值。

一般情况下,大多是使用所有路径的,只有在极少数有特殊需求的时候,会设置路径,这种情况下只在指定的路径中才会传递cookie值,可以节省数据的传输,增强安全性以及提高性能。

当我们设置了有效路径的时候,不在当前路径的时候则看不到当前cookie。

setcookie('test', '1',0, '/path');  
var_dump($_COOKIE['test']);  

6、使用js操作cookie

var Cookie={
    set: function(key,val,expiresDays){
        //判断是否设置expiresDays
        if(expiresDays){
            var date=new Date();
            date.setTime(date.getTime()+expiresDays*24*3600*1000);//格式化时间,因为是微秒,所以最后要乘以1000
            var expiresStr="expires="+date.toGMTString()+';';
        }else{
            var expiresStr='';
        }
        document.cookie=key+'='+escape(val)+';'+expiresStr;
    },
    get: function(key){
        var getCookie=document.cookie.replace(/[ ]/g, '');
        var resArr=getCookie.split(';');
        var res;
        for(var i=0,len=resArr.length;i<len;i++){
            var arr=resArr[i].split('=');
            if(arr[0]==key){
                res=arr[1];
                break;
            }
        }
        return unescape(res);
    }
};

7、cookie的封装类

详情请见代码

<?php
/**
 *Cookie的设置、读取、更新、删除
 */
class CustomCookie{
    static private $_instance=null;
    private $expire=0;
    private $path='';
    private $domain='';
    private $secure=false;
    private $httponly=false;
     
    /**
    * 构造函数完成Cookie参数初始化工作
    * @param array $options Cookie相关选项
    */
    private function __construct(array $options = array()){
        $this->setOptions($options);
    }
    
    /**
     * 设置相关选项
     * @param [array] $options Cookie相关选项
     */
    private function setOptions(array $options = array()){
        if(isset($options['expire'])){
            $this->expire=(int)$options['expire'];
        }
        if(isset($options['path'])){
            $this->path=(int)$options['path'];
        }
        if(isset($options['domain'])){
            $this->domain=(int)$options['domain'];
        }
        if(isset($options['secure'])){
            $this->secure=(bool)$options['secure'];
        }
        if(isset($options['httponly'])){
            $this->httponly=(bool)$options['httponly'];
        }
        
    }

    /**
    * 单例模式
    * @param [array] $options Cookie相关选项
    * @return object          对象实例
    */
    public static function getInstance(array $options = array()){
        if(is_null(self::$_instance)){
            $class=__CLASS__;
            self::$_instance=new $class($options);
        }
        return self::$_instance;
    }
    /**
     * 设置Cookie
     * @param string $name     Cookie的名称
     * @param mixed $value     Cookie的值
     * @param array $options   Cookie相关选项
     */
    public function set($name, $value, array $options = array()){
        if(is_array($options) && count($options)>0){
            $this->setOptions($options);
        }
        if(is_array($value) || is_object($value)){
            $value = json_encode($value);
        }
        setcookie($name, $value, $this->expire, $this->path, $this->domain, $this->secure, $this->httponly);
    }
    
    /**
     * 得到指定Cookie
     * @param string $name Cookie名称
     * @return mixed       返回null或者对象或者标量
     */
    public function get($name){
        if(isset($_COOKIE[$name])){
            return substr($_COOKIE[$name],0,1)=='{'?json_decode($_COOKIE[$name]):$_COOKIE[$name];
        }else{
            return null;
        }
    }
    
    /**
     * 删除指定Cookie
     * @param string $name       Cookie名称
     * @param array $options     相关选项
     */
    public function delete($name, array $options=array()){
        if(is_array($options) && count($options)>0){
            $this->setOptions($options);
        }
        if(isset($_COOKIE[$name])){
            setcookie($name,'',time()-1,$this->path,$this->domain,$this->secure,$this->httponly);
            unset($_COOKIE[$name]);
        }
    }
    
    /**
     * 删除所有Cookie
     * @param array $options     相关选项
     */
    public function deleteAll($options = array()){
        if(is_array($options) && count($options)>0){
            $this->setOptions($options);
        }
        if(!empty($_COOKIE)){
            foreach($_COOKIE as $name=>$value){
                setcookie($name,'',time()-1,$this->path,$this->domain,$this->secure,$this->httponly);
                unset($_COOKIE[$name]);
            }
        }
    }
}

8、cookie的缺点和localStorage的基本使用

8.1、cookie的缺点

1.cookie中不要存储敏感数据,cookie不是很安全,截取cookie之后可以用来进行cookie欺骗。
2.不要把cookie当做客户端的存储器使用,首先每个域名允许cookie存储的空间是有限制的,根据同的浏览器这个限制也不同。 cookie中保存数据的最大字节数是4Kb。
3.cookie设置之后每次都会附着在http的报头中,浪费带宽。

8.2、HTML5 localStorage——cookie的存储替代方案。
localStorage和Cookie的区别.png

1.判断浏览器是否支持localStorage,可以在浏览器的控制台输入如下图的代码,判断是否支持。

判断浏览器是否支持localStorage.png

目前除了欧朋迷你浏览器大部分都支持。
2.常用API
(1)设置:localStorage.setItem(key,value)
(2)读取:localStorage.getItem(key)
(3)删除指定key:localStorage.removeItem(key)
(4)全部清除:localStorage.clear()
(5)获取指定的键名:localStorage.key(i)
注意:localStorage中键和值都只能用字符串来存储,比如我想用localStorage.setItem(key,value)方法存一个json对象,我就必须用JSON,stringify(Object)方法转成字符串。相应的取出字符串之后,可以用JSON.parse(String)来还原。

9、扩展localStorage

让localStorage也具有cookie有效期属性的封装,具体如下代码,以后可以直接用:

var Custom_localStorage={
    //添加缓存时间:7天
    set:function(key,value,days){
        var item={
            data:value,
            endTime:new Date().getTime()+days*24*3600*1000
        }
        localStorage.setItem(key,JSON.stringify(item));
    },
    get:function(key){
        var val=localStorage.getItem(key);
        if(!val){
            return null;
        }
        val=JSON.parse(val);
        if(new Date().getTime()>val.endTime){
            val=null;
            localStorage.removeItem(key);
        }
        return val.data;
    },
    remove:function(key){
        localStorage.removeItem(key);
        return null;
    },
    removeAll:function(){
        localStorage.clear();
        return null;
    }
};

10、session与cookie的异同

cookie将数据存储在客户端,建立起用户与服务器之间的联系,通常可以解决很多问题,但是cookie仍然具有一些局限:

cookie相对不是太安全,容易被盗用导致cookie欺骗
单个cookie的值最大只能存储4k
每次请求都要进行网络传输,占用带宽

session是服务器和浏览器保有共同秘密信息的时间段,它将用户的会话数据存储在服务端,没有大小限制,通过一个session_id进行用户识别,PHP默认情况下session id是通过cookie来保存的,因此从某种程度上来说,seesion依赖于cookie。但这不是绝对的,session id也可以通过参数来实现,只要能将session id传递到服务端进行识别的机制都可以使用session。session的工作原理如下图所示:

session的工作原理.png

session工作原理如下:
①服务器在收到请求之后,会检测请求中是否有session_id,如果没有的话就在内存中建立一个。
②服务器将session_id发送给客户端浏览器保存,通常是保存在cookie中,当然也有别的方式。
③ 浏览器再次访问服务器之后,服务器会检测请求中是否有session_id,如果有的话就表明认识此客户端。
④ 服务器在此session_id中存入任意的、经过序列化后的会话数据。
⑤每次浏览器访问服务器,都可以根据此session_id认领自己的信息。
⑥如果想销毁会话,可以删除会话或者会话中的数据。
session的使用:
①开启会话:session_start()
②通过$SESSION查询或设置相关的值。
③销毁session:session_desdory(),当然还有其他方法,后面会介绍到。
④session_id是默认以临时cookie存储在浏览器中,如果想设置过期时间,可以这样写:

<?php
//开启session
session_start();
//设置session的值
$_SESSION['a']='aaaaaaaaaa';
//给session_id设置过期时间
setcookie(session_name(),session_id(),time()+3600);

⑤session还可以以数组的方式进行存储:

<?php
//开启session
session_start();
//设置数组的值
$myinfo = [
    'user1'=>['username'=>'Tom','userid'=>'elsielduiwu','userage'=>23],
    'user2'=>['username'=>'Mary','userid'=>'sdfef8s5e2g','userage'=>24],
    'user3'=>['username'=>'Frank','userid'=>'elssdfesdfef','userage'=>25]
];
$_SESSION=$myinfo;

下面的代码是一个简单的session使用机制:

<?php
//开始使用session
session_start();
//设置一个session
$_SESSION['test'] = time();
//显示当前的session_id
echo "session_id:".session_id();
echo "<br>";
//显示当前session的名字
echo "session的名字:".session_name();

//读取session值
echo $_SESSION['test'];

//销毁一个session
unset($_SESSION['test']);
echo "<br>";
var_dump($_SESSION);

11、session中相关的配置选项

这些配置都在php.ini文件中可以修改

(1)session.save_handler

它定义了来存储和获取与会话关联的数据的处理器的名字,默认为files,如果我们想用数据库或者memecache以及redis来存储session,就修改这个选项。

(2)session.save_path

它定义了传递给存储处理器的参数,如果选择了默认的files文件处理器,则此值是穿件文件的路径。

(3)session.name

指定会话名以用作cookie的名字,只能又字母数字组成,默认为PHPSESSID

(4)session.auto_start

如果它开启了(值为1),那么每个页面就默认启动session了,不用再写session_start();这个语句,但是它默认是不启动的(值为0),因为开启后会很浪费资源。

(5)session.serialize_handler

当它开启时,session在保存的时候默认使用序列化的形式。

(6)session.gc_probablity与session.gc_divisor

这两个设置合起来用来管理gc(garbage collection 垃圾回收)进程启动的概率,默认为session.gc_probablity1,session.gc_divisor默认值为100,它俩合起来表示有1/100(1%)的概率被垃圾回收机制销毁session。

(7)session.gc_maxlifetime

它用来设置session的最大生存周期,默认为1440秒(24分钟),指定过了多少秒之后就会被视为“垃圾”并被清除。

(8)session.use_cookies

它指定是否在客户端用cookie来存放会话ID,默认为1(启用)。

(9)session.use_only_cookies

它代表只使用cookie来存放会话ID。如果用户的浏览器禁止使用cookie,那么使用URL传参来传递会话ID也是可以的。

(10)session.cookie_lifetime

保存session ID的cookie的生命周期,默认为0(会话cookie,关闭即销)。

(11)session.use_trans_sid

它代表是否使用SID常量,默认为0。

12、cookie被禁用了如何传递session

我们可以把sessionID放到URL中存储,例如我们写个链接:

<?php
session_start();
$_SESSION['a'] = 'aaa';
$_SESSION['b'] = 'bbb';
echo "<a href='dump1.php?".session_name()."="."session_id()"."'>查看信息</a>";

然后在dump1.php中进行处理:

<?php
session_id($_GET[session_name()]);//将传过来的PHPSESSID指定为sessionID
session_start();
print_r($_SESSION);

注意:这个方式不建议使用,因为容易泄密,还是使用cookie进行存储为上。

13、使用session

在PHP中使用session非常简单,先执行session_start方法开启session,然后通过全局变量$_SESSION进行session的读写。

session_start();
$_SESSION['test'] = time();
var_dump($_SESSION);

session会自动的对要设置的值进行encode与decode,因此session可以支持任意数据类型,包括数据与对象等。

session_start();
$_SESSION['ary'] = array('name' => 'jobs');
$_SESSION['obj'] = new stdClass();
var_dump($_SESSION);

默认情况下,session是以文件形式存储在服务器上的,因此当一个页面开启了session之后,会独占这个session文件,这样会导致当前用户的其他并发访问无法执行而等待。可以采用缓存或者数据库的形式存储来解决这个问题,这个我们会在一些高级的课程中讲到。

14、删除与销毁session

(1)第一步将$_SESSION这个数组赋成空数组即$_SESSION=[]或者null即$_SESSION=null
(2)第二步将cookie中的session相关数据清除,可以使用setcookie()方法实现。
(3)调用session_destroy()来销毁会话。

删除某个session值可以使用PHP的unset函数,删除后就会从全局变量$_SESSION中去除,无法访问。

session_start();
$_SESSION['name'] = 'jobs';
unset($_SESSION['name']);
echo $_SESSION['name']; //提示name不存在

如果要删除所有的session,可以使用session_destroy函数销毁当前session,session_destroy会删除所有数据,但是session_id仍然存在。

session_start();
$_SESSION['name'] = 'jobs';
$_SESSION['time'] = time();
session_destroy();

值得注意的是,session_destroy并不会立即的销毁全局变量$_SESSION中的值,只有当下次再访问的时候,$_SESSION才为空,因此如果需要立即销毁$_SESSION,可以使用unset函数。

session_start();
$_SESSION['name'] = 'jobs';
$_SESSION['time'] = time();
unset($_SESSION);
session_destroy(); 
var_dump($_SESSION); //此时已为空

如果需要同时销毁cookie中的session_id,通常在用户退出的时候可能会用到,则还需要显式的调用setcookie()方法删除session_id的cookie值。

15、使用session来存储用户的登录信息

session可以用来存储多种类型的数据,因此具有很多的用途,常用来存储用户的登录信息,购物车数据,或者一些临时使用的暂存数据等。

用户在登录成功以后,通常可以将用户的信息存储在session中,一般的会单独的将一些重要的字段单独存储,然后所有的用户信息独立存储。

$_SESSION['uid'] = $userinfo['uid'];
$_SESSION['userinfo'] = $userinfo;

一般来说,登录信息既可以存储在sessioin中,也可以存储在cookie中,他们之间的差别在于session可以方便的存取多种数据类型,而cookie只支持字符串类型,同时对于一些安全性比较高的数据,cookie需要进行格式化与加密存储,而session存储在服务端则安全性较高。
下面的代码展示了用户登录的信息原理:

<?php
session_start();
//假设用户登录成功获得了以下用户数据
$userinfo = array(
    'uid'  => 10000,
    'name' => 'spark',
    'email' => 'spark@imooc.com',
    'sex'  => 'man',
    'age'  => '18'
);
header("content-type:text/html; charset=utf-8");

/* 将用户信息保存到session中 */
$_SESSION['uid'] = $userinfo['uid'];
$_SESSION['name'] = $userinfo['name'];
$_SESSION['userinfo'] = $userinfo;

//* 将用户数据保存到cookie中的一个简单方法 */
$secureKey = 'imooc'; //加密密钥
$str = serialize($userinfo); //将用户信息序列化
//用户信息加密前
$str = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($secureKey), $str, MCRYPT_MODE_ECB));
//用户信息加密后
//将加密后的用户数据存储到cookie中
setcookie('userinfo', $str);

//当需要使用时进行解密
$str = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($secureKey), base64_decode($str), MCRYPT_MODE_ECB);
$uinfo = unserialize($str);
echo "解密后的用户信息:<br>";
print_r($uinfo);

16、使用数据库存储千万级session信息

(1)设置用户自定义会话存储函数——session_set_save_handler()

关于此函数的具体介绍,请见手册。此笔记只记录使用接口方式实现自定义存储session。

(2)更改配置

将php.ini中的session.save_handler中files改成user(即数据库)。

(3)实现SessionHandlerInterface接口

新建一个类,实现SessionHandlerInterface接口:

<?php
class CustomSession implements SessionHandlerInterface{
    private $link;
    private $lifeTime;
    
    public function open($savePath,$session_name){
        $this->lifeTime=get_cfg_var('session.gc_maxlifetime');
        $this->link=mysqli_connect('localhost','root','123456');
        mysqli_set_charset($this->link,'utf8');
        mysqli_select_db($this->link,'test');
        if($this->link){
            return true;
        }else{
            return false;
        }
    }
    public function close(){
        mysqli_close($this->link);
        return true;
    }
    public function read($session_id){
        $id=mysqli_escape_string($this->link,$session_id);;
        $sql="select * from sessions where session_id='{$session_id}'and session_expires>".time();
        $result=mysqli_query($this->link,$sql);
        if(mysqli_num_rows($result)==1){
            return mysqli_fetch_assoc($result)['session_data'];
        }else{
            return '';
        }
    }
    public function write($session_id,$session_data){
        $newExp=time()+$this->lifeTime;
        $session_id=mysqli_escape_string($this->link,$session_id);
        //查询是否存在指定的session_id,如果存在就更新数据,否则就说明是第一次,则写入数据
        $sql="select * from sessions where session_id='{$session_id}'";
        $result=mysqli_query($this->link,$sql);
        if(mysqli_num_rows($result)==1){
            $sql="update sessions set session_expires='{$newExp}',session_data='{$session_data}' where session_id='{$session_id}'";
        }else{
            $sql="insert into sessions values('{$session_id}','{$session_data}','{$newExp}')";
        }
        mysqli_query($this->link,$sql);
        return mysqli_affected_rows($this->link)==1;
    }
    public function destroy($session_id){
        $session_id=mysqli_escape_string($this->link,$session_id);
        $sql="delete from sessions where session_id='{$session_id}'";
        mysqli_query($this->link,$sql);
        return mysqli_affected_rows($this->link)==1;
    }
    public function gc($maxlifetime){
        $sql="delete from sessions where session_expires<".time();
        mysqli_query($this->link,$sql);
        if(mysqli_affected_rows($this->link)>0){
            return true;
        }else{
            return false;
        }
    }
}

调用方法如下:
①增加session(自动更新修改):

require_once 'CustomSession.php';
$CustomSession=new CustomSession;
ini_set('session.save_handler','user');
session_set_save_handler($CustomSession,true);
session_start();
$_SESSION['username']='Xuezhi';
$_SESSION['age']=27;
$_SESSION['test']='this is a test';
$_SESSION['email']='844800670@qq.com';

②查和删:

<?php
require_once 'CustomSession.php';
$CustomSession=new CustomSession;
ini_set('session.save_handler','user');
session_set_save_handler($CustomSession,true);
session_start();
//print_r($_SESSION);
session_destroy();

三、文件系统

1、读取文件内容

PHP具有丰富的文件操作函数,最简单的读取文件的函数为file_get_contents,可以将整个文件全部读取到一个字符串中。

$content = file_get_contents('./test.txt');

file_get_contents也可以通过参数控制读取内容的开始点以及长度。

$content = file_get_contents('./test.txt', null, null, 100, 500);

PHP也提供类似于C语言操作文件的方法,使用fopen,fgets,fread等方法,fgets可以从文件指针中读取一行,freads可以读取指定长度的字符串。

$fp = fopen('./text.txt', 'rb');
while(!feof($fp)) {
    echo fgets($fp); //读取一行
}
fclose($fp);
$fp = fopen('./text.txt', 'rb');
$contents = '';
while(!feof($fp)) {
    $contents .= fread($fp, 4096); //一次读取4096个字符
}
fclose($fp);

注意:使用fopen打开的文件,最好使用fclose关闭文件指针,以避免文件句柄被占用。

2、对文件进行判断

一般情况下在对文件进行操作的时候需要先判断文件是否存在,PHP中常用来判断文件存在的函数有两个is_file与file_exists.

$filename = './test.txt';
if (file_exists($filename)) {
    echo file_get_contents($filename);
}

如果只是判断文件存在,使用file_exists就行,file_exists不仅可以判断文件是否存在,同时也可以判断目录是否存在,从函数名可以看出,is_file是确切的判断给定的路径是否是一个文件。

$filename = './test.txt';
if (is_file($filename)) {
    echo file_get_contents($filename);
}

更加精确的可以使用is_readable与is_writeable在文件是否存在的基础上,判断文件是否可读与可写。

$filename = './test.txt';
if (is_writeable($filename)) {
    file_put_contents($filename, 'test');
}
if (is_readable($filename)) {
    echo file_get_contents($filename);
}

3、获取文件的属性(一)

文件有很多元属性,包括:文件的所有者、创建时间、修改时间、最后的访问时间等。

fileowner:获得文件的所有者
filectime:获取文件的创建时间
filemtime:获取文件的修改时间
fileatime:获取文件的访问时间
其中最常用的是文件的修改时间,通过文件的修改时间,可以判断文件的时效性,经常用在静态文件或者缓存数据的更新。

$mtime = filemtime($filename);
echo '修改时间:'.date('Y-m-d H:i:s', filemtime($filename));

4、获取文件的属性(二)

通过filesize函数可以取得文件的大小,文件大小是以字节数表示的。

$filename = '/data/webroot/usercode/resource/test.txt';
$size = filesize($filename);

如果要转换文件大小的单位,可以自己定义函数来实现。

function getsize($size, $format = 'kb') {
    $p = 0;
    if ($format == 'kb') {
        $p = 1;
    } elseif ($format == 'mb') {
        $p = 2;
    } elseif ($format == 'gb') {
        $p = 3;
    }
    $size /= pow(1024, $p);
    return number_format($size, 3);
}

$filename = '/data/webroot/usercode/code/resource/test.txt';
$size = filesize($filename);

$size = getsize($size, 'kb'); //进行单位转换
echo $size.'kb';

值得注意的是,没法通过简单的函数来取得目录的大小,目录的大小是该目录下所有子目录以及文件大小的总和,因此需要通过递归的方法来循环计算目录的大小。

5、写入内容到文件

与读取文件对应,PHP写文件也具有两种方式,最简单的方式是采用

file_put_contents。

$filename = './test.txt';
$data = 'test';
file_put_contents($filename, $data);

上例中,$data参数可以是一个一维数组,当$data是数组的时候,会自动的将数组连接起来,相当于$data=implode('', $data);
注意:file_put_contents()如果文件不存在,则创建一个新文件。
同样的,PHP也支持类似C语言风格的操作方式,采用fwrite进行文件写入。

$fp = fopen('./test.txt', 'w');
fwrite($fp, 'hello');
fwrite($fp, 'world');
fclose($fp);

6、删除文件

跟Unix系统命令类似,PHP使用unlink函数进行文件删除。
unlink($filename);
删除文件夹使用rmdir函数,文件夹必须为空,如果不为空或者没有权限则会提示失败。
rmdir($dir);
如果文件夹中存在文件,可以先循环删除目录中的所有文件,然后再删除该目录,循环删除可以使用glob函数遍历所有文件。

foreach (glob("*") as $filename) {
   unlink($filename);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343