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;
}
}
?>

Thursday, November 17, 2005

Boost your Website with APC

Two weeks ago I plugged APC onto my main customers site, and I'm really satisfied by it now. I already tried it some time ago, but back then it had some problems with PEARs Quickforms and similiar heavy OO code, but those problems are fixed for about 90% now, some 8% can be easily fixed by reordering require statements and the like and finally the remaining ~2% are going to be fixed by Rasmus in the foreseeable future.


Note that I'm using APC with Debians PHP-4.3.10 and Apache2 package, which proofs a lot IMO ;) and there shouldn't be much that hinders you from trying it too!


Here's a very simple instruction to boost your PHP enabled webserver:


~$ pear download pecl/apc
~$ tar xzf APC-3.0.8.tgz
~$ rm -f package.xml
~$ cd APC-3.0.8
~/APC-3.0.8$ /usr/bin/phpize
~/APC-3.0.8$ ./configure --with-php-config=/usr/bin/php-config
--enable-apc --enable-apc-mmap
~/APC-3.0.8$ make all
~/APC-3.0.8$ sudo make install

APC is now installed somewhere in /usr/lib/php.


Some lines to add to php.ini:


extension=apc.so

[apc]
apc.enabled = 1
apc.cache_by_default = 1
apc.shm_size = 32
apc.num_files_hint = 500
apc.mmap_file_mask = /tmp/apc.XXXXXX

If you want to cache only a certain virtual host, set apc.cache_by_default to 0 in php.ini and add the respective php_admin_value to your <VirtualHost> directive for the vhost.


The package also contains a useful status script, just copy the shipped apc.php to your document root. You'll see how the usage of the SHM segment grows by time, and after some time you'll also see what settings to use for apc.num_files_hint and apc.shm_size when the whole site is cached.


Sean recently added some APC docs, too!


Quod est demonstrandum, Website boosted! ;)

Wednesday, November 16, 2005

Time to say Good Bye

Yeah, it may come suddenly, but it's time to say good bye to curly braces used as string indexing operator.


PHP-5.1 will issue an E_STRICT error, and PHP-6 will probably don't know this syntax any more.


I don't know why, but I've got some strange sentimental feelings :-) , maybe because I've never used those other brackets for string indexing...


So, beloved, I already miss you! :'(

Submitting a bug for PHP

There will surely be the time where you find a bug in PHP, but prior submitting half hearted bug report make sure you've accomplished the following things:



