A couple of months back I read an article, perhaps you read it too (link at the end). Like all great articles, this one forced me to re-evaluate my thinking. The central premise was that unlike most other languages PHP suffers from bad design. So much so that it is actually systemic. Many test cases and examples were presented to prove the point. There were a few that I was already familiar with, and several that I hadn’t encountered. Here’s just a sample of the more basic ones:

echo phpversion()."\n";
if (NULL < -1) echo "NULL is less than -1\n";
if (NULL == 0) echo "NULL is somehow also equal to 0\n";
if ("foo" == TRUE) echo "the string foo equals TRUE\n";
if ("foo" == 0) echo "the string foo also equals 0\n";
if (TRUE != 0) echo "but TRUE doesn't equal 0\n";

The above outputs:

5.3.14
NULL is less than -1
NULL is somehow also equal to 0
the string foo equals TRUE
the string foo also equals 0
but TRUE doesn't equal 0

At first, I reacted to the article with a little incredulity. The tone of it reads with exasperation and annoyance, which is somewhat off-putting. But after giving it a little rest and returning to it, I had to admit that the author had many good points. The end result, for me, was that I was spurred on to begin re-evaluating other languages again – something I had been meaning to do for some time.

If you look at it honestly, there’s a number of things about PHP that just don’t make any sense. Some are quirks, unexpected behavior – some are downright bugs. One can make solid applications with PHP, developers are doing so all the time, including those at respected companies. I’ve personally written many applications in PHP that have proved efficient and stable – several I’m quite proud of. There’s also certainly something to be said for the ease with which PHP can be adopted and implemented. Still, all things considered, one has to wonder if the web would be more productive as a whole if developers weren’t silently working around PHP’s oddities.

Conclusion? I’m not certain that abandoning PHP entirely is necessary. However, learning another language in addition to PHP can only improve your abilities as a developer. Analyzing and understanding how other languages work will help you to think more via design principle and not simply as a PHP implementor. If you choose to use PHP, do so with knowledge. Get to know its flaws and idiosyncrasies and learn to avoid them (but do so with the realization that such quirks aren’t normal). Use a well-designed PHP framework, one that knows what it’s about and has shaped itself through strong design principles. One such framework is Lithium.

Here’s the article that got me thinking: PHP: a fractal of bad design

Posted in PHP.

It’s been a busy few months. I’ve been busy at Totsy.com a startup that is taking the best of Lithium, MongoDB, and my time. If you want the lowdown of what I’m talking about check out the slides from Mitch Pirtle CTO at Totsy.

To the business at hand – Simple ACL with Lithium and MongoDB. Let’s get down to it!

The heart of this ACL functionality rests with the Li3 Router. With the Router you can connect a URL to a specific Class::action. This is handy when you want to have /login route to app\controllers\UsersController::login(). Of course, your routes can become more complicated and robust with a little regex goodness. Take a look at a default route for example:

    Router::connect('/{:controller}/{:action}/{:args}');

When going to the url http://yourapp.com/users/login/23423  then Li3 will map to the users controller, login action (or method) and pass the 23423 as the argument or input to that method. That’s all we really need to hookup a route and in turn give us the ability to setup ACL.

If there are no routes hooked up in your app/config/routes.php then requests will go nowhere. Without some default routes hooked up you’ll app will be sitting dead without anywhere to go. This is the perfect time to setup an ACL. How do we get this all going?

The next step is to call upon MongoDB. With MongoDB we can embed an array with the route and parameter into a mongo document object. I would embed this array with the user document that has the information used for login. This way, you can pull the entire document with one query from Mongo. Too simple to be true? Lets take a step by step approach:

1) Hook up your code in routes.php to loop through the document pulled from your user document:

    if (isset($session['acls'])) {
        foreach ($session['acls'] as $acl) {
             Router::connect($acl['route'], $acl['connection']);
        }
    }

Note that in my app I put the necessary information in the users session after they successfully login.

2) Embed the acls array as part of your user document schema. For example:

{
    "_id" : ObjectId("4c8d53e0ce64e5f449992400"),
    "created_date" : "Sun Sep 12 2010 18:27:44 GMT-0400 (EDT)",
    "email" : "test@lcs.com",
    "firstname" : "Test",
    "lastname" : "User",
    "password" : "b119670d125b0bd4271b25ce39fa166bc3bf79a0",
    "acls" : [
        {
            "route" : "/orders",
            "connection":"Orders::index"
        },
        {
           "route" : "/posts/view/{:item:[a-z0-9\-]+}",
           "connection":"Events::view"
        }
    ]
}

3) Do a happy dance because you’re done!!!

With a little admin work you can put together groups and more robust configurations for ACL.  At the end of the day all you really need to do is loop through an array of routes and connections and you’ll have the exact ACL you need to keep everyone in order.

Posted in PHP.

If you were like me you started throwing all kinds of files into MongoDB with GridFS. When you took a look at the db.fs.files collection you saw something like this for a document:

	
{
	"_id" : ObjectId("4c40affcce64e5e275c60100"),
	"filename" : "My First File.jpg",
	"uploadDate" : "Fri Jul 16 2010 15:16:12 GMT-0400 (EDT)",
	"length" : 55162,
	"chunkSize" : 262144,
	"md5" : "46aa378be7f6f1f3660efd7de5c1cbb6"
}

