Igor Kromin |   Consultant. Coder. Blogger. Tinkerer. Gamer.

Whenever I come across what looks like a bug in the 3rd party library I'm using, I try to see what it's doing before assuming that there's a bug. Sometimes the process is as simple as looking at the library's code on GitHub, other times it involves decompiling a library and manually editing its files with a hex editor. Recently I've come across what looked like an issue in the php-gds library used to access the Google Cloud DataStore in PHP.

The issue I was seeing was that any DateTime data type that was explicitly set to a null was being returned as the Unix Epoch date - January 1, 1970 (midnight UTC/GMT). Since my code was relying on nulls being returned and instead was getting valid DateTime objects, the behaviour of my code was not as expected.

So I started to make my way through php-gds source code. The file of interest in this case was src/GDS/Mapper/ProtoBuf.php. Specifically the extractDatetimeValue() function which was used to return a DateTime object to the client. The function looked like this...
 GDS/Mapper/ProtoBuf.php
protected function extractDatetimeValue($obj_property)
{
return \DateTime::createFromFormat(
self::DATETIME_FORMAT_UDOTU,
sprintf('%0.6F', bcdiv($obj_property->getTimestampMicrosecondsValue(), self::MICROSECONDS))
);
}


That got my attention, so what if I replaced all of the constants with their string literals and just tried using 0 as the value for the microseconds? The code then became...
 GDS/Mapper/ProtoBuf.php
\DateTime::createFromFormat('U.u', sprintf('%0.6F', bcdiv(0, 1000000)));


Printing out the value of this object using the var_export function then gave this output...
 Output
DateTime::__set_state(array( 'date' => '1970-01-01 00:00:00.000000', 'timezone_type' => 1, 'timezone' => '+00:00', ))


That certainly looked familiar! So for some reason the getTimestampMicrosecondsValue() call was returning 0 to php-gds. This function was from the appengine-php-sdk library. The file of interest here was google/appengine/datastore/entity_v4_pb.php. I don't love the way Google mashed all of the classes into a single file here so it took a bit scrolling to get to the place of interest, eventually though I found the Value class and the getTimestampMicrosecondsValue() function...
 google/appengine/datastore/entity_v4_pb.php
namespace google\appengine\datastore\v4 {
class Value extends \google\net\ProtocolMessage {
...
public function getTimestampMicrosecondsValue() {
if (!isset($this->timestamp_microseconds_value)) {
return "0";
}
return $this->timestamp_microseconds_value;
}
...
}




Well then! That is obvious! Since the statement `!isset($this->timestamp_microseconds_value)` would return true i.e. the value does not exist because it is set to null, it was returning 0.

This can be validated with this test case...
 PHP
$obj_schema = (new GDS\Schema('Test'))->addDatetime('testDate');
$obj_store = new GDS\Store($obj_schema);
$obj_entity = new GDS\Entity();
$obj_entity->testDate = null;
$obj_store->upsert($obj_entity);
foreach ($obj_store->fetchAll() as $entity) {
echo var_export($entity->testDate, true);
}


Since I needed a null, I added a workaround that checked the timestamp value on the DateTime object and if it was set to 0, it reset the entity property to a null. It was not the best way to do this, in fact if the date was actually set to the Epoch start date, this code would incorrectly return a null. Still...this was a workaround and it worked for my case.
 PHP Workaround
if ($entity->testDate->getTimestamp() == 0) {
$entity->testDate = null;
}


Now the code from Google does have hasTimestampMicrosecondsValue() function, php-gds just does not use it. So there was a fix that could be added to php-gds in this case. To my surprise however, none of the functions that convert DataStore values to what php-gds sets on its Entity objects have any guard statements that check if the property exists or not.

So after all that I can conclude that this was indeed a bug in the php-gds code! I'll raise an issue ticket (or maybe even a pull request with updates) with Tom Walder in the next couple of days and hopefully he can rectify it in the 4.0 release.

-i

Did you like this post or found it useful? Considering supporting this Blog to keep its web servers running, any amount helps! Thanks!
Have comments or feedback on what I wrote? Please share them below!
comments powered by Disqus
Other posts you may like...