APC extension and objects

I am getting an array of objects when I use the Twitter API to get the recent tweets for a particular account.


$arrContext = stream_context_create(array(
			'http' => array(
				'timeout' => 3)
		));

$strTweetsBody = (string) file_get_contents("http://api.twitter.com/1/statuses/user_timeline/{$strUser}.json", FALSE, $arrContext);

$arrTweets = json_decode($strTweetsBody);

I am trying to use the APC extension to cache the data so the site does not have to call the API each time the script fires.


apc_add('Tweets', $arrTweets, 600);

When I try to retrieve it, I get an array of empty key/value pairs.

$arrTweets = apc_fetch('Tweets');

Array ( [0] => [1] => [2] => [3] => [4] => [5] => [6] => [7] => [8] => [9] => [10] => [11] => [12] => [13] => [14] => [15] => [16] => [17] => [18] => )

I have checked that APC is loaded and working. I even tested it successfully by caching a simple string.

Does APC not like objects/arrays of objects?

Hmm, should work fine.

Try using apc_store instead of apc_add ? (add() won’t change what is cached if the key already exists).

Thanks for the suggestion, Ren. Unfortunately, apc_store() returned the same result.

I’ve echoed the data being stored at every stage in the process to see if a mistake in my code is somehow erasing the elements, but the data is there before the apc_add method.

Aah, ok - because apc_add does not store multidimensional arrays you have to serialize the data first (then unserialize after).

Source: PHP: apc_store - Manual

[del]Are you using PHP from the command line?

If so it could be http://php.net/manual/en/apc.configuration.php#ini.apc.enable-cli ?[/del]

Ah… I see, thought apc serialized for you…

Atleast this code works for me.


function o($i) { $r = new \\stdClass(); $r->i = $i; return $r; }
	
	$a1 = array(o(1), o(2), o(3));
	
	apc_add('a1', $a1);
	
	$a2 = apc_fetch('a1');
	
	var_dump($a1);
	var_dump($a2);
	
	assert($a1 == $a2);

using APC 3.1.8-dev

Version 3.1.7 seems to have got serialize hooks, http://pecl.php.net/package/APC/3.1.7 so that must be the difference.

I’m quite sute it did store multidimensional arrays a lot earlier. Not sure about arrays of objects - never tried that one before.

serialize hooks that come in 3.1.7 allow you to change the default serializer (to e.g. igbinary), so it’s not that they added serializing support. It just became more customizable. But who knows, maybe they fixed some issues at the same time.

I don’t think it needed to serialize arrays, it could just copy the hash table as is. So guess the problem must arise with objects.

Thanks for the insight :smiley:

I think the objects will need __sleep() and __wakeup() methods (not 100% sure), and also the class should be loaded when the objects are retrieved from APC.

Possibly it’s easier to create an array of the data you need and save that to APC.

Multidimensional arrays should be saved OK, but objects require special attention when using APC.

Why not just store the string returned from Twitter API? I mean, before you do json_decode() - just store the original string. It will work, then when you need to reuse it -just get it from cache and then json_decode() it. Simple enough? Makes sense?

Nope, could you use hand puppets or interpretive dance instead? :wink: j/k

If the solution is simply to serialize then unserialize the data, why not just keep it that way so when you retrieve the data it’s good to go?

I don’t know what you really need to do, but if you having problem storing object in cache then just store the original string and then use json_decode().
Also a solution may be as simple as passing second ‘true’ argument to json_decode
json_decode($string, true);
Then you get associative array instead of object. Then try to store in in APC, it may automatically serialize on save/unserialize on fetch

Just try it, I have a feeling it will work.

Or, building on what lampcms suggest, extract all the necessary data from the object into an array and save that instead.

Sort of like this:

Try



class TweetStore extends ArrayObject {
  public function fetchURL( $url ) {
    $this->exchangeArray( json_decode(
      (string)file_get_contents( $url, false, stream_context_create(
        array(
          'http' => array( 
            'timeout' => 3
          )
        )
      ))
    ));
  }
}

This creates an Array object to hold your tweet stuff that can be stored by APC cache. An array object is like an array - that is you can reach it’s contents through array operator . You can foreach it, anything you want to do array like an array object can do. But as you can see above since it is an object you can define behaviors for it. Specifically we have the behavior for fetching the Tweets defined as part of the object.

The object gets used like this.


  $myTweets = new TweetStore();
  $myTweets->fetch("http://api.twitter.com/1/statuses/user_timeline/{$strUser}.json");

And that’s all - its ready to go. If that URL isn’t going to change you could place it into the object as well.

ArrayObject also implements the serializable interface, which is preferred to the magic __sleep and __wakeup methods. The base definition for ArrayObject is to serialize the contents of the ArrayObject, but you can extend the methods to refine this - just be careful because they are as of yet undocumented in the php site.

For more information go to PHP: ArrayObject - Manual

Thanks for the help :smiley:

Actually, one question: Why do you use the exchangeArray method? What is the old array we are exchanging?

The storage of ArrayObject is private - we cannot directly access it from our child class. It will be empty immediately after creating the class instance. The assumption is that if we are being asked to fetch and parse a tweet URL we’ll be throwing away whatever we had before. If holding the old value is important the external calling code can use getArrayCopy to get the data. This function doesn’t need the old array though so it drops it.

Or in code…



$tweets = new TweetStore();

// At this point the TweetStore is empty.  We can see this by print_r
print_r($tweets);

// And we can call exchangeArray since it's public to see that we do indeed get an empty array.
$oldstorage = $tweets->exchangeArray( array() );
print_r($oldstorage);

// It isn't much use to us until we populate so,
$tweets->fetchURL($url);

// And now if we use exchange array we'll get the array the ArrayObject wraps around.
$t = $tweets->exchangeArray( array() );

// And now tweets is once again empty.

Bottom line is that you NEED to pass second arg to json_decode($string, true);
otherwise it will return object of type stdClass and that class is not serializable. You really need to get array back from json_decode - for that you must pass true as 2nd arg.

It very simple, really. There is no need to complicate things with another abstraction class unless you really going to use it for something other than just an abstraction class for an actual array.

Yeah, I tested it with the second argument and it worked like you said - taught me to always double-check the PHP manual.

As for ‘needing’ to do it that way, I am a bit confused since simply using serialize and unserialize when adding/fetching works, however, I agree passing the second boolean argument does save me a couple steps when working with the cached data.