If you're in doubt, read this if you didn't already (which you shouldn't tell anyone by the way).

Submitting a patch for PHP

If you once have that odd feeling that something in PHP is missing and you're brave enough to write something up (a patch) that would change that situation in your eyes, don't expect such a submission will make it into old branches.


Questions to include new features in PHP-4 or PHP-5.0 will most probably be declined (for a good reason), so the chance that the invested time is wasted time is rather high.


If you still feel confident about your addition, write the patch against the CVS HEAD or at least 5.1 branch, but for PHP-5.1 which is around the corner the probability for acceptance of new features has decreased a lot (or rather completely).


On the other hand, if your patch is trying to fix a bug, don't hesitate to provide a patch for every single CVS branch ;)

Sunday, November 13, 2005

Obviously...

...morons don't die off

PHP-5.1 around the corner

Here's a tidied up excerpt of the current NEWS file with all relevant changes since PHP 5.0, which may help on the decision to upgrade (even from PHP 4 :))


Fixes



  • More than 350


Changes



  • Changed PDO constants to class constants (PDO::CONST_NAME)

  • Changed SQLite extension to be a shared module in Windows distribution

  • Changed "instanceof" and "catch" operators, is_a() and is_subclass_of() functions to not call __autoload()

  • Changed sha1_file() and md5_file() functions to use streams instead of low level IO

  • Changed abstract private methods to be not allowed anymore

  • Changed stream_filter_(ap|pre)pend() to return resource

  • Changed mysqli_exception and sqlite_exception to use RuntimeException as base if SPL extension is present


Extensions moved to PECL



  • cpdf

  • dio

  • fam

  • ingres_ii

  • mcve

  • mnogosearch

  • oracle

  • ovrimos

  • pfpro

  • w32api

  • yp


Upgraded PEAR



  • to channel-featuring v1.4


Upgraded bundled libraries



  • PCRE library to version 6.2

  • SQLite 3 library in ext/pdo_sqlite to 3.2.7

  • SQLite 2 library in ext/sqlite to 2.8.16


Upgraded bundled libraries in Windows distribution



  • zlib 1.2.3

  • curl 7.14.0

  • openssl 0.9.8

  • ming 0.3b

  • libpq (PostgreSQL) 8.0.1


Improvements and Additions



  • Improved SPL extension

    • Moved RecursiveArrayIterator from examples into extension

    • Moved RecursiveFilterIterator from examples into extension

    • Added SplObjectStorage

    • Made all SPL constants class constants

    • Renamed CachingRecursiveIterator to RecursiveCachingIterator to follow Recursive<*>Iterator naming scheme



  • Added support for class constants and static members for internal classes

  • Added PDO::MYSQL_ATTR_USE_BUFFERED_QUERY parameter for pdo_mysql

  • Added date_timezone_set() function to set the timezone that the date functions will use

  • Added pg_fetch_all_columns() function to fetch all values of a column from a result cursor

  • Added support for LOCK_EX flag for file_put_contents()

  • Implemented feature request #33452

  • Improved PHP extension loading mechanism with support for module dependencies and conflicts

  • Allowed return by reference from internal functions

  • Rewrote strtotime() with support for timezones and many new formats. Implements feature requests #21399, #26694, #28088, #29150, #29585 and #29595

  • Added bindto socket context option

  • Added offset parameter to the stream_copy_to_stream() function

  • Added offset & length parameters to substr_count() function

  • Removed php_check_syntax() function which never worked properly

  • Removed garbage manager in Zend Engine which results in more aggressive freeing of data

  • Improved interactive mode of PHP CLI

  • Improved performance of:

  • general execution/compilation

  • switch() statement

  • several array functions

  • virtual path handling by adding a realpath() cache

  • variable fetches

  • magic method invocations

  • Improved support for embedded server in mysqli

  • Improved mysqli extension

  • added constructor for mysqli_stmt and mysqli_result classes

  • added new function mysqli_get_charset()

  • added new function mysqli_set_charset()

  • added new class mysqli_driver

  • added new class mysqli_warning

  • added new class mysqli_execption

  • added new class mysqli_sql_exception

  • Improved SPL extension

  • added standard hierarchy of Exception classes

  • added interface Countable

  • added interfaces Subject and Observer

  • added spl_autoload*() functions

  • converted several 5.0 examples into c code

  • added class FileObject

  • added possibility to use a string with class_parents() and class_implements()

  • Added man pages for "phpize" and "php-config" scripts

  • Added support for .cc files in extensions

  • Added PHP_INT_MAX and PHP_INT_SIZE as predefined constants

  • Added user opcode API that allow overloading of opcode handlers

  • Added an optional remove old session parameter to session_regenerate_id()

  • Added array type hinting

  • Added the tidy_get_opt_doc() function to return documentation for configuration options in tidy

  • Added support for .cc files in extensions

  • Added imageconvolution() function which can be used to apply a custom 3x3 matrix convolution to an image

  • Added optional first parameter to XsltProcessor::registerPHPFunctions to only allow certain functions to be called from XSLT

  • Added the ability to override the autotools executables used by the buildconf script via the PHP_AUTOCONF and PHP_AUTOHEADER environmental variables

  • Added several new functions to support the PostgreSQL v3 protocol introduced in PostgreSQL 7.4

  • pg_transaction_status() - in-transaction status of a database connection

  • pg_query_params() - execution of parameterized queries

  • pg_prepare() - prepare named queries

  • pg_execute() - execution of named prepared queries

  • pg_send_query_params() - async equivalent of pg_query_params()

  • pg_send_prepare() - async equivalent of pg_prepare()

  • pg_send_execute() - async equivalent of pg_execute()

  • pg_result_error_field() - highly detailed error information, most importantly the SQLSTATE error code

  • pg_set_error_verbosity() - set verbosity of errors

  • Added optional fifth parameter "count" to preg_replace_callback() and preg_replace() to count the number of replacements made. FR #32275

  • Added optional third parameter "charlist" to str_word_count() which contains characters to be considered as word part. FR #31560

  • Added interface Serializeable

  • Added pg_field_type_oid() PostgreSQL function

  • Added zend_declare_property_...() and zend_update_property_...() API functions for bool, double and binary safe strings

  • Added possibility to access INI variables from within .ini file

  • Added variable $_SERVER['REQUEST_TIME'] containing request start time

  • Added optional float parameter to gettimeofday()

  • Added apache_reset_timeout() Apache1 function

  • Added sqlite_fetch_column_types() 3rd argument for arrays

  • Added optional offset parameter to stream_get_contents() and file_get_contents()

  • Added optional maxlen parameter to file_get_contents()

  • Added SAPI hook to get the current request time

  • Added new functions:

  • array_diff_key()

  • array_diff_ukey()

  • array_intersect_key()

  • array_intersect_ukey()

  • array_product()

  • DomDocumentFragment::appendXML()

  • fputcsv()

  • htmlspecialchars_decode()

  • inet_pton()

  • inet_ntop()

  • mysqli::client_info property

  • posix_access()

  • posix_mknod()

  • SimpleXMLElement::XPathNamespace()

  • stream_context_get_default()

  • stream_socket_enable_crypto()

  • stream_wrapper_unregister()

  • stream_wrapper_restore()

  • stream_filter_remove()

  • time_sleep_until()

  • Added DomDocument::$recover property for parsing not well-formed XML Documents

  • Added Cursor support for MySQL 5.0.x in mysqli

  • Added proxy support to ftp wrapper via http

  • Added MDTM support to ftp_url_stat

  • Added zlib stream filter support

  • Added bz2 stream filter support

  • Added max_redirects context option that specifies how many HTTP redirects to follow

  • Added support of parameter=>value arrays to xsl_xsltprocessor_set_parameter()

Saturday, November 12, 2005

Upgraded, finally

Yessir! I finally managed to upgrade s9y. It was quite a PITA because I had to go through all releases from 0.6 to 0.9, because it didn't work in one jump...


Could be that I'll be blogging more from now on.. (yeah, sounds like a threat, eh?)

Tuesday, October 4, 2005

References in PHP

Derick made his article about variable references - published in PHP Architects June edition - available online.


If you ever wanted to know how variables and references in PHP look like, this read is a MUST.

Monday, September 12, 2005

Tutorial -kinda- for pecl/http

I've added a tiny tutorial, or sort of collection of usage examples, to the pecl/http repository.


You can read it in the extended post, or just check it out from CVS. Please note that the code for the XMLRPC client has just been put into CVS.


GET Queries


The HttpRequest class can be used to execute any HTTP request method. The following example shows a simple GET request where a few query parameters are supplied. Additionally potential cookies will be read from and written to a file.



<?php

$r
= new HttpRequest('http://www.google.com');

// store Googles cookies in a dedicated file
$r->setOptions(
array(
'cookiestore' => '../cookies/google.txt',
)
);

$r->setQueryData(
array(
'q' => '+"pecl_http" -msg -cvs -list',
'hl' => 'de'
)
);

// HttpRequest::send() returns an HttpMessage object
// of type HttpMessage::TYPE_RESPONSE or throws an exception
try {
print
$r->send()->getBody();
} catch (
HttpException $e) {
print
$e;
}
?>

Multipart Posts


The following example shows an multipart POST request, with two form fields and an image that's supposed to be uploaded to the server. It's a bad habit as well as common practice to issue a redirect after an received POST request, so we'll allow a redirect by enabling the redirect option.



<?php

$r
= new HttpRequest('http://dev.iworks.at/.print_request.php',
HTTP_METH_POST);

// if redirects is set to true, a single redirect is allowed;
// one can set any reasonable count of allowed redirects
$r->setOptions(
array(
'cookies' => array('MyCookie' => 'has a value'),
'redirect' => true,
)
);

// common form data
$r->setPostFields(
array(
'name' => 'Mike',
'mail' => 'mike@php.net',
)
);
// add the file to post (form name, file name, file type)
$r->addPostFile('image', 'profile.jpg', 'image/jpeg');

try {
print
$r->send()->getBody();
} catch (
HttpException $e) {
print
$e;
}
?>

Parallel Requests


It's possible to execute several HttpRequests in parallel with the HttpRequestPool class. HttpRequests to send, do not need to perform the same request method, but can only be attached to one HttpRequestPool at the same time.



<?php

try {
$p = new HttpRequestPool;
// if you want to set _any_ options of the HttpRequest object,
// you need to do so *prior attaching* to the request pool!
$p->attach(new HttpRequest('http://pear.php.net',
HTTP_METH_HEAD));
$p->attach(new HttpRequest('http://pecl.php.net',
HTTP_METH_HEAD));
} catch (
HttpException $e) {
print
$e;
exit;
}

try {
$p->send();
// HttpRequestPool implements an iterator
// over attached HttpRequest objects
foreach ($p as $r) {
echo
"Checking ", $r->getUrl(), " reported ",
$r->getResponseCode(), "n";
}
} catch (
HttpException $e) {
print
$e;
}
?>

Parallel Requests?


You can use a more advanced approach by using the protected interface of the HttpRequestPool class. This allows you to perform some other tasks while the requests are executed.



<?php

class Pool extends HttpRequestPool
{
public function
__construct()
{
parent::__construct(
new
HttpRequest('http://pear.php.net',
HTTP_METH_HEAD),
new
HttpRequest('http://pecl.php.net',
HTTP_METH_HEAD)
);

// HttpRequestPool methods socketPerform() and
// socketSelect() are protected; one could use
// this approach to do something else
// while the requests are being executed
print "Executing requests";
for (
$i = 0; $this->socketPerform(); $i++) {
$i % 10 or print ".";
if (!
$this->socketSelect()) {
throw new
HttpException("Socket error!");
}
}
print
"nDone!n";
}
}

try {
foreach (new
Pool as $r) {
echo
"Checking ", $r->getUrl(), " reported ",
$r->getResponseCode(), "n";
}
} catch (
HttpException $ex) {
print
$e;
}
?>

Cached Responses


One of the main key features of HttpResponse is HTTP caching. HttpResponse will calculate an ETag based on the http.etag_mode INI setting as well as it will determine the last modification time of the sent entity. It uses those two indicators to decide if the cache entry on the client side is still valid and will emit an "304 Not Modified" response if applicable.



<?php

HttpResponse
::setCacheControl('public');
HttpResponse::setCache(true);
HttpResponse::capture();

print
"This will be cached until content changes!n";
print
"Note that this approach will only save the clients download time.n";
?>

Bandwidth Throttling


HttpResponse supports a basic throttling mechanism, which is enabled by setting a throttle delay and a buffer size. PHP will sleep the specified amount of seconds after each sent chunk of specified bytes.



<?php

// send 5000 bytes every 0.2 seconds, i.e. max ~25kByte/s
HttpResponse::setThrottleDelay(0.2);
HttpResponse::setBufferSize(5000);
HttpResponse::setCache(true);
HttpResponse::setContentType('application/x-zip');
HttpResponse::setFile('../archive.zip');
HttpResponse::send();
?>

KISS XMLRPC Client



<?php

class XmlRpcClient
{
public
$namespace;
protected
$request;

public function
__construct($url, $namespace = '')
{
$this->namespace = $namespace;
$this->request = new HttpRequest($url, HTTP_METH_POST);
$this->request->setContentType('text/xml');
}

public function
setOptions($options = array())
{
return
$this->request->setOptions($options);
}

public function
addOptions($options)
{
return
$this->request->addOptions($options);
}

public function
__call($method, $params)
{
if (
$this->namespace) {
$method = $this->namespace .'.'. $method;
}
$this->request->setRawPostData(
xmlrpc_encode_request($method, $params));
$response = $this->request->send();
if (
$response->getResponseCode() != 200) {
throw new
Exception($response->getBody(),
$response->getResponseCode());
}
return
xmlrpc_decode($response->getBody(), 'utf-8');
}

public function
getHistory()
{
return
$this->request->getHistory();
}
}
?>

Simple Feed Aggregator



<?php

class FeedAggregator
{
public
$directory;
protected
$feeds = array();

public function
__construct($directory = 'feeds')
{
$this->setDirectory($directory);
}

public function
setDirectory($directory)
{
$this->directory = $directory;
foreach (
glob($this->directory .'/*.xml') as $feed) {
$this->feeds[basename($feed, '.xml')] = filemtime($feed);
}
}

public function
url2name($url)
{
return
preg_replace('/[^w.-]+/', '_', $url);
}

public function
hasFeed($url)
{
return isset(
$this->feeds[$this->url2name($url)]);
}

public function
addFeed($url)
{
$r = $this->setupRequest($url);
$r->send();
$this->handleResponse($r);
}

public function
addFeeds($urls)
{
$pool = new HttpRequestPool;
foreach (
$urls as $url) {
$pool->attach($r = $this->setupRequest($url));
}
$pool->send();

foreach (
$pool as $request) {
$this->handleResponse($request);
}
}

public function
getFeed($url)
{
$this->addFeed($url);
return
$this->loadFeed($this->url2name($url));
}

public function
getFeeds($urls)
{
$feeds = array();
$this->addFeeds($urls);
foreach (
$urls as $url) {
$feeds[] = $this->loadFeed($this->url2name($url));
}
return
$feeds;
}

protected function
saveFeed($file, $contents)
{
if (
file_put_contents($this->directory .'/'. $file .'.xml', $contents)) {
$this->feeds[$file] = time();
} else {
throw new
Exception(
"Could not save feed contents to $file.xml");
}
}

protected function
loadFeed($file)
{
if (isset(
$this->feeds[$file])) {
if (
$data = file_get_contents($this->directory .'/'. $file .'.xml')) {
return
$data;
} else {
throw new
Exception(
"Could not load feed contents from $file.xml");
}
} else {
throw new
Exception("Unknown feed/file $file.xml");
}
}

protected function
setupRequest($url)
{
$r = new HttpRequest($url);
$r->setOptions(array('redirect' => true));

$file = $this->url2name($url);

if (isset(
$this->feeds[$file])) {
$r->setOptions(array('lastmodified' => $this->feeds[$file]));
}

return
$r;
}

protected function
handleResponse(HttpRequest $r)
{
if (
$r->getResponseCode() != 304) {
if (
$r->getResponseCode() != 200) {
throw new
Exception("Unexpected response code ".
$r->getResponseCode());
}
if (!
strlen($body = $r->getResponseBody())) {
throw new
Exception("Received empty feed from ".
$r->getUrl());
}
$this->saveFeed($this->url2name($r->getUrl()), $body);
}
}
}
?>

Friday, August 26, 2005

PECL HTTP, lap 12

Today I released PECLs HTTP extension version 0.12


A lot of work and fixes have gone into it, and I hope it's becoming stable even this century ;)


Major changes are a general API cleanup where AuthBasic hooks have been removed and many HttpRequest methods have been sanitized.


The HttpResponse class should now work with PHP5 as a webserver module as well and should not any longer have hard dependencies on ext/session and ext/zlib.


Additionally, if you have libmhash (note: not necessarily ext/mhash) you can choose a hashing algorithm provided by libmhash to generate your ETags, which is done through the following INI setting: http.etag_mode = MHASH_SHA256 (MHASH_SHA256 is an example and can be replaced by any MHASH constant if you have ext/mhash).


The message and header parsers have been vastly improved to be able to parse (in principle invalid) messages and headers that only have a single LF instead of CRLF.


And last but not least it should again flawlessly build for PHP4.


So far, have fun and be sure to check it out! :)

