php

You are currently browsing articles tagged php.

Due to working with Drupal for most of this year, I’ve seen this construct so many times it’s killed me a little inside:

switch($a) {
    case TRUE:
        switch($b) {
            case TRUE:
                break;
            case FALSE:
                break;
        }
        break;
    case FALSE:
        switch($b) {
            case TRUE:
                break;
            case FALSE:
                break;
        }
        break;
}

Someone I work with, who wishes to keep his name off the net, came up with this:

switch(array($a, $b)) {
    case array(false, false):
        break;
    case array(false, true):
        break;
    case array(true, false):
        break;
    case array(true, true):
        break;
}

I’d rather avoid the need for nested switches altogether, but this construct makes it easy to collapse similar branches, and is, I think, a useful addition to my toolkit:

switch(array($a,$b)) {
    case array(true, true):
        break;
    case array(false, false):
    case array(false, true):
    case array(true, false):
    default:
        break;
}

It’s worth mentioning that arrays are compared for equality (==) rather than identicality (===).

A fragment of PHP that really shouldn’t work. I hope nobody ever finds a use for this:

switch($n)
{
    case 1;
        echo 'one';
        break;
    case 2;
        echo 'two';
        break;
    case 3;
        echo 'three';
        break;
}

Check this out:

if (isset($a, $b, $c))
{
    unset($a, $b, $c);
}

Mulitple arguments. Looks like they’ve been there since an early PHP4 release, at least. Why does nobody tell me these things?

The problem:

If you include() or require() files with relative paths, APC will search the include_path on every request, and you won’t gain the full benefit of turning apc.stat off.

Using absolute paths portably in your own code is easy – just use a constant, eg. “require APP_PATH . ‘file.php’;”. External libraries, however, are harder to deal with – you either accept the performance hit, or go through them adding constants by hand (and merging your changes every time there’s a new release).

The solution:

This script rewrites PHP scripts, turning relative paths into absolute paths, allowing you to deploy third party libraries within your build process and still take full advantage of APC.

Some caveats:

It reads from stdin and writes to stdout.

It relies on it’s own include_path; in the real world, you’ll probably want to pass that in on the command line.

It only deals with the most common subset of include statements (variations on T_INCLUDE, T_CONSTANT_ENCAPSED_STRING, T_SEMICOLON).

The Windows support is a bit wing-and-a-prayer.

It’s memory-greedy when dealing with very large files.

It doesn’t play 100% nicely with safe_mode_include_dir.

Thrown out there in the hope it will be useful to someone. I’d be delighted to accept any feedback or additions anyone wants to offer.

I was trying to figure out the best way to test the myfputcsv() function I posted yesterday. Reading the data from disc before comparing it seemed like a step where errors could creep in, but there was no obvious way round that, as the purpose of the function is to write to disc.

Then I realised I could use PHP’s (fairly) new IO streams to dump the function’s output to a temporary buffer, and read it back in for comparison. Not perfect, but it removes concerns about file mutexes, permissions, unique filenames, etc. and speeds up the tests, as they never touch disc.

It wasn’t worth building on top of PHPUnit for this… checking failure conditions can wait for another day. I just wrote a quick script that compares the output of fputcsv() and myfputcsv(). (And it worked – I’ve already fixed two errors in yesterday’s post).

This is the first time I’ve reached for php://memory. It’s obviously useful for testing code that writes to disc, but I’m now wondering where else it might come in handy.

<?php
 
require_once 'myfputcsv.php';
 
function write( $function, $array )
{
    $fp = fopen( 'php://memory', 'w+' );
    $function( $fp, $array );
    rewind( $fp );
    return fread( $fp, 1024 );
}
 
function test( $array )
{
    $fputcsv = write( 'fputcsv', $array );
    $myfputcsv = write( 'myfputcsv', $array );
    if ( assert( '$fputcsv === $myfputcsv' ) )
    {
        echo "OK" . PHP_EOL;
    }
}
 
