Search code examples
phpzend-frameworkserializationzend-cache

Strange Behavior Serializing Objects and Caching Them


I'm running into an issue that I can't seem to be able to narrow down. In a Zend Framework application, I am using Zend Cache to cache tide and weather data contained within a custom Response object. The point at which the data is initially created, everything works fine. I serialize it and cache it. Then when I hit refresh and it pulls the data from the cache, I get the following error:

Message: String could not be parsed as XML

Stack trace:

0 /home/cillosis/mysites/tidely/application/views/scripts/tides/location.phtml(38): SimpleXMLElement->__construct('')

1 /home/cillosis/mysites/tidely/library/Zend/View.php(108): include('/home/cillosis/...')

2 /home/cillosis/mysites/tidely/library/Zend/View/Abstract.php(888): Zend_View->_run('/home/cillosis/...')

...

This occurs in my view where I am accessing XML contained in my custom "Response Object" with this:

<div class="data-box">

<h3>Current Weather</h3>
<hr>
<?php

// *** THIS IS LINE 38  ***                         
$weather_XML = new SimpleXMLElement($this->response->_weatherdata->weatherResults);
$params = $weather_XML->data->parameters;

$img_path = $params->{'conditions-icon'}->{'icon-link'};

echo("<img src='".$img_path."'>");

...

Here is a dump of the relevant portion of the object on the first run (prior to cache):

["_weatherdata"]=>
  object(Tidely_WeatherData)#79 (6) {
    ["weatherResults"]=>
    string(6399) "<?xml version="1.0"?>
       <dwml version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="http://graphical.weather.gov/xml/DWMLgen/schema/DWML.xsd">
          ...
       </dwml> "

Where you see the "..." there is a lot of other XML data. The XML comes from NWS (National Weather Service) API and I've run it through an XML validator and it shows no errors. The problems happen as soon as I serialize my object and cache it. Here is how I have my Zend Cache setup:

// Setup caching
$frontendOptions = array('lifeTime' => 30, 'automatic_seralization' => false);
$backendOptions = array('cache_dir' => '../application/cache');
$this->_cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);

I am doing the caching and retrieval with this:

// Assign cache id
// Example query: "Chesapeake Bay Virginia"
$cache_id = 'request_results_' . $this->response->_querydata->query);
$cache_id = str_replace(' ', '_', $cache_id);
$cache_id = str_replace('-', '_', $cache_id);

// Check cache for this query
if ( ($results = $this->_cache->load($cache_id)) === false)
{
   // Cache not found, process request and generate response
   ...

   // Cache request output for specific query
   $serialized_data = serialize($this->response);
   $this->_cache->save($serialized_data, $cache_id);

   // Return processed request results
   return $this->response;
}
else
{
   // Return results from cache
   return unserialize($results);
}

Once I make a request and it caches it, the $this->response object output like this:

