Archive for the 'PHP/MySQL' Category


PHP: How to Redirect Server-Side (Code 301 to 307)

Saturday, April 23rd, 2011

I needed a way to do a server-side PHP redirect based on the RFC 2616 spec. After some research and some help on the net, I put together this:

Code:
function redirect($to,$code=307) {
		$location = null;
		$sn = $_SERVER['SCRIPT_NAME'];
		$cp = dirname($sn);
		if (substr($to,0,4)=='http') $location = $to; // Absolute URL
		else {
			$schema = $_SERVER['SERVER_PORT']=='443'?'https':'http';
			$host = strlen($_SERVER['HTTP_HOST'])?$_SERVER['HTTP_HOST']:$_SERVER['SERVER_NAME'];
			if (substr($to,0,1)=='/') $location = "$schema://$host$to";
			elseif (substr($to,0,1)=='.') {
				$location = "$schema://$host/";
				$pu = parse_url($to);
				$cd = dirname($_SERVER['SCRIPT_FILENAME']).'/';
				$np = realpath($cd.$pu['path']);
				$np = str_replace($_SERVER['DOCUMENT_ROOT'],'',$np);
				$location.= $np;
				if ((isset($pu['query'])) && (strlen($pu['query'])>0)) $location.= '?'.$pu['query'];
			}
		}

		$hs = headers_sent();
		if ($hs==false) {
			if ($code==301) header("301 Moved Permanently HTTP/1.1"); // Convert to GET
			elseif ($code==302) header("302 Found HTTP/1.1"); // Conform re-POST
			elseif ($code==303) header("303 See Other HTTP/1.1"); // dont cache, always use GET
			elseif ($code==304) header("304 Not Modified HTTP/1.1"); // use cache
			elseif ($code==305) header("305 Use Proxy HTTP/1.1");
			elseif ($code==306) header("306 Not Used HTTP/1.1");
			elseif ($code==307) header("307 Temorary Redirect HTTP/1.1");
			else trigger_error("Unhandled redirect() HTTP Code: $code",E_USER_ERROR);
			header("Location: $location");
			header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
		}
		elseif (($hs==true) || ($code==302) || ($code==303)) {
			// todo: draw some javascript to redirect
			$cover_div_style = 'background-color: #ccc; height: 100%; left: 0px; position: absolute; top: 0px; width: 100%;';
			echo "<div style='$cover_div_style'>\n";
			$link_div_style = 'background-color: #fff; border: 2px solid #f00; left: 0px; margin: 5px; padding: 3px; ';
			$link_div_style.= 'position: absolute; text-align: center; top: 0px; width: 95%; z-index: 99;';
			echo "<div style='$link_div_style'>\n";
			echo "<p>Please See: <a href='$to'>".htmlspecialchars($location)."</a></p>\n";
			echo "</div>\n</div>\n";
		}
		exit(0);
	}

PHP: Functions to Check if String Starts & Ends With String

Saturday, April 23rd, 2011

Another example of functions that aren’t part of PHP, but should be. Luckily there was a good solution on StackOverflow:

Code:
function startsWith($haystack,$needle,$case=true) {
    if($case){return (strcmp(substr($haystack, 0, strlen($needle)),$needle)===0);}
    return (strcasecmp(substr($haystack, 0, strlen($needle)),$needle)===0);
}

function endsWith($haystack,$needle,$case=true) {
    if($case){return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)),$needle)===0);}
    return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)),$needle)===0);
}

PHP: Splitting an Array into Multiple Arrays

Saturday, April 23rd, 2011

I had an array that I needed to split into multiple arrays, surprised there wasn’t a built in function for this, here it is:

Code:
function array_split($array, $pieces=2) {
	if ($pieces < 2) {
		return array($array);
	}
	$newCount = ceil(count($array)/$pieces);
	$a = array_slice($array, 0, $newCount);
	$b = $this->array_split(array_slice($array, $newCount), $pieces-1);
	return array_merge(array($a),$b);
}

Using PHP to Do [Some kind] of Spelling Suggest (“Did you mean”) [PHP]

Tuesday, February 22nd, 2011

I wanted to figure out a way to have a “Did you mean” spell check for search terms that didn’t return any results. By default the .dll is not available in XAMPP, so need to:

1. Install Pspell (full install)
2. Install the English dictionary on the same page
3. Copy over aspell-15.dll from C:\Program Files\Aspell\bin\ to C:\xampp\apache\bin\
4. Enable extension=php_pspell.dll in php.ini

In Debain it’s simple:

Code:
apt-get install php5-pspell

Now to figure out some good code.. I’ll update it here if I come up with anything interesting

Update

Using pspell suggest alone doesn’t really give the best results. It’s smarter to calculate the metaphone string of the word and then compare that against the suggested results. Also, you’ll need to create your own dictionary so it doesn’t suggest a correct word. This is basically the function I made to do that (I have a cron job to update the dictionary nightly):

Code:
function addString($string) {
	global $pspell_link;
	$exp = explode(" ",$string);
	foreach ($exp as $e) {
		if (strpos($e,'-') === false && $e != '&') {
			pspell_add_to_personal($pspell_link, rtrim($e,","));
		}
	}
}

Here is the code that does the suggest, I’ve just ripped it out of a class I made, but it shows 95% of it:

Code:
		$result = array();
		$wordExp = explode(' ',$term);
		$i = 0;
		foreach ($wordExp as $w) {
			$wordMeta = metaphone($w);
			$newWord = '';
			if (!pspell_check($this->pspell, $w)) {
				//
				$suggest = pspell_suggest($this->pspell, $w);
				//$util->out($suggest);
				// loop all suggestions looking for the first metaphone match
				foreach ($suggest as $s) {
					// returns back case-sensative results
					if (strtolower($s) != strtolower($w)) {
						// also returns back words with spaces and dashes
						if (metaphone($s) == $wordMeta && strpos($s,' ') === false && strpos($s,'-') === false) {
							$newWord = $s;
							break;
						}
					}
				}
				// if no metaphone match
				if ($newWord == '') {
					foreach ($suggest as $s) {
						// just grab the first result with no space or dash
						if (strpos($s,' ') === false && strpos($s,'-') === false) {
							$newWord = $s;
							break;
						}

					}
					// if there is still no new word, just return the original
					if ($newWord == '') {
						$newWord = $w;
					}
				}
			} else {
				$newWord = $w;
			}
			//
			$result[$i][0] = $w;
			$result[$i][1] = strtolower($newWord);
			$i = $i + 1;
		}
		return $result;

So the function would return back and array with the original word and the suggested word:

Code:
Array
(
    [0] => Array
        (
            [0] => cranberrry
            [1] => cranberry
        )

)

How to Properly Remove Special Characters a String [PHP]

Tuesday, February 15th, 2011

After getting ridiculously frustrated with all the confusing regex out there for stripping special characters from a string, I thought: “there must be a PHP way of doing this” – and then it clicked in -

Code:
filter_var($string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH)

That is all.

Twitter: How To (Auto) Gain Followers & Influence Others (No One)

Saturday, April 10th, 2010

Update: Don’t do this. It’s seems obvious now, but for some reason I didn’t realize this would get you banned. Indeed the Twitter account used in the experiment was banned. Luckily we explained ourselves, promised to stop and Twitter re-instated the account :)

_________________________________________________________________

