One man's voice Thoughts, rants and commentary of a simple man

14Mar/120

Storing singleton objects in PHP sessions

A friend of mine, who is working on a sick little diet and exercise tracking application, asked me today if PHP allowed singleton objects to be stored in a session. My first reaction was absolutely they can be so long as you follow the normal rules of unserializing objects in PHP... namely, making sure the class is in scope before attempting to create an object from it OR having an autoloader that does that for you. But as I thought about it more I began to think that maybe this might not work as expected since singletons generally have no public access to their constructors. However, when an object is unserialized, it is instantiated, for lack of a better term, and populated with the data that was exposed from the serialization process.

Note: for those of you that are unfamiliar with the way PHP handles session data or are just wondering why I am using serialization so much in my examples, the basics are you start a session and PHP assigns a random MD5 hash to it as an id. You write data to the session via the $_SESSION superglobal array and when the script terminates or you call session_write_close(), PHP serializes the $_SESSION array and writes that serialized string to the file system for retrieval on subsequent requests so long as the session time limit has not expired. I really did not want to run this entire session process and inspect what was being written to my file system so I decided to mock that interaction myself by creating an arbitrary array, serializing it, grabbing the serialized string and unserializing it.

As I began to dig into this I started playing with PHP's magic __sleep() and __wakeup() methods. Those seemed to do what I wanted them to do except in the case of protected and private properties of my class. As soon as I added those in my serialization took a crap all over itself, even when I cloned the singleton object and got the public properties exposed through get_object_vars(). So, thanks to the PHP manual - the most awesome tool in any PHP developer's arsenal - I discovered the PHP Serializable interface.

This interface defines two methods: serialize() and unserialize(). These methods override the default behavior of __destruct() and __construct() so if you are using these you should keep that in mind and either put your __destruct() and __construct() code into these methods or allow for calls to __destruct() and __construct() from within these methods. Your homework is to decide which is the better implementation. :)

Now, back to the lesson at hand... the following object - let's say it is saved to a file named dummy.php - is a super simple implementation of a basic singleton pattern. It includes both a serialize() and unserialize() method to allow it to be serialized for storage.

<?php
class DummySingleton implements Serializable {
    /**
     * Test properties of various visibility
     * @var string
     */
    public $dProp;
    public $dPropX = 'Nothing to see here folks';
    private $_prop = 'y u try see dis?';
 
    /**
     * Instance holder for this singleton
     * 
     * @var DummySingleton
     */
    private static $_instance = null;
 
    /**
     * Private singleton constructor, simply sets a property
     */
    final private function __construct() {
        $this->dProp = 'I was finally built';
    }
 
    /**
     * Singleton instance getter
     * 
     * @return DummySingleton
     */
    public static function getInstance() {
        if (self::$_instance === null) {
            self::$_instance = new DummySingleton();
        }
 
        return self::$_instance;
    }
 
    /**
     * Implementation of the Serializable interface, called automatically just 
     * before this object is serialized. This will override any calls to a
     * __destruct method, so if you have a destructor you should call it from
     * here.
     * 
     * @return string
     */
    public function serialize() {
        return serialize(get_object_vars($this));
    }
 
    /**
     * When this object is created from an unserialization this method is called. 
     * It implements the Serializable interface and uses the data from the 
     * serialize() method herein. Since this method overrides the __construct
     * method you should make any accomodations for constructing your object
     * in this method.
     */
    public function unserialize($data) {
        // "Instantiate" our singleton
        self::getInstance();
 
        // Set our values
        if (is_array($data)) {
            foreach ($data as $k => $v) {
                $this->$k = $v;
            }
        }
    }
}
?>

If you build up this object then serialize it, then rebuild it from the serialization, you can see that it remains in tact. Let's test this by simulating the session array, first creating an array then adding the DummySingleton object to that array, then serializing it:

<?php
// Grab the class
include 'dummy.php';
 
// Get our instance
$o = DummySingleton::getInstance();
 
// Set something into it for testing
$o->setFromOutside = 'What the hell is this madness?';
 
// Make an array of stuff
$a = array();
$a['name'] = 'Robert';
$a['id'] = 23;
 
// Add the singleton
$a['dummy'] = $o;
 
// Serialize it
$s = serialize($a);
?>

The serialized output from this yields:

