Link to home
Start Free TrialLog in
Avatar of christiandillon
christiandillon

asked on

incomplete object error - followup to Q_21299176

original: https://www.experts-exchange.com/questions/21299176/how-to-handle-objects-in-sessions.html
got that to work on Windows server and PHP 5
moved to production FreeBSD box and php4 and it died
session.auto_start = 0

test1.php:
   require_once("section.class.php");
   session_start();
   $_SESSION['section'] = new section();
   echo "<pre>".print_r($_SESSION['section'],true)."</pre><hr>";

= cool and groovy

test2.php:
   require_once("section.class.php");
   session_start();
   echo "<pre>".print_r($_SESSION['section'],true)."</pre><hr>";

= section Object
(
    [_db_type] => mysql
    [_db_host] => localhost
    [_db_user] => *****
    [_db_password] => *****
    [_db_name] => cc_section
    [_db] =>
    [db] => __PHP_Incomplete_Class Object
        (
            [__PHP_Incomplete_Class_Name] => adodb_mysql
            [dataProvider] => mysql

[section] object is using adoDB class for persistent connection which I think is broken if I serialize/unserialize. So [section] object survives but not DB object within [section] object

what am I doing wrong?

thanks,
cd
Avatar of Marcus Bointon
Marcus Bointon
Flag of France image

Have you not seen the __sleep and __wakeup functions? They allow you to recreate dynamic properties (such as DB connections) on serialize/unserialize. The actual particular db resource doesn't matter - as long as there is a working one around. They're documented here:

http://www.php.net/serialize

Given that ado is now PHP5 aware, I would expect it to be dealing with this for you, but I guess it may not be.
Avatar of christiandillon
christiandillon

ASKER

same result

test1.php
   require_once("section.class.php");
   session_start();
   $section = new section();
   $_SESSION['section'] = serialize($section);

test2.php
   require_once("section.class.php");
   session_start();
   $section = unserialize($_SESSION['section']);
   echo "<pre>".print_r($section,true)."</pre><hr>";

= section Object
(
    [_db_type] => mysql
    [_db_host] => localhost
    [_db_user] => *****
    [_db_password] => *****
    [_db_name] => cc_section
    [_db] =>
    [db] => __PHP_Incomplete_Class Object
        (
            [__PHP_Incomplete_Class_Name] => adodb_mysql
            [dataProvider] => mysql

I know the adoDB works separately and the phpGACL works (a very cool class by the way), just not together in my code.
Obviously, you should REALLY be running similar configurations for development and production.  If you are using different PHP versions, let alone different OSes, hard to say what might be going wrong...

From a discussion on a blog:
"...if you do a print_r() on it, you will find that it is of class type  __PHP_Incomplete_Class instead of whatever it should be. This is because WP/PHP has not yet loaded the class definition for that object, which causes it to become that type..."

Basically, this would indicate to me (I've never tried storing a class in a session before!) that the class adodb_mysql hasn't yet been defined, possibly needing to be done before the session_start tries to retrieve session state.

Two things you can try:
1. Easier: switch you dev box to the SAME php4 version as the server (same extensions, etc., etc.!).
2. Harder: switch the production box up to php5, as there were some known issues in php4 that might have been fixed.

Either one will help prove if there's some easier fix under the covers.

-d
I understand that my [section] object is incomplete because it can't load the adoDB class. I tried include'ing adoDB before the session_start() on both pages but same result.

Switched dev machine to php4 (xampp makes it easy and I'm lazy) and it worked as it should. I'm a rookie in object handling so if there's better way please share but it makes sense to me that I should be able to build a class from another class - then create on object and pass it from page to page.

sysadmin thought the problem might be related to caching on BSD box?

<project name="crunchtime" value="on">
quick things to check would be the exact versions of php4 in use on both systems, and maybe compare the phpinfo() dump from the two machines, comparing the PHP Core sections and the Session sections, see if any differences jump out at you.

I also assume you have the same copy of adodb_mysql code on both machines.  Just sanity checking!

Everything I've read (I too don't have a lot of experience with saving serialized objects in sessions...) points to the class not being instantiated prior to the session unserializing the object.

The reason to check versions and phpinfo is because as we've all learned, all it takes is one little option to throw a bunch of things out of whack.  I could see one session INI setting screwing things up, or something like register globals...

I've been reading all the samples, examples, suggestions, etc., between php.net man pages and various php sites, and haven't seen anything specific.  So best to try step by step to isolate differences between the two machines, hope something sticks out ...

-d
How about trying to store the ado object separately, just for testing purposes, as in:

   require_once 'section.class.php';
   session_start();
   $section = new section();
   $_SESSION['section'] = serialize($section);
   $_SESSION['ado'] = serialize($section->db);

and see if survives by itself.
Squinky,
same result:

1:
      $_SESSION['section'] = serialize($section);
      $_SESSION['ado'] = serialize($section->db);

2:
      $section = unserialize($_SESSION['section']);
      $ado = unserialize($_SESSION['section->db']);

= Incomplete Object error
Then I require'd the ado class in the first page and same result.

davebytes,
My sysadmin thought so too and he was comparing the two last night when I left. At first glance, nothing jumped out related to sessions. We'll see what fresh eyes find today.

From your posts and others elsewhere, I'm confident that the code *should* work so it seems I'm looking for some difference between the two machines. Dang free software.

cd
How about generating some debug output in the __wakeup() handler of your object so you can see if it's even being called? You could probably introspect (in PHP5) to see explicitly if the class is defined before you call unserialize.
CORRECTION: it doesn't work on my machine this way either, that's why I had to come up with the approach from my original question Q_21299176:

index:

   require_once("mygacl.php");
   require_once("mygacl_api.inc.php");
   session_start();
   $gacl = new mygacl();
   $_SESSION['gacl'] = $gacl;
   $gacl_api = new mygacl_api();
   $_SESSION['gacl_api'] = $gacl_api;

subsequent:

   require_once("mygacl.inc.php");
   require_once("mygacl_api.inc.php");
   session_start();
   //  print_r :   [gacl] => __PHP_Incomplete_Class Object
   $gacl = new mygacl();
   $gacl = $_SESSION['gacl'];
   $gacl_api = new mygacl_api();
   $gacl_api = $_SESSION['gacl_api'];


So maybe the question needs to be how to instantiate three objects from three classes that each instantiate an object (adoDB) and have them all survive across pages?
OK, this works on production box:

1:
   require_once("section.class.php");
   session_start();
   $section = new section();
   $_SESSION['section'] = serialize($section);

2:
   require_once("section.class.php");
   session_start();
   $section = new section();   // instantiates adoDB object
   $section = unserialize($_SESSION['section']);

So it seems the need to instantiate the 'section' object again in page 2 is to instantiate the adoDB again before restoring its values. Now I need to check if the persistent DB connection remains alive.
I think that includes inside class definitions don't happen unless the code is actually run, so perhaps it doesn't know about ADO at unserialize time - how about requiring it as well as the other classes? Ugly solution though!
To clarify, this include won't happen until you instantiate the class:

<?php
class thing {
  include 'someclass.php';
}
?>

though this one will:
<?php
include 'someclass.php';
class thing {
}
?>

Because ADO will be a property of your class rather than its parent, it's probably arranged like the former case.
I tried to include the ADO with the other classes but no effect.

So I've managed to keep the objects alive in the session but the persistent DB connection dies. Do you think that's related to the serialize/unserialize (which I'm not doing on my dev machine where this all worked) or might that be a config issue on production?
Well, I've not checked if it's mean to do it anyway, but ADO would definitely need a __wakeup function that re-creates a connection on unserialize - there's no point in preserving an existing connection resource as the other end (over which you have no control) may have died by the time you wake up again.
I think this is it though I don't understand how to use it. Can you shed any light?
From php.net:

unserialize_callback_func directive:  It's possible to set a callback-function which will be called, if an undefined class should be instantiated during unserializing. (to prevent getting an incomplete object "__PHP_Incomplete_Class".) Use your php.ini, ini_set() or .htaccess to define 'unserialize_callback_func'. Everytime an undefined class should be instantiated, it'll be called. To disable this feature just empty this setting.

Production server php.ini:
; The unserialize callback function will be called (with the undefined class'
; name as parameter), if the unserializer finds an undefined class
; which should be instanciated.
; A warning appears if the specified function is not defined, or if the
; function doesn't include/implement the missing class.
; So only set this entry, if you really want to implement such a
; callback-function.
unserialize_callback_func=

My dev php.ini has nothing set either though.

cd
Sorry, we were posting at the same time. You're saying I need to add the __wakeup function to the ADO class or my class that uses ADO?
I added a simple wakeup function to my 'section' class and I got farther than before so I think you're right. Now I need to figure out what needs to be in sleep and wakeup. I'm trying to distill the php.net page on these magic functions.
ASKER CERTIFIED SOLUTION
Avatar of Marcus Bointon
Marcus Bointon
Flag of France image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I'm using phpGACL for access control within my app and it uses ADO so I adopted ADO within my class. One ACL object for checking access to various components, another ACL object for the API get/set's, and my object represents the user.

I tried a simple db->Pconnect in the __wakeup but it did not seem to work. The notes on php.net make it sound like there's more that needs to be included. I'm still digesting.

I will look into the TAdodb wrapper, Squinky.

Still trying to figure out why it works in dev but not production because I don't want to face this or something similar again.
I'm wondering if it could be some other difference with the FreeBSD configuration.  I'm wracking my brain trying to guess at other areas that might be different, certainly the PHP code/build itself is physically different.

Did you go and explicitly check the version of PHP4 up on the FreeBSD box?  And what modules are compiled in, etc?  Not trying to beat a dead horse there... ;)

-d
both 4.3.10

what would I be looking for?

should I post the differences?
No clue... I'm throwing darts here, as if the two machines really were identical setups, they should perform identical.  Obviously, there are two OSes involved, and how php gets invoked, the actual php.exe for the two platforms, etc., could all make subtle differences.  You are sure that all PHP-based code is identical on the two boxes?  We're just looking at OS/version/config possible issues?

-d
Let's backtrack and make sure I'm using __wakeup properly. What specifically should be in the __wakeup function and in which class since they all use ADOdb? Remember, I'm not using the __wakeup on my dev machine and I don't think my dev code is proper, though it works.

cd
I think I figured out what goes in the __wakeup function. A single $section->db survives across pages on both machines. It's when I add another object that also uses ADOdb that both objects fail to fully instantiate on either machine.
Have you checked the ADODB class to see if it does anything on wakeup itself? I don't think it really matters whether you use persistent connections or not - if you request a new connection on wakeup you don't really care if it re-uses an existing one or makes a new one. I don't see why having connections shared by different object should be an issue - unless the wakeup of one somehow messes up the other?

One little test you could try to spot differences between your servers: capture a php -i on a command line on each, then do a diff between them. Better than trying to spot differences yourself...
ADOdb has no wakeup. I've stopped trying to figure out why it works on dev but not production because the original dev code was a hack (see previous question - link at top) due to my lack of experience with objects. What difference a day makes. Now it's working with simple test code and three classes all instantiating ADOdb objects:

To reiterate:

page 1
      require_once("section.class.php");
      require_once("mygacl.inc.php");
      require_once("mygacl_api.inc.php");
      session_start();
      $section = new section();
      $gacl = new mygacl();
      $gacl_api = new mygacl_api();
      echo "<pre>".print_r($section,true)."</pre><hr>";
      echo "<pre>".print_r($gacl,true)."</pre><hr>";
      echo "<pre>".print_r($gacl_api,true)."</pre><hr>";
      $_SESSION['section'] = serialize($section);
      $_SESSION['gacl'] = serialize($gacl);
      $_SESSION['gacl_api'] = serialize($gacl_api);


page 2

      require_once("section.class.php");
      require_once("mygacl.inc.php");
      require_once("mygacl_api.inc.php");
      session_start();
      $section = unserialize($_SESSION['section']);
      $gacl = unserialize($_SESSION['gacl']);
      $gacl_api = unserialize($_SESSION['gacl_api']);
      echo "<pre>".print_r($section,true)."</pre><hr>";
      echo "<pre>".print_r($gacl,true)."</pre><hr>";
      echo "<pre>".print_r($gacl_api,true)."</pre><hr>";

      $r = $section->str_rand();
      echo "str_rand = $r<br>";

      $r = print_r($section->getUserList(),true);
      echo "getUserList = <pre>$r</pre><br>";

      $merchantAccess = $gacl->getMerchantAccess('enterprises', 'chris.dillon@checkcare.com');
      echo "merchantAccess = $merchantAccess<br>";

      $r = print_r($gacl_api->get_group_children(14,'ARO'),true);
      echo "get_group_children = <pre>$r</pre><br>";


all works fine. But the actual application is losing an object definition somewhere so I need to look for places where the object may be accessed by I haven't unserialized it. Or something like that.

Bottom line, with your all's help I ran up the learning curve and got it working. I will split the points for your effort and insights.

Anybody recommend a good book on PHP Development for the intermediate?

Thanks,
cd
Glad it turned out to be something simple! Thanks for the points. I can recommend 'PHP 5 power programming'  (I've taken up using single quotes when I don't want to embed anything ;^)) by Andi, Stig & Derick (who should know about this stuff!). It's a really good overview of PHP5 with some great advice, and it's definitely not for beginners. I don't have them, but I've seen nothing but good things about Harry Fuecks' PHP anthology: http://www.sitepoint.com/books/phpant1/ 

Nice working with you Dave.