Skip to content

php

PDO_MYSQL: now with native prepared statements (mysql 4.1 and up)

I've had a number of people ask me about this feature recently, and now it's here. What took so long? I just didn't have mysql 4.1 installed anywhere, or enough time to set aside to look into it. A patch from Guy Harrison (for multiple rowsets) caught me at just the right time today, and set me up for some hacking.

In CVS (and showing up on snaps soon), the pdo_mysql driver now has support for the following features when compiled against mysql 4.1 or higher:

  • Support for queries returning multiple rowsets, via PDOStatement::nextRowset().
  • Supports "native" sqlstate error codes, for "better" error reporting.
  • Supports native prepared statements and bound parameters.

I'm looking for feedback on these changes; even if you have an older mysql installation, please try the snapshot, as I want to ensure that it continues to build and work fine there (should be fine, but there's nothing better than physically confirming it).

Any problems, then hop along to http://bugs.php.net/ and file a bug report; thanks!

(PS: installing mysql 4.1 on a gentoo box when you only have a 56k modem takes a while)

Stupid error in PHP 5.1beta

Well, if you haven't tried it already, and you're interested in PDO, don't: just grab the latest snapshot from snaps instead.

Lack of communication, and perhaps some politics, meant that 5.1beta shipped with a stupid bug in the PDO core extension, causing a infinite loop on request shutdown.

I'm somewhat annoyed at this, because it's a showstopper that we could and should have caught before announcing the beta.

Oh well, such is life.

I'm just getting stuck into packing things up ready to move into our first home in the USA (we've been living in an apartment for a few months), so I'm not going to let it bother me.

Have a good weekend.

A Decade of PHP

As Zak kindly reminds us, it is the eve of PHP's tenth birthday. My first involvement with PHP was as an alternative to ASP for a client in Japan; they wanted a dynamic web site, but it had to run on their Solaris servers. At that time, PHP 4 was still in beta, but already felt superior to ASP. As that project grew, I felt that it would be cool if I could use fsockopen() to talk to a credit card payment processor.

In September of 2000 I came up with a crude patch that got SSL to work with fsockopen(). I went on to refine this patch (a great deal!) and wound up implementing the Streams layer.

I've always been surprised and impressed at how easily my contributions were accepted by the PHP project, especially because the patch to enable streams was so wide-ranging (it touched pretty much every file in the source tree), and even more so because I'd never previously met anyone from the PHP project, either in real life or even just "around" online.

Getting involved with PHP has had a profound effect on my life; being invited to speak at conferences in Holland, Germany, USA, the Carribean, Toronto and more recently in Mexico has been a hell of an experience. And I mustn't forget that I wouldn't be working here with George Schlossnagle at OmniTI, on a different continent from where I was born, if it wasn't for PHP.

I owe the majority of my salary over the last few years to PHP, and so I want to send my thanks to Rasmus for getting the ball rolling, keeping it rolling, and pimping my name when I was looking for work. I'd also like to extend my thanks to Christine; based on the reactions of my own wife (Juliette) to some of the PHP things I've done (and that's nothing compared to Rasmus), I have some idea of how much effort you must have put out over the years. Thanks to you both!

Long live PHP :-)

PS: thanks to everyone that has contributed to the PHP project over the years.

PHP Podcast

I've just finished chatting with Marcus Whitney for part of the next installment of his Pro PHP Podcast show. It's my first recorded conversation/interview for public consumption. I'm kinda nervous and hope that I didn't ramble on too much. But that's what it's all about; getting more personality out of people than you would with just text alone.

As ever, technology has a wicked sense of humour, so there are probably going to be a couple of stops-and-starts (perhaps more if I really rambled on and Marcus has to edit ;-). Oh yeah, I might come across as sounding more like Barry White than I would normally.

I'm looking forward to seeing how the show develops--listening rather than reading is not just a refreshing change but also something you can do while working on other things.