a:3:{s:4:"name";s:6:"Robert";s:2:"id";i:23;s:5:"dummy";C:14:"DummySingleton":187:{a:4:{s:5:"dProp";s:19:"I was finally built";s:6:"dPropX";s:25:"Nothing to see here folks";s:5:"_prop";s:16:"y u try see dis?";s:14:"setFromOutside";s:30:"What the hell is this madness?";}}}

If you take that string and subsequently load it and unserialize it then inspect it, you'll see that the singleton object has been restored along with all private properties of it:

<?php
// $s is our serialized string from above
$o = unserialize($s);
var_dump($o);
?>

Output:

array(3) {
  ["name"]=>
  string(6) "Robert"
  ["id"]=>
  int(23)
  ["dummy"]=>
  object(DummySingleton)#1 (3) {
    ["dProp"]=>
    NULL
    ["dPropX"]=>
    string(25) "Nothing to see here folks"
    ["_prop":"DummySingleton":private]=>
    string(16) "y u try see dis?"
  }
}

Since this is, in essence, the process that PHP follows for its default session management it's safe to say that you can, indeed, store singleton objects in a PHP session. Now onto to bigger and better questions, like...

Why would you ever want to store a singleton object instance in a PHP session?

26Feb/122

Javascript, WebKit and ‘closed’

The other day I was playing around with a simple letter scrambler script that I had written to assist my daughter with a birthday party game. The concept is simple enough... enter a phrase, hit a button and the phrase gets "cryptoquoted", a process where each letter in the phrase is substituted with another letter. One of the things I designed this program to do was hide the input box so that, if you were viewing it on the web, the solution would not be visible to anyone looking at the puzzle. But I also programmed it so that if you click a link, the input box would be revealed. This toggling of the visibility of the input box is simple enough to do. Just add a little jQuery and BAM!, you have a simple revealer.

And this is exactly how this performed until the last time I tried it a couple of days ago. My toggler suddenly stopped working right, but only in Chrome and Safari. All of the javascript was firing in the proper order, and the function I had written was being called but my slider was not sliding. After digging into this a little bit, and doing a bit of debugging, I found out that a variable name I was using the monitor the state of the visibility of the input box was being clobbered in Chrome and Safari. Firefox worked just fine as it always has. The variable name I was using was closed.

// Set the closed flag
var closed = true;
 
/**
 * Toggles visibility of the input box
 */
function toggleSolution() {
	if (closed) {
		closed = false;
		$('#puzzle-key').slideDown();
		$(this).text('Hide solution');
	} else {
		closed = true;
		$('#puzzle-key').slideUp();
		$(this).text('Show solution');
	}
}

When inspecting this code in Chrome Developer Tools, regardless of any actions, events or any other browser interaction, the closed variable always was valued as false. I still don't know why this is happening, but I do know this is a WebKit issue. And I also know that changing the variable name from closed to _closed solved the issue I was having. Also, as a simple test, without declaring the variable anywhere in my code, I opened up the console on a page without any javascipt at all and, sure enough, the variable was set to the value false.

So if you writing javascript and run across an issue where you are using a variable named closed in your code that always seems to be valued at false, be aware this might be an issue with your browser and simply changing the name of your variable in your code might make things all better for you.

22Feb/120

A simple PHP regular expression tester

I love making my own tools for the simple and mundane tasks I face during the day. One of those tasks is the repeated need for testing regular expressions that I am faced with when programming. Yes, there are a multitude of regular expression testers on the web today, and all of them have a laundry list of cool things they do. But none of them are mine, and none of them are necessarily simple. And few of them let you see how they work. So with that, I decided to take one of the many tools I've built for myself and offer it here for you.

My (very) simple regular expression is just that... a simple tester that takes a regular expression and a subject string and gives you back your matches as PHP would see them in your application. There is one small bit of robustness (I had to use robustness... the word is just so cool) added to this tester that will allow you to group your matches together (PREG_SET_ORDER) and allow you to include the string position of the match in the subject (PREG_OFFSET_CAPTURE). But that is where the robustness ends the simple kicks in again.

So if you want a simple little regular expression tester, or just want to see how I've coded mine, feel free to use my tester for your own needs. Have fun, stay curious and GO BIG.

28Nov/110

Simple PHP table maker