test( array() );
test( array( "Hello", "World" ) );
test( array( "He\nllo", "World" ) );
test( array( "He\\"llo", "World" ) );
test( array( "He llo", "World" ) );
test( array( "He\tllo", "World" ) );
test( array( "He\"\"llo", "World" ) );
test( array( "He,llo", "World" ) );

I recently needed an fputcsv() with a couple of modifications (I needed fields quoted unconditionally, and more than one character in the delimiter field). I looked at a couple of versions from the man page comments, but they were buggy in one way or another, and PHP4-specific.

The function below is as close as I can get to fputcsv()’s behaviour. I’m throwing it out there in the hope that it will be useful to someone, someday. It should be easy enough to modify to suit specific requirements.

/**
 * myfputcsv()
 *
 * Mimics the observed behaviour of PHP's fputcsv()
 *
 * Requires at least PHP 5.2.0 due to reliance on __toString
 *
 * @param resource $fp valid file pointer
 * @param array $fields array of values
 * @param string $delimiter optional parameter sets the field delimiter character. Defaults to ','
 * @param string $enclosure optional parameter sets the field enclosure character. Defaults to '"'
 * @return bool the length of the written string, or FALSE on failure
 */
function myfputcsv( $fp, $fields, $delimiter = ',', $enclosure = '"' )
{
    /**
     * Validate incoming values
     *
     * Weird corner cases are checked for here, so we can mimic fputcsv() as closely
     * as possible. Eg we check whether or not an object passed as $delimiter or
     * $enclosure implements __toString
     */
    if ( !is_resource( $fp ) )
    {
        trigger_error( __FUNCTION__ . '() expects parameter 1 to be resource, ' . gettype( $fp ) . ' given', E_USER_WARNING );
        return false;
    }
    if ( !is_array( $fields ) )
    {
        trigger_error( __FUNCTION__ . '() expects parameter 2 to be array, ' . gettype( $fields ) . ' given', E_USER_WARNING );
        return false;
    }
    if ( is_object( $delimiter ) && method_exists( $delimiter, '__toString' ) )
    {
        $delimiter = ( string ) $delimiter;
    }
    if ( is_object( $enclosure ) && method_exists( $enclosure, '__toString' ) )
    {
        $enclosure = ( string ) $enclosure;
    }
    if ( $delimiter == null )
    {
        trigger_error( __FUNCTION__ . '(): delimiter must be a character', E_USER_WARNING );
        return false;
    }
    if ( $enclosure == null )
    {
        trigger_error( __FUNCTION__ . '(): enclosure must be a character', E_USER_WARNING );
        return false;
    }
    if ( !is_scalar( $delimiter ) )
    {
        trigger_error( __FUNCTION__ . '() expects parameter 3 to be string, ' . gettype( $delimiter ) . ' given', E_USER_WARNING );
        return false;
    }
    if ( !is_scalar( $enclosure ) )
    {
        trigger_error( __FUNCTION__ . '() expects parameter 4 to be string, ' . gettype( $enclosure ) . ' given', E_USER_WARNING );
        return false;
    }
    if ( strlen( $delimiter ) > 1 )
    {
        trigger_error( __FUNCTION__ . '(): delimiter must be a single character', E_USER_NOTICE );
        $delimiter = $delimiter[0];
    }
    if ( strlen( $enclosure ) > 1 )
    {
        trigger_error( __FUNCTION__ . '(): enclosure must be a single character', E_USER_NOTICE );
        $enclosure = $enclosure[0];
    }
 
    /**
     * Prepare fields for writing to file by escaping them and wrapping them
     * in $enclosure
     */
    for( $i = 0; $i < sizeof( $fields ); $i++ )
    {
        /**
         * Make a decision on whether or not to use $enclosure
         */
        $use_enclosure = false;
        if ( strpos( $fields[$i], $delimiter ) !== false )
        {
            $use_enclosure = true;
        }
        if ( strpos( $fields[$i], $enclosure ) !== false )
        {
            $use_enclosure = true;
        }
        if ( strpos( $fields[$i], "\" ) !== false )
        {
            $use_enclosure = true;
        }
        if ( strpos( $fields[$i], "\n" ) !== false )
        {
            $use_enclosure = true;
        }
        if ( strpos( $fields[$i], "\r" ) !== false )
        {
            $use_enclosure = true;
        }
        if ( strpos( $fields[$i], "\t" ) !== false )
        {
            $use_enclosure = true;
        }
        if ( strpos( $fields[$i], " " ) !== false )
        {
            $use_enclosure = true;
        }
 
        if ( $use_enclosure == true )
        {
            $fields[$i] = explode( "\$enclosure", $fields[$i] );
            for( $j = 0; $j < sizeof( $fields[$i] ); $j++ )
            {
                $fields[$i][$j] = explode( $enclosure, $fields[$i][$j] );
                $fields[$i][$j] = implode( "{$enclosure}{$enclosure}", $fields[$i][$j] );
            }
            $fields[$i] = implode( "\$enclosure", $fields[$i] );
            $fields[$i] = "{$enclosure}{$fields[$i]}{$enclosure}";
        }
    }
 
    /**
     * Write fields as a $delimiter-delimited string, and return number of
     * bytes written
     */
    return fwrite( $fp, implode( $delimiter, $fields ) . "\n" );
}

A base class is "a class from which other classes are derived".

Many OO languages have the concept of a single base class from which all other classes are explicitly or implicitly descended. For example, Ruby, Java and .NET all have Object.

It’s a very common belief that PHP implements stdClass as a base class for all objects, but this is in fact not the case:

<?php
 
class DoesNotExtend {}
 
class DoesExtend extends stdClass {}
 
$doesNotExtend = new DoesNotExtend();
$doesExtend = new DoesExtend();
 
var_dump($doesNotExtend instanceof stdClass);
var_dump($doesExtend instanceof stdClass);

Outputs:

bool(false)
bool(true)

When a language is defined by its implementation rather than a standard, it can sometimes be tricky to decide what should be considered correct behaviour and what should be considered an implementation bug.

What follows isn’t so much a PHP trick as a fix for something that really should work, but doesn’t. Although the manual implies that the behaviour described below is specific to Zend Engine 1, all my tests were performed against Zend Engine 2.2, PHP 5.2.5.

Quoting from the manual:

The Zend Engine 1, driving PHP 4, implements the static and global modifier for variables in terms of references. For example, a true global variable imported inside a function scope with the global statement actually creates a reference to the global variable. This can lead to unexpected behaviour which the following example addresses:

<?php
function test_global_ref() {
    global $obj;
    $obj = &new stdclass;
}
 
function test_global_noref() {
    global $obj;
    $obj = new stdclass;
}
 
test_global_ref();
var_dump($obj);
test_global_noref();
var_dump($obj);
?>

Executing this example will result in the following output:

NULL
object(stdClass)(0) {}

http://uk2.php.net/static

The example above uses instances of stdClass, but attempts to assign references to scalar values, arrays or resources to global variables have the same result: the attempt to modify the global fails without error (even with error_reporting(E_ALL)), and the global retains whatever value it had before the function call.

The workaround is very simple – assign the reference via the $GLOBALS superglobal:

<?php
 
function test_global_ref()
{
    $GLOBALS['obj'] = &new stdclass;
}
 
test_global_ref();
var_dump($obj);

Outputs:

object(stdClass)#1 (0) { }

I think one of these scripts must expose an implementation bug – either assigning a reference to a global variable should work, in which case the first script should not fail, or it should not work and the second script should fail. It would be interesting to get an opinion from someone involved in language internals on how PHP should behave. In either case, it’s very hard to understand why the first script doesn’t currently cause a notice or error to be thrown when the assignment fails.

PHP Singleton

I recently had a bad deer-in-the-headlights moment over a simple Singleton pattern. In an effort to turn a negative into a positive, and to burn something new (it never occurred to me that it was necessary to implement the __clone() magic method) into my feeble memory, here’s an implementation of Singleton in PHP, along with a couple of unit tests.

Of course, Singleton is still just another name for global variable.

 
/**
 * EmptySingleton
 *
 * Does nothing, but only one of it can exist at a time
 *
 * @version $Id$
 */
class EmptySingleton
{
    private static $_self = null;
 
    /**
     * EmptySingleton::init()
     *
     * Create an instance of EmptySingleton if necessary, and return it
     *
     * @access public
     * @return EmptySingleton
     */
    public static function init()
    {
        if ( self::$_self == null )
        {
            self::$_self = new EmptySingleton();
        }
 
        return self::$_self;
    }
 
    /**
     * EmptySingleton::__construct()
     *
     * The only place this can be called from is EmptySingleton::init()
     *
     * @access private
     */
    private function __construct()
    {
    }
 
    /**
     * EmptySingleton::__clone()
     *
     * If this somehow manages to get itself called, it throws an error
     *
     * @access private
     * @throws Exception
     */
    /** @codeCoverageIgnoreStart */
    private function __clone()
    {
        throw new Exception();
    }
    /** @codeCoverageIgnoreEnd */
}
 
class EmptySingletonTest extends PHPUnit_Framework_TestCase
{
    public function testCanInit()
    {
        $this->_obj = EmptySingleton::init();
        $this->assertTrue( $this->_obj instanceof EmptySingleton );
    }
 
    public function testOnlyOneInstance()
    {
        $instance_one = EmptySingleton::init();
        $instance_two = EmptySingleton::init();
 
        $instance_one->temp = 42;
 
        $this->assertEquals( $instance_one->temp, $instance_two->temp );
    }
}
 
$suite = new PHPUnit_Framework_TestSuite( 'EmptySingletonTest' );
PHPUnit_TextUI_TestRunner::run( $suite );

Update: Poking around online, I came across this interesting singleton implementation. He’s using class variables rather than instance variables, meaning that every instance of the class shares the same state. As thread safety isn’t really an issue in PHP it will work, but I think forcing class users to avoid the constructor offers a useful hint that something unusual’s going on with that class.

From the PHP manual:

A valid variable name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. As a regular expression, it would be expressed thus: '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'

In other words, variable names can’t begin with a numeral. However there are a couple of ways to slip an illegal variable name into the symbol table:

$var = '1';
 
$$var = 'hello world';

or

${'1'} = 'hello world';

You can prove to your own satisfaction that the variables really exist with PHP’s rather obscure compact() function:

print_r( compact( '1' ));

Please note: if you use any of this in production code – and that includes compact() – I will come to your house and beat you with something heavy.

« Older entries