object(Tidely_ResponseData)#65 (7) { ["_querydata"]=> object(Tidely_QueryData)#66 (9) { ["query"]=> string(40) "bayou-la-batre-mississippi-sound alabama" ["source"]=> string(15) "TidesController" ["request_type"]=> string(3) "url" ["url_components"]=> array(2) { ["state"]=> string(7) "alabama" ["location"]=> string(32) "bayou-la-batre-mississippi-sound" } ["return_type"]=> string(4) "JSON" ["time_range"]=> array(2) { ["start"]=> object(DateTime)#64 (3) { ["date"]=> string(19) "2012-04-05 18:06:51" ["timezone_type"]=> int(3) ["timezone"]=> string(3) "UTC" } ["end"]=> object(DateTime)#63 (3) { ["date"]=> string(19) "2012-04-06 18:06:51" ["timezone_type"]=> int(3) ["timezone"]=> string(3) "UTC" } } ["hasError"]=> bool(false) ["userMessage"]=> NULL ["logMessage"]=> NULL } ["_locationdata"]=> object(Tidely_LocationData)#80 (31) { ["location_id"]=> string(5) "18921" ["station_name"]=> string(33) "Bayou La Batre, Mississippi Sound" ["station_url_name"]=> string(32) "bayou-la-batre-mississippi-sound" ["station_type"]=> string(11) "Subordinate" ["station_id"]=> string(7) "8739051" ["station_lat"]=> string(8) "+30.3717" ["station_lon"]=> string(8) "-88.2750" ["station_height_offset_high"]=> string(5) "*1.23" ["station_height_offset_low"]=> string(5) "*1.23" ["station_time_offset_high"]=> string(3) "112" ["station_time_offset_low"]=> string(2) "74" ["station_ref_station_id"]=> string(7) "8760551" ["state_id"]=> string(1) "1" ["state_name"]=> string(7) "Alabama" ["state_url_name"]=> string(7) "alabama" ["timezone"]=> string(2) "-6" ["dst"]=> string(1) "1" ["city_name"]=> string(14) "Bayou La Batre" ["city_zip"]=> string(5) "36509" ["city_lat"]=> string(7) "30.4014" ["city_lon"]=> string(8) "-88.2467" ["adjacentStations"]=> string(321) "a:10:{i:8739051;d:2.6562027890531361;s:7:"TEC4393";d:8.0529290086617618;i:8740448;d:11.564338218805837;i:8735180;d:14.637787569358004;i:8741196;d:17.602722038773138;i:8734635;d:17.824002491920826;i:8733810;d:19.474837234444919;i:8737048;d:24.414028460259932;i:8742221;d:27.466706516499691;i:8731952;d:31.251405048051701;}" ["city_distance"]=> string(15) "2.6562027890531" ["multipleResults"]=> string(0) "" ["multipleCount"]=> int(0) ["hasMultiple"]=> bool(false) ["showStates"]=> bool(false) ["showLocations"]=> bool(false) ["hasError"]=> bool(false) ["userMessage"]=> NULL ["logMessage"]=> NULL } ["_tidedata"]=> object(Tidely_TideData)#68 (6) { ["tideResults"]=> NULL ["queryStart"]=> NULL ["queryEnd"]=> NULL ["hasError"]=> bool(false) ["userMessage"]=> NULL ["logMessage"]=> NULL } ["_weatherdata"]=> object(Tidely_WeatherData)#79 (6) { ["weatherResults"]=> string(6399) " meteorological forecast 2012-04-05T18:06:57Z http://graphical.weather.gov/xml/ Meteorological Development LaboratoryProduct Generation Branch http://www.nws.noaa.gov/disclaimer.html http://www.weather.gov/ http://www.weather.gov/images/xml_logo.gif http://www.weather.gov/feedback.php point1 http://forecast.weather.gov/MapClick.php?textField1=30.37&textField2=-88.28 k-p24h-n1-1 2012-04-05T08:00:00-05:00 2012-04-05T20:00:00-05:00 k-p24h-n1-2 2012-04-05T20:00:00-05:00 2012-04-06T09:00:00-05:00 k-p3h-n5-3 2012-04-05T13:00:00-05:00 2012-04-05T16:00:00-05:00 2012-04-05T19:00:00-05:00 2012-04-05T22:00:00-05:00 2012-04-06T01:00:00-05:00 Daily Maximum Temperature 81 Daily Minimum Temperature 63 Temperature 79 79 74 69 67 Dew Point Temperature 66 65 64 64 61 Wind Speed 12 11 11 13 14 Wind Direction 240 260 280 300 330 Relative Humidity 64 63 71 84 81 Weather Type, Coverage, and Intensity Conditions Icons http://forecast.weather.gov/images/wtf/tsra60.jpg http://forecast.weather.gov/images/wtf/tsra60.jpg http://forecast.weather.gov/images/wtf/scttsra60.jpg http://forecast.weather.gov/images/wtf/nscttsra20.jpg http://forecast.weather.gov/images/wtf/nsct.jpg " ["queryStart"]=> float(1333649211.6989) ["queryEnd"]=> float(1333649217.2038) ["hasError"]=> bool(false) ["userMessage"]=> NULL ["logMessage"]=> NULL } ["hasError"]=> bool(false) ["userMessage"]=> NULL ["logMessage"]=> NULL }

The "weatherResults" section has the XML there when I View Source, I'm not copying and pasting it because it's rather long. Here is what happens when I press refresh and it uses the cached copy of the above object:

