php中浅复制与深复制的例子

 更新时间:2016年11月25日 16:45  点击:1798
前段时间头一次听说浅复制与深复制了,当时就是看的java例子,下文我来为各位分享一些小编总结的php中浅复制与深复制的例子供各位学习


周末闲来无事看到了原型模式,其中谈到了浅复制和深复制,想到PHP中的对应赋值、克隆以及克隆是浅复制还是深复制。

先来看看赋值,例如有一个简历类,有身高和体重两个属性:

class Resume 

{

    public $height;

    public $weight;

 

    public $workExperience;

}

$ResumeA = new Resume();

$ResumeB = $ResumeA;

此时实例化了一个Resume类并赋值给了$ResumeA变量,然后将$ResumeA变量赋值给$ResumeB。PHP手册上有说:

自PHP5起,new运算符自动返回一个引用,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。 当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。

所以若通过$ResumeB修改height属性,则$ResumeA也会跟着变。如果想要复制一个全新的对象,则可以通过clone来实现,如:

$ResumeB = clone $ResumeA;

此时将$ResumeA的值拷贝到新的变量$ResumeB中,改变其中一个不影响另一个,修改$ResumeB中height属性,$ResumeA不会跟着改变。
但如果该类引用了其他对象,则所有的引用仍然指向到原来的对象。clone的这种复制方式就是浅复制。被赋值对象的所有变量都还有与原来对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。

如果上面类中workExperience为WorkExperience类的引用,当克隆的时候,克隆前后的workExperience属性还是指向到同一个对象内容。

与浅复制对应的是深复制,深复制把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。

PHP中可以通过两种方式来实现深复制。第一种是__clone魔术方法:

public function __clone()

{

    $this->workExperience = new WorkExperience();

}

深复制涉及深的层次,通过clone魔术方法实现需要知道有几层然后对每一层依次实现。还有一种是可以通过序列化对象的方式,先将对象序列化之后再反序列化,如:

$ResumeB = unserialize(serialize($ResumeA));

clone还算常用的拷贝方式,整理的目的只是为了记录一下clone是浅复制,需要注意一下对象的引用

我们再举一个更实用的例子来说明一下PHP clone这种浅拷贝带来的后果:

class testClass
{
   public $str_data;
   public $obj_data;
}
$dateTimeObj = new DateTime("2014-07-05", new DateTimeZone("UTC"));
$obj1 = new testClass();
$obj1->str_data ="aaa";
$obj1->obj_data = $dateTimeObj;
$obj2 = clone $obj1;
var_dump($obj1);    // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);    // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
$obj2->str_data ="bbb";
$obj2->obj_data->add(new DateInterval('P10D'));      //给$obj2->obj_date 的时间增加了10天
var_dump($obj1);     // str_data:"aaa"   obj_data:"2014-07-15 00:00:00"  !!!!
var_dump($obj2);     // str_data:"bbb"   obj_data:"2014-07-15 00:00:00"
var_dump($dateTimeObj)  // 2014-07-15 00:00:00"


这一下可以更加清楚的看到问题了吧。 一般来讲,你用clone来复制对象,希望是把两个对象彻底分开,不希望他们之间有任何关联, 但由于clone的shallow copy的特性, 有时候会出现非你期望的结果,上面的例子中,
1) $obj1->obj_data =$dateTimeObj 这句话实际上是个引用类型的赋值. 还记得前面提到的PHP中对象直接的赋值是引用操作么?除非你用$obj1->obj_dat = clone $dataTimeObj!

2) $obj2 = clone $obj1 这句话生成了一个obj1对象的浅拷贝对象,并赋给obj2. 由于是浅拷贝,obj2中的obj_data也是对$dateTimeObj的引用!

3)$dateTimeObj, $obj1->obj_data, $obj2->obj_data 实际上是同一个内存区对象数据的引用,因此修改其中任何一个都会影响其他两个!

 

如何解决这个问题呢? 采用PHP中的 __clone方法 把浅拷贝转换为深拷贝(这个方法给C++中的copy constructor概念上有些相似,但执行流程并不一样)

class testClass
{
 public $str_data;
 public $obj_data;
 public function __clone() {
   $this->obj_data = clone $this->obj_data;
}
$dateTimeObj = new DateTime("2014-07-05", new DateTimeZone("UTC"));
$obj1 = new testClass();
$obj1->str_data ="aaa";
$obj1->obj_data = $dateTimeObj;
$obj2 = clone $obj1;
var_dump($obj1);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
$obj2->str_data ="bbb";
$obj2->obj_data->add(new DateInterval('P10D'));
var_dump($obj1);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);  // str_data:"aaa"  obj_data:"2014-07-15 00:00:00"
var_dump($dateTimeObj);  //"2014-07-05 00:00:00"


