Posts Tagged ‘php’

Share JSON Encoding and Decoding in PHP

Wednesday, September 23rd, 2009

PHP Logo JSON (also known as JavaScript Object Notation) is a handy way to serialize data for passing through mediums that do not recognized complex data types – such as HTTP. While using it to pass data today I was bit by a gotcha in how associative array data is encoded and decoded.

<?php 
$associative_array = array("key1"=>"value1","key2"=>"value2");
$regular_array = array("value1","value2");
$class_object = (object) array("key1"=>"value1","key2"=>"value2");
 
// The original data
print_r($associative_array);
print_r($regular_array);
print_r($class_object);
 
// Just encode the data
print "assoc: " . json_encode($associative_array) . "\n";
print "array: " . json_encode($regular_array) . "\n";
print "class: " . json_encode($class_object) . "\n";
 
// Encode and decode the data
print_r(json_decode(json_encode($associative_array)));
print_r(json_decode(json_encode($regular_array)));
print_r(json_decode(json_encode($class_object)));
?>

Notice when we run this, the first two print_r() calls correctly return an array and the third returns an stdClass Object. Now look at the last three lines. Notice how the associative array and the stdClass Object both return the same JSON? Now look at the third set of print_r() calls. You can see the associative array data has been converted to a stdClass.

// The original data
Array
(
    [key1] => value1
    [key2] => value2
)
Array
(
    [0] => value1
    [1] => value2
)
stdClass Object
(
    [key1] => value1
    [key2] => value2
)
 
// Encoded for JSON
assoc: {"key1":"value1","key2":"value2"}
array: ["value1","value2"]
class: {"key1":"value1","key2":"value2"}
 
// Encoded and Decoded
stdClass Object
(
    [key1] => value1
    [key2] => value2
)
Array
(
    [0] => value1
    [1] => value2
)
stdClass Object
(
    [key1] => value1
    [key2] => value2
)

To avoid this behavior, simply cast the stdClass object to an array when you are decoding it. Cheers!

$json_data = '{"key1":"value1","key2":"value2"}';
$associative_array = (array) json_decode($json_data);
print_r($associative_array);
Array
(
    [key1] => value1
    [key2] => value2
)

EDIT: You can also pass in an optional parameter to json_decode() to create an associative array instead of casting with (array). The following code is functionally equivalent.

$associative_array = (array) json_decode($json_data);
$associative_array = json_decode($json_data, true);

Share Getting up and Running with Gearman

Monday, April 27th, 2009

Gearman Gearman is a job scheduling service and I’m very excited about it. I’m using it in a development capacity so your mileage may vary in production but I wanted to share my experience thus far. As I said, I’m very bullish on this project and I see it as hugely helpful in eliminating latency in applications that often get bogged down during unnecessary synchronous communications.

Compiling gearman required installing a package that wasn’t part of my default Fedora Core install and for me wasn’t intuitive to locate. The UUID header file was located in the package e2fsprogs-devel which I found using yum provides "*/uuid.h". After that it was rather smooth to get it up and running. gearmand -d -u nobody got it up and running as a damon and I was able to connect to it using telnet over port 4730. Next I compiled the source for the PHP client and got that hooked into PHP by adding an extension file include in /etc/php.d to load the module and restarted Apache so it would be loaded there too.

Process to install and get running:

// First the server
tar -xzvf gearmand-0.5.tar.gz.tar
cd gearmand-0.5
yum install e2fsprogs-devel
./configure; make && make install
gearmand -d -u nobody
 
// Next the PHP client
tar -xzvf gearman-php-ext-0.2.tar.gz.tar
cd gearman-php-ext-0.2
phpize
./configure; make && make install
echo "extension=gearman.so" > /etc/php.d/gearman.ini
service httpd restart

So now to do some work, even if it’s useless, that takes a long time. It just so happens that creating a file with 1,000,000 sequential numbers takes a few seconds on a small EC2 instance, perfect for my test. I realize this is a highly insecure process, NEVER pass filenames as parameters in production code. Here’s the worker that creates a file (passed as the parameter) on the current system’s /tmp directory.

$worker = new gearman_worker();
$worker->add_server('127.0.0.1', 4730);
$worker->add_function('fill_file', 'fill_file_fn');
 
while(1) $worker->work();
 
function fill_file_fn($job){
	$data = $job->workload();
	$fh = fopen("/tmp/" . $data, "w");
	for($i=1;$i<1000000;$i++){
		fwrite($fh, $i . "\n");
	}
	fclose($fh);
	return;
}