A couple months ago, I decided to spend an evening to try and figure out how I could automate the finding & following process on Twitter. First off let me be clear, this wasn’t for my personal twitter account @gapcm. God knows I don’t care about having lots of followers, nor do I care for all the self-promotion and spam that comes along with having lots of followers. That said, there are cases were a person would want to grow their follower base, even at the expense of following a ton of people – that’s were my script comes into play! First a little background..

About two years ago, my fiance and I started a site called Thursday for Dinner. Thursday for Dinner is a video blog dedicated to preserving family recipes. Our mission, as the site says, is to capture our family’s most treasured recipes and make them available to everyone. The Thursday for Dinner twitter account @tfdtv would be used as the guinea pig. Before I started this experiment, @tfdtv had about 600 followers and was listed about 8 times. After about 1.5 months of using my scripts @tfdtv is up to 2160 followers and is listed 126 times.

The idea of the script is this: If you have API credits left and your followers count is greater than your friends count, search Twitter for a specific term (you can also randomly choose from an array of terms). I always want to have more followers than friends (for this script), otherwise you’re looking a bit desperate. For each user who tweeted the given term, check to see if their already in our database as recently added. Assuming their not in the db, and that their followers to friends ratio is within a certain range, then follow them and add their user name to our database. The ratio is calculated like this:

Code:
$ratio = (($followers_count - $friends_count) / $followers_count);

It’s important to follow people within a specific ratio, cause if their follower to friends count is reasonably close, their more than likely going to follow you back. More important than just the ratio, is the term you use to search Twitter. The term you search needs to be relevant to your twitter stream/site content. For Thursday for Dinner, I search Twitter for the term foodie. It’s obvious that people who Tweet the word foodie, are going to like the premise of our site. Just before the script finishes, it checks all users in the database that are older than 3 days. If the given user is not following back, they are unfollowed. Whether their following or not, their user name is removed from the database.

To use this script, your going to need to know PHP/MySQL/Apache/REST. Your also going to need PEAR MDB2 and curl running. If you don’t know what I’m talking about, save yourself the trouble. First things first, you need to create a new MySQL database with the following table:

Code:
CREATE TABLE IF NOT EXISTS `twitter_users` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(128) NOT NULL,
  `date` datetime NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=2259 ;

I created a class called SessionControl to manage the whole MySQL process. I won’t show that here, but you can download it in the zip below. The main class is the twitter.class.php. This is were the bulk of the work is done.

Code:
<?

require_once 'sessionControl.class.php';

class Twitter {
    //
    private $username = 'twitter_username';
    private $password = 'twitter_password';
    //
    public function __construct() {

    }
    //
    public function getUsername() {
        return $this->username;
    }
    //
    private function getCURL() {
        if (!$curld = curl_init()) {
            echo "Could not initialize cURL session<br>";
            exit();
        }
        return $curld;
    }
    //
    public function friendshipDetails($u1,$u2) {
        $url = "http://twitter.com/friendships/show.json?source_screen_name=$u1&target_screen_name=$u2";
        //echo $url;
        //exit;
        $curld = $this->getCURL();
        curl_setopt($curld, CURLOPT_GET, true);
        curl_setopt($curld, CURLOPT_URL, $url);
        curl_setopt($curld, CURLOPT_HEADER, false);
        curl_setopt($curld, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curld, CURLOPT_HTTPHEADER, array('Expect:'));
        $output = curl_exec($curld);
        $pile = json_decode($output,true);
        return $pile;

    }
    //
    public function follow($u) {
        $user = SessionControl::getInstance();
        //
        $url = "http://$this->username:$this->[email protected]/friendships/create/$u.json";
        $curld = $this->getCURL();
        curl_setopt($curld, CURLOPT_POST, true);
        curl_setopt($curld, CURLOPT_URL, $url);
        curl_setopt($curld, CURLOPT_HEADER, false);
        curl_setopt($curld, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curld, CURLOPT_HTTPHEADER, array('Expect:'));
        $output = curl_exec($curld);
        //$this->out($output);
        $pile = json_decode($output,true);
        //$this->out($pile);
        //
        $sql = "INSERT INTO twitter_users (id, name, date) VALUES (NULL, ".$user->quote($u,"text").", NOW())";
        $user->query($sql);
        //
        return $pile;

    }
    //
    public function isUserInDB($u) {
        $user = SessionControl::getInstance();
        $result = false;
        $sql = 'SELECT * FROM twitter_users WHERE name = '.$user->quote($u,"text");
        $sqlRes = $user->query($sql)->fetchAll();
        if (!empty($sqlRes)) {
            $result = true;
        }
        return $result;
    }
    //
    private function destroy($u) {
        $user = SessionControl::getInstance();
        //
        $url = "http://$this->username:$this->[email protected]/friendships/destroy/$u.json";
        $curld = $this->getCURL();
        curl_setopt($curld, CURLOPT_POST, true);
        curl_setopt($curld, CURLOPT_URL, $url);
        curl_setopt($curld, CURLOPT_HEADER, false);
        curl_setopt($curld, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curld, CURLOPT_HTTPHEADER, array('Expect:'));
        $output = curl_exec($curld);
        $pile = json_decode($output,true);
        //
        return $pile;
    }
    //
    private function out($v) {
        echo '<pre>';
        print_r($v);
        echo '</pre>';
    }
    //
    public function unfollow($u = '', $days = 3) {
        $user = SessionControl::getInstance();
        if ($u == '' && $days > 1) {
            $sql = "SELECT * FROM twitter_users WHERE date < ".$user->quote($this->getSQLDate(time() - ($days * 24 * 60 * 60)),'text');
            $uf = $user->query($sql)->fetchAll();
            foreach ($uf as $u) {
                $details = $this->friendshipDetails($this->username,$u->name);
                //$this->out($details);
                //exit();
                if (isset($details['relationship']['target']['following'])) {
                    $key = $details['relationship']['target']['following'];
                } else {
                    $key = 0;
                }
                // not following, remove
                if ($key != 1) {
                    $this->destroy($u->name);
                    $this->out('Removed ' . $u->name);
                }
                //exit();
                // either way remove from db
                $sqlDel = "DELETE FROM twitter_users WHERE id = $u->id";
                $user->query($sqlDel);
            }
        }
    }
    //
    public function getSQLDate($t) {
        return date('Y-m-d H:i:s',$t);
    }
    //
    public function getUser($u) {
        $url = "http://twitter.com/users/show/$u.json";
        $curld = $this->getCURL();
        curl_setopt($curld, CURLOPT_GET, true);
        curl_setopt($curld, CURLOPT_URL, $url);
        curl_setopt($curld, CURLOPT_HEADER, false);
        curl_setopt($curld, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curld, CURLOPT_HTTPHEADER, array('Expect:'));
        $output = curl_exec($curld);
        $pile = json_decode($output,true);
        return $pile;
    }
    //
    public function getSearch($s) {
        $url = "http://search.twitter.com/search.json?q=$s";
        $curld = $this->getCURL();
        curl_setopt($curld, CURLOPT_GET, true);
        curl_setopt($curld, CURLOPT_URL, $url);
        curl_setopt($curld, CURLOPT_HEADER, false);
        curl_setopt($curld, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curld, CURLOPT_HTTPHEADER, array('Expect:'));
        $output = curl_exec($curld);
        $pile = json_decode($output,true);
        return $pile;
    }
    //
    public function getLimit() {
        $url = "http://twitter.com/account/rate_limit_status.json";
        $curld = $this->getCURL();
        curl_setopt($curld, CURLOPT_GET, true);
        curl_setopt($curld, CURLOPT_URL, $url);
        curl_setopt($curld, CURLOPT_HEADER, false);
        curl_setopt($curld, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curld, CURLOPT_HTTPHEADER, array('Expect:'));
        $output = curl_exec($curld);
        $pile = json_decode($output,true);
        return $pile;

    }
    //
    public function __sleep() {
        return array_keys(get_object_vars($this));
    }
    //
    public function __wakeup() {

    }
}