关于 __clone() , PHP官方的文档: Once the cloing is complete, if a __clone() method is defined, then the newly created object’s __clone() method will be called, to allow any necessary properties that need to be changed.

按照这个定义,事实上__clone方法可以做很多事情,但我目前能想到的就只有把 浅拷贝变成深拷贝 这个场景的应用了, 如果有其他用法,欢迎大家提出来

这两个函数是对于数据进行json一个转换了,一个是正转一个是反转了,下面我们来看看json_encode()和json_decode()的用法,有兴趣的朋友可一起来看看。

从5.2版本开始,PHP原生提供json_encode()和json_decode()函数,前者用于编码,后者用于解码。

一、json_encode()

string json_encode ( mixed $value [, int $options = 0 ] ):该函数主要用来将数组和对象,转换为json格式。$value为要转换的数据,除了resource 类型之外,可以为任何数据类型。

该函数只能接受 UTF-8 编码的数据,否则会得到空字符或者null。

$arr = array ('a'=>1,'b'=>2,'c'=>3,'d'=>4,'e'=>5);

echo json_encode($arr);

输出如下:

{"a":1,"b":2,"c":3,"d":4,"e":5}
 
对于对象的转换:
$test->site = 'ido321.com';

$test->id = 22;

$test->name = 'dwqs';

echo json_encode($test);
输出是:

{
"site":"ido321.com",

"id":22,

"name":"dwqs",
}

二、编码数组
 
PHP支持关联和索引数组,但由于javascript不支持关联数组,所以json_encode()只将索引数组(indexed array)转为数组格式,而将关联数组(associative array)转为对象格式。
$arr = Array('one', 'two', 'three');

echo json_encode($arr);
结果为:
["one","two","three"]
而对于关联数组
$arr = Array(1=>'one', 2=>'two', 3=>'three');
echo json_encode($arr);
结果为
{"1":"one","2":"two","3":"three"}
注意,数据格式从”[]”(数组)变成了”{}”(对象)。如果你需要将”索引数组”强制转化成”对象”,可以这样写

$arr = Array('one', 'two', 'three');
echo json_encode((object)$arr);
结果是
{"0":"one","1":"two","2":"three"}
还有一种方式
echo json_encode($arr,JSON_FORCE_OBJECT);

//输出:{"0":"one","1":"two","2":"three"}

三、对类的转换

class Test
    {
    const TEST="test";
    public $name="dwqs";
    private $age=21;
    protected $sex="male";
   
    public function getTest()
        {
        return self::TEST;
    }
}

$obj=new Test;
echo json_encode($obj);
输出结果
{"name":"dwqs"}

除了公开变量(public),其他东西(常量、私有变量、方法等等)都遗失了
 
四、json_decode()
 
该函数主要是对 JSON 格式的字符串进行编码,mixed json_decode ( string $json [, bool $assoc ] ),接受一个 JSON 格式的字符串并且把它转换为 PHP 变量。
<?php
$json = '{"a":1,"b":2,"c":3,"d":4,"e":5}';
var_dump(json_decode($json));
var_dump(json_decode($json, true));
?>
对应的输出是

object(stdClass)#1 (5) {
["a"] => int(1)
["b"] => int(2)
["c"] => int(3)
["d"] => int(4)
["e"] => int(5)
}

array(5) {
["a"] => int(1)
["b"] => int(2)
["c"] => int(3)
["d"] => int(4)
["e"] => int(5)
}

通常情况下,json_decode()总是返回一个PHP对象,而不是数组;如果想要强制生成PHP关联数组,json_decode()需要加一个参数true,就能返回关联数组。
对于json解码,很容易出错,

$bad_json = "{ 'bar': 'baz' }";

$bad_json = '{ bar: "baz" }';

$bad_json = '{ "bar": "baz", }';
 
对这三个字符串执行json_decode()都将返回null,并且报错。
第一个的错误是,json的分隔符(delimiter)只允许使用双引号,不能使用单引号。第二个的错误是,json名值对的”名”(冒号左边的部分),任何情况下都必须使用双引号。第三个的错误是,最后一个值之后不能添加逗号(trailing comma)。

另外,json只能用来表示对象(object)和数组(array),如果对一个字符串或数值使用json_decode(),将会返回null。