Did you see the MD5 hash? It’s there for a reason you know.

Since my PHP/MongoDB application has an administrative backend multiple people are loading up files. There is always a possibility that they will upload the same file. Of course this would be a very inefficient use of storage especially when the file is a video or picture.  That’s where the MD5 field in fs.files comes in handy.

In PHP you can use the md5_file() method to get the MD5 hash before you save the file to MongoDB. Running a findOne query using the md5 of your tmp file will let you know if  a document for that file already exists. If it does exist, then you’ll get back the fs.files document of the preloaded file. Then you can use the _id as a reference and don’t bother saving the file. Can you imagine all the money you save in storage fees on Amazon S3?

This is a very common and reliable way of doing things since byte for byte you know the files are the same. The sample script below is a snapshot of code in a Lithium application (Lithium is a new PHP 5.3+ framework). I’m basically running a findOne({“md5″ : “$md5″}) query:

	
protected function write() {
		$success = false;
		$grid = File::getGridFS();
		$this->fileName = $this->request->data['Filedata']['name'];
		$md5 = md5_file($this->request->data['Filedata']['tmp_name']);
		$file = File::first(array('conditions' => array('md5' => $md5)));
		if ($file) {
			$success = true;
			$this->id = (string) $file->_id;
		} else {
			$this->id = (string) $grid->storeUpload('Filedata', $this->fileName);
			if ($this->id) {
				$success = true;
			}
		}
		return $success;
}

It was great attending and presenting at the MongoSF conference held on April 30th in San Francisco – Thanks 10Gen.

Speaking with a few attendees I got the feeling that many are moving forward with Mongo production deployments. I should be a bit more specific with my relative use of the word “many”:

“The Many” – Yes, I’m slightly jealous that the Ruby community was there in full force and vocal.

“The Other Many” – There were also folks who were jumping right in with Java and Python.

“The Not So Many” – I’m not getting the same feeling of enthusiasm from the PHP community.

The PHP community doesn’t seem as audible with their success/concerns/thoughts of using Mongo. There are developers using it and liking it. But it doesn’t “feel” as accepted yet. Maybe this feeling has some substance to it. According to a survey taken back in February 2010 the Ruby community dominated by representing 40% of the MongoDB users. We’re even outnumbered by Python users. Of course, one can question how far reaching this survey was. However, considering the fact that several PHP frameworks have made a concerted effort to work with MongoDB I’m wondering why I’m not hearing more about it. I’m probably not listening in the right circles.

With all that said and MongoSF in the past it’s time to prepare for MongoNY. I’m going to ask those aforementioned questions to the PHPers who show up so be ready! During the talk I’ll also discuss each framework and what it’s doing with MongoDB. Come to MongoNY and lets have a lively PHP/MongoDB discussion. You can use the discount code “lightcube” at checkout for a 25% discount.

In the meantime, here are my slides from the MongoSF presentation:

