Archive for the ‘php’ Category

Share Validating Credit Card Numbers

Wednesday, August 18th, 2010

The easiest way to pre-verify a credit card value is entered correctly is to use the Luhn algorithm to check for mistakes. This is commonly referred to as a mod-10, mod10 or mod 10 check. It’s always important when handling user data to ensure it validates not only on the client, saving a trip to the server, but also server side should the client not support JavaScript. For that reason, we test on both ends.

There are more “elegant” implementations of this algorithm – see the Wikipedia entry referenced above – but they will likely be more difficult for a novice developer to understand. Further, the functions provided here are tolerant of common data entry conventions, such as using spaces and dashes to delimit blocks of numbers as is common with credit card numbers.

View Code JAVASCRIPT
function mod10_check(val){
	var nondigits = new RegExp(/[^0-9]+/g);
	var number = val.replace(nondigits,'');
	var pos, digit, i, sub_total, sum = 0;
	var strlen = number.length;
	if(strlen < 13){ return false; }
	for(i=0;i<strlen;i++){
		pos = strlen - i;
		digit = parseInt(number.substring(pos - 1, pos));
		if(i % 2 == 1){
			sub_total = digit * 2;
			if(sub_total > 9){
				sub_total = 1 + (sub_total - 10);
			}
		} else {
			sub_total = digit;
		}
		sum += sub_total;
	}
	if(sum > 0 && sum % 10 == 0){
		return true;
	}
	return false;
}

You can use the JavaScript version to validate a field on any javascript event. The following snippet illustrates how you might use the onblur event to notify the user the card number appears to be invalid with jQuery.

...
<input type="text" 
	name="cc_number" 
	onblur="if(mod10_check(this.value)){$('#cc_error').hide(); } else { $('#cc_error').show(); }" 
	value="" />
<span id="cc_error" style="display:none;">The card number is invalid.</span>
...

You can then use the following test in PHP to validate the data once it gets to your server.

function mod10_check($val){
	$number = preg_replace('/[^0-9]+/', '', $val);
	$sum = 0;
	$strlen = strlen($number);
	if($strlen < 13){ return false; }
	for($i=0;$i<$strlen;$i++){
		$digit = substr($number, $strlen - $i - 1, 1);
		if($i % 2 == 1){
			$sub_total = $digit * 2;
			if($sub_total > 9){
				$sub_total = 1 + ($sub_total - 10);
			}
		} else {
			$sub_total = $digit;
		}
		$sum += $sub_total;
	}
	if($sum > 0 && $sum % 10 == 0){ 
		return true; 
	}
	return false;
}

You can see the JavaScript implementation in action here. The only difference from the above example is the addition of an onkeyup event and the inclusion of a valid card message.

The card number appears to be invalid.

Some card numbers you can use to test the code.

1234-5678-9012-3456 - Invalid
4111 1111 1111 1111 - Valid (MC/Visa Like)
3111 111111 11117 - Valid (Amex Like)
6011111111111117 - Valid (Discover Like)

Share Escaping Unicode Characters to HTML Entities in PHP

Tuesday, August 17th, 2010

PHP LogoThis is a severe hack I used to get around some character translation issues encountered with non-latin character sets. You should at all costs use a character set that supports international characters whenever possible, probably UTF-8 if your application language supports it. That said, sometimes you need a work around, and this will at least make your pages readable for your users. This is NOT necessary if you are on a single unified platform.

I was working with mixed platforms. One application utilized PHP and UTF-8, while the other leveraged .NET and displayed in HTML with the ISO-8895-1 charset. Behind both was a shared Microsoft SQL Server database. The problem arose when UTF-8 data stored from the PHP application was displayed in the .NET app. Changing the .NET app over to UTF-8 wasn’t an option, so conversion to a character set that would work was required.

PHP has a number of tools to assist developers converting from one character set to another. iconv and the multi-byte string (mb_*) library both have utilities for handling exactly this type of conversion. The mb_* functions were not installed on the server and in this case, the iconv() function did not work as well as expected.