The calling client just invokes this 20 times in the background.

$client = new gearman_client();
$client->add_server('127.0.0.1', 4730);
for($i=0; $i<20; $i++){
	$client->do_background('fill_file', 'file' . $i . '.txt');
}

Workers are started from the command line with something like this, “php worker.php &” and if you want more, just run more of them. You can also kill off some if they’re no longer needed.

The client completes it’s run in about 5 seconds while 5 worker threads toil away in the background until they get their work done about 3 minutes later. The use cases from the gearman team show the utility of this as a spider and for image manipulation. I see uses for sending mass emails to distribution lists using a template and substitute parameters to create a unique email for each person on the worker instead of the client – thus reducing the processing time to get the mail ready and speeding the delivery using multiple worker threads for sending (that can even be on remote machines). This product is definitely worth checking out.

Hopefully this helps you get up and running with Gearman!

Share Twitter Like Timestamps

Tuesday, February 10th, 2009

Time Strings on Autolists.com Following up on the post about Facebook Like Datelines, this post provides a simple function for creating Twitter like aged timestamps in natural language. Looking at a date time isn’t always helpful, for example “2/9/2004 19:30am” doesn’t give the reader a quick understanding if this is current or not. Twitter, Facebook and other sites uses the difference in times to provide a more meaningful expression of when an action took place which also provides relevance to current events. The great part about using this method is the need to handle time-zones goes away because the calculation of the time elapsed is relative regardless of what time-zone the data is tagged in. See it in action with the General Motors news tags on AutoLists.

The function below simply displays an English string that provides the difference in times. This is obviously in English, but can be internationalized very easily. Twitter stops at 24 hours, but this extends the natural language formatting out to about 1 week then reverts to a full precision time-stamp. As you can see by the very simple math and logic involved, you can quickly extend this as far out as you would like.

function time_offset($t, $f = 'h:ma M. j Y T'){
	$o = time() - $t;
	switch($o){
		case($o <= 1): return "just now"; break;
		case($o < 20): return $o . " seconds ago"; break;
		case($o < 40): return "half a minute ago"; break;
		case($o < 60): return "less than a minute ago"; break;
		case($o <= 90): return "1 minute ago"; break;
		case($o <= 59*60): return round($o / 60) . " minutes ago"; break;
		case($o <= 60*60*1.5): return "1 hour ago"; break;
		case($o <= 60*60*24): return round($o / 60 / 60) . " hours ago"; break;
		case($o <= 60*60*24*1.5): return "1 day ago"; break;
		case($o < 60*60*24*7): return round($o / 60 / 60 / 24) . " days ago"; break;
		case($o <= 60*60*24*9): return "1 week ago"; break;
		default: return date($f, $t);
	}
}

Share Facebook Datelines For Your Data

Tuesday, February 10th, 2009

Autolists.com Date String Example I really like the date strings in the Facebook Newsfeed. They somehow make the data feel more personal when they relate to me with more natural language “Today”. This in place of callous standardized formatting so many websites present such as “2/10/2009″ or worse yet “02/10/2004″ or even worse still “2009-02-10″. The following code illustrates how you can create a quick and easy formatting of data that’s ordered by date and output it in a meaningful way to your users. You can see an example of this in action on the top stories feed on AutoLists.com as well.

The one flaw with this method as presented here is it is tied to the webserver’s time zone and the developers regionalization preferences. This, however, can easily be extended to match the users time-zone and internationalization needs with a little more effort. In the interest of simplicity I am providing it as simple as possible to illustrate the logic. The dateline class follows the code listing.

// Query for your data that contains a datetime field
 
$date_str = today_yesterday(time());
while($record = $query_result->fetch_assoc()){
	if($date_str != today_yesterday(strtotime($record['date_published']))){
		$date_str = today_yesterday(strtotime($record['date_published']));
		print '<div class="dateline">" . $date_str . "</div>';
	}
 
	// Do whatever logic you need to with each record here...
	printf($record_format, $record['title'], $record['link'], $record['image']);
 
}
 