?>

From here, the main control file called go.php runs everything. This is were the steps are executed. Here it is:

Code:
<?php

require_once 'twitter.class.php';

set_time_limit(-1);

$twitter = new Twitter();
$limit = $twitter->getLimit();

function getSearchTerm() {
    //$terms = array("twitter_search_term_1",);
    //$r = mt_rand(0,2);
    //return $terms[$r];
    return 'twitter_search_term';
}

if ($limit['remaining_hits'] > 0){
    $me = $twitter->getUser($twitter->getUsername());
    if (($me['followers_count']) > $me['friends_count']) {
        $sterm = getSearchTerm();
        dump('Hits Left: '.$limit['remaining_hits'].' -- Searching: ' . $sterm);
        $search = $twitter->getSearch($sterm);
        foreach ($search['results'] as $r) {
            $user = $r['from_user'];
            if (!$twitter->isUserInDB($user)) {
                $info = $twitter->getUser($user);
                $friends_count = $info['friends_count'];
                $followers_count = $info['followers_count'];
                if ($friends_count > 0 && $followers_count > 0) {
                    $ratio = (($followers_count - $friends_count) / $followers_count);
                    if ($ratio < 0.2 && $ratio > -0.4) {
                        $twitter->follow($user);
                        dump($user . ' followed -> friends: ' . $friends_count . ' - Followers: ' . $followers_count);
                    }
                }
            } else {
                dump($user . ' in db');
            }
        }
    }
    //
    $twitter->unfollow();
} else {
    dump($limit);
}

function dump($r) {
    echo '<pre>';
    print_r($r);
    echo '</pre>';
}
?>

Key points

  • Twitter has a limit on the number of API calls you can execute based on a user name and IP address. So don’t run this a) too often and b) on your personal computer. You’ll need to run this on a separate box with a separate IP address.
  • You’ll need to automate this with a cron job. I run it every 10 minutes. If your hosting company doesn’t allow for cron jobs, you can use WebBasedCron, which I made 4-5 years ago.
  • The code is released under GPL. Use at your own risk. You can download the files here.

PHP: Removing All Non-Printable (Special Characters) From String

Friday, February 12th, 2010

I was actually trying to find a way to do this in JavaScript, but eventually gave up. I basically want to remove all non-printable special characters from a string. So for a string like this:

Code:
$str = 'Characters like© and ® are not all®wed.';

I would want ‘Characters like and are not allwed.’. The following matches anything in the ASCII range of 0-31 & 128-255 and removes it.

Code:
$str = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $str); 

Apache: Disabling ETags to Improve Performance

Saturday, January 30th, 2010

By removing the ETag header, you disable caches and browsers from being able to validate files, so they are forced to rely on your Cache-Control and Expires header. Basically you can remove If-Modified-Since and If-None-Match requests and their 304 Not Modified Responses.

Entity tags (ETags) are a mechanism web servers and the browser use to determine whether a component in the browser’s cache matches one on the origin server. Since ETags are typically constructed using attributes that make them unique to a specific server hosting a site, the tags will not match when a browser gets the original component from one server and later tries to validate that component on a different server.

Doing this is simple. First step make sure the Headers mod is enabled:

Code:
a2enmod headers

Then, within your apache2.conf file, add the following:

Code:
Header unset ETag
FileETag None

PHP: Remove New Lines From a String

Tuesday, January 19th, 2010

You would think a function to remove new lines from a string would exist, but surprisingly it doesn’t. My first thought was to use the nl2br function. nl2br “inserts HTML line breaks before all newlines in a string” and to then strip the br’s out. So something like this:

Code:
$string = strip_tags(nl2br($string));

The key word above is “before” though. The function preserves the new lines and just adds a br after it. So I decided to write a function to do it for me:

Code:
function removeNewLines($string) {

    $string = str_replace( "\t", ' ', $string );
    $string = str_replace( "\n", ' ', $string );
    $string = str_replace( "\r", ' ', $string );
    $string = str_replace( "\0", ' ', $string );
    $string = str_replace( "\x0B", ' ', $string );

    return $string;

}

XAMPP: Setting Up Virtual Hosts Using Apache Friends

Thursday, October 29th, 2009

This is something I do often for development, but never actually knew if I was doing it correctly. I think I’ve finally gotten this right. What I want is: To be able to setup VirtualHosts in XAMPP and also define a PHP include path within that. I’m assuming you generally have an idea how to setup a VirtualHost in Apache.

The first step is to modify your Hosts file to point traffic to your local computer. My hosts file is here C:\WINDOWS\system32\drivers\etc\hosts, you’ll have to search for yours.

Code:
127.0.0.1		www.sun.com
127.0.0.1		sun.com

The next step is to define the VirtualHost within the httpd-vhosts.conf file in the conf directory of your XAMPP setup.

Code:
NameVirtualHost *
<VirtualHost *>
	DocumentRoot "C:\xampp\htdocs"
	ServerName localhost
</VirtualHost>
<VirtualHost *>
	DocumentRoot "C:\xampp\htdocs\sun"
	ServerName www.sun.com
	ServerAlias sun.com
        ErrorDocument 404 /error.php
	<Directory "C:\xampp\htdocs\sun">
		Order allow,deny
		Allow from all
	</Directory>
	<Directory "C:\xampp\htdocs\sun">
                 php_value include_path ".;C:\php\pear;C:\htdocs\sun\xx"
	</Directory>
</VirtualHost>

Using a php_value directive you can define the include path for that VirtualHost right from conf file – thank God.

MySQL: Backup Database and Upload to Amazon S3 via PHP

Saturday, August 29th, 2009

Update (Dec 30/10) The following is how to load the dump into a db:

Code:
mysql -u #username# -p #database# < #dump_file#

I wrote a post a few years ago about a quick way to backup a MySQL database using email. Since then I’ve updated that script to upload the file to Amazon S3 instead of emailing it. I use an Amazon S3 PHP Class you can find here to make it happen. Your also going to need cURL enabled on your server. On Ubuntu/Debian that’s as easy as:

Code:
sudo apt-get install php5-curl 

Here is the script:

Code:
require_once 'S3.php';

if (!defined('awsAccessKey')) define('awsAccessKey', 'xxxxxxxxxxxxxxxxxxx');
if (!defined('awsSecretKey')) define('awsSecretKey', 'xxxxxxxxxxxxxxxxxxx');
//
// Check for CURL
if (!extension_loaded('curl') && !@dl(PHP_SHLIB_SUFFIX == 'so' ? 'curl.so' : 'php_curl.dll')) {
	exit("\nERROR: CURL extension not loaded\n\n");
}

$tmpDir = "/tmp/";
$user = "dbuser";
$password = "dbpassword";
$dbName = "dbname";
$prefix = "db_";

$sqlFile = $tmpDir.$prefix.date('Ymd_hisA').".sql";
$attachment = $tmpDir.$prefix.date('Ymd_hisA').".tgz";