In practice, rather common characters like Microsoft single right “smart quotes” didn’t work with iconv(). Further, accented characters from languages like French and Spanish didn’t convert properly despite having suitable replacements in the ISO-8895-1 character set. This would leave my string littered with ? characters where suitable replacements weren’t found. So the hack I opted for was to convert all multi-byte characters to their HTML escaped equivalents. This places the burden on the browser to decide if it can display a symbol or not. It also turns out that scripting this conversion is much simpler than trying to handle the numerous potential characters and their named HTML equivalents. For any hexadecimal unicode character, \u2019 the right single quotation mark for example, can be re-written as ’ which will then show as a single right quote.

It also turns out that the built-in json_encode() function converts multi-byte characters to unicode notation when serializing, so it’s easy to hand the curly quotes like \u2018 and \u2019. A simple regular expression with preg_replace() the string for the \u#### pattern and replaces it with the correct HTML escape sequence. This was tested with western european languages and it works sufficient for my purpose.

For small strings, such as values entered as part of a typical web based tool, the additional processing time is minimal. It may be faster to strip the json encoding quotes from the beginning and end of the string using substr(). However, for the length of data, the number of times this routine executes and for clarity, this was sufficient.

function unicode_escape_sequences($str){
	$working = json_encode($str);
	$working = preg_replace('/\\\u([0-9a-z]{4})/', '&#x$1;', $working);
	return json_decode($working);
}

Share Working with Big Integers in PHP

Tuesday, August 10th, 2010

This is the start of a class I developed for working with URL shortening and doing math with large numbers such as Facebook User Ids.

There are some issues with the implementation of base convert, but if you are going between base 10 and higher it’s fine.

<?php
/**
* @author Erik Giberti
* @package Big Integer Math
*/
 
class bigint {
 
	public static function base_convert($value_in = false, $source_base = 10, $target_base = 36){
		// We use these values for our conversions
		$values = array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','-','_');
		$characters = array_flip($values);
		$string_out = '';
 
		// Easy answers
		if($source_base	== $target_base){ return $value_in; }
 
		$len = strlen($value_in);
		$sum = array();
		if($source_base > $target_base){
			for($i=0;$i<$len;$i++){
				$char = substr($value_in, $len - $i - 1, 1);
				$sum[$i] = $characters[$char] * pow($source_base, $i);
			}
		} else {
			if($source_base != 10){
				$value = bigint::base_convert($value_in, $source_base, 10);
			} else {
				$value = $value_in;
			}
 
			$cnt = 0;
			while($value > 0){
				$times = $value / $target_base;
				$sum[] = bigint::mod($value, $target_base);
				$value = bigint::floor($times); // get rid of our remainder
			}
 
			for($i=count($sum);$i>0;$i--){
				$string_out .= $values[$sum[$i-1]];
			}
 
			return $string_out;
		}
 
		if($target_base == 10){ return array_sum($sum); }
		return implode($sum);
	}
 
	public static function mod($val, $mod){ return $val - floor($val/$mod) * $mod; }
 
	public static function floor($val){
		$bits = explode('.',$val);
		return $bits[0];
	}
}

Share Simple File Based Caching in PHP

Friday, July 30th, 2010


Depending on the access pattern for the your data, file based caches are often more economical, despite being slower, than an in memory cache like Memcached or APC. Disk space is cheap and is therefore a good option for caching data that’s not frequently accessed, but is expensive to generate. That’s exactly where this class comes in. On a small EC2 instance, I was able to cache API calls, time was reduced from 0.4 seconds to 0.001 seconds per call. The reduced the overall latency of the application and freed up system resources to work on other tasks.

Because API result sets can be very large, consider a friend list from Facebook which may be 10-50K per person, caching them in memory can often push out more valuable data reducing the effectiveness of the cache. The file based cache is of course persistent and can also be distributed across multiple servers using rsync (or if you’re feeling adventurous, you alter this code to store it in an S3 bucket).

One drawback of this approach is the cache is not limited in size, leaving the files on disk until they’re deleted or replaced with new data. For that reason, I recommend you store data on a different drive where possible. File base caches, also have a potential security risk if the data is stored in a web accessible directory. This risk is not unique to this implementation. The potential exists for a clever hacker to discover your cache and gain access to sensitive data or worse compromise your server by storing executable code and executing it. You can reduce this risk by encrypting your data before storing it in the cache and by moving the data to a directory outside of the web root.

To use this cache, save this source to disk, I recommend a filename like FileCache.php and include it in your existing code files where you will be using it. Then, start caching your data! See the usage example in the comments for how to get started. Because the files are not encrypted, you can easily look in them to see what your data looks like.

