15

We're randomly getting some very strange error logs. They don't happen on every page hit, even with the same parameters/actions/etc, and they don't seem repeatable, each one is different in its crash location, and context. But almost all have incorrect __PHP_Incomplete_Class_Name as the cause.

One such error is:

main(): The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "LoginLogging" 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

The problem being, there is no "LoginLogging" class. The object it's referring to was of type ScormElement when it was saved into the session. Doing a dump of the variable gives:

__PHP_Incomplete_Class::__set_state(array(
 '__PHP_Incomplete_Class_Name' => 'LoginLogging',
 'intUserId' => '64576',
 '__intUserId' => '64576',
 'intScormId' => '665',
 '__intScormId' => '665',
 'intScoId' => '9255',
 '__intScoId' => '9255',
 'strElement' => 'cmi.core.lesson_location',
 '__strElement' => 'cmi.core.lesson_location',
 'strValue' => '1',
 'dttTimeModified' => QDateTime::__set_state(array(
   'blnDateNull' => false,
   'blnTimeNull' => false,
   'strSerializedData' => '2011-08-31T08:05:22-0600',
   'date' => '2011-08-31 08:05:22',
   'timezone_type' => 1,
   'timezone' => '-06:00',
 )),
 '__strVirtualAttributeArray' => array (),
 '__blnRestored' => true,
 'objUser' => NULL,
 'objScorm' => NULL,
)

All the properties are retained correctly, and match the class definition for ScormElement. But the class name is wrong. There is no class named LoginLogging.

What is causing this and how do we fix it???

Edit: This is just an example. Other errors are very similar in structure, but affect other class types, and have different incomplete names. However, ALL incomplete names have the same string length of the correct class name.

Edit 2011-10-27: I'm still seeing these error logs, and have had no success in finding a solution. Any help would be appreciated.

PHP 5.3.3, APC, default session handler.

VexedPanda
  • 393
  • 1
  • 2
  • 15
  • can you show what the `main()` function looks like? or the class that is called looks like? – Naftali Sep 01 '11 at 15:46
  • You seem to be refering to 'LoginLogging' in your code (second line) - what is this supposed to be doing? I'm guessing this is the cause of the error... – DaveRandom Sep 01 '11 at 15:48
  • How are you getting that object? unserialize? session? – netcoder Sep 01 '11 at 15:50
  • The second line is _not_ my code. That's the dump of the variable causing the problem.My code does not include "LoginLogging" as a self-contained reference. There is an FM_LoginLogging class and a FmLoginlogging class, but nowhere do we have a "LoginLogging" class / variable name / function / etc. – VexedPanda Sep 01 '11 at 16:10
  • There is no function called main(). :P The class definition for ScormElement is quite large, but you get a pretty good idea of it based on the serialized version shown. – VexedPanda Sep 01 '11 at 16:11
  • In all the cases of these errors, the object is retrieved from the session. – VexedPanda Sep 01 '11 at 16:11
  • I should note that this is one example, we are getting Attempt objects with incomplete class names of "GetCont", QHistoryItem objects with incomplete class names of "EscapeIdenti", StructureObject objects with incomplete class names of "GetInAdminLevel", etc. It appears to be pulling incomplete class names from random strings of code stored in other files. One thing I just noticed is that the string LENGTH is always correct, just the content is wrong. – VexedPanda Sep 01 '11 at 16:18
  • @VexedPanda: What session handler are you using? – netcoder Sep 01 '11 at 20:53
  • We initialize session with session_start(), there is no custom session handler. PHP's session.save_handler setting is "files". – VexedPanda Sep 01 '11 at 22:22
  • @VexedPanda: *session.save_path*? PHP version? – netcoder Sep 02 '11 at 01:48
  • @VexedPanda What is value of session.auto_start in php.ini – Ramil Amerzyanov Feb 22 '12 at 06:17
  • auto_start is off, PHP is 5.3.3 – VexedPanda Feb 22 '12 at 16:00
  • As stated elsewhere, We utilize QCodo/QCubed, but I'm intimately familiar with how it works and don't believe it's affecting anything. – VexedPanda Feb 29 '12 at 16:56

10 Answers10

25

As written in the quote in your question __PHP_Incomplete_Class_Name is a special class name in PHP that is used whenever the class definition could not be found when unserializing (unserialize()).

It's suggested to either ensure the class definition is available or to provide some autoloading for the missing class.

You commented that PHP is looking for the wrong classname here:

wrong: LoginLogging
right: ScormElement

It's hard to say with the information given why the classname is being changed on serialization/unserialization. The information you've given (especially the dump) in your question is incomplete.

So the options are limited to some general suggestions:

You could inspect the serialized string which classname is given in there so you could at least say if it happens with serialization or unserialization. You can easily inspect serialized data with the Serialized PHP Library which has a text-dumper for debug purposes.

Additionally there is the unserialize_callback_func Ini-Directive you can use to further trace the problem.

<?php
$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';

// unserialize_callback_func directive available as of PHP 4.2.0
ini_set('unserialize_callback_func', 'mycallback'); // set your callback_function

function mycallback($classname) 
{
    // just include a file containing your classdefinition
    // you get $classname to figure out which classdefinition is required
}

As you write the problem does not occur any longer, I suspect that at some point the serialization on your site was broken storing invalid data. Which framework/libraries/application are you using? Which component takes care about serialization?

hakre
  • 193,403
  • 52
  • 435
  • 836
  • The problem is intermittent, and does continue to happen occasionally. I suspect it is more likely during high load, but can't confirm due to the infrequent nature of the problem. It is also in code that runs exactly the same without problem thousands of times, making any debug code impossible to implement. – VexedPanda Feb 27 '12 at 16:19
  • 1
    What you could do to debug this as you said it's session related: register the callback, when it happens (and the class is not available via autoload) obtain the session ID, make a copy of the binary session data (e.g. copy the file to a temp dir of it's own), and dump the stacktrace as well as globals next to it in a second file suffixed ".dump" or so. You're then able to review the serialized form of the session as well as backtrace (just in case something was called already up to that point) and global variables. – hakre Feb 27 '12 at 16:39
  • While I can do that (and have in the past when the error was fresh enough that the session was still on file), it appeared that the content of the session file was incorrect (it had the incorrect class name). That said, that still doesn't leave me with anywhere to go, and with a sample size of one I'm not confident that's always the case. Assuming it's written to disk incorrectly, what are my next steps? – VexedPanda Feb 27 '12 at 19:56
  • I doubt that a file get's written that wrong to disk that only these specific classnames become wrong. Probably the class lookup-table was out of sync. So it's not to be debugged on loading but while storing I'd say. At which point are is the session saved in your application? Do some of the objects therein have their own [`Serializable`](http://php.net/manual/en/class.serializable.php) interface? – hakre Feb 27 '12 at 20:30
  • No, these are standard PHP classes and we're using the default PHP session handler, which does the serialization on its own. – VexedPanda Feb 28 '12 at 16:02
  • Which version of PHP are you using? – hakre Feb 28 '12 at 16:05
  • Also, it's worth reiterating that the strings and places in code where it breaks appears to be different the vast majority of the time, with the only patterns appearing to be that code run more frequently gets more errors. – VexedPanda Feb 28 '12 at 20:50
  • Do you know when in the application flow the sessions are serialized and stored? Or haven't you looked so far? E.g. is there some explicit `session_write_close`? – hakre Feb 29 '12 at 02:18
  • There is no explicit session handling in the code. Apart from the one call to session_start(), it's all up to default PHP. – VexedPanda Feb 29 '12 at 16:02
  • Is there probably a callback registered with [`session_register_shutdown`](http://php.net/session_register_shutdown)? You could use that function as well for debugging. See as well [`register_shutdown_function`](http://php.net/register_shutdown_function). – hakre Feb 29 '12 at 16:31
  • There are no callbacks registered. What would you suggest I do with it to help me track down this problem? – VexedPanda Feb 29 '12 at 16:56
  • I have no suggestion right now, only a uncertainty: How does PHP serializes an object if the class definition isn't available any longer? Will it put `__PHP_Incomplete_Class_Name` into the serialized form (one could test by faking this). And an idea: If the application you use has a central point of shutdown, explicitly use `session_write_close`. Also it might be worth to search PHP src about the serialization on shutdown and the bugtracker because you use a quite old PHP version. – hakre Feb 29 '12 at 17:06
  • I'm reasonably sure it serializes the object when it's assigned to the session array, not on shutdown. Which means it _has_ to know about the class definition already. Besides which, your reasoning may make sense for a consistent crash, but the exact same code with the same parameters is crashing inconsistently, working the vast majority of the time. – VexedPanda Feb 29 '12 at 20:29
  • Well I was not reasoning, the last comment were merely guesses. However session serialization does not take place when you assign it to the [`$_SESSION`](http://php.net/_SESSION) array but on `exit`/shutdown or explicitly with `session_write_close`. – hakre Mar 01 '12 at 01:06
  • 1
    Investigating session_write_close, it appears that this may be related after all. http://news.php.net/php.internals/46999 We will begin testing adding explicit calls to session_write_close() in order to ensure session data is written correctly prior to leaving the application code path. – VexedPanda Mar 02 '12 at 20:12
  • If you're using file-based-sessions, you'll gain a slight benefit as well if you write-close (flush/commit) the session earliest possible because it relases the file-locking and other (parallel) processes can obtain the session then. – hakre Mar 02 '12 at 21:02
  • 1
    From the sounds of it, this may be a PHP/APC bug where class definitions are unloaded prior to the session being written to disk, driven by a race condition that could explain why we see it intermittently, and under load. I like this for the cause, and we have added register_shutdown_function('session_write_close'); to our bootstrap to ensure sessions are written before any memory cleanup. I'll update the question with the results in a couple months. :) – VexedPanda Mar 02 '12 at 21:14
13

If your trying to access a property method of an object or serialized value you've stored in a $_SESSION variable and you included the class after calling session_start() try including the class before calling session_start();

gus
  • 765
  • 3
  • 14
  • 26
  • Again. This is NOT a standard session order problem. This code runs correctly for thousands of attempts, and breaks maybe 0.01% of the time for unknown reasons. – VexedPanda Feb 27 '12 at 16:20
  • Mmmm, maybe you might be better off logging a bug report with PHP and see if you cant get a solution that way. – gus Feb 27 '12 at 16:30
  • Without reproducible code, I can't log a valid bug report. :( – VexedPanda Feb 27 '12 at 19:59
  • Why not set up some tests then on your dev server, its not that hard to set up "simulated" testing -iterate it and log it to file nothing is impossible to solve. You just have to do some more profiling until you nail it, but without seeing a single line of your code its kinda just speculation, best of luck! – gus Feb 28 '12 at 08:03
  • Our dev server does not exhibit these errors. (Or they're uncommon enough to have never triggered.) – VexedPanda Feb 28 '12 at 16:03
  • Not sure if any of these might be helpful: http://www.php.net/manual/en/function.unserialize.php#93451 https://bugs.php.net/bug.php?id=29985 – gus Feb 29 '12 at 00:29
  • That bug report lists it as fixed in a version far older than 5.3.3 – VexedPanda Feb 29 '12 at 16:01
  • Again, my code has the correct class definitions, and the autoloader is aware of them. The problem is that the __PHP_Incomplete_Class is being assigned the WRONG THING. – VexedPanda Feb 29 '12 at 20:26
  • Is the class name being assigned actually a property name? Just a thought, you have APC why not use that instead of the session. See if the issue goes away. – gus Mar 01 '12 at 01:58
  • The only way it seems that __PHP_Incomplete_Class is being assigned the WRONG THING as you say, is if the values are being re-assigned in the process or the input was wrong in the first place or its a PHP bug. I assume you have register_globals off. – gus Mar 01 '12 at 02:49
5

This happens when we try to initialize the session before loading the class definitions for the object we are trying to save into the session.

you can use simply json methods in PHP

json_encode that array and again json_decode that array and save it in a variable and than print that variable. you will get a simple array.

eg.

$array = array(); // this is your array variable to whom you are trying to save in a session
$encode = json_encode($array);
$decode = json_decode($encode);
echo '<pre>';
print_r($decode);

it will work surely.

Shiv Aggarwal
  • 499
  • 6
  • 14
3

To include the class before the session_start() works fine for me. =)

Wellington Lorindo
  • 2,405
  • 19
  • 21
3

Are you, by any chance, using some opcode cache (e.g. APC, XCache) or some debugger? They sometimes cause weird things to happen. If there isn't and never was a LoginLogging class in your project, but yours is not the only project on the server, I would bid on the cache.

a sad dude
  • 2,775
  • 17
  • 20
  • Agreed, I have seen opcode cache corruption like this, particularly under Windows. – MightyE Feb 28 '12 at 13:34
  • Hmm, we do use APC. Are there any solutions? It provides a significant enough performance improvement (and is built into later versions of PHP), so disabling it is not ideal. – VexedPanda Feb 28 '12 at 16:01
2

Hakre's suggestion to look at session_write_close led me to what appears to be a reliable fix:

register_shutdown_function('session_write_close');

This forces the session to be written out before any memory cleanup and class unloading occurs. This is important due to a change in PHP that can result in a race condition between APC removing class references and PHP writing out the session data: http://news.php.net/php.internals/46999

VexedPanda
  • 393
  • 1
  • 2
  • 15
2

When you try to unserialize a variable, php must know the structure of the object. Try to include your class before unserialize. Which class? the one that holds those properties shown in your code. include('pathtoyourclass/classname.php') or include_once('pathtoyourclass/classname.php').

Good luck!

FerYepes
  • 71
  • 1
1

I use this function to solve this problem

$user = $_SESSION['login'];
$auth_user= fixObject($user);
function fixObject(&$object) {
   if (!is_object($object) && gettype($object) == 'object')
      return ($object = unserialize(serialize($object)));
   return $object;
}
Adam
  • 1,546
  • 2
  • 18
  • 23
IT Sekuro
  • 11
  • 1
0

It is the serialization issue in your code.It may be your code for some reason could not initialize the object and in next part of your code tries to convert that object to string or some other data type.

Just inspect your code, properly initialize your objects.

Check if you are using serialize()/unserialize() method.

Dev
  • 458
  • 1
  • 5
  • 13
  • Again, the problem is that the Object Class name that PHP is looking for is INCORRECT. There is no way in application code to SET that object class name, so it is not a simple serialize/deserialize issue. Even if we include the correct class definition, then re-serialize and unserialize the object, it STILL looks for the WRONG class name. – VexedPanda Sep 20 '11 at 18:01
0

Are you receiving this serialized object from a service/stream or reading it from a file? I've seen it happen in a client-server setting the client side object and the server side objects differ very slightly... maybe have a property that the other doesn't have. This also happens when you serialize an object, save it to file, then refactor code that removes a class and then try's to deserialize the object.

Then when it gets deserialized and tries to instantiate the needed classes they don't exist.

The error is clear and straightforward, you're creating a object for which the class has not been loaded.

I'd do a string search on your code, find where the needed class is located, and make a simple autoloader (or just a raw include/require) of the file. Just get the class it needs loaded somehow.

If the class really doesn't exist anywhere, try checking earlier revisions (I'm assuming you have version control of some type) for existence of the class. If you're deserializing an object generated by another system check that system out as well.

Ray
  • 40,256
  • 21
  • 101
  • 138
  • I'm using the standard file-based sessions provided by the default PHP session handler, so this information is purely server-side. And no, you are incorrect. The class definition IS loaded. It is trying to load the WRONG definition. As proof, sometimes it picks a random string that actually DOES match with a defined class. In these cases, it creates an instance of that class from the session content, and then (our code) proceeds operate on it with functions that don't exist in that incorrect class, which is where we get some error logs as well. – VexedPanda Feb 27 '12 at 16:23
  • @VexedPanda try logging the object to a file (use var_export on the object and save it) right before you serialize it with a timestamp. The next time you see the error, check the log to see the state of the object prior to serializing to confirm the object is as expected going into the session. – Ray Feb 27 '12 at 22:07
  • @VexedPanda also, you mention you have a FM_LoginLogging class... I use zend framework 1.x. Before php 5.3, Zend had a bunch of work arounds for namespacing and autoloading. I know it allowed you to namespace and autoload without class prefixes... so for example a class FM_LoginLogging could be set to autoload sans the "FM_" prefix. In this case you'djust use 'LoginLogging' and it would map the class. Something like this could be a problem if PHP saves the object with it's true class name, but an internal object reference inside the framework uses the shortend class name (or vice versa). – Ray Feb 27 '12 at 22:13
  • There is no one object or one piece of code that triggers this. We see it pretty randomly throughout the code base, making putting any debug code in anywhere close to a real crash pretty much impossible. 'LogginLogging' is an example of an invalid string, not something we want to make work. The class was of type "ScormElement", and there is no kind of mapping between the two. The actual incorrect string differs in each case. – VexedPanda Feb 28 '12 at 16:06
  • @VexedPanda Are you running this inside a framework? Zend, Cake, Symfony? – Ray Feb 28 '12 at 16:59
  • We utilize QCodo/QCubed, but I'm intimately familiar with how it works and don't believe it's affecting anything. – VexedPanda Feb 28 '12 at 20:22