$creatBackup = "mysqldump -u ".$user." --password=".$password." ".$dbName." > ".$sqlFile;
$createZip = "tar cvzf $attachment $sqlFile";
exec($creatBackup);
exec($createZip);

if (!file_exists($attachment) || !is_file($attachment)) {
	die("ERROR: No file");
}

$s3 = new S3(awsAccessKey, awsSecretKey);
$result = false;
if ($s3->putObjectFile($attachment, 'bucket-name', baseName($attachment), S3::ACL_PRIVATE)) {
	$result = true;
}

unlink($sqlFile);
unlink($attachment);

Apache: Stripping/Remove the www from URLS

Wednesday, August 19th, 2009

For reasons I won’t go into, I wanted to force Apache to strip the www from the URLs. So if someone typed in http://www.abc.com, Apache would automatically send them to http://abc.com. First thing to do is make sure ModRewrite is enabled, once that’s done simply put the following in your VirtualHost configuration or in the associated .htaccess file:

Code:
RewriteEngine on
RewriteCond %{HTTP_HOST} ^www\.(.+)$
RewriteRule ^(.*)$ http://%1/$1 [R=301,L]

Setting Different PHP Include Paths per Apache Virtual Hosts

Wednesday, July 15th, 2009

I was always curious how to set different PHP include paths per Virtual Host in Apache. I didn’t want to use an .htaccess file hack or the PHP function set_include_path. After some digging, I came across a post on a mailing list showing how it’s done using a Directory directive:

Code:
# PHP OPTIONS ###########################
    <Directory /path/to/site>
        php_value include_path ".:/path/to/site:/another/path"
        php_admin_value session.entropy_file "/dev/urandom"
        php_admin_value session.entropy_length "512"
        php_admin_value session.auto_start 1
        php_admin_value session.use_cookies 1
        php_admin_value session.use_only_cookies 1
        php_admin_value session.cookie_lifetime 0
        php_admin_value session.cookie_secure 1
    </Directory>
# PHP OPTIONS ###########################

Setting Up Memcached on Ubuntu/Debian and Windows XAMPP

Monday, March 9th, 2009

I have a big problem with writing things down. This inevitably leads me to spending excessive amount of time looking all over the internet for how to solve a problem. Most recently I was trying to remember the steps to get Memcached going on my Debian server and on my laptop.

Luckily I came across this comment on php.net, which explains nicely how to get this going on Debian/Linux. The only thing I needed to do was make sure g++ was installed (i.e. apt-get install g++). Here are the steps:

Code:
-------------------------
# Prerequisite Install
-------------------------
# Download & install libevent (memcached dependency)
wget http://www.monkey.org/~provos/libevent-1.4.8-stable.tar.gz
tar xfz libevent-1.4.8-stable.tar.gz
cd libevent-1.4.8-stable
./configure && make && sudo make install

# Create a symlink to libevent
sudo ln -s /usr/local/lib/libevent-1.4.so.2 /usr/lib

# Download & install memcached
wget http://danga.com/memcached/dist/memcached-1.2.6.tar.gz
tar xfz memcached-1.2.6.tar.gz
cd memcached-1.2.6
./configure && make && sudo make install

# Run memcached as a daemon (d = daemon, m = memory, u = user, l = IP to listen to, p = port)
memcached -d -m 1024 -u root -l 127.0.0.1 -p 11211

-------------------------
# PHP5-Memcache Install
-------------------------
# Download the extension module
apt-get install php5-memcache

# Edit /etc/php5/conf.d/memcache.ini and uncomment the following line by removing the semi-colon
extension=memcache.so

# Restart apache
/etc/init.d/apache2 restart

-------------------------
# Test Install
-------------------------
# Create a file 'memcache_test.php' in your webroot and paste the following:
<?php
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");

$version = $memcache->getVersion();
echo "Server's version: ".$version."<br/>\n";

$tmp_object = new stdClass;
$tmp_object->str_attr = 'test';
$tmp_object->int_attr = 123;

$memcache->set('key', $tmp_object, false, 10) or die ("Failed to save data at the server");
echo "Store data in the cache (data will expire in 10 seconds)<br/>\n";

$get_result = $memcache->get('key');
echo "Data from the cache:<br/>\n";

var_dump($get_result);
?>

In Windows (assuming you have XAMPP installed) go into your php.ini (check phpinfo for location) file and un-comment the following:

Code:
...
extension=php_memcache.dll
...
[Memcache]
memcache.allow_failover = 1
memcache.max_failover_attempts=20
memcache.chunk_size =8192
memcache.default_port = 11211
...

Then head to Jellycan Code and download the Windows memcached port (binary). At this point you should be able to launch the .exe and run the sample code above.

RSA: Encrypting in JavaScript and Decrypting in PHP

Monday, October 27th, 2008

I came across a really cool JavaScript library a while back that allows you to do client-side RSA encryption & decryption. If you don’t know much about RSA, read up on it here. Essentially, it’s an algorithm for public/private key encryption/decryption. You encrypt using the Public Key and Modulus and decrypt using the Private Key and Modulus.

More often than not, you would want to encrypt client-side (browser) and decrypt server-side. For my purposes, I wanted to encrypt passwords 13 characters or less in JavaScript and decrypt them in PHP. The creator of the RSA library also created a convenient Windows application that generates RSA keys for you. Using that program I created the following 128bit keys:

e = Public Key = 553799486327459813656784787218239817 (6aa86c39bbe678f7f7967a587a1149)
d = Private key = 1401845535567450041611005523755666809 (10dfc5235ef69148bdfdc6832860d79)
m = Modulus = 11570601966616835094916890432003700913 (8b46ab8a951615d07b66bdd2420f8b1)

By default the RSA library outputs encrypted strings in Hex, but because there is no built in PHP function to convert from Hex to Binary, I decided to modify the RSA.js file slightly (within the encryptedString function – line 62) to output the full Integer:

var text = key.radix == 16 ? biToDecimal(crypt)/*biToHex(crypt)*/ : biToString(crypt, key.radix);

The Key Generator also conveniently creates the JavaScript needed to create the new key pair. I’ve removed the decryption (Private) key:

setMaxDigits(19);
key = new RSAKeyPair(“6aa86c39bbe678f7f7967a587a1149″, “”, “8b46ab8a951615d07b66bdd2420f8b1″);
w.value = encryptedString(key,”1234567890123″ + “\x01″);

The trick here is adding “\x01″ to the end of the string you want to encrypt. The PEAR library used to decrypt the encrypted string will look for this, and throws an error if not found. That said, the above encrypted string is:

1276850306890326374886324100793107985

The PEAR package used to decrypt this string is called Crypt_RSA. You install this just like any other PEAR package:

PEAR install Crypt_RSA

The package depends on one of 3 Math libraries (BCMath, GMP or big_int), of which BCMath is slowest but installed by default (at least in my version of PHP 5.2.0-8+etch13 & XAMPP-win). The Crypt_RSA package works in binary, so the keys and encrypted text needs to be converted before calling the package. The following code converts the Integers to Binary and performs the decryption. Bare in mind your dealing with very large numbers, so you need to use one of the above Math libraries to work with them.