<?php
 
	/**
	*
	* @package FileCache - A simple file based cache
	* @author Erik Giberti
	* @copyright 2010 Erik Giberti, all rights reserved
	* @license http://opensource.org/licenses/gpl-license.php GNU Public License
	*
	* Class to implement a file based cache. This is useful for caching large objects such as
	* API/Curl responses or HTML results that aren't well suited to storing in small memory caches 
	* or are infrequently accessed but are still expensive to generate.
	*
	* For security reasons, it's *strongly* recommended you set your cache directory to be outside
	* of your web root and on a drive independent of your operating system.
	*
	* Uses JSON to serialize the data object.
	*
	* Sample usage:
	*
	* $cache = new FileCache('/var/www/cache/');
	* $data = $cache->get('sampledata');
	* if(!$data){
	*      $data = array('a'=>1,'b'=>2,'c'=>3);
	*      $cache->set('sampledata', $data, 3600);
	* }
	* print $data['a'];
	*
	*/
 
 
// Requires the native JSON library
if (!function_exists('json_decode') || !function_exists('json_encode')) {
  throw new Exception('Cache needs the JSON PHP extensions.');
}
 
class FileCache {
 
	/**
	* Value is pre-pended to the cache, should be the full path to the directory
	*/
	protected $root = null;
 
	/**
	* For holding any error messages that may have been raised
	*/
	protected $error = null;
 
	/**
	* @param string $root The root of the file cache. 
	*/
	function __construct($root = '/tmp/'){
		$this->root = $root;
	}
 
	/**
	* Saves data to the cache. Anything that evaluates to false, null, '', boolean false, 0 will 
	* not be saved.
	* @param string $key An identifier for the data
	* @param mixed $data The data to save
	* @param int $ttl Seconds to store the data
	* @returns boolean True if the save was successful, false if it failed
	*/
	public function set($key, $data = false, $ttl = 3600){
		if(!$key) {
			$this->error = "Invalid key";
			return false; 
		}
		if(!$data){
			$this->error = "Invalid data";
			return false;
		}
		$key = $this->_make_file_key($key);
		$store = array(
			'data' => $data,
			'ttl'  => time() + $ttl,
		);
		$status = false;
		try {
			$fh = fopen($key, "w+");
			if(flock($fh, LOCK_EX)){
				ftruncate($fh, 0);
				fwrite($fh, json_encode($store));
				flock($fh, LOCK_UN);
				$status = true;
			}
			fclose($fh); 
		} catch (Exception $e) { 
			$this->error = "Exception caught: " . $e->getMessage();
			return false; 
		}
		return $status;
	}
 
	/**
	* Reads the data from the cache
	* @param string $key An identifier for the data
	* @returns mixed Data that was stored
	*/
	public function get($key){
		if(!$key) {
			$this->error = "Invalid key";
			return false; 
		}
 
		$key = $this->_make_file_key($key);
		$file_content = null;
 
		// Get the data from the file
		try {
			$fh = fopen($key, "r");
			if(flock($fh, LOCK_SH)){
				$file_content = fread($fh, filesize($key));
			}
			fclose($fh);
		} catch (Exception $e) { 
			$this->error = "Exception caught: " . $e->getMessage();
			return false; 
		}
 
		// Assuming we got something back...
		if($file_content){
			$store = json_decode($file_content, true);
			if($store['ttl'] < time()){ 
				unlink($key);	// remove the file
				$this->error = "Data expired";
				return false; 
			}
		}
		return $store['data'];
	}
 
	/**
	* Remove a key, regardless of it's expire time
	* @param string $key An identifier for the data
	*/
	public function delete($key){
		if(!$key) {
			$this->error = "Invalid key";
			return false; 
		}
 
		$key = $this->_make_file_key($key);
 
		try {
			unlink($key);	// remove the file
		} catch (Exception $e) { 
			$this->error = "Exception caught: " . $e->getMessage();
			return false; 
		}
 
		return true;
	}
 
	/**
	* Reads and clears the internal error
	* @returns string Text of the error raised by the last process
	*/
	public function get_error(){
		$message = $this->error;
		$this->error = null;
		return $message;
	}
 
