Friday, December 30, 2005

s/fluent/decorated/

I've read some posts about "fluent interfaces" and I have to agree that it's a bad idea to sacrifice a reasonable API to be able to write english code.


It's anyway possible to accomplish that to some extent on a need-by-need basis with a class of only 20 lines of code:



<?php
class ReturnThisDecorator
{
private
$object;
public
$result;

function
__construct($object)
{
if (
is_object($object)) {
$this->object = $object;
} else {
throw new
InvalidArgumentException(
"Expected an object as argument"
);
}
}

function
__call($method, $params)
{
$this->result = call_user_func_array(
array(
$this->object, $method), $params
);
return
$this;
}
}

$sms = new ReturnThisDecorator(new SMS);
echo
$sms->from('...')->to('...')->message('...')->send()->result;
?>

Thursday, December 15, 2005

PECL::HTTP 0.20.0 outta door

Version 0.20.0 of pecl/http has been released!
This is considered the most stable and friggin best release so far ;) so you're really encouraged to upgrade.


Release notes follow:


! Request functionality requires libcurl >= 7.12.3 now

+ Added 'bodyonly' request option

+ Added IOCTL callback for cURL

+ Added ssl_engines array and cookies array to the request info array

+ Added http_parse_cookie() to parse Set-Cookie headers

- Renamed http_connectcode to connect_code in the request info array
- Enable "original headers" previously stripped off by the
message parser:
o X-Original-Transfer-Encoding (Transfer-Encoding)
o X-Original-Content-Encoding (Content-Encoding)
o X-Original-Content-Length (Content-Length)
- RequestExceptions thrown by HttpRequestPool::__construct() and
send() are now wrapped into the HttpRequestPoolException
object's $exceptionStack property
- Removed http_compress() and http_uncompress()
(http_deflate/inflate ambiguity)

* Fixed bug which caused GZIP encoded archives to be decoded
* Fixed bug with DEFLATE encoded response messages
* Fixed several memory leaks and inconspicuous access violations
* Fixed some logical errors in the uri builder

Tuesday, December 6, 2005

Hash Extension

If you didn't notice yet, there's a new, seemingly unimpressive, nevertheless very useful, extension on the horizon, namely ext/hash.


The initial version, proposed by Sara and Stefan -and it seems that Rasmus had his hands on it too- already featured the most common algorithms, and it has recently been extended to support now fairly every algo which libmhash provides.


There's not been any public release yet, which means you'd need to build from CVS. pecl4win though, already provides fresh modules for Windows users.


It's usage is simple and intuitive


Hashing a string:



<?php
echo hash('sha384', 'The quick brown fox jumps over the lazy dog');
?>

Hashing a file:



<?php
echo hash_file('md5', 'release-1.0.tgz');
?>

There's also an incremental interface available.


The recently released version of the HTTP extension now uses the HASH extension instead of libmhash for generating its etag hashes.


Update:


Just in case you missed, Sara released pecl/hash today.
Check it out -- It'll be bundled with PHP-5.1.2!

PECL::HTTP - A nasty bug and a new example

I just fixed a nasty bug which caused GZIP encoded files (speak tgz etc.) to be decoded. While it'll just eat some memory on a 200 response (besides that the body is not what one would expect), it'll eat all memory on 206 (partial content) responses because the part is fed through zlib. I'll just need to revisit the HTTP RFC to check if checking for the "Vary" response header is the best bet before I drop the new release.


Ok, that's it about the bad news -- I also added a new example to the tutorial which shows how one could efficiently download big files. Using the example code would look like:



<?php
$bigget
= BigGet::url('http://example.com/big_file.bin');
$bigGet->saveTo('big_file.bin');
?>



<?php
class BigGetRequest extends HttpRequest
{
public
$id;
}

class
BigGet extends HttpRequestPool
{
const
SIZE = 1048576;

private
$url;
private
$size;
private
$count = 0;
private
$files = array();

static function
url($url)
{
$head = new HttpRequest($url, HttpRequest::METH_HEAD);
$headers = $head->send()->getHeaders();
$head = null;

if (!isset(
$headers['Accept-Ranges'])) {
throw new
HttpExcpetion("Did not receive an ".
"Accept-Ranges header from HEAD $url");
}
if (!isset(
$headers['Content-Length'])) {
throw new
HttpException("Did not receive a ".
"Content-Length header from HEAD $url");
}

$bigget = new Bigget;
$bigget->url = $url;
$bigget->size = $headers['Content-Length'];
return
$bigget;
}

function
saveTo($file)
{
$this->send();
if (
$w = fopen($file, 'wb')) {
echo
"nCopying temp files to $file ...n";
foreach (
glob("bigget_????.tmp") as $tmp) {
echo
"t$tmpn";
if (
$r = fopen($tmp, 'rb')) {
stream_copy_to_stream($r, $w);
fclose($r);
}
unlink($tmp);
}
fclose($w);
echo
"nDone.n";
}
}

function
send()
{
// use max 3 simultanous requests with a req size of 1MiB
while ($this->count < 3 &&
-
1 != $offset = $this->getRangeOffset()) {
$this->attachNew($offset);
}

while (
$this->socketPerform()) {
if (!
$this->socketSelect()) {
throw new
HttpSocketException;
}
}
}

private function
attachNew($offset)
{
$stop = min($this->count * self::SIZE + self::SIZE,
$this->size) - 1;

echo
"Attaching new request to get range: $offset-$stopn";

$req = new BigGetRequest(
$this->url,
HttpRequest::METH_GET,
array(
'headers' => array(
'Range' => "bytes=$offset-$stop"
)
)
);
$this->attach($req);
$req->id = $this->count++;
}

private function
getRangeOffset()
{
return (
$this->size >=
$start = $this->count * self::SIZE) ? $start : -1;
}

protected function
socketPerform()
{
try {
$rc = parent::socketPerform();
} catch (
HttpRequestPoolException $x) {
foreach (
$x->exceptionStack as $e) {
echo
$e->getMessage(), "n";
}
}

foreach (
$this->getFinishedRequests() as $r) {
$this->detach($r);

if (
206 != $rc = $r->getResponseCode()) {
throw new
HttpException(
"Unexpected response code: $rc");
}

file_put_contents(
sprintf("bigget_%04d.tmp", $r->id),
$r->getResponseBody());

if (-
1 != $offset = $this->getRangeOffset()) {
$this->attachNew($offset);
}
}

return
$rc;
}
}
?>