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

Hope you found this post useful...

...so please read on! I love writing articles that provide beneficial information, tips and examples to my readers. All information on my blog is provided free of charge and I encourage you to share it as you wish. There is a small favour I ask in return however - engage in comments below, provide feedback, and if you see mistakes let me know.

If you want to show additional support and help me pay for web hosting and domain name registration, donations, no matter how small, are always welcome!

Use of any information contained in this blog post/article is subject to this disclaimer.
comments powered by Disqus
Other posts you may like...