	/**
	* Can be used to inspect internal error
	* @returns boolean True if we have an error, false if we don't 
	*/
	public function have_error(){
		return ($this->error !== null) ? true : false;
	}
 
	/**
	* Create a key for the cache
	* @todo Beef up the cleansing of the file.
	* @param string $key The key to create
	* @returns string The full path and filename to access
	*/	
	private function _make_file_key($key){
		$safe_key = str_replace(array('.','/',':','\''), array('_','-','-','-'), trim($key));
		return $this->root . $safe_key;
	}
}

Share Zend Framework’s Podcast Reader Documentation

Friday, June 11th, 2010

Using the Zend Framework can be incredibly frustrating due to the lack of clear documentation. Today I ran into an issue while trying to display an additional field from a RSS feed. The RSS feed is fairly simple, it was originally built to feed iTunes and we’re simply repurposing it. Originally, the client only needed the title and mp3 file. However, a new podcast with multiple contributors made it necessary to include the author field in the display that we’re now generating as well.

The following is a snippet of the feed that’s being generated by the other service.

...
<item> 
<title>i GaGa - Podcast #1</title> 
<author>Steve Jobs</author> 
<itunes:author>Steve Jobs</itunes:author> 
<enclosure url="http://www.example.com/d/pc001.mp3" length="2500000" type="audio\/mpeg" /> 		<guid>http://www.example.com/d/pc001.mp3</guid> 
<pubDate>Fri, 28 May 2010 00:00:00 GMT</pubDate> 
<itunes:keywords>apple, iphone, twitter</itunes:keywords> 
</item> 
...

As you can see it’s incomplete and there are some unnecessary fields included, but overall, the data is there and should be easy to scrape out. The basic code to grab the title and attachment was already scripted using the Zend Feed Reader.

function parse($xml_string){
	$feed = Zend_Feed_Reader::importString($string_data);
	$data = array(
		'title' => $feed->getTitle(),
		'description' => $feed->getDescription(),
		'link' => $feed->getLink(),
		'entries' => array(),
	);
	foreach($feed as $entry){
		$item = array(
				'id' => $entry->getId(),
				'title'=>$entry->getTitle(),
				'enclosure' => $entry->getEnclosure(),
		);
		$data['entries'][] = $item;
	}
	return $data;
}

So just adding the getAuthor() method, should grab the author field, at least that’s what the official documentation says. An alternate method getAuthors() should return an associative array. However, this is clearly overkill in this scenario because we already know that data doesn’t exist. Ultimately, neither of these actually worked with my content. Both methods simply returned NULL.

It turns out it’s VERY simple to get the author value from the feed above. The ability to extend the Zend Framework easily is a real strength, but it’s not intuitive. After skimming a few blog articles about extending the reader classes, I decided to go back and re-read the documentation on extending the reader classes. I thought if I could find a working extension, I could use that as a starting point for writing my own. It turns out that there’s already a Podcast extension which is excatly what I had set out to write. Oh, and it’s also enabled by default!

Great! it’s there, I don’t even have to register it because it’s registered by default… W00t! It just isn’t working, or more accurately, I have no idea how to use it. Each of the built in extensions have their own methods for grabbing information. Finding documentation on these methods is difficult. This is one place the php.net documentation really excels and I had expected Zend would provide something similar in nature. Unfortunately I was wrong. I did figure out what methods existed (and what they did) after grep’ng the code. The change I needed was simple, use getCastAuthor() to set my $item['author'] field, my code was ready.

function parse($xml_string){
	...
	foreach($feed as $entry){
		$item = array(
				'id' => $entry->getId(),
				'title'=>$entry->getTitle(),
				'author'=>$entry->getCastAuthor(),
				'enclosure' => $entry->getEnclosure(),
		);
		$data['entries'][] = $item;
	}
	return $data;
}

I suspect using the Zend Studio product includes much of this documentation making it easier to write code leveraging the power of the Framework.

The Missing Manual

To save some of you the hassle of having to grep the code and read it yourself, I offer this guide to the methods added with the Podcast extension. This is current as of revision 22418 from the subversion standard trunk. If you are attempting to create an iTunes compliant feed, be aware that there is a separate iTunes writer extension built directly into the Framework as well. Also be sure to read the Apple RSS Specification documents that outlines exactly how they use each field.