magic_quotes_gpc是用来判断我们apache是不是开启了自动转译功能了,为了让各位更好的理解魔法引用magic_quotes_gpc()函数用法下面来给各位总结与举一些例子。

magic_quotes_gpc的设定值将会影响通过Get/Post/Cookies获得的数据


这两天接入百度SDK处理支付回调时碰到了签名通不过的情况,签名规则很简单,md5(transdata + appkey) 和 接受到的sign比较,请求方式为POST。

于是乎通过php://input记录下了原始数据和记录下了POST数据,通过日志查看到结果类似如下:

//原始数据
transdata={"exorderno":"2014031223","transid":"05514312314566",
"waresid":1,"appid":"1","feetype":0,"money":1,"count":1,"result":0,
"transtype":0,"transtime":"2014-03-12 15:33:19","paytype":401}&sign=xxxx
 
//post数据
[transdata] => {\"exorderno\":\"2014031223452345234\",\"transid\":
\"05514031215312314566\",\"waresid\":1,\"appid\":\"1\",\"feetype\":0,
\"money\":1,\"count\":1,\"result\":0,\"transtype\":0,
\"transtime\":\"2014-03-12 15:33:19\",\"paytype\":401}
[sign] => xxxx

可见接收到post数据时引号自动转义了,而程序上未做到该操作,很容易就联想到服务器的魔法引用打开了,查看
php版本

PHP 5.2.14 (cli) (built: Jun 7 2012 20:39:40)
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2010 Zend Technologies

例子说明:

$data1 = $_POST['aaa']; 

 $data2 = implode(file('1.txt')); 

 if(get_magic_quotes_gpc()){ 

    //把数据$data1直接写入数据库 (自动转译) 

 }else{ 

    $data1 = addslashes($data1); 

    //把数据$data1写入数据库,用函数(addslashes()转译) 

 } 

 if(get_magic_quotes_runtime()){ 

    //把数据$data2直接写入数据库(自动转译) 

 //从数据库读出的数据要经过一次stripslashes()之后输出,stripslashes()的作用是去掉:\ ,和addslashes()作用相反 

 }else{ 

    $data2 = addslashes($data2); 

     //把数据$data2写入数据库 

   

 //从数据库读出的数据直接输出 

}

最关键的区别是就是上面提到的2点:他们针对的处理对象不同

magic_quotes_gpc的设定值将会影响通过Get/Post/Cookies获得的数据

注意的是没有 set_magic_quotes_gpc()这个函数,就是不能在程序里面设置magic_quotes_gpc的值。

魔法引用5.4才删掉的,那极有可能这里打开在,查看配置文件确实如此,根据条件开关strip一下即可。

问题很快就解决了,但如果不熟悉这块可能还需要点时间,之前在CI的全局参数xss设置中有类似的地方,当进行全局处理之后对于这种接口、密钥可能会带来一些影响,所以全局参数过滤需要注意点。

矛盾可分为主要矛盾和次要矛盾,我们在程序设计中也常有这种思想,改最少的地方,过滤大部分参数,少数特殊处理。php中把它去掉了并不说明它没有存在的价值,有了魔法引用少了很多注入,但同时也让一些东西变得混乱,哪里需要转义,要怎么转义,通过什么方式来转义等等。客观看待,汲取中间有用的部分

file_get_contents函数我们通常是拿来对文件操作了,下面一起来看看file_get_contents函数的高级使用方法吧.


首先解决file_get_contents的超时问题,在超时返回错误后就象js中的settimeout那样进行一次尝试,错误超过3次或者5次后就确认为无法连线伺服器而彻底放弃。
这?就简单介绍两种解决方法:

一、增加超时的时间限制

注意:set_time_limit只是设定你的PHP程式的超时时间,而不是file_get_contents函数读取URL的超时时间。
我一开始以为set_time_limit也能影响到file_get_contents,后来经测试是无效的。真正的修改file_get_contents延时可以用resource $context的timeout参数:
PHP程式码

    $opts = array(
        'http'=>array(
            'method'=>"GET",
            'timeout'=>60,
        )
    );

    $context = stream_context_create($opts);

    $html =file_get_contents('http://www.111cn.net', false, $context);
    fpassthru($fp);

二、多次尝试

PHP程式码
    $cnt=0;
    while($cnt < 3 && ($str=@file_get_contents('http...'))===FALSE){
      $cnt++;
    }

以上方法对付超时已经OK了。接下来演示一下用file_get_contents实现Post,如下:
PHP程式码

    function Post($url, $post = null){
        $context = array();
        if (is_array($post)) {
            ksort($post);

            $context['http'] = array (
                'timeout'=>60,
                'method' => 'POST',
                'content' => http_build_query($post, '', '&'),
             );
        }

        return file_get_contents($url, false, stream_context_create($context));
    }

    $data = array (
        'name' => 'test',
        'email' => 'test@gmail.com',
        'submit' => 'submit',
     );

     echo Post('http://www.111cn.net', $data);

注意档案头的Set_time_out否则整个档案都得超时了

提取链接是一个很简单的做法了,下面这个例子相对来讲是比较全面了,下面我们一起来看看这个php curl采集页面内容并提取所有的链接例子.

本文承接上面两篇,本篇中的示例要调用到前两篇中的函数,做一个简单的URL采集。一般php采集网络数据会用file_get_contents、file和cURL。不过据说cURL会比file_get_contents、file更快更专业,更适合采集。今天就试试用cURL来获取网页上的所有链接。示例如下:


<?php
/*
 * 使用curl 采集111cn.net下的所有链接。
 */
include_once('function.php');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://www.111cn.net/');
// 只需返回HTTP header
curl_setopt($ch, CURLOPT_HEADER, 1);
// 页面内容我们并不需要
// curl_setopt($ch, CURLOPT_NOBODY, 1);
// 返回结果,而不是输出它
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$html = curl_exec($ch);
$info = curl_getinfo($ch);
if ($html === false) {
 echo "cURL Error: " . curl_error($ch);
}
curl_close($ch);
$linkarr = _striplinks($html);
// 主机部分,补全用
$host = 'http://www.111cn.net/';
if (is_array($linkarr)) {
 foreach ($linkarr as $k => $v) {
  $linkresult[$k] = _expandlinks($v, $host);
 }
}
printf("<p>此页面的所有链接为:</p><pre>%s</pre>n", var_export($linkresult , true));
?>


function.php内容如下(即为上两篇中两个函数的合集):

<?php
function _striplinks($document) {
 preg_match_all("'<s*as.*?hrefs*=s*(["'])?(?(1) (.*?)\1 | ([^s>]+))'isx", $document, $links);
 // catenate the non-empty matches from the conditional subpattern
 while (list($key, $val) = each($links[2])) {
  if (!empty($val))
   $match[] = $val;
 } while (list($key, $val) = each($links[3])) {
  if (!empty($val))
   $match[] = $val;
 }
 // return the links
 return $match;
}
/*===================================================================*
 Function: _expandlinks
 Purpose: expand each link into a fully qualified URL
 Input:  $links   the links to qualify
    $URI   the full URI to get the base from
 Output:  $expandedLinks the expanded links
*===================================================================*/
function _expandlinks($links,$URI)
{
 $URI_PARTS = parse_url($URI);
 $host = $URI_PARTS["host"];
 preg_match("/^[^?]+/",$URI,$match);
 $match = preg_replace("|/[^/.]+.[^/.]+$|","",$match[0]);
 $match = preg_replace("|/$|","",$match);
 $match_part = parse_url($match);
 $match_root =
 $match_part["scheme"]."://".$match_part["host"];
 $search = array(  "|^http://".preg_quote($host)."|i",
      "|^(/)|i",
      "|^(?!http://)(?!mailto:)|i",
      "|/./|",
      "|/[^/]+/../|"
     );
 $replace = array( "",
      $match_root."/",
      $match."/",
      "/",
      "/"
     );
 $expandedLinks = preg_replace($search,$replace,$links);
 return $expandedLinks;
}
?>


具体想要和file_get_contents做一个比较的话,可以利用linux下的time命令查看两者执行各需多长时间。据目前测试看是CURL更快一些。最后链接下上面两个函数相关介绍。

匹配链接函数: function _striplinks()

相对路径转绝对:function _expandlinks()

[!--infotagslink--]

相关文章

  • 源码分析系列之json_encode()如何转化一个对象

    这篇文章主要介绍了源码分析系列之json_encode()如何转化一个对象,对json_encode()感兴趣的同学,可以参考下...2021-04-22
  • php中去除文字内容中所有html代码

    PHP去除html、css样式、js格式的方法很多,但发现,它们基本都有一个弊端:空格往往清除不了 经过不断的研究,最终找到了一个理想的去除html包括空格css样式、js 的PHP函数。...2013-08-02
  • Linux下PHP安装curl扩展支持https例子

    安装curl扩展支持https是非常的重要现在许多的网站都使用了https了,下面我们来看一篇关于PHP安装curl扩展支持https例子吧。 问题: 线上运行的lamp服务器,默认yu...2016-11-25
  • MySQL快速复制数据库数据表的方法

    某些时候,例如为了搭建一个测试环境,或者克隆一个网站,需要复制一个已存在的mysql数据库。使用以下方法,可以非常简单地实现。假设已经存在的数据库名字叫db1,想要复制一份,命名为newdb。步骤如下:1. 首先创建新的数据库newd...2015-10-21
  • index.php怎么打开?如何打开index.php?

    index.php怎么打开?初学者可能不知道如何打开index.php,不会的同学可以参考一下本篇教程 打开编辑:右键->打开方式->经文本方式打开打开运行:首先你要有个支持运行PH...2017-07-06
  • PHP中func_get_args(),func_get_arg(),func_num_args()的区别

    复制代码 代码如下:<?php function jb51(){ print_r(func_get_args()); echo "<br>"; echo func_get_arg(1); echo "<br>"; echo func_num_args(); } jb51("www","j...2013-10-04
  • PHP编程 SSO详细介绍及简单实例

    这篇文章主要介绍了PHP编程 SSO详细介绍及简单实例的相关资料,这里介绍了三种模式跨子域单点登陆、完全跨单点域登陆、站群共享身份认证,需要的朋友可以参考下...2017-01-25
  • PHP实现创建以太坊钱包转账等功能

    这篇文章主要介绍了PHP实现创建以太坊钱包转账等功能,对以太坊感兴趣的同学,可以参考下...2021-04-20
  • php使用floor去掉小数点的例子

    floor会产生小数了如果我们不希望有小数我们是可以去除小数点的了,下面一聚教程小编来为各位介绍php使用floor去掉小数点的例子,希望对各位有帮助。 float floor (...2016-11-25
  • php微信公众账号开发之五个坑(二)

    这篇文章主要为大家详细介绍了php微信公众账号开发之五个坑,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2016-10-02
  • C# 复制与删除文件的实现方法

    这篇文章主要介绍了C# 复制与删除文件的实现方法的相关资料,希望通过本文能帮助到大家,让大家理解掌握这部分内容,需要的朋友可以参考下...2020-06-25
  • JavaScript深度复制(deep clone)的实现方法

    本文给大家介绍JavaScript深度复制(deep clone)的实现方法,涉及到js深度复制相关知识,本文介绍的非常详细,特此分享脚本之家平台供大家参考...2016-02-21
  • percona-toolkit对MySQL的复制和监控类操作教程

    复制类工具 1. pt-heartbeat 功能介绍: 监控mysql复制延迟 用法介绍: pt-heartbeat [OPTION...] [DSN] --update|--monitor|--check|--stop 测量复制落后主mysql或者主PostgreSQL多少时间,你可以使用这个脚本去更新主或...2015-11-24
  • C#中数组初始化与数组元素复制的方法

    这篇文章主要介绍了C#中数组初始化与数组元素复制的方法,涉及C#中数组的创建、初始化及使用Array.Copy方法复制数组元素的技巧,需要的朋友可以参考下...2020-06-25
  • PHP如何通过date() 函数格式化显示时间

    这篇文章主要介绍了PHP如何通过date() 函数格式化显示时间,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-11-13
  • ThinkPHP使用心得分享-ThinkPHP + Ajax 实现2级联动下拉菜单

    首先是数据库的设计。分类表叫cate.我做的是分类数据的二级联动,数据需要的字段有:id,name(中文名),pid(父id). 父id的设置: 若数据没有上一级,则父id为0,若有上级,则父id为上一级的id。数据库有内容后,就可以开始写代码,进...2014-05-31
  • mysql(master/slave)主从复制原理及配置图文详解

    这篇文章主要介绍了mysql(masterslave)主从复制原理及配置图文详解,以前脚本之家小编发过相关的内容,但这么好的非常少见特分享一下,需要的朋友可以参考下...2016-06-12
  • step by step配置mysql复制的具体方法

    采用全库复制的方案,方便管理mysql 5.5 + rhel5.810.4.11.12 master10.4.11.13 slave---Master端设置1, 创建复制帐号,需要复制和slave权限mysql> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl@'10....2013-10-04
  • golang与php实现计算两个经纬度之间距离的方法

    这篇文章主要介绍了golang与php实现计算两个经纬度之间距离的方法,结合实例形式对比分析了Go语言与php进行经纬度计算的相关数学运算技巧,需要的朋友可以参考下...2016-07-29
  • PHP正则表达式过滤html标签属性(DEMO)

    这篇文章主要介绍了PHP正则表达式过滤html标签属性的相关内容,实用性非常,感兴趣的朋友参考下吧...2016-05-06