require_once ‘Crypt/RSA.php’;
//
$wrapper_name = “BCMath”;
$math_obj = &Crypt_RSA_MathLoader::loadWrapper($wrapper_name);
//
$d = $math_obj->int2bin(“1401845535567450041611005523755666809″);
$m = $math_obj->int2bin(“11570601966616835094916890432003700913″);
//
$pk = new Crypt_RSA_Key($m, $d, “private”, $wrapper_name);
$rsa_obj = new Crypt_RSA;
$rsa_obj->setParams(array(‘dec_key’ => $pk));
$dec = $rsa_obj->decryptBinary($math_obj->int2bin(“1276850306890326374886324100793107985″));
echo $dec;

You can download my sample code here.

Installing PECL Filter on Debian Linux (AMD64)

Thursday, October 9th, 2008

I just finished installing the PECL filter extension on my Debian install (AMD64), and by God, it was painful. This process was so brutal with so many ways to screw up. Here are the steps:

Install php5-dev package. During the process of not figuring out what was going on, I also installed the php4-dev package. I’m pretty sure I only needed php5-dev.

Download PCRE – Perl Compatible Regular Expressions from here. Be sure you have g++ installed (not just gcc as I was getting a strange error). Extract, configure, make and make install. If your running AMD64, be sure to compile using the -fPIC flag. See here for information why. Filter will look for the library in /usr/local/, so you’ll have to setup a link or copy it from /usr/local/lib/ so the PECL install can find it.

The Filter package also looks for the header files from the php_pcre extension. The only way I found to get at these header files was to download the PHP source. So depending on what version of PHP you have, go here and download the zip. Copy the entire pcre folder from within the extension (ext) folder into your /usr/include/php5/ext/ folder.

Now it’s time to attempt the actual PECL Filter install. If your doing this from the tarball and your running on an AMD64, as above, be sure to add the -fPIC flag. I used the PECL installer, so I ran: pecl install filter. Since the package is not “stable” yet, you’ll need to point right to the channel, just read the message, in my case it was: pecl install channel://pecl.php.net/filter-0.11.0. After the compile/install is done, add extension=filter.so to your php.ini file. If you can’t find it, type find / -name php.ini from your command line.

That’s it. I may have forgotten a step, if so add a comments please.

Book: Learning PHP Data Objects

Thursday, January 3rd, 2008

I don’t normally read programming books, but I was given a copy of Learning PHP Data Objects as a gift a couple moths ago and thought I’d give it a spin. Though the book is a beginners guide to PDO, it requires the reader to have a working knowledge of PHP and Data Abstraction. I was particularly curious to read more about PDO, since I had always used the PEAR database abstraction classes (now MDB2), and wanted to see what, if anything, I was missing out on. I know what your thinking: PDO is compiled into PHP and the PEAR classes need to be parsed just like any other PHP script. This is true, but when you add in op-code level caching like APC, I doubt the speedup is THAT drastic (though I could be very wrong). As a beginners guide, I would have liked the book to compare the various database abstraction options available (i.e. PDO, MDB2, ADOdb, etc.) and provided a quick cost/benefit analysis. Overall the book is a good beginners guide and will teach you what you need to know to get started quickly.

Apache SVN: Auto-Update Server Copy on Commit

Tuesday, October 2nd, 2007

It seems these days I spend more time figuring out annoying problems rather than doing any real work. The following gave me another big headache: Getting SVN to auto-update properly via it’s hooks. SVN provides you the ability to specify hooks after certain events occur with the repository in question. For example, say you have a live application under version control and you want SVN to auto-update the live copy (on the server) after every commit. I guess that makes sense, especially if you want to develop and test in your production environment, on the fly.

In the directory were your repository exists there should be a hooks directory. This directory includes a bunch of tmpl files that get called depending on the event that just occurred. The file that I cared about was the post-commit.tmpl file.

Assuming your running SVN using Apache, then most likely when your post-commit script gets called it will be executed using the user www-data. This is important because it means your repository needs to be checked out via the same user to ensure no permission problems occur. Assuming that OK, then add the following line to your post-commit.tmpl file:

Code:
/usr/bin/svn update <path to repo> --username xxxxxx --password xxxxxx

Hardcoding your SVN password in the file is probably not the best idea, but if your the only one who would have access to the file, then who cares. Notice how I put a full path to SVN, that’s because when the hook scripts get called all environment variables, etc. get removed. The next step, and probably most important, rename the file removing the .tmpl extension and change the ownership so the file becomes executable.

Code:
cp post-commit.tmpl post-commit
chmod +x post-commit

That should do it, so when you update your repository, SVN should auto-update your live working copy. Most likely you’ll run into problems, if so check the links below for more info.

Helpful Link #1
Helpful Link #2
Helpful Link #3

PHP: Creating a singleton class for serialization

Thursday, September 20th, 2007

There’s a lot of examples on the internet about creating and using the singleton pattern within your application. From the PHP Docs:

The Singleton pattern applies to situations in which there needs to be a single instance of a class. The most common example of this is a database connection. Implementing this pattern allows a programmer to make this single instance easily accessible by many other objects.

Within any class you want to have use the singleton pattern, just add a getInstance method, like I have in the Obj code listing below. To access that instance use the scope resolution operator:

Code:
$obj = Obj::getInstance();

A problem arises when you want to serialize/unserialize that object from the session variable. The issue is this: When unserialize is called, PHP will attempt to reconstruct the object in question – everything will get created properly but the $instance variable will now be NULL because it would be pointing to the object reference from the previous page. So the next time you would call getInstance, the function would return an entirely new object with no serialized data. All the serialized data would exist in whatever variable the unserialize function was assigned to initially.

Once you understand this subtlety fixing the problem is easy. Create a function called setInstance, which takes in an object reference and assigns the object to the static $instance variable within the class. So once you unserialize your object from the session, immediately call setInstance to let the class know the object exists:

Code:
$obj = unserialize($_SESSION['SessionControl']);
$obj->setInstance($obj);

The full code listings follow so you can test this out. Someone out there is probably thinking, why not just called setInstance from within the __wakeup function with $this as a parameter? That won’t work, the object won’t serialize properly.

Code:
<?php

class Obj {

        static private $instance = NULL;

        public function __construct() { }

	public static function getInstance() {
		if (self::$instance == NULL) {
			self::$instance = new Obj;
		}
		return self::$instance;
	}

	public function setInstance($o) {
		self::$instance = $o;
	}

        public function __sleep() {
		return array_keys(get_object_vars($this));
       }

       public function __wakeup() { }
}

?>

Code:
<?php

require 'obj.class.php';

session_start();

if(!empty($_SESSION['SessionControl'])) {
        $obj = unserialize($_SESSION['SessionControl']);
	$obj->setInstance($obj);
} else {
	$obj = Obj::getInstance();
}

function save_session() {
	$obj = Obj::getInstance();
	$_SESSION['SessionControl'] = serialize($obj);
}

register_shutdown_function(save_session);

?>

PHP: Including scripts from within class functions

Monday, September 3rd, 2007

This surprised me today: Using PHP you can include scripts from within a class functions and access the included functions globally. It seems to go against what I feel should occur, but it works perfectly fine. I posted on a discussion forum at devshed to see if anyone has some insight into this.

temp_include.php:

Code:
<?php

function test_funct() {
    echo "function called";
}

?>

Main script:

Code:
<?php

Class ABC {
    public function __construct() {
        require_once 'temp_include.php';
    }
}

$o = new ABC();
test_funct();

?>

PHP: Caching is my new best friend

Wednesday, August 29th, 2007