Feed (Zend_Feed_Reader_Extension_Podcast_Feed)

These additional methods are made available to the feed, which is what’s returned from the call to Zend_Feed_Reader::importString() or whatever import method you are using.

getCastAuthor()
This will return the contents of <itunes:author> tag.

getBlock()
This will return the contents of <itunes:block> tag.

getCategories()
This will return the contents of the <itunes:category> as an array of values.

getExplicit()
This will return value the of the <itunes:explicit> tag.

getImage()
This will return the href value of the <itunes:image>, suitable for use in an <img> tag.

getKeywords()
This will return the contents of the <itunes:keywords> tag.

getNewFeedUrl()
This will return the contents of <itunes:new-feed-url> tag.

getOwner()
This will return a string that concatenates intelligently the values contained in <itunes:owner>’s <itunes:email> and <itunes:name> values.
For example, the following XML would return: tony@bp.com (Tony Hayward)

<itunes:owner>
   <itunes:name>Tony Hayward</itunes:name>
   <itunes:email>tony@bp.com</itunes:email>
</itunes:owner>

getSubtitle()
This will return the contents of <itunes:subtitle> tag.

getSummary()
This will return the contents of <itunes:summary> tag.

Entry (Zend_Feed_Reader_Extension_Podcast_Entry)

These additional methods are made available on a per entry item level.

getCastAuthor()
This will return the contents of <itunes:author> tag.

getBlock()
This will return the contents of <itunes:block> tag.

getDuration()
Unique to the item field, this will return the contents of <itunes:duration> tag.

getExplicit()
This will return value the of the <itunes:explicit> tag.

getKeywords()
This will return the contents of the <itunes:keywords> tag.

getSubtitle()
This will return the contents of <itunes:subtitle> tag.

getSummary()
This will return the contents of <itunes:summary> tag.

Share Remove an Array Element

Friday, May 7th, 2010


Edit: An astute reader pointed out I am doing a bunch of work for nothing! PHP’s built in array_splice() is actually the tool I was looking for. If you only skim the documentation you’ll miss this additional way the function can be used. The remainder of the post is left here for anyone who may already be using a similar code pattern and would want to improve their methodology.

$arr = array('a','b','c','d','e');
array_splice($arr, 2, 1);
print_r($arr);
 
/* 
Array
(
    [0] => a
    [1] => b
    [2] => d
    [3] => e
)
*/

Original Post: PHP has a number of great ways to manipulate an array of values. Using the standard array_shift() and array_pop() both work to remove the next element in the stack you are processing. However, I needed a way to remove an element from an array that wasn’t necessarily at the beginning or the end. Depending on how you’ll be later processing the data in the array, using unset may not work the way you are expecting because it preserves the original index values for each element:

$arr = array('a','b','c','d','e');
unset($arr[2]); // removes the value 'c'
print_r($arr);
 
/* 
Array
(
    [0] => a
    [1] => b
    [3] => d
    [4] => e
)
*/
 
// This code will not run as expected
for($i=0;$i<count($arr);$i++){
	echo $arr[$i];
}

This led me to build the function below. If you need to keep a value for each position, you’ll want to actually cut the array apart and re-merge as a new array.

// Function to remove an element at the specified index
function array_remove_index(&$arr, $idx){
	$val = $arr[$idx];
	$arr = array_merge(array_slice($arr, 0, $idx), array_slice($arr, $idx + 1));
	return $val;
}
 
$arr = array('a','b','c','d','e');
array_remove_index($arr, 2); // removes the value 'c'
print_r($arr);
 
/*
Array
(
    [0] => a
    [1] => b
    [2] => d
    [3] => e
)
*/

To keep the function consistent with the built in array_shift() and array_pop() functions, the array is passed by reference and the function returns the element from the index that’s being removed. Notice the indexed values from the array are sequential.

Share Double Decode MIME Attachments

Thursday, April 29th, 2010

While working on a project to parse emails, I found that binary attachments require double decoding to get back to binary formats. I was surprised to find a general lack of usable pre-built classes for handling or parsing email attachments, despite numerous libraries (Zend being my favorite) for creating MIME email. Zend does have one for working with linux mailbox files, but I found that the parsing of headers and some other fields wasn’t as reliable as I needed. The following example is shortened to make it more readable. Base 64 encoded files tend to be quite large.