Wednesday, June 29, 2005

Is PHP staying the language I want to work with?

While some of the core PHP internals already see theirself that there's something wrong with the so-called generic way ZE2 looks up classes, many stolidly ignore the claims of PHP users...


Look at this MARC Thread and that MARC Thread.


Now something to laugh (or cry) at


WTF?

Friday, June 10, 2005

Gadget 1.1.0

I just came along to release the Gadget Web Development Kit 1.1.0


List of changes:


Improvements:



  • vastly enhanced and simplified Gadget_Formatter::quoteText()

  • simplified Gadget_Template_Action class layout and loading

  • added Gadget_Template_Action_function

  • refactored Gadget_Tree_Search and companions

  • Gadget_Tree::fputs() now attempts to create the destination directory

  • Gadget_Uploads nameToSafe() and nameToUniq() now return the new names

  • Gadget_Upload::nameToSafe() now allows hyphens

  • Gadget_Widget::load() now uses readfile() instead of include() if template won't be rendered

  • added no_prefetching option to Gadget_Service_Security


Bug Fixes:



  • Gadget_Mail::deleteMail() deleted the mail after moving it to the Trash folder

  • Gadget_Tree::fromNode() was not really returning a refernce to the node

  • Gadget_Template_Compiler did an additional save of XML parsed template data

  • Gadget_Template_Action_while was infunctional

  • Gadget_Tree_XML::toFile() added an additional XML declaration

  • Gadget_Service_Mailaccount didn't properly act upon errors

  • Gadget_Search_Fulltext wrongly calculated expiration

  • Gadget_Temaplate_XmlParser put phpized code for comparison of form option element hooks into template