This time around, I’m being proactive about programming caching into my code. From what I’ve read APC has the best performance in terms of object level caching, but Memcache is built with a distributed architecture allowing it to scale with relative ease. In any case, I’m using Memcache for object level caching, APC for opcode level caching and XDebug for performance profiling. I use http_load to test multiple fetches in parallel and to see the throughput of the server. I ran my code using 5 parallel fetches for a total of 1000 fetches on my development server. The improvement I found through APC (for opcode caching only) was drastic:

Without APC (comment out extension=apc.so):

Code:
root@fork:/home/dev/http_load# ./http_load -parallel 5 -fetches 1000 url_file
1000 fetches, 5 max parallel, 451000 bytes, in 58.1554 seconds
451 mean bytes/connection
17.1953 fetches/sec, 7755.09 bytes/sec
msecs/connect: 0.154203 mean, 47.333 max, 0.031 min
msecs/first-response: 290.431 mean, 13220.5 max, 57.739 min
HTTP response codes:
  code 200 -- 1000

With APC (add extension=apc.so):

Code:
root@fork:/home/dev/http_load# ./http_load -parallel 5 -fetches 1000 url_file
1000 fetches, 5 max parallel, 451000 bytes, in 10.8297 seconds
451 mean bytes/connection
92.3386 fetches/sec, 41644.7 bytes/sec
msecs/connect: 0.107207 mean, 0.205 max, 0.031 min
msecs/first-response: 54.0108 mean, 6252.95 max, 10.541 min
HTTP response codes:
  code 200 -- 1000

Fetches per second went from 17.2 to 92.3 and milliseconds per first response went from 290.4 to 54.0. Granted these results are somewhat anecdotal because their based on my application and my current dev-server setup. Any way you look at it though the improvement is very impressive.

PHP: Double quotes vs Single quotes

Monday, August 20th, 2007

If you don’t know the difference between using double quotes or single quotes when using Strings in PHP, you should. The PHP Manual page for Strings covers all this, but basically strings created with double quotes will be parsed by PHP. This example shows what I mean:

Code:
// they both output 'Yahoo Google'
$str = 'Google';
echo 'Yahoo' . $str;
echo "Yahoo $str";

At first glance you might think, who cares? The key note is deep in the manual page:

Parsing variables within strings uses more memory than string concatenation. When writing a PHP script in which memory usage is a concern, consider using the concatenation operator (.) rather than variable parsing.

PHP: Sorting an array of objects by a method/member

Sunday, August 19th, 2007

Earlier today I was looking to sort an array of object by a given datetime member. After a couple quick tests I realized that PHP was capable of comparing MySQL DATETIME strings natively. I guess that makes sense, since all they really have to do is convert the string to a timestamp via strtotime. In any case, after a bit of searching I found a method similar to the one below in a comment posted on the PHP Sort manual page. I changed the method a bit to compare using a public class method, sort in descending order and return a new array. I hard coded the function name cause from what I know you can’t pass in the function name as a parameter (you can if it was a key).

Code:
function sortObject($data) {
	for ($i = count($data) - 1; $i >= 0; $i--) {
		$swapped = false;
		for ($j = 0; $j < $i; $j++) {
			if ( $data[$j]->getTime() < $data[$j + 1]->getTime() ) {
				$tmp = $data[$j];
                $data[$j] = $data[$j + 1];
                $data[$j + 1] = $tmp;
                $swapped = true;
			}
		}
		if (!$swapped) {
			return $data;
		}
	}
}

Here is an example of the above function sorting four datetime strings:

Code:
class DObject {

	private $dt;

	public function __construct($d) {
         $this->dt = $d;
    }

	public function getTime() {
		return $this->dt;
	}

}

$date1 = '2009-08-05 03:24:57';
$date2 = '2006-08-05 23:44:56';
$date3 = '2007-08-05 20:14:56';
$date4 = '2005-02-10 12:24:06';

$toSort = array();

array_push($toSort, new DObject($date1));
array_push($toSort, new DObject($date2));
array_push($toSort, new DObject($date3));
array_push($toSort, new DObject($date4));

echo "<pre>";
print_r($toSort);
$result = sortObject($toSort);
print_r($result);
echo "</pre>";

Object-Oriented Features in PHP 5

Thursday, August 2nd, 2007

I came across a good writeup on linuxjournal.com talking about the OO features/capabilities in PHP 5. I guess the most important thing to note is that objects (i.e. objects of classes, not arrays) are now passed by reference. The writeup, which was taken from the book Object-Oriented PHP by Peter Lavin, explains their rational for doing this and how the entire scripting engine underlining PHP had to be re-written. All other types of variables are still passed by value (including arrays), but you can easily pass them by reference by adding an & to the function definition.

Code:
$tempArr = array();
$tempArr[0] = 1982;
$tempArr[1] = 2007;

echo "originally:";
printArray($tempArr);
tempFunc2($tempArr);
echo "after chang in function";
printArray($tempArr);

function tempFunc2(&$obj) {

	$obj[0] = 1;
	$obj[1] = 2;
}

function printArray($ta) {
	echo "<pre>";
	print_r($ta);
	echo "</pre>";

}

A closer look into what Google Adwords charges..

Saturday, March 10th, 2007

This is interesting, though on a small scale, it’s still relevant. I run Google Adwords (display ads on specific search terms with Google) for a service I created called WebBasedCron. A little while ago, I decided to start tracking what ads were really being clicked — i.e. when an advertisement was clicked, it would send them to a special URL (ex. www.webbasedcron.com/?action=advwbc). Then I would use PHP code like the following to record the persons IP, the date, time and their session id.

Code:
if ( $read_action == "advwbc" ) {
        $session = $user->db->quote(session_id());
        $ip = $user->db->quote($_SERVER['REMOTE_ADDR']);
        $date = $user->db->quote(date("'Y-m-d'"));
        $time = $user->db->quote(date('H:i:s'));
        $sqlInsert = "INSERT INTO webcron_google(date,time,ip,session) VALUES ($date,$time,$ip,$session)";
        $user->db->query($sqlInsert);
}

The session is important because it tells me if the advertisement was clicked in the same instance of the browser or not (when you restart your browser, you get a new session). So lets look at the results for March 9th (yesterday). I changed nothing, only hid half the persons IP address.

Code:
2007-03-09  	04:16:35  	211.30.xxx.xx  	9fbe09012f7ce71854b1e040b0b0fe3c
2007-03-09 	04:17:20 	211.30.xxx.xx 	9fbe09012f7ce71854b1e040b0b0fe3c
2007-03-09 	04:17:47 	211.30.xxx.xx 	9fbe09012f7ce71854b1e040b0b0fe3c
2007-03-09 	04:18:07 	211.30.xxx.xx 	9fbe09012f7ce71854b1e040b0b0fe3c
2007-03-09 	04:18:10 	211.30.xxx.xx 	9fbe09012f7ce71854b1e040b0b0fe3c
2007-03-09 	04:18:13 	211.30.xxx.xx 	9fbe09012f7ce71854b1e040b0b0fe3c
2007-03-09 	04:18:34 	211.30.xxx.xx 	9fbe09012f7ce71854b1e040b0b0fe3c
2007-03-09 	04:18:36 	211.30.xxx.xx 	9fbe09012f7ce71854b1e040b0b0fe3c
2007-03-09 	07:47:21 	193.71.xx.x 	 1895a2c135512d29fa7002e6a8126e65
2007-03-09 	09:51:47 	59.144.xxx.xxx 	b5c090a6f26d83b890fd2faf79c17464