...
--part15544-boundary-637137341-642608562
Content-Transfer-Encoding: base64
Content-Type: application/pdf; name="document.pdf" 
 
/9j/4QEsRXhpZgAASUkqAAgAAAAJAA8BAgATAAAAegAAABABAgAQAAAAjgAAABIBAwABAAAAAQAA
ABoBBQABAAAAngAAABsBBQABAAAApgAAACgBAwABAAAAAgAAADIBAgAUAAAArgAAABMCAwABAAAA
... 
69NSBJvcmXOKkVueP50c1tgV/tD4jk/WpIwQeT+GaHfcbHjIXj+eKfGRupX6itbRjjjH9acOlVFt
biskSITnNPJ+XA4zyaUtdgsmIHOfanK3PFDvcm2p/9k=
 
--part15544-boundary-637137341-642608562--

If you want to save the file out to disk, you can use the following code:

// code that parses your mime data
$mime_filename = 'document.pdf';
$mime_data = '/9j/4QEsR ... vcm2p/9k='; 
 
$binary_data = base64_decode(base64_decode($mime_data));
$fh = fopen($mime_filename, "w");
fwrite($fh, $binary_data);
fclose($fh);

You can find more information on base64_decode() and MIME.

Share De Duping an Array in PHP the Easy Way

Friday, April 9th, 2010

PHP Logo This is a quick and dirty way to clean up the values in an array, removing duplicates while preserving (as much as possible) the original order of the values. This method works well for smaller sorted data sets, having less than a few hundred elements in the array. Of course this type of processing is heavily dependent on your equipment so your mileage may vary.

$arr = array("a","b","c","c","d","e","e","f","g","h","h","h","i");
 
// removing the duplicates
$tmp = array();
while(count($arr) > 0){
	$item = array_pop($arr);
	if(!in_array($item, $tmp))
		array_unshift($tmp, $item);
}
$arr = $tmp;
unset($tmp);
 
// array now holds unique values
// array("a","b","c","d","e","f","g","h","i");

You can extend this to work with larger arrays if the original data is sorted by replacing the in_array() function. The in_array() function looks over the entire set of values in the array from the first element to the last until it finds a match, so the execution time increases with each additional element you find.

You can also use this method to work with complex array elements, such as an array of associative arrays, although the process is slightly different. In this case you will use a helper array (called $keys below) to keep track of the primary key value you are de duping against.

$arr = array(
	array("id"=>"1", "data"=>"apple", "qty"=>10),
	array("id"=>"1", "data"=>"apple", "qty"=>10),
	array("id"=>"2", "data"=>"orange", "qty"=>8),
	array("id"=>"2", "data"=>"orange", "qty"=>8),
	array("id"=>"3", "data"=>"pear", "qty"=>12),
	array("id"=>"3", "data"=>"pear", "qty"=>12),
);
 
// Remove duplicate values using 'id' as the unique value
$keys = array();
$tmp = array();
while(count($arr) > 0){
	$item = array_pop($arr);
	if(!in_array($item['id'], $keys)){
		array_unshift($keys, $item['id']);
		array_unshift($tmp, $item);
	}
}
$arr = $tmp;
unset($tmp);
unset($keys);

In case your wondering why the process starts at the end of the array – using array_pop() – and work towards the beginning, for any row that is a duplicate of the prior value in_array() will find the match quickly because it is the first element in the array. This of course doesn’t provide any real benefit for values that are not sorted and any value that doesn’t match still requires a scan of the entire $tmp or $keys array.

Share Microsoft SQL Server Driver for PHP returns DateTime Object

Saturday, March 13th, 2010

Interesting gotcha feature with the Microsoft SQL Server extension for PHP on Windows. When querying against a column defined as a datetime, the native PHP SQL Server extension returns a string where as the Microsoft extension returns a DateTime object. So if you are expecting a string, you’ll need to adjust your code accordingly.

I personally like to have my dates and times as timestamps but you can rework this for your needs. I could see extending this return a formatting date string instead of a timestamp or perhaps include a date format parameter to the function.

public function date_normalizer($d){
	if($d instanceof DateTime){
		return $d->getTimestamp();
	} else {
		return strtotime($d);
	}
}

Another route is to pass in the connection parameters necessary to make the conversion at the driver level. This is done by adding the “ReturnDatesAsStrings” parameter to your connection parameters.