A little while ago I was in need of a way to take tab separated data sets and make HTML tables out of them cleanly. Mostly this was from data copied from a spreadsheet, but sometimes copying from web pages or even text documents has brought this need up for me. So in keeping with my mantra of "Why not write a program to do that?" I decided to write a simple HTML table maker that will take in tab separated, structured data and return an HTML table from it, complete with column headings, a caption and a summary (sidenote: I know that table summaries are being deprecated... still, I included it because at the moment they are still in use for pre-HTML5 markup).

So without further ado, I bring you my simple HTML table maker. It isn't the most robust thing in the world, but it is still pretty neat. Plus? It totally serves my purpose to the tee. So if you don't like it, why not gank the code for it and make it better?

20May/110

Simple PHP Class generator

Someone on the Professional PHP Group asked the question yesterday about whether Dreamweaver has a utility to write setters and getters to your PHP classes for you. I know that ZendStudio has this feature and I am pretty sure that NetBeans has it as well.

But as I thought about this, I thought "How cool would it be to be able to take a list of PHP properties and build a class out of that list complete with your getters and setters?". That's when my inner nerd took over and I created a very simple PHP Class Generator.

It is not the most robust tool in the world, but if you are looking for a quick way to build out a class of getters and setters from a simple list of variables, this little tool might be right up your alley. I'd encourage you to play with it and see if it can help you. If it does, tell me about it in the comments.

Oh yeah, a major thank you goes out to Jason Memory of Full Throttle Web Solutions for his help and wizardry in all things regex.

22Oct/091

ZendCon 2009: Day 3

And so ends the Zend PHP Developers Conference for 2009...

The last day of ZendCon is always a mixed bag when it comes to wrapping things up. On the one hand you, as a programmer, are loving the learning and education and networking and mixing things up with your peers. But on the other hand you, as a regular person, are tired from all the learning and education and networking and mixing things up with your peers (and drinking... and eating... and more drinking). Still, when all is said and done, all good things must come to an end. And so it goes with ZendCon.

Today started out pretty much the same as the other days except we knew we were in for a half day of sessions instead of a whole day. With that we chose the sessions we wanted to attend and headed for our rooms.

Session 0: Design Patterns for PHP developers
This was the second talk of Cal's that I attended this ZendCon and, much like the first, Cal gave a great presentation on Design Patterns for PHP. Covering the basics of design patterns, he made sure to mention the most important patterns we could use, like Factory, Strategy, Observer, Model-View-Controller, Facade and Singleton. He made the samples very easy to understand and was quick to answer the questions asked of him.

Design Patterns have a special place in my heart as a programmer (I have given talks to local meetup groups about design patterns) and I love how knowing design patterns levels out the disparity between programming languages. Knowing them makes one language for all programmers and allows anyone from any language background to be able to communicate programming concepts and paradigms in a common tongue, if you will. That said, Cal delivered big-time in his talk on patterns. He also inspired me to pursue my ambition of putting together a PHP Design Patterns web site for developers that have not seen, or do not understand, design patterns applied to PHP.

Slide for Cal's presentation can be downloaded here. (.pdf)

Session 1: Authorization with OAuth
Rob Richards gave a talk about OAuth and the difference between authentication and authorization. He moved pretty quickly into an actual implementation of using OAuth for authentication of a user from an application and, in my opinion, moved a little too fast into too concrete of an implementation. Still, the presentation was very informative and useful for anyone that is thinking of utilizing OAuth as an authentication mechanism.

Day 3 && ZendCon wrap-up
I was a little saddened to see the end of the conference come up so fast. Still, I am glad its over because I can use the rest.

I'm thinking next year I want to see if I can get in on giving a talk. I'd love to present and would love to not have to pay to get into the conference :) . I also think that it would be freaking awesome to have a small group of the guys I have met over the last two hears at ZendCon over to my home for a BBQ or something, though I should probably bring that up with my wife before I even think of doing anything like that (I will honey, I promise :P ).

Overall, this year's conference was better than last years in a couple of ways. First, the subject of many of the talks was NOT scaling. That, in and of itself, made this conference better than last year.

Second, the location was also a bit better than last year, being closer to downtown and all that being in downtown has to offer.

On the other hand, this year's conference was deficient in several area. First off, the lack of power in the lobby and in the conference rooms was painful. Having a laptop battery with only a 38% capacity made it difficult to attend a session that didn't have sufficient power.

Secondly, the vendor fair this year kinda sucked. It was nice to have them there, as always, but they didn't seem to want to interact with people this year. The shirts were awesome and the shwag was also very nice -who doesn't like free, right? Still, I think having more relevant vendors with more accessibility to relevant and pertinent information would have been nice.