Yeah - I kow - still no website...

Tuesday, June 7, 2005

HttpRequestPool in PECL::HTTP

I just checked in a first working version of HttpRequestPool into CVS.
It's curl_multi that's doing its job underneath and is used tp send several HttpRequests at once.



<?php

$urls
= array(
'http://www.php.net/',
'http://pear.php.net/',
'http://pecl.php.net/'
);

$pool = new HttpRequestPool;
foreach (
$urls as $url) {
$pool->attach(new HttpRequest($url, HTTP_METH_HEAD));
}

try {
$pool->send();
foreach (
$pool as $r) {
$status = $r->getResponseCode();
printf("%-20s is %sn",
$r->getUrl(),
$status == 200 ? "ALIVE" : "NOT OK ($status)"
);
}
} catch (
HttpException $ex) {
echo
$ex;
}
?>

Sunday, June 5, 2005

mod_domaintree

Friday evening I put together a tiny Apache2 module simliar to mod_vhost_alias or mod_vd. mod_domaintree maps host names to a filesystem tree. While mod_vhost_alias and mod_vd seem to be more flexible, they seem less useful to me.


mod_vhost_alias lets you define very precisely which part of the host name maps to which directory but adds odd underscores if the part (number) of the hostname does not exist.


mod_vd lets you define the numerical amount of host name parts to strip.