$connectionParams = array(
	'USR'=>'user',
	'PASS'=>'pass',
	'Database'='myDatabase',
	'ReturnDatesAsStrings'=>true,
	'Timeout'=>1,
);
$conn = sqlsrv_connect('127.0.0.1',$connectionParams);

Share PHP & 64-bit Integer Modulus (Almost)

Wednesday, October 28th, 2009

While at times PHP seems to be capable of 64 bit math, it’s important to understand what’s really going on. Beyond 32 bit integers, PHP is silently converting your integers to floats. While this usually isn’t a problem, many of the operations you might perform on an int, such as modulus choke when attempting to convert back to a 32 bit integer internally.

This is actually the cause of the sprintf / printf issue I encountered before. The code below provides the maximum signed value for integers between 1 and 64 bit in PHP along with the result of the built in modulus operand “%” and a function I wrote mod() which doesn’t go all the way to 64 bits, but gets us a lot closer leveraging the built in data types. If you can install external modules, you might review and test the performance of BCMath or GMP which can both handle much larger values.

<?php
 
// Find out what our internal values are capable of
print "PHP_INT_MAX: " . PHP_INT_MAX . "\n";
print "PHP_INT_SIZE: " . PHP_INT_SIZE . " bytes (" . (PHP_INT_SIZE * 8) . " bits)\n";
 
// Generate an array of maximum signed 32 bit values
$ints = array();
for($pwr = 0; $pwr < 64; $pwr++){ $ints[] = pow(2,$pwr) - 1; }
 
// Generate a table of values
print "bits\t%100\tmod()\t%s\n";
$bits = 0;
foreach($ints as $int){
	$bits++;
	printf("%d\t%s\t%s\t%s\n", $bits, $int%100, mod($int,100), $int);
}
 
// (60 bit) - 1 aware modulus function
function mod($val, $mod){ return $val - floor($val/$mod) * $mod; }
?>

This generates the following table. Notice the internal value for integers is capped at 2,147,483,647 and the modulus operation goes kaput beyond 32 bits. The function provided seems to holds up through 59 bits before failing to function properly at 60.

PHP_INT_MAX: 2147483647
PHP_INT_SIZE: 4 bytes (32 bits)
bits	%100	mod()	%s
1	0	0	0
2	1	1	1
3	3	3	3
4	7	7	7
5	15	15	15
6	31	31	31
7	63	63	63
8	27	27	127
9	55	55	255
10	11	11	511
11	23	23	1023
12	47	47	2047
13	95	95	4095
14	91	91	8191
15	83	83	16383
16	67	67	32767
17	35	35	65535
18	71	71	131071
19	43	43	262143
20	87	87	524287
21	75	75	1048575
22	51	51	2097151
23	3	3	4194303
24	7	7	8388607
25	15	15	16777215
26	31	31	33554431
27	63	63	67108863
28	27	27	134217727
29	55	55	268435455
30	11	11	536870911
31	23	23	1073741823
32	47	47	2147483647
33	-1	95	4294967295
34	-1	91	8589934591
35	-1	83	17179869183
36	-1	67	34359738367
37	-1	35	68719476735
38	-1	71	137438953471
39	-1	43	274877906943
40	-1	87	549755813887
41	-1	75	1099511627775
42	-1	51	2199023255551
43	-1	3	4398046511103
44	-1	7	8796093022207
45	-1	15	17592186044415
46	-1	31	35184372088831
47	-1	63	70368744177663
48	-1	27	1.4073748835533E+14
49	-1	55	2.8147497671066E+14
50	-1	11	5.6294995342131E+14
51	-1	23	1.1258999068426E+15
52	-1	47	2.2517998136852E+15
53	-1	95	4.5035996273705E+15
54	-1	91	9.007199254741E+15
55	0	84	1.8014398509482E+16
56	0	68	3.6028797018964E+16
57	0	32	7.2057594037928E+16
58	0	64	1.4411518807586E+17
59	0	32	2.8823037615171E+17
60	0	0	5.7646075230342E+17
61	0	0	1.1529215046068E+18
62	0	0	2.3058430092137E+18
63	0	0	4.6116860184274E+18
64	0	0	9.2233720368548E+18
© 1998-2008 AF-Design, All rights reserved.