// Date String Formatting Function
function today_yesterday($t, $f = 'l, F j'){
	if(date("Ymd") == date("Ymd", $t)){
		return "Today";
	} elseif (date("Ymd", time()-(24*60*60)) == date("Ymd",$t)){
		return "Yesterday";
	} else {
		return date($f, $t);
	}
}
div.dateline { border-bottom:1px solid #ccc; color: #ccc; font-size:12pt; }

Share PHP MySQL vs MySQLi Database Access Metrics

Friday, January 30th, 2009

Last week I had a MySQL DBA/Developer tell me, “MySQLi sucks” when I asked why some code I was reviewing used the mysql_* extensions provided in PHP instead of mysqli_*. This really didn’t sit well with me. I turned to a trusted source for PHP information, and found they recommend mysqli_* for connecting to MySQL servers versions 4.1.3 and above. Although, I read many anecdotal stories about how mysqli_* was faster, I was unable to find any quantified proof of just how much faster. So in keeping with my performance measurement track I’ve been on this week, I decided to conduct my own test. I was surprised by the result. MySQL is faster in some cases.

The database I used is a pet project of mine that tracks some vehicle information. It uses a varchar primary key based on an automotive VIN number and the sample table I queried against has about 30K rows. The average row length is about 150bytes. I conducted 2 types of tests. The first was a completely unrealistic test measuring how fast a single row could be returned using each of the extension types. The other also returned a random sampling of rows. Since this isn’t a performance test of MySQL, I allowed each test to run one time completely to ensure the query cache was fully populated in MySQL. Then each test was run 3 subsequent times. Each test executed 1,000,000 queries to the server.

Single Hard Coded Query:

The first listing is the mysql_* calls. You’ll notice the

mysql_free_result()

function is commented out. I did test with this enabled and disabled and found that with this function enabled the queries returned at the same rate as with it enabled. So in the interest of fairness, I disabled it to more closely mimic the code executing with the mysqli_* calls.

<?php
	$tq = 1000000;
	$mt = microtime(true);
	$conn = mysql_connect("127.0.0.1","user","");
	mysql_select_db("test_schema", $conn);
	for($i=0; $i<$tq; $i++){
		$data = mysql_query("SELECT * FROM vehicles LIMIT 1", $conn);
		$data_object = mysql_fetch_assoc($data);
		// mysql_free_result($data);
	}
	mysql_close($conn);
	$et = microtime(true) - $mt;
	print "Elapsed time: " . number_format($et) . "s\n";
	print "QPS: " . ($tq / $et) . "\n";
?>

The next listing is the same code converted to leverage the mysqli_* calls. You’ll see that the result is not freed each time through the loop.

<?php
	$tq = 1000000;
	$mt = microtime(true);
	$conn = mysqli_connect("127.0.0.1","user","","test_schema");
	for($i=0; $i<$tq; $i++){
		$data = $conn->query("SELECT * FROM vehicles LIMIT 1");
		$data_obj = $data->fetch_assoc();
	}
	$conn->close();
	$et = microtime(true) - $mt;
	print "Elapsed time: " . number_format($et) . "s\n";
	print "QPS: " . ($tq / $et) . "\n";
?>

The results were surprising to me. The mysql_* extension actually ran considerably faster than the mysqli_* extenstion. In fact, the mysql_* extension ran 3.6% faster. Perhaps this DBA was on to something? I also tested this same methodology using prepared statements with MySQLi and found the results actually degraded. Discouraged, I devised a new test that would hopefully be more realistic. The values provided are queries per second.

Method Run #1 Run #2 Run #3 Avg
mysql_* 7,227.7 7,222.2 7,220.7 7,223.5
mysqli_* 6,963.0 6,974.0 6,974.4 6,970.5
mysqli prepared statements 4,849.6 5,005.3 4,978.7 4,944.5

Parameterized Queries:

More often than not, we do not run the same select query 1 million times, and we are actually more likely to run a query over a series of data sets. While this in itself is a terrible idea in production code because you should re-write the query to do it all as a single database call, it provides a better picture of how the database might be interacted with in PHP; especially if persistent connections are being utilized. However, if you find that you have some code that fits the patterns used here, it might be to your advantage to review the prepared statement code below.

The basic flow of the scripts is to first fetch 10 thousand (~1/3) of the table’s primary keys and populate an array. Then the code will select 1 million rows using a randomly selected primary key (vin). The data is still relatively small, and fits into the query cache after a warm up period. This shows how efficiently the query passing is instead of how efficient MySQL is at answering different questions, making it a better comparison of the two technologies. The three test scripts I ran are provided here for your reference.

Leveraging the mysql_* calls

 
<?php
	print "Fetching VIN array...\n";
	$vin_array = array();
	$conn = mysql_connect("127.0.0.1","user","");
	mysql_select_db("test_schema",$conn);
	$vins = mysql_query("SELECT vin FROM vehicles LIMIT 10000", $conn);
	while($vin = mysql_fetch_assoc($vins)){
		$vin_array[] = $vin['vin'];
	}
	mysql_close($conn);
	unset($conn);
 
	print "Starting test...\n";
	$tq = 1000000;
	$mt = microtime(true);
	$conn = mysql_connect("127.0.0.1","user","");
	mysql_select_db("test_schema", $conn);
	for($i=0; $i<$tq; $i++){
		$data = mysql_query("SELECT * FROM vehicles WHERE vin = '" . $vin_array[rand(0,count($vin_array)-1)] . "'", $conn);
		$data_object = mysql_fetch_assoc($data);
		// mysql_free_result($data);
	}
	mysql_close($conn);
	$et = microtime(true) - $mt;
	print "Elapsed time: " . number_format($et) . "s\n";
	print "QPS: " . ($tq / $et) . "\n";
?>

Leveraging the mysqli_* calls

<?php
	print "Fetching VIN Array...\n";
	$vin_array = array();
	$conn = mysqli_connect("127.0.0.1","user","","test_schema");
	$vins = $conn->query("SELECT vin FROM vehicles LIMIT 10000");
	while($vin = $vins->fetch_assoc()){
		$vin_array[] = $vin['vin'];
	}
	$conn->close();
	unset($conn);
 
	print "Starting test...\n";
	$tq = 1000000;
	$mt = microtime(true);
	$conn = mysqli_connect("127.0.0.1","user","","test_schema");
	for($i=0; $i<$tq; $i++){
		$data = $conn->query("SELECT * FROM vehicles WHERE vin = '" . $vin_array[rand(0,count($vin_array)-1)] . "'");
		$data_obj = $data->fetch_assoc();
	}
	$conn->close();
	$et = microtime(true) - $mt;
	print "Elapsed time: " . number_format($et) . "s\n";
	print "QPS: " . ($tq / $et) . "\n";
?>

Leveraging mysqli_ calls and prepared statements.

<?php
	print "Fetching VIN array...\n";
	$vin_array = array(); 
	$conn = mysqli_connect("127.0.0.1","user","","test_schema");
	$vins = $conn->query("SELECT vin FROM vehicles LIMIT 10000");
	while($vin = $vins->fetch_assoc()){
		$vin_array[] = $vin['vin'];
	}
	$conn->close();
	unset($conn);
 
	print "Running tests...\n";
	$tq = 1000000;
	$mt = microtime(true);
	$conn = mysqli_connect("127.0.0.1","user","","test_schema");
	$stmt = $conn->stmt_init();
	$stmt->prepare("SELECT * FROM vehicles WHERE VIN = ?");
	for($i=0; $i<$tq; $i++){
		$stmt->bind_param("s", $vin_array[rand(0,count($vin_array) - 1)]);
		$stmt->execute();
		$stmt->bind_result($a,$b,$c,$d,$e,$f,$g,$h,$z,$j,$k,$l,$m,$n);
		$stmt->fetch();
	}
	$conn->close();
	$et = microtime(true) - $mt;
	print "Elapsed time: " . number_format($et) . "s\n";
	print "QPS: " . ($tq / $et) . "\n";
?>

The performance of this “harder” task degraded for all technologies. Again, the mysql_* extension was about 3.6% faster, but the prepared statements were 9.7% faster than the old mysql_* calls and 13.7% faster than the mysqli_ calls. This improvement makes a good case for transitioning repetitive calls in your code to prepared statements leveraging the MySQLi extenstions. Again, the values are measured in queries per second.

Method Run #1 Run #2 Run #3 Avg
mysql_* 4,063.6 4,016.7 4,054.0 4,044.8
mysqli_* 3,902.3 3,905.8 3,896.0 3,901.4
mysqli prepared statements 4,443.8 4,421.8 4,445.7 4,437.1

Without testing stored procedures, it’s difficult to say if they would have continued to improve the performance of this test, however, it is clear that the speed advantages of mysqli_* extensions are only present if you are utilizing the advanced features of the more recent versions of the database.

Conclusion:

If your code is purely hard coded statements, the old school MySQL syntax may be your best bet for a performance lift. However, most likely your code would benefit more from prepared statements and some of the other performance enhancements provided by the MySQLi extension set. I wouldn’t say, “MySQLi Sucks” but it does appear there is a valid argument for continuing to use it if you are not leveraging any of the performance enhancements of the newer versions of MySQL.

System Configuration:

  • Mac OS 10.5.6
  • 2.16Ghz Intel Core Duo
  • 2 Gb RAM
  • PHP 5.2.6
  • MySQL Server 5.0.51b

Share PHP Memory Caching Performance

Wednesday, January 28th, 2009

Today I had the great pleasure to work with both APC and Memcached in a production environment. I’ve read that the performance of APC is roughly 3-5x faster than Memcache. So I decided to do my own test to see which performed better on my rig. If you want more numbers that may better mimic a production server environment, be sure to read Peter from the MySQL Performance Blog’s initial article which found APC to be roughly 3x faster. Although, I found that his results are spot on a little over 2 years later.

Being ultimately more concerned with the number of requests Apache serves back to my clients, I opted to use metrics similar to Jay Pipes method, leveraging ApacheBench to see determine the best way to push more content to the users faster. I created two test files which are nearly identical, accessing the cache layer using the simplest calls possible.

The tests were run against localhost after a pre-warmup to ensure the correct number of Apache workers were initiated and the cache had the correct content. This is important, because these numbers are a good comparison of read performance, but do not provide write throughput. The test was performed against a desktop system, so background processes may have varied slightly from test to test despite efforts to disable everything.

Apache bench was run over 10,000 requests with concurrency of 50. The values are the average of 3 consecutive runs. Standard deviation for connection time was consistent over each of the runs. APC proved significantly faster (30%) even with Memcached on the localhost. However, memcached scales out where as APC is tied to the local machine. That alone may be sufficient reason to use it over APC in your environment despite the performance benefit.

Some large scale applications benefit from multiple layers of caching. According to Matt Raible’s notes from OSCON 2008 Facebook uses $GLOBALS, APC and Memcached as their first lines of caching defense. This seems to further validate Peter’s findings.

Results:

APC Memcached
Requests/Second 2,088.43 1,611,.59 APC ~30% More
Time/Request (mean) 0.48ms 0.62ms APC ~23% Faster
99% of Requests Finished in 63ms 102ms APC ~39% Faster

Test Code:

The scripts are fairly straightforward. I didn’t want this to be a comparison of MySQL database accesses and so I manually created a simple object to cache.

<?php
 
	// APC Cache 
 
	$data = apc_fetch("test_object", &$success);
	if($success){
		print_r($data);
	} else {
		// store an object in the cache for the next call
		$test_object = array();
		$test_object['key1'] = 1;
		$test_object['key2'] = "hello world";
		$test_object['key3'] = array(1,2,3);
		$result = apc_store("test_object", $test_object, 3600);
		print_r($data);
	}
?>
 
<?php
 
	// Memcached Cache 
 
	$memcache = new Memcache();
	$memcache->addServer("127.0.0.1","11211");
	$data = $memcache->get("test_object");
	if($data){
		print_r($data);
	} else {
		// store an object in the cache for the next call
		$test_object = array();
		$test_object['key1'] = 1;
		$test_object['key2'] = "hello world";
		$test_object['key3'] = array(1,2,3);
		$memcache->set("test_object", $test_object, 3600);
		print_r($data);
	}
?>

System Configuration:

  • Mac OS 10.5.6
  • 2.16Ghz Intel Core Duo
  • 2 Gb RAM
  • PHP 5.2.6
  • APC 3.1.2
  • Memcached 1.1.12
  • Apache 2.2.9
  • ApacheBench 2.3

Share PHP Gotcha: Bigint with sprintf()

Wednesday, December 3rd, 2008

PHP LogoToday I am working on modification of an FB application to permit installation on “pages” in addition to profiles. Facebook has switched (several months ago) to using 64bit integer values for id’s site wide. In the code I use sprintf to create stored procedure calls and couldn’t figure out why the values the the database weren’t matching up with my expectations… the following code would output 536035818 instead of the much larger 30600806890 value I expected. Clearly this is a 32bit vs 64bit.

$big_int = 30600806890;
$format = "%d";
$new_string = sprintf($format, $big_int);
print $new_string;

Since updating to a fully 64 bit platform and so on really isn’t an option, I needed a work around. I composed a quick test for my value and ran it. The test as you can see, it provides output for each of the valid substitution types in sprintf.

$big_int = 30600806890;
$type = array("%b","%c","%d","%e","%u","%f","%F","%o","%s","%x","%X");
for($i=0;$i<count($type);$i++){
        print $type[$i] . ": " . sprintf($type[$i], $big_int) . "\n";
}

This results with the list below. Obviously floating point numbers and strings are the only way to handle these bigint values. A simple drop in value is of course to swap %s for %d as needed, but you lose some type catching which may cause security issues. Instead, I recommend using %0.0f which will suppress the decimal portion – keeping the int an int – while keeping the numeric type checking intact.

%b: 11111111100110100000111101010
%c: ?
%d: 536035818
%e: 3.06008e+10
%u: 536035818
%f: 30600806890.000000
%F: 30600806890.000000
%o: 3774640752
%s: 30600806890
%x: 1ff341ea
%X: 1FF341EA

Share Memcached with PHP on Mac OS X

Saturday, October 18th, 2008

Nate Haug provides a great script for installing memcached along with some very detailed instructions on setting up a sandbox environment. I’m not using his MAMP sandbox, instead opting for the built in PHP / Apache install, so I needed to change a few things from his tutorial. My system is a fully updated Intel MacBook Pro running OS X 10.5.5 with the Xcode tools installed – YMMV. PHP is currently reporting version 5.2.6.

  1. I added my revised start script for memcached.
  2. The PHP version that ships with OS X doesn’t have PECL, so I downloaded the source and compiled manually.
    phpize; configure; make; sudo make install
  3. Edit to /etc/php.ini: Changed: extension_dir = /usr/lib/php/extensions/no-debug-non-zts-20060613/
  4. Edit to /etc/php.ini: Added: extension=memcache.so

You can skip his Apache scripts. Restart apache by restarting Web Sharing in the System Preferences.

The major changes I made from Nate’s memcached startup script were the singular instance and binding the service to localhost (127.0.0.1) only. This keeps memcached slightly more secure by only having it listen on the loopback adapter. If you need more space, just change the -m attribute to be higher; it’s measured in MB.

# /bin/sh
memcached -m 1 -l 127.0.0.1 -p 11211 -d

NOTE: As with any other service running on your system, opening a web server exposes your system to potential attack and worse. Be sure to keep production data away from your test environment. Someone at Starbucks, sharing your WiFi connection, may be surfing your development site too. Consider yourself warned!

Share Data Gotcha’s or Learn to Love CAST()

Wednesday, October 1st, 2008

PHP Loves Microsoft SQL Server In the last post, I explained my work around to get data out of a Microsoft SQL Server using stored procedures. Since then, I’ve continued learning more about the limitations of the FreeTDS driver in PHP and hit another snafu when working with the data. Fields that are exported as VARCHAR are truncated at 256 characters! Whoa! big gotcha when today’s database tables might contain values in VARCHAR(1024) or even bigger! So what’s a PHP script to do? CAST the values!!!

 
-- original statement
SELECT myBigVarChar 
     FROM myTable 
     WHERE myId = 1;
 
-- revised statement
SELECT CAST(myBigVarChar AS text) AS myBigVarChar 
     FROM myTable 
     WHERE myId = 1;

Notice the statement’s a little more unruly to look at, but is functionally the same. The case to text allows the FreeTDS driver to properly convert the data. I also noticed this issue with UNIQUEIDENTIFIER type columns coming through as a BLOB. My fix was to CAST the identifier to a VARCHAR(36) to safely hold the string representation of the UUID.

Hope this saves you some head scratching!

Share Running a Stored Procedure from MS-SQL Server with PHP on Linux

Wednesday, October 1st, 2008

PHP Loves Microsoft SQL Server After some effort getting the MSSQL drivers in PHP, I was ready to hook into the RDBMS and use my nicely developed stored procedures. FreeTDS required minimal setup and configuration and I was able to establish a connection and run simple queries against tables in no-time.

However, when I attempted to execute stored procedures, they would fail. To make troubleshooting harder, the MSSQL functions do not return error messages — at least none that I could access in PHP 5.1. My work around to the proper methodology init(), bind(), and execute() process was to use query() and pass in the procedure and parameters as needed. It’s slightly less elegant but opens the data up to you.

// Establish the connection and run the procedure
$connection = mssql_connect("123.123.123.123","db_login","db_password");
mssql_select_db("db_schemaname",$connection);
 
// Run the procedure
$username = "user"; // sanitized!!!
$password = "secret_password"; // sanitized!!!
$result = mssql_query("CheckLogin '$username', '$password'", $connection);
 
// handle the result as needed

The standard disclaimers apply about validating, escaping and bullet proofing any data passed into Microsoft SQL Server using this method. Seriously, heed these warnings, sanitize any data, or you will find your site vulnerable to SQL Injection attacks which are serious business.

© 1998-2008 AF-Design, All rights reserved.