But I don't want to strip always one part of the host name like "www" - I just want to strip the first part of the host name (i.e. www) if it occurs in the sent hostname.


If you want to try it out, there's nothing more to do then (w)getting it and running



sudo /usr/sbin/apxs2 -a -i -c mod_domaintree.c


Sample Configuration:



DomainTreeEnabled On
DomainTreeMaxdepth 25
DomainTreeStripWWW On
DomainTreePrefix /sites
DomainTreeSuffix /html

Mapped Hosts: (accepting "www" as prefix if DomainTreeStripWWW is enabled)



  • company.co.at

  • sub1.company.co.at

  • sub2.company.co.at

  • organisation.or.at

  • example.at

  • example.com


Resulting Filesystem Tree:



/sites
+- /at
| +- /co
| | +- /company
| | +- /html
| | +- /sub1
| | | +- /html
| | +- /sub2
| | +- /html
| +- /or
| | +- /organisation
| | +- /html
| +- /example
| +- /html
+- /com
+- /example
+- /html

PS: I was very suprised how easy it is to build a simple module for Apache (if you have some templates to look at though). :)

Monday, May 30, 2005

Using Gadget_Upload

The GWDK comes with a handy -KISS- upload handler.


Using it is as simple as:



<?php

define
('GADGET_CHARSET', 'ISO-8859-1');

require_once
'PEAR.php';
require_once
'Gadget/Upload.php';

if (
$pdf = &Gadget_Upload::getFile('uploads_form_name')) {
// use M or K as shorthand
if ($pdf->checkSize('2M')) {
// check content type
if ($pdf->checkType('application/pdf')) {
$pdf->nameToSafe();
// the only method that returns PEAR_Error
$error = $pdf->moveTo('../uploads');
}
}
}
?>

Just squeeze that into a while loop for multiple uploads:



<?php

while ($img = &Gadget_Upload::getFile('images')) {
if (
$img->checkSize('50k')) {
// check for general "image" content type
if ($img->checkType('image')) {
// pass prefix for uniqid() as parameter
$img->nameToUniq('img');
// the only method that returns PEAR_Error
$error = $img->moveTo('./images');
}
}
}
?>

Have fun...