Regardless, I got nothing but love for the organizers of this conference. Specifically, kudos need to go out to Eli White (@eliw) for his incredible support of the conference and attendees, and Keith Casey (@CaseySoftware) for the indescribable work he does on the conference uncon sessions. I cannot wait for ZendCon2010.

21Oct/090

ZendCon 2009: Day 2

Yesterday was an amazing day at ZendCon. Lots of talks, lots of information, lots of networking, lots of geekdom. The day was long but very informative and at the end of it I couldn't wait to get back for today.

Today started off a bit different than yesterday because today I was scheduled to take the Zend Framework Certification exam and my coworker was scheduled to take the Zend Certification Exam. So we got to the conference and camped out for the morning to get some last minute studying in.

As we studied we were joined by Aaron Wormus. I'm not sure why, but when presenters and other people who are widely known in PHP are close by I always feel like I am in the presence of a celebrity. Last year I was like a little kid at a football game, meeting everyone, shaking hands, being excited, enjoying the conference. This year? Well, kinda the same, except I haven't seen to have as much time this year to find people to meet.

Still, it was nice to be able to spend a couple of hours trying to familiarize myself with the Zend Framework. The exam preparation study guide is 214 pages long, and as of 12:15, when it was lunch time, I had gotten through about 110 page of it. So much for being totally prepared for the exam.

After lunch was over my coworker and I headed to the exam room where we took our test. And wouldn't you know it... I passed. So now I am not only a Zend Certified PHP Engineer, I am also Zend Framework certified as well. Sweet!

After the certifications were over we were able to get back into the swing of the sessions...

MySQL Server Performance Tuning 101
Cal Evans, filling in for Legaya Turmelle, did an excellent job of describing how to optimize your MySQL server and sent queries to enhance the performance of your applications. Filled with an incredible amount of technical data, this presentation was still a vibrant session that involved the attendees throughout the entire session. And I managed to sneak away two things that will be a help to me as soon as I get back to work:

/* Show all of your global variables */
mysql > SHOW GLOBAL VARIABLES;
 
/* Show your statuses */
mysql > SHOW GLOBAL STATUS;

Both of these can filter your queries by adding a "LIKE '%{filter}%'" to the query. Very handy pieces of information.

Architecting Your Models
Matthew Weier O'Phinney gave an awesome talk about architecting models (the M in MVC) and using your models to handle business logic. The logic that was employed was very similar to the Introduction to Zend Framework talk he gave on Monday (kudos for consistency) and covered data access, table/row gateways and service layers.

This is one of those presentations that I would recommend you get the slides for because just the programming practices employed in his presentation make it an absolutely stellar session and one that can only help you as a programmer. [Note: as soon as the links to the slides are posted I will put that link here. Sorry.]

Building Desktop RIAs with JavaScript and PHP
Ed Finkler gave a sweet presentation on taking PHP and Javascript from the web to the desktop in his talk on building Rich Internet Applications. His talk was dynamic, funny, had a couple of well handled snafus and totally captivated the audience.

Using Adobe AIR, Appcelerator's Titanium and PHP, he built a couple of cool little desktop apps that are driven by server hosted PHP applications. Though the apps were just for example, what you can do with AIR or Titanium, jQuery and PHP is freaking amazing and I can't wait to try some of this stuff when I get back to work.

Links to his presentation and associated code samples:

Day 2 wrap-up
Sitting here, at the end of the last session, I am overwhelmed with the amount of information I learned in the last couple of days. I am so looking forward to how ZendCon will be wrapping up tomorrow and cannot wait to meet some of the people of I haven't yet had a chance to meet.

16Oct/091

Handling multiple MySQL results in PHP

Back in the day (June of 2007 to be a little more exact) I posted a little snippet about using PHP to hand multiple MySQL query results with the MySQLi extension. At the time it was something I was using just to see what was coming back from the database when I called a stored procedure that happened to have multiple results in it. Skip to how this really works...

Since then I have improved my little handler, added some library code to actually handle queries and results (and timers and requests and... dude, so much). The new code, which is much fatter (not necessarily better, just fatter in that it has a lot more features in it), now encapsulates much of what the original, simple script was doing. Unfortunately, the script is no longer as simple as I wanted it to be. However, it does now come in several files which might be of use to you in some of your own code.