(Yes, my JSON business card on slide #2 validates!)

You’ve quickly put together the code to add files to MongoDB. But as any good DBA and Developer knows not everything that goes into your database should stay there.

Yes, you already figured out it’s as simple as $grid->remove(). Here is something to keep in mind. MongoGridFS::remove won’t present you with a message if it failed. To ensure that a file was successfully removed use MongoDB::lastError(). You’ll get an array in return that will show something along the lines of:

Array
(
[err] =>
[n] => 0
[ok] => 1
)

The field to watch is “ok”. The value of “1″ will return if the remove went according to plan. Otherwise you’ll get “0″ and it’s time to check the value for “err”. If you’re wondering about the “n” its the number of files that were removed.

In practice your code could look something like this:

/*
* function remove
* Removes files from Mongo and captures error if failed
* @return mixed
*/
public function remove($criteria)
{
    //Get the GridFS Object
    $grid = $db->getGridFS();

    //Setup some criteria to search for file
    $id = new MongoID($criteria['_id']);

    //Remove file
    $grid->remove(array('_id'=>$id), true);

    //Get lastError array
    $errorArray = $db->lastError();
    if ($errorArray['ok'] == 1 ) {
        $retval = true;
    }else{
        //Send back the error message
        $retval = $errorArray['err'];
    }

    return $retval;
}

With a few short lines you’re able to capture the error message and do whatever you like with it. Hopefully this helps keep your fs.files and fs.chunks collections clean.

As a quick followup to the last blog, I was asked to demonstrate how we could include some metadata with a file during or after upload.

This is a great feature since we can retrieve all the information attached with a document during a query. Including metadata (i.e. comments, dates, votes or WHATEVER ELSE YOU LIKE) is just as easy.

We are going to pick up the ball at the point where you’ve got all your MongoDB objects ready:

<?php
$date = new MongoDate();
//Use $set to add metadata to a file
$metaData = array('$set' => array(“comment”=>”MongoDB is awesome”, “date”=>$date));

//Just setting up search criteria
$criteria = array('_id' => $id);

//Update the document with the new info
$db->grid->update($criteria, $metaData);

?>

Did you break a sweat? Probably not.

BTW, I threw in a little extra tidbit there with MongoDate(). It’s a better way of inputting a date into Mongo instead of using PHP date with a bunch of formatting to get it just right.

Live from NYC!

It was a wonderful day at the Joomla! Developer Conference.  Keeping to my usual style I took notes in MindMap. I’m sharing as a PDF in case you would like to click some of the links or copy some of the text. Feel free to share any comments.

Joomla Dev ConferenceNYC

A big thanks to all who organized and presented. We at LCS really enjoyed it and look forward to the next few months as Joomla! 1.6 becomes solidified.

I was looking around for concepts in building a reasonably secure HTML login form without using SSL, and I came across an interesting article (link at end of post). The concept it outlines is fairly simple, and I’m a little annoyed that I didn’t think of this myself earlier.

Essentially, the idea is that the password never actually leaves the client machine. Instead, the client sends a cryptographic hash of the password. For other security reasons, we also don’t want the server to store the password in plain text, so it should only store the hash value of the password.

Of course, this alone isn’t enough, because anyone scanning the wire could simply capture the hash and send that along to the server and authenticate. What we need is a way for the server and the client to agree that they have the same hash value for the password, without actually sending it. To accomplish this, we can set up the server to generate a random string and send that to the client. Then, both server and client append the password’s hash to the random string and perform a hash sum on the combined string. The client then sends that string to the server and if it agrees with the result the server got, we have a valid authentication.

The article referenced above included some sample code to illustrate this functionality, but I believe I can simplify it even further. It’s not a practical, real world example, because we’re not sending a user name or retrieving a password from a stored location on the server. But it should be enough to illustrate the concept and give a developer a head start in however they wish to implement. Personally, I plan to instantiate the code in a class and use XMLHttpRequest instead of traditional POST methods.

Anyway, on to the example code. Note: This example doesn’t actually look up any stored user login information. Instead it simply uses a pre-defined password: ‘password’.

We’ll need two files. The first file generates the server’s shared key and passes along the value to the client as well as the HTML and JavaScript needed to input a password, generate hash values and submit the form to the server.

Create main.php with the content:

<?php
// We'll use PHP's session handling to keep track of the server-generated key

session_start();

// Function to generate a random key.
// Modified from code found at: http://www.totallyphp.co.uk/code/create_a_random_password.htm

function randomString($length) {
    $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    $str = NULL;
    $i = 0;
    while ($i < $length) {
        $num = rand(0, 61);
        $tmp = substr($chars, $num, 1);
        $str .= $tmp;
        $i++;
    }
    return $str;
}

// Call the function and set the shared key

$key = $_SESSION['key'] = randomString(20);
?>

<html>
  <head>
    <!-- JavaScript that contains the functions which perform the actual hashing -->
    <script type="text/javascript" src="http://pajhome.org.uk/crypt/md5/sha1.js"></script>

    <!-- The following function creates the hash of the concatenated key and password hash
and submits the content to the server via a form -->
    <script type="text/javascript">
	function login() {
		var p = hex_sha1(document.getElementById('pass').value);
		var k = document.getElementById('key').value;
		var h = hex_sha1(k+p);
		var hash = document.getElementById('hash');
		hash.value = h;
		var f = document.getElementById('finalform');
		f.submit();
	}
    </script>
  </head>
  <body>
    <form action="javascript:login()" method="post" >
	<input type="hidden" id="key" value="<?php echo $key; ?>" />
	<input type="password" id="pass" />
	<input type="submit" value="Submit" />
    </form>
    <form action="login.php" method="post" id="finalform">
	<input type="hidden" name="hash" id="hash" />
    </form>
  </body>
</html>

Next we need the file to handle the submitted values and compare the results. Create login.php with the following contents:

<?php
session_start();
$hash = $_POST['hash'];

$pass = sha1('password');
$key = $_SESSION['key'];

$server_hash = sha1($key.$pass);

if ($server_hash == $hash) {
	echo "MATCH!";
} else {
	echo "NO MATCH!";
}
?>

That’s pretty much it. If you want to see a little more fluid example in action, see: http://www.lightcubesolutions.com/~jhuntwork/secure_login/

Referenced article: PHP – Implementing Secure Login with PHP, JavaScript, and Sessions (without SSL)

In my first post (Light it up!), I mentioned that LightCube Solutions has an opportunity to pioneer an open source courseware application. Here are a few more details:
In a nutshell, the idea is to create a web application (at the moment it is powered by PHP and MySQL) that allows High School students to study content on their school’s intranet. Teachers will have access to add/create content and publish tests. When the students take the test, their scores are recorded in their profile. Teachers and other administrators can monitor their progress, scores, course history and so on. We want to keep it open source to allow a wider scope of input and collaboration.

Currently, the project is being organized here: http://www.lightcubesolutions.com/ScribbleAppTrac/
And a demo of the current code is here: http://www.lightcubesolutions.com/ScribbleApp/
The name of the project is likely to change, so stay tuned for more info.
JH