The next installment of the show should hit the 'net in the next couple of days.

Re: PDO meeting in Cancun

Just a quick response to Lukas' blog entry.

I know Lukas would love to see things work a little differently in PDO, but there are good technical reasons why things are the way they are now. Here are some reasons in random order:

  • It's impossible to build a magic database abstraction layer that works for everything
  • PDO is data-access abstraction (not database abstraction). The primary goal is to make things similar enough that you don't end up cursing at the API. The abstraction is also present for the benefit of extension developers, making it less of a headache to build database drivers for PHP.
  • No two underlying database client APIs are the same
  • Most don't separate the concept of a prepared statement handle from a result set.
  • Most overload functions like RowCount() to have the dual meaning that Lukas mentioned.
  • the MySQL client API is a bad example of a typical database client API. Don't use it as your baseline for comparisons to the others.

A closing comment regarding the naming of the PDO methods; I didn't follow Lukas' "Unofficial subjectively observed key design principles"; the PDO APi just happens to follow the APIs of the underlying database clients because it means that PDO doesn't have to do too much work on top of those APIs, and because those particular APIs have been designed by people that live and breathe databases. I like to avoid the NIH (Not Invented Here) syndrome where I can (although I confess that I do succumb to it from time to time). Regarding the length of the method names, I don't believe that they should be as short as possible, but don't believe inOverlyLongNamesForTheSakeOfMakingTheCodeReadLikeEnglish().

Computer Languages History

This site has an interesting timeline that shows the how around 50 major programming languages are inter-related and how they developed over time.

Guru - Multiplexing

[The following mini-article is something that I wrote for the International PHP Magazine a while back, as part of the 'ask a guru' column; I'm re-publishing it here because it's useful and because people have asked me about the topic twice in the last two days]

Question:

Is there a way to do a form of threading in PHP?

Say for instance you write a PHP application to monitor a service on a number of servers, it would be nice to be able query a number of servers at the same time rather then query them one-by-one.

Can it be done?

Answer:

People often assume that you need to fork or spawn threads whenever you need to do several things at the same time - and when they realize that PHP doesn't support threading they move on to something less nice, like perl.

The good news is that in the majority of cases you don't need to fork or thread at all, and that you will often get much better performance for not forking/threading in the first place.

Say you need to check up on web servers running on a number of hosts to make sure that they are still responding to the outside world. You might write a script like this:

<?php
$hosts = array("host1.sample.com", "host2.sample.com", "host3.sample.com");
$timeout = 15;
$status = array();
foreach ($hosts as $host) {
    $errno = 0;
    $errstr = "";
    $s = fsockopen($host, 80, $errno, $errstr, $timeout);
    if ($s) {
        $status[$host] = "Connected\\n";
        fwrite($s, "HEAD / HTTP/1.0\\r\\nHost: $host\\r\\n\\r\\n");
        do {
            $data = fread($s, 8192);
            if (strlen($data) == 0) {
                break;
            }
            $status[$host] .= $data;
        } while (true);
        fclose($s);
    } else {
        $status[$host] = "Connection failed: $errno $errstr\\n";
    }
}
print_r($status);
?>

This works fine, but since fsockopen() doesn't return until it has resolved the hostname and made a successful connection (or waited up to $timeout seconds), extending this script to monitor a larger number of hosts makes it slow to complete.

There is no reason why we have to do it sequentially; we can make asynchronous connections - that is, connections where we don't have to wait for fsockopen to return an opened connection. PHP will still need to resolve the hostname (so its better to use IP addresses), but will return as soon as it has started to open the connection, so that we can move on to the next host.

There are two ways to achieve this; in PHP 5, you can use the new stream_socket_client() function as a drop-in replacement for fsockopen(). In earlier versions of PHP, you need to get your hands dirty and use the sockets extension.

Here's how to do it in PHP 5:

<?php
$hosts = array("host1.sample.com", "host2.sample.com", "host3.sample.com");
$timeout = 15;
$status = array();
$sockets = array();
/* Initiate connections to all the hosts simultaneously */
foreach ($hosts as $id => $host) {
    $s = stream_socket_client("$host:80", $errno, $errstr, $timeout, 
        STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT);
    if ($s) {
        $sockets[$id] = $s;
        $status[$id] = "in progress";
    } else {
        $status[$id] = "failed, $errno $errstr";
    }
}
/* Now, wait for the results to come back in */
while (count($sockets)) {
    $read = $write = $sockets;
    /* This is the magic function - explained below */
    $n = stream_select($read, $write, $e = null, $timeout);
    if ($n > 0) {
        /* readable sockets either have data for us, or are failed
         * connection attempts */
        foreach ($read as $r) {
            $id = array_search($r, $sockets);
            $data = fread($r, 8192);
            if (strlen($data) == 0) {
                if ($status[$id] == "in progress") {
                    $status[$id] = "failed to connect";
                }
                fclose($r);
                unset($sockets[$id]);
            } else {
                $status[$id] .= $data;
            }
        }
        /* writeable sockets can accept an HTTP request */
        foreach ($write as $w) {
            $id = array_search($w, $sockets);
            fwrite($w, "HEAD / HTTP/1.0\\r\\nHost: "
                . $hosts[$id] .  "\\r\\n\\r\\n");
            $status[$id] = "waiting for response";
        }
    } else {
        /* timed out waiting; assume that all hosts associated
         * with $sockets are faulty */
        foreach ($sockets as $id => $s) {
            $status[$id] = "timed out " . $status[$id];
        }
        break;
    }
}
foreach ($hosts as $id => $host) {
    echo "Host: $host\\n";
    echo "Status: " . $status[$id] . "\\n\\n";
}
?>

We are using stream_select() to wait for events on the sockets that we opened. stream_select() calls the system select(2) function and it works like this: The first three parameters are arrays of streams that you want to work with; you can wait for reading, writing and exceptional events (parameters one, two and three respectively). stream_select() will wait up to $timeout seconds for an event to occur - when it does, it will modify the arrays you passed in to contain the sockets that have met your criteria.

Now, using PHP 4.1.0 and later, if you have compiled in support for ext/sockets, you can use the same script as above, but you need to replace the regular streams/filesystem function calls with their equivalents from ext/sockets. The major difference though is in how we open the connection; instead of stream_socket_client(), you need to use this function:

<?php
// This value is correct for Linux, other systems have other values
define('EINPROGRESS', 115);
function non_blocking_connect($host, $port, &$errno, &$errstr, $timeout) {
    $ip = gethostbyname($host);
    $s = socket_create(AF_INET, SOCK_STREAM, 0);
    if (socket_set_nonblock($s)) {
        $r = @socket_connect($s, $ip, $port);
        if ($r || socket_last_error() == EINPROGRESS) {
            $errno = EINPROGRESS;
            return $s;
        }
    }
    $errno = socket_last_error($s);
    $errstr = socket_strerror($errno);
    socket_close($s);
    return false;
}
?>

Now, replace stream_select() with socket_select(), fread() with socket_read(), fwrite() with socket_write() and fclose() with socket_close() and you are ready to run the script.

The advantage of the PHP 5 approach is that you can use stream_select() to wait on (almost!) any kind of stream - you can wait for keyboard input from the terminal by including STDIN in your read array for example, and you can also wait for data from pipes created by the proc_open() function.

If you want PHP 4.3.x and want to use the native streams approach, I have prepared a patch that allows fsockopen to work asynchronously. The patch is unsupported and won't be in an official PHP release, however, I've provided a wrapper that implements the stream_socket_client() function along with the patch, so that your code will be forwards compatible with PHP 5.

Resources:

documentation for stream_select()
documentation for socket_select()
patch for PHP 4.3.2 and script to emulate stream_socket_client(). (might work with later 4.3.x versions).