Because of the change that I made to the way this code works, I have decided to package it into a little zip file for your downloading pleasure. The zip file includes:

  • index.php (the actual procedure/result tester)
  • A lib directory, which contains:
    • memorystats.php (A memory use and reporting object)
    • mysql.php (The database connection and query object)
    • mysqlresult.php (The database result object)
    • request.php (The HTTP request object)
    • timer.php (The process timer object)

It should be ready to use right out of the box, with the exception of making sure to add your own username, password and database name to the $db->connect() call (at about line 85 in index.php). I'm sure you could expand it to be more of what you want in a tester (like adding a database chooser to it - right now I have a database server chooser, but not a database chooser within a server) but overall it is a very nice little script that does pretty much what it was built to do: run a query/procedure and return all of the results in it.

Please note: This is seriously not something that you should think to use in a production environment. The farthest this has ever made in any of my architectures is the dev machine. Not that the code is not good. It is just that putting something that has free form access to your database onto a production machine is, in my opinion, a remarkable stupid thing to do and I would hate to have my name associated with code that was used to exploit your business because you chose to put a huge gaping hole into your network through a simple little test script. Just sayin'.

Under the hood
Before diving into that mash of code I included in the zip file, it might be a good idea to see just the relevant code so you can, if you want to, just tap into the multiple result set handling of MySQL results in PHP. Without further ado...

<?php
// Set a query
$sql = "SHOW TABLES FROM `mysql`; SELECT VERSION() AS `version`; SELECT NOW() AS `date`;";
 
// Change the params to you own here...
$mysql = new mysqli('HOSTNAME', 'USERNAME', 'PASSWORD', 'DATABASE');
 
if (mysqli_connect_errno()) {
	die(printf('MySQL Server connection failed: %s', mysqli_connect_error()));
}
 
// Initialize our result set array
$results = array();
 
/** 
 * Check our query results
 * 
 * This is where the magic happens, and it must be this way anytime you use a
 * stored procedure. The reason for that is the MySQL server always sends a 
 * status return with any query, even a select query. That means that even a
 * single result set select query will return two results. Those results will
 * not necessarily make it to your application (I think the client handles 
 * that) but in my experience I have always had to use multi_query to get this
 * to work.
 */
if ($mysql->multi_query($sql)) {
	do {
		// Create the records array
		$records = array();
 
		// Lets work with the first result set
		if ($result = $mysql->use_result()) {
			// Loop the first result set, reading the records into an array
			while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
				$records[] = $row;
			}
 
			// Close the record set
			$result->close();
		}
 
		// Add this record set into the results array
		$results[] = $records;
	} while ($mysql->next_result());
}
 
// Close the connection
$mysql->close();
 
// Set our output type
header('Content-Type: text/plain');
 
// What do we have
print_r($results);

And there you have it. Nice, simple, clean and easy to use all by itself.

13Oct/092

Scope? Nope. Dope!

Today I made a classic blunder in PHP programming. It is one that many make and certainly one I have made before. But the frustrating thing about this issue is it probably would have been caught by a simple unit test or, in lieu of that, something as simple as trying to capture output of my script from the go.

I was working on the command line (something I have been spending a lot more time doing) building a background processing application that will be triggered by the web. The process is expected to take between 45 minutes and an hour each time it is ran and will be ran one time a month at the discretion of an administrator (hence the reason it is not set as a cron job).

One of the very first lines of the CLI script looks like this:

<?php
// Define our path for all includes and file writes
define('APPPATH', realpath('.'));
?>

It would seem simple enough, right? Basically that little line of code defines a constant called APPPATH that would essentially describe the path to the location of the file that is setting the constant. At least that is what I thought. And every test I ran led me to believe that I had indeed made the right choice to set the path the way I did.

I ran the script, and even other scripts that derived code from the script, from the command line on my local Ubuntu machine, from the command line on our Fedora dev machine and from the command line on our Ubuntu production server. I have similar snippets that are working on all of our machines, and these snippets work interactively, through cron and through the web.

But today something happened that I did not intend. As I attempted to run my last process test of this long running script through the web I found myself in a place where my script would not run. It was being triggered properly. It was just not actually doing anything.

After an hour of trying to figure out what I was doing wrong I solicited the wisdom and advice of two of my colleagues who are seasoned Unix professionals and after a couple of minutes I was able to begin to see what was wrong. Can you guess what it was?