You may notice, that the first 8 lines were from the same IP address (211.30.xxx.xx) clicked between 04:16:35 to 04:18:36. It’s easy to see this person got pretty click happy, clicking the ad, hitting back, clicking the ad, etc. Furthermore, the session id is the same for all the 8 clicks, which means he/she was using the same browser instance. I would expect Google to be able to detect this type of click fraud and only charge me for 2 legitimate clicks, the ones placed at 07:47:21 and 09:51:47 (not from 211.30.xxx.xx). The strange part, is I was charged for 4 clicks that day. That means of the 8 illegitimate clicks, Google charged me for 2 of them.

Believe me, at an average cost per click of $0.11, it really doesn’t matter, but I can imagine for larger companies, Google must be making a fortune off these people. Its really hard to side with Google on this, I mean if they deemed 6 of those clicks illegitimate, why not the remaining 2?? Do people really click the same advertisement 8 times in 2 minutes, with many of them 3 seconds apart? It’s easy to see that even through blatant click fraud Google shamelessly makes money.

Using PEAR on a shared host

Tuesday, February 27th, 2007

I’ve had a couple people ask this question and I thought I’d post a quick answer. I had this problem also, back when I made StockBoulevard (now I manage my own server, Ubuntu Linux). I used PEAR to develop my application on my laptop, but when the time came to go live, my hosting provider didn’t have PEAR installed. What you need to remember, is that PEAR is just PHP scripts, there’s no compiled DLLs, etc. So all you need to do, is download the PEAR source files from pear.php.net and FTP them to your webserver. Just put everything in a directory called PEAR in your root and explicitly point your include:

Code:
<?php

// require_once("MDB2.php");
require_once("../../PEAR/MDB2.php");

....
......

?>

Friday wrap-up #2

Friday, October 20th, 2006

CASCON was running this week from Monday to Thursday. I didn’t realize this was a free conference for everyone, including people outside IBM. I saw a bunch of professors from McMaster also, some were giving talks.

I went to the Ruby on Rails workshop and it was going OK until one of the presenters starting comparing Ruby on Rails (RoR) to PHP. People need to get something straight: Ruby is the programming language and Rails is a framework. Comparing RoR to PHP isn’t fair, since PHP is the programming language with no framework. If you’re going to compare them, make sure your using some type of template engine like Smarty, etc.

Tuesday, Wednesday and Thursday I had a display at the Technology Showcase from 11:30am to 1:30pm, presenting WDSC and JWL. The experience was good, I met a lot of great people really interested in AJAX and JSF.

Doug Crockford is a smart guy, he’s a JavaScript guru and the creator of JSON. Earlier this year he wrote a paper describing JSONRequest, here’s the abstract:

XMLHttpRequest has a security model which is inadequate for supporting the next generation of web applications. JSONRequest is proposed as a new browser service that allows for two-way data exchange with any JSON data server without exposing users or organization to harm. It exchanges data between scripts on pages with JSON servers in the web. It is hoped that browser makers will build this feature into their products in order to enable the next advance in web application development.

It’s a good read and not a bad idea (though received a lot of criticism). There has also been heavy discussion about this on the Dojo mailing list. Doug’s personal website is filled with great JavaScript articles and resources – especially this article on objects in JavaScript.

I read Chapter 5 of Roy Fielding’s dissertation, which introduced REST or Representational State Transfer. REST is a lot like the Relational Database, conceptually its easy, but has the potential to revolutionize how we do certain things.

How To: Making a PHP REST client to call REST resources

Friday, October 20th, 2006

A lot of companies these days (including Amazon and Yahoo!) are exposing their web services in the form of REST resources. At a high level REST is pretty easy to understand, all you’re doing is exposing a web service in the form of a URL. Users can then query this URL, through HTTP methods like GET and POST. REST calls generally return some type of XML or Object Encoding like JSON.

An example would be Yahoo!’s Geocoding API, with the following URL:

Code:
http://api.local.yahoo.com/MapsService/V1/geocode?appid=YahooDemo&street=701+First+Street&city=Sunnyvale&state=CA

I would get:

Code:
<Result precision="address">
   <Latitude>37.416384</Latitude>
   <Longitude>-122.024853</Longitude>
   <Address>701 FIRST AVE</Address>
   <City>SUNNYVALE</City>
   <State>CA</State>
   <Zip>94089-1019</Zip>
   <Country>US</Country>
</Result>

So Yahoo! exposes the Geocode URL and allows you to query this resource using URL parameters like appid and street. Dynamically building your URL to query a given resource is OK, generally that’s what people do, like the following:

Code:
$base = 'http://xml.amazon.com/onca/xml3';
$query_string = "";

$params = array( 'ManufacturerSearch' => "O'Reilly",
    'mode'  => 'books',
    'sort'  => '+salesrank',
    'page'  => 1,
    'type'  => 'lite',
    'f'     => 'xml',
    't'     => 'trachtenberg-20' ,
    'dev-t' => 'XXXXXXXXXXXXXX' ,
);

foreach ($params as $key => $value) {
    $query_string .= "$key=" . urlencode($value) . "&";
}

$url = "$base?$query_string";
$output = file_get_contents($url);

The problem here, is that REST is meant to take advantage of HTTP methods GET, POST, PUT, DELETE, etc.. When people are showing examples which dynamically build queries and call file_get_contents, the average user doesn’t appreciate (understand) what type of request is being made. Do they care? Should they care? We’ll that’s another story. Eventually though, more intense REST resources will become widely available, and it will be critical the user (developer) understands if their making a POST or PUT request.

I came across a great PEAR package the other day called HTTP_REQUEST, which among many things supports GET/POST/HEAD/TRACE/PUT/DELETE, basic authentication, proxy, proxy authentication, SSL, file uploads and more. Using this package, I got started on a simple wrapper class called RESTclient, which gives intuitive support for making REST resource calls.

So if I was going to use RESTclient to call the Geocode API above, it would look like this:

Code:
<?php

require_once "RESTclient.php";

$rest = new RESTclient();

$inputs = array();
$inputs["appid"] = "YahooDemo";
$inputs["street"] = "701 First Street";
$inputs["city"] = "Sunnyvale";
$inputs["state"] = "CA";

$url = "http://api.local.yahoo.com/MapsService/V1/geocode/"
$rest->createRequest("$url","POST",$inputs);
$rest->sendRequest();
$output = $rest->getResponse();
echo $output;

?>

At this point, you might be thinking, who cares — what’s the difference between using a loop to dynamically generate the URL or RESTclient. There are lots of reasons, first off, I can easily call the Geocode resource again using another address just by changing:

Code:
$inputs["street"] = "1600 Amphitheatre Parkway";
$inputs["city"] = "Mountain View";

There was no need to re-generate the URL. Furthermore, I’m explicitly specifying a POST request. Just as easily I can make a PUT, DELETE, etc. request on a given resource by changing the createRequest method parameters.

The class below is RESTclient. Note, I put this together to prove a point, it still needs some work if you plan on using it.

Code:
<?php

require_once "HTTP/Request.php";

class RESTClient {

    private $root_url = "";
    private $curr_url = "";
    private $user_name = "";
    private $password = "";
    private $response = "";
    private $responseBody = "";
    private $req = null;

    public function __construct($root_url = "", $user_name = "", $password = "") {
        $this->root_url = $this->curr_url = $root_url;
        $this->user_name = $user_name;
        $this->password = $password;
        if ($root_url != "") {
            $this->createRequest("GET");
            $this->sendRequest();
        }
        return true;
    }

