Categories: PHP | Tags: | Views: 403

 

行文原因

 

为什么要写这篇文章? 因为我对于度娘和谷爹里搜索到关于这个问题的文章耿耿于怀, 忿忿不已. 这些文章都只说了个所以, 却没说然.

在这篇文章里, 我将详细地说一下Incomplete Object的成因, 以及这其中session, serialize及unserialize引起的问题和所起的作用

 

网上给出的解决方案

 

1. 网上有个很典型的代码, 说是session_start()位置不正确:

MyClass.php:

 <?php 
class MyClass{ 
    private $_counter = 0; 
    public function increment() { 
        return $this->_counter++;
    }
}
?>
 <?php 
session_start(); 
if (! isset($_SESSION['myObject'])) { 
    require_once __DIR__ . '/MyClass.php'; 
    $_SESSION['myObject'] = new MyClass(); 
}
 
echo get_class($_SESSION['myObject']), "\n";
echo $_SESSION['myObject']->increment(), "\n"; 
?>

而给出的解决方案是: 

 <?php 
require_once __DIR__ . '/MyClass.php'; 
session_start();
if (! isset($_SESSION['myObject'])) { 
    $_SESSION['myObject'] = new MyClass(); 
} 
echo get_class($_SESSION['myObject']), "\n"; 
echo $_SESSION['myObject']->increment(), "\n"; 
?>

仔细看一下代码, 不难发现, 其实根本就不是session_start()位置不正确, 看看那个if逻辑, 在$_SESSION['myObject']

值存在时, 人为不去引用MyClass.php文件.

我们把if条件去掉, 不改变session_start()的位置, 程序正常运行!!!

2. 用serialize序列化类实例, 然后再用unserialize解序列化.

<?php 
session_start(); 
if (! isset($_SESSION['myObject'])) { 
    require_once __DIR__ . '/MyClass.php'; 
    $_SESSION['myObject'] = serialize(new MyClass()); 
} 
$myObject = unserialize($_SESSION['myObject']); 
echo get_class($myObject), "\n"; 
echo $myObject--->increment(), "\n"; 
?>

  很显然, 问题依旧.

 

问题的真正成因

 

很显然, 所谓的session_start()位置, 是否用serialize序列化, 都不是这个问题真正的成因.

从网上给出的第一个解决方案,  如果我们把if条件去掉, 程序正常运行.

实际上, 这个if语句阻止了程序再次运行时去引用类定义文件MyClass.php.

这和用不用serialize和unserialize一毛钱的关系都没有.

 

说到这里, 为了解决为什么不引用类定义文件, 而出现Incomplete Object的情况, 我们要先理解一下

我们把类实例(Object)传给session,那在服务器上是如何存储的呢?

实际上, 在服务器上, php会先把这个object序列化serialize, 再存储到文件里, 当调用的时候, 就会

自动反序列化unserialize,并且重新实例化这个类.

 

那我们现在就可以解释问题的成因了. 当把object写进一个session时$_SESSION['myObject'] = new MyClass(),

把它实例化, 而在取出的时候就会自动重新实例化这个类, 而在实例化这个类的时候, 发现这个类并没有定义, 于是就会出现

Incomplete Object错误.

 

简单的说, 反序列化一个未定义的类的实例, 就会将object转化为Incomplete Object, 操作时就会报错.

 

我们可以来做个实验.

先定义一个类, 然后获取它的实例, 再获取这个实例的序列化字符串:

 

$ php -r 'class A{var $x=0; function p(){echo ++$x;}} $a=new A(); echo serialize($a);'
O:1:"A":1:{s:1:"x";i:0;}

我们来反序列化这个字符串, 并调用p方法:

 

 

$ php -r '$a=unserialize("O:1:\"A\":1:{s:1:\"x\";i:0;}");$a->p();'

最后我们看到报出Incomplete Object的错误:

 

Fatal error: Unknown: The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "A" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide a __autoload() function to load the class definition  in Command line code on line 1

如果我们在反序列化这个字符串之前, 给出类A的声明:
$ php -r 'class A{var $x=0; function p(){echo ++$x;}} $a=unserialize("O:1:\"A\":1:{s:1:\"x\";i:0;}");$a->p();'
1

那么这个程序就可以正常运行.

 
其实session的情况 同样也适用于将object序列化后存入文件, 然后再从文件中取出反序列化的情况.
 
虽然, 如果我们不执行p方法, 程序不会报错, 但是在反序列化的时候, 如果类没有定义, 那么这个object就会是一个Incomplete Object了:
看下面的代码:
$ php -r '$a=unserialize("O:1:\"A\":1:{s:1:\"x\";i:0;}");print_r($a);'
__PHP_Incomplete_Class Object
(
    [__PHP_Incomplete_Class_Name] => A
    [x] => 0
)

解决办法

 

其实真正的解决办法, 上面已经给出来了, 就是在反序列化之前引入声明这个类, 或者引入这个类的实例.

为了避免session在取值自动反序列化将object转成Incomplete Object, 这个时候, 我们就需要显示地去serialize一个object, 然后我们取值时

取到的只是序列化的字符串, 这时php是不会去检测类是否定义的. 那么我们就可以在这个时候解决问题了.

 

下面给出一个解决方案:

假设命名规则是这样的A.php文件定义了类A, 而我们在session中,以小写a为key来保存:

 

<?php
session_start();
$key = 'a';
if( !isset($_SESSION[$key]) ){
    require_once strtoupper($key) . '.php';
    $class = strtoupper($key);
    $_SESSION[$key] = serialize(new $class());
}
// try to include class declaration file before use it
if( !class_exists(strtoupper($key), FALSE) ){
    require_once strtoupper($key). '.php';
    $obj = unserialize($_SESSION[$key]);
    // TODO: whatever you what to do with $obj ...
}
?>

当然, 你也可以用auto_loader来加载类的声明文件.

 

 

 

 

 

 

RELATED POSTS:

  1. 类外函数调用类的成员变量和成员函数
  2. 使AutoBlogroll分两列显示链接
  3. php的mysql数据库操作类
  4. wordpress非插件防垃圾评论方法
  5. Call global variables in PHP and Python
  6. 随机记录分页
No comments yet.
;) :| :x :twisted: :roll: :oops: :o :mrgreen: :lol: :idea: :evil: :cry: :arrow: :P :D :?: :? :) :( :!: 8O 8)

你可以使用@somebody:开头, 来邮件通知somebody你回复了他的留言(用户名区分大小写).