That's right... PHP, on the web, was applying its scope to the CLI script that it was calling. The reason it had worked in all cases prior to triggering it by the web was that I was logged in as me and calling the script as a CLI script interactively, through the prompt. That means that the setting of the APPPATH constant was happening as expected and to what I expected.

However, from the web, when PHP runs as an apache module, it calls the CLI script from the scope of the web server. That means that the use of realpath() on the location '.' (current working directory) was assuming the working directory was the web server root, not the location of where the script actually resided.

That's right folks, I lost track of the scope in which I was working. Like a dope.

The simple solution to this problem was to change where the script looks to set its own directory. Can you think of what I could have used, instead of '.' as the location to pass to realpath() so that it knows, without a doubt, what its own path is?

<?php
// Get the real path to the current directory location OF THIS FILE
define('APPPATH', realpath(dirname(__FILE__)));/
?>

I hate it when something this simple causes such deep pain and suffering, needlessly.

4Apr/099

Getting books details by ISBN in PHP

Sometimes I love being a geek. Today, as I set out to inventory my collection of almost 500 books or so, I wanted to find a fast way to get the information on the book I wanted based on the books ISBN. This was because I was already getting tired of typing five books into the inventory (yes, I am that lazy) and I really did not want to keep typing out the authors name, the title and the subtitle for each book.

So I went to ISBNdb.com and entered an ISBN thinking I would be able to just copy and paste the book information from the output. That turned out to be overwhelmingly difficult since their output of the search is really convoluted. I knew what I wanted to get and they didn't offer that.

But they did offer an API and, after reading their docs, I realized that I could write a script that would take an ISBN and return to me the information I was looking for in a way that I could just copy and paste it. Remarkably, it was faster and easier than I thought it would be. I actually developed this little snippet, freely available for you here, in about 5 minutes. Hope it helps you in some way, if you are looking for such a thing.

NOTE: Before using this code make sure to sign up for your own API key. In order to do that you will need to register for an account with ISBNdb.com and then create a key. But as soon as you do you will have immediate access to their API. The API is simple, responding to the request with a simple XML output. Anyhow, without further ado, here is the little script I put together to fetch me the data I was looking for the way I wanted it.

<?php
/**
 * Set this value to your own API key
 */
$apikey = '12345678';
 
/**
 * Initialize this var for use when forms are not posted
 */
$isbn = null;
 
/**
 * Initialize the result set var
 */
$rs = null;
 
/**
 * See if the form is posted
 */
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  /**
   * Get the ISBN from the form
   * 
   * No, there is no validation on this. This 
   * was for me and I knew I would not be at
   * all trying to trick my own script. USE 
   * THIS AS IS AT YOUR OWN PERIL.
   */
  $isbn = $_POST['isbn'];
 
  /**
   * Get the result as a SimpleXML object
   */
  $rs = simplexml_load_file("http://isbndb.com/api/books.xml?access_key=$apikey&index1=isbn&value1=$isbn");
}
?>
<html>
<head><title>ISBN Check</title></head>
<body>
<form method="post" action="<?php echo basename(__FILE__) ?>">
  <p>
    Enter an ISBN:<br />
    <input type="text" size="40" name="isbn" id="isbn" value="<?php echo $isbn ?>" />
  </p>
  <p>
    <input type="submit" name="submit" value="Search ISBN" />
  </p>
</form>
<?php
/**
 * Only output more stuff if there is a result
 */
if ($rs) : ?>
<hr />
Results for ISBN: <?php echo $isbn ?>:
<?php 
/**
 * Results for a book are in $rs->BookList->BookData
 * 
 * You can see this using var_dump($rs);
 */ 
$book = $rs->BookList->BookData;
?>
<p>
  <strong><?php echo $book->Title; ?></strong><br />
  <?php echo !empty($book->TitleLong) ? $book->TitleLong . '<br />' : null; ?>
  <?php echo $book->AuthorsText; ?><br />
  <?php echo $book->PublisherText; ?><br />
  <?php echo $isbn; ?>
</p>
<?php endif; ?>
</body>
</html>

Typical output might look like (from one of the books I was checking on):


Results for ISBN: 0316116955:

The day the universe changed
James Burke
Boston : Little, Brown, c1985.
0316116955


I hope you enjoy this, if you happen to be looking for an ISBN search tool that you can run on your own. ;)