    public function createRequest($url, $method, $arr = null) {
        $this->curr_url = $url;
        $this->req =& new HTTP_Request($url);
        if ($this->user_name != "" && $this->password != "") {
           $this->req->setBasicAuth($this->user_name, $this->password);
        }        

        switch($method) {
            case "GET":
                $this->req->setMethod(HTTP_REQUEST_METHOD_GET);
                break;
            case "POST":
                $this->req->setMethod(HTTP_REQUEST_METHOD_POST);
                $this->addPostData($arr);
                break;
            case "PUT":
                $this->req->setMethod(HTTP_REQUEST_METHOD_PUT);
                // to-do
                break;
            case "DELETE":
                $this->req->setMethod(HTTP_REQUEST_METHOD_DELETE);
                // to-do
                break;
        }
    }

    private function addPostData($arr) {
        if ($arr != null) {
            foreach ($arr as $key => $value) {
                $this->req->addPostData($key, $value);
            }
        }
    }

    public function sendRequest() {
        $this->response = $this->req->sendRequest();

        if (PEAR::isError($this->response)) {
            echo $this->response->getMessage();
            die();
        } else {
            $this->responseBody = $this->req->getResponseBody();
        }
    }

    public function getResponse() {
        return $this->responseBody;
    }

}
?>

How to: Building dynamic PHP objects and URL decoding example

Friday, October 6th, 2006

PHP is a powerful programming language; with every release I’m amazed with the new power and functionality. The ability to build dynamic objects (the way I’ll describe) I believe was introduced as of PHP 5. In any case, this is good stuff. There are lots of reasons why you would want to create a dynamic object, in my example below; you’ll see how to create a dynamic object of the URL query parameters.

At the heart of building dynamic PHP objects are the PHP Magic Methods. From the PHP manual:

The function names __construct, __destruct (see Constructors and Destructors), __call, __get, __set, __isset, __unset (see Overloading), __sleep, __wakeup, __toString, __set_state, __clone and __autoload are magical in PHP classes. You cannot have functions with these names in any of your classes unless you want the magic functionality associated with them.

Magic methods get called automatically through the parser when certain events occur. For example, just before you transition pages all objects you’ve created will call their respective __sleep methods. Most likely you wouldn’t have this method defined, so nothing would happen.

So now lets look at what happens when a certain method (or variable) of a given PHP object is called. Take the following code as an example:

Code:
$x = new obj();
$x->method1();
$x->var1;

When a method of a given object is called (like method1) the first thing the parser does is look for the associated name in the instance of the object. If the method is found, then the parser gives control to the method. If the method is not found, then the __call function is called with the function name and input values as a parameter. If this function returns true, then the object will return successfully. If a __call function does not exist, the parser will check the parent class (if exists) and so forth.

Similarly, if a variable of an object is requested (like var1) the parser looks in the object for a variable with that name. If a variable does not exist, the __get method is called with the name of the variable as a parameter, and so forth.

You should be able to see how we can now simulate (fake) the existence of methods and variables by using these magic methods. The code below shows the parent class G_Dynamic. This class would be the parent to specific subclasses that would require dynamic method/variable access.

Code:
<?php

/*  */

/**
 *
 * PHP versions 5.1.4
 *
 * George A. Papayiannis
 *
 * This class provides the magic functions needed to create
 * a dynamic object.  Subclasses would extend this object
 * and call the constructor with a parsed array.  See
 * g_url_decode.class.php for an example of creating a
 * dynamic object from the URL query string.
 *
 */

/**
 * Class definition
 */

class G_Dynamic {

    private $param = array();

    public function __construct($init) {
        $this->param = $init;
    }

    private function __get($name) {
        if (isset($this->param[$name])) {
            $res = $this->param[$name];
        } else {
            $res = false;
        }
        return $res;
    }

    private function __set($name, $val) {
        if (isset($this->param[$name])) {
            $this->param[$name] = $val;
            $res = true;
        } else {
            $res = false;
        }
        return $res;
    }

    private function __isset($name) {
        return isset($this->param[$name]);
    }

    private function __unset($name) {
        unset($this->param[$name]);
    }

    private function __call($name, $var) {
        // add code to simulate function call
        // return TRUE for success
    }

}

?>

As an example, I created a subclass to G_Dynamic called G_URL_Decode. This class takes the URL query string as input, parses it into an array and calls the parents (G_Dynamic) constructor. The code for G_URL_Decode is below:

Code:
<?php

/*  */

/**
 *
 * PHP versions 5.1.4
 *
 * George A. Papayiannis
 *
 * This class extends the G_Dynamic class to create a
 * dynamic object of the URL query string.  In another
 * file, you would have:
 *
 * require_once "g_url_decode.class.php";
 * $x = new G_URL_Decode($_SERVER['QUERY_STRING']);
 *
 * then you could have $x->(url param name) to access
 *
 */

require_once "g_dynamic.class.php";

/**
 * Class definition
 */

class G_URL_Decode extends G_Dynamic {

    private $queryParam = array();

    public function __construct($qs) {
         parent::__construct($this->parseURL($qs));
    }

    public function getQueryParam() {
        return $this->queryParam;
    }

    private function parseURL($qs) {
        $this->queryParam = array();
        $qs_parsed = explode("&", $qs);
		foreach ($qs_parsed as $value) {
			$paramVal = explode("=",$value);
			if (array_key_exists(1,$paramVal)) {
				$this->queryParam[htmlspecialchars(urldecode($paramVal[0]))] = htmlspecialchars(urldecode($paramVal[1]));
			}
		}
		return $this->queryParam;
    }
}

?>

Once the URL query string is successfully parsed, you can access the parameter names through the object. The code to bring it all together is below.

Code:
<?php

require_once "g_url_decode.class.php";
$x = new G_URL_Decode($_SERVER['QUERY_STRING']);
echo $x->var1;

?>

Using this as a base, you should be able to build some pretty cool dynamic objects.
Click here to download the source.

The best (free) PHP IDE..

Friday, October 6th, 2006

Update (Mar. 5/07): Eventually you would need some type of version control and I just saw an Eclipse Plugin for SVN.

I’ve been a long time fan of simple development environments. Give me EditPlus with syntax highlighting and I’m good to go. I’ve always known about TM (Target Management) or better known around here at Remote Systems Explorer. As the name says, the plug-in lets you work with remote systems from within Eclipse. So no matter were I am, I can connect to my server (through SSH, FTP, etc.) and work remotely. I can even launch a shell from within Eclipse! The TM group hasn’t released 1.0 yet, but it should be there within a couple weeks.

Along the same lines, a couple weeks ago I got exposed to the PHP IDE project. This is another Eclipse project in the works, being sponsored mainly by Zend with support from IBM and others. Their in version 0.7 right now, and expect to release version 1.0 sometime near the end of the year. The plug-in is stable, but I’ve noticed small problems ranging from automatic formatting issues to context assist errors. Aside from the small problems, this plug-in is amazing — The syntax highlighting is great, there is automatic publishing and an integrated debugger in the works.

With RSE + PHP IDE almost at 1.0, I have a feeling Eclipse will soon be my development environment of choice — If only Eclipse loaded as fast as EditPlus. Here are some screen shots of Eclipse + TM + PHP IDE. Click the image to see a large shot.


Eclipse


Eclipse