object(Tidely_ResponseData)#65 (7) { ["_querydata"]=> object(Tidely_QueryData)#66 (9) { ["query"]=> string(40) "bayou-la-batre-mississippi-sound alabama" ["source"]=> string(15) "TidesController" ["request_type"]=> string(3) "url" ["url_components"]=> array(2) { ["state"]=> string(7) "alabama" ["location"]=> string(32) "bayou-la-batre-mississippi-sound" } ["return_type"]=> string(4) "JSON" ["time_range"]=> array(2) { ["start"]=> object(DateTime)#64 (3) { ["date"]=> string(19) "2012-04-05 18:10:30" ["timezone_type"]=> int(3) ["timezone"]=> string(3) "UTC" } ["end"]=> object(DateTime)#63 (3) { ["date"]=> string(19) "2012-04-06 18:10:30" ["timezone_type"]=> int(3) ["timezone"]=> string(3) "UTC" } } ["hasError"]=> bool(false) ["userMessage"]=> NULL ["logMessage"]=> NULL } ["_locationdata"]=> object(Tidely_LocationData)#67 (31) { ["location_id"]=> string(0) "" ["station_name"]=> string(0) "" ["station_url_name"]=> string(0) "" ["station_type"]=> string(0) "" ["station_id"]=> string(0) "" ["station_lat"]=> string(0) "" ["station_lon"]=> string(0) "" ["station_height_offset_high"]=> string(0) "" ["station_height_offset_low"]=> string(0) "" ["station_time_offset_high"]=> string(0) "" ["station_time_offset_low"]=> string(0) "" ["station_ref_station_id"]=> string(0) "" ["state_id"]=> string(0) "" ["state_name"]=> string(0) "" ["state_url_name"]=> string(0) "" ["timezone"]=> string(0) "" ["dst"]=> string(0) "" ["city_name"]=> string(0) "" ["city_zip"]=> string(0) "" ["city_lat"]=> string(0) "" ["city_lon"]=> string(0) "" ["adjacentStations"]=> string(0) "" ["city_distance"]=> string(0) "" ["multipleResults"]=> string(0) "" ["multipleCount"]=> int(0) ["hasMultiple"]=> bool(false) ["showStates"]=> bool(false) ["showLocations"]=> bool(false) ["hasError"]=> bool(false) ["userMessage"]=> NULL ["logMessage"]=> NULL } ["_tidedata"]=> object(Tidely_TideData)#68 (6) { ["tideResults"]=> NULL ["queryStart"]=> NULL ["queryEnd"]=> NULL ["hasError"]=> bool(false) ["userMessage"]=> NULL ["logMessage"]=> NULL } ["_weatherdata"]=> object(Tidely_WeatherData)#69 (6) { ["weatherResults"]=> NULL ["queryStart"]=> NULL ["queryEnd"]=> NULL ["hasError"]=> bool(false) ["userMessage"]=> NULL ["logMessage"]=> NULL } ["hasError"]=> bool(false) ["userMessage"]=> NULL ["logMessage"]=> NULL }

In the process of serializing/unserializing and saving the cache, it somehow sets all the strings to empty or NULL! So of course, I am getting the XML error, because it's empty.

Has anybody else run into problems caching objects with Zend Cache (or any other caching library for that matter)? Does this have to do with the serialization? Is there a cache setting I am missing? Sorry for the long explanation and thank you in advance for your help!


Solution

  • PHP resources cannot be serialized, this is why you can't serialize your object (which will indirectly serialized a SimpleXML object).

    Before serialization, you need to convert your object to a well-formated XML string (SimpleXMLElement::asXML()) and once your object is unserialized, reconvert your string into a SimpleXML object (simplexml_load_string()).

    See here for more information. This question can also be useful.


    Edit after new details: with these new elements in your question, I understand that your problem doesn't come from a serialized SimpleXML object. Do you have any reason to not use automatic_serialization? (with a "i" by the way, I guess you misspelled it) instead of letting Zend_Cache automatically serialized/unserialized your object.


    Edit #2: read comments bellow from cillosis for the final solution.