Making use of PHP5 features

at 2007-01-29 in Editorial by friebe (0 comments)

Today at work we had a presentation called "PHP5 features", where Alex and me presented what we consider the most appealing new possibilities.

Here's a wrap-up:

Method call chaining
Method call chaining is how we call foo()->bar()->baz(). You may also know it as "dereferencing"; the exact terminology is unknown:)

Examples in the XP framework:

<?php 
// Old
$cm= ConnectionManager::getInstance();
$conn= $cm->getByHost('vod', 0);

// New
$conn= ConnectionManager::getInstance()->getByHost('vod', 0);

// Old
$l= Logger::getInstance();
$cat= $l->getCategory();

// New
$cat= Logger::getInstance()->getCategory();
?>


Please note the following:
  • Don't write a script to replace these occurences automatically, and don't blindly refactor sourcecode. There might be a need for the $cm or $l variables later on - this has happened to me a couple of times.
  • Make sure that any part of the chain does not return NULL (or FALSE, or an array, or ...). For example, XPClass::getMethod($name) returns NULL if there is no method by the name $name.
    <?php 
    $return= XPClass::getMethod($name)->invoke($instance, array());
    ?>

    may cause a "Fatal Error".
  • No chaining after new: new StringBuffer()->append(...) will producea syntax error; this is a limitation in the PHP5 language parser and is fixable but probably won't be - mail php-internals (php.net) if you think this should be possible:)
The possibility to chain method calls will have an impact on design decisions made:
  • Make getXXX() methods throw exceptions, provide hasXXX() method to check for existance. This will allow nice chains without the worries about fatal errors.
  • Add support for "fluent interfaces" - for an example, look at the rdbms.Criteria class. Its add() method returns $this, thus allowing for the following code:
    <?php 
    $criteria= Criteria::newInstance()->add(...)->add(...);
    ?>

Exceptions
With "real" exceptions supported by PHP5, we can optimize the following:
  • No more "useless" rethrowing
    In PHP4, we often put try/catch $e/throw $e blocks inside our sourcecode because of the limitation of our exception simulation. These are no longer necessary because they resemble the default behaviour:

    <?php 
    try
    {
    // source
    } catch (Throwable $e) {
    throw
    $e;
    }
    ?>

    ...is the same as:
    <?php 
    // source
    ?>

  • No more try/catch/printStackTrace and exit boilerplate
    If you're using xp::sapi('cli'), there is no need to write sourcecode which catches exceptions, prints them and exits, this is what happens automatically.

    <?php 
    try
    {
    $bean= Remote::forName($rsn)->lookup('xp/demo/MessageSender');
    } catch (RemoteException $e) {
    $e->printStackTrace();
    exit
    (1);
    }
    ?>

    ...is the same as:
    <?php 
    $bean= Remote::forName($rsn)->lookup('xp/demo/MessageSender');
    ?>

    when using xp::sapi('cli').

  • Advanced: Return inside catch
    <?php 
    try
    {
    return ConnectionManager::getInstance
    ()->getByHost('vod', 0);
    } catch (ConnectionNotRegisteredException $e) {
    throw
    new HttpScriptletException(
    $e->getMessage(),
    HTTP_PAYMENT_REQUIRED
    // :)
    );
    }
    ?>

    This has the advantage of not having to watch our for the codepath in the case of catching an exception; plus the reader can see right ahead what you are trying to do, and can examine what you do for error handling later.

  • Transaction template:
    We used to use this as for database transactions:
    <?php 
    try
    {
    $conn= ConnectionManager::getByHost('XXX', 0);
    $tran= $conn->begin(new Transaction('update_it'));

    // Some updates, inserts and/or deletes here

    } catch (SQLException $e) {
    $tran->rollback();
    throw
    $e; // Or whatever
    }
    $tran->commit();
    ?>

    This "template" has the problem that $tran may not be set, e.g. due to "begin transaction" failing. It is suggested to use the following in the catch-block:
    <?php 
    $tran && $tran->rollback();
    ?>

    This will prevent fatal errors.

    At the same time, the $tran->commit() can be moved inside the try/catch block (right above the catch), which has the benefit that an error during committing a transaction would also be handled.

The "self" keyword
Self can be used like parent, only that it refers to the class it's been placed in rather than the parent.
You all know this:
<?php 
class A {
public
function foo() {
// ...
}
}

class B extends A {
public
function foo() {
parent::foo
(); // Instead of A::foo();
// ...
}
}
?>

With "self", you get around typing (and potentially mistyping) the
classname:
<?php 
class ConnectionManager {

// ... $instance initialization omitted ...

public
static function getInstance() {
// Note the typo: return ConnetcionManager::$instance;
// with self, less likely to happen:)
return self::
$instance;
}
}
?>

Also nice for equals()-methods:
<?php 
public
function equals($cmp) {
return
(
$cmp instanceof self &&
($this->getTime() === $cmp->getTime()
);
}
?>

Type hints
At the moment, we don't make use of type hints much. Type hints are supported in PHP5 for method arguments, as follows:

<?php 
class Node extends Object {
public
function addChild(Node $n) {
// ...
}
}
?>

...and will raise an E_RECOVERABLE_ERROR ("PHP Catchable fatal error") when the type is mismatched (an instanceof check is used here, so enableDebug(Traceable $instance) will work for any object whose class implements the Traceable interface).

The type hint may either be the word "array" or any other string which will then reference a class by that name. Type hints for primitives are not supported.

Type hints do not accept NULL values per default, this can be accomplished by making the parameter optional:

<?php 
class Leaf extends Object {
public
function setParent(Leaf $x= NULL) { }
}
?>

Now the setParent() method can be called as follows:
<?php 
$leaf->setParent(new Leaf());
$leaf->setParent(NULL);
$leaf->setParent();
?>
...but not as:
<?php 
$leaf->setParent(1);
$leaf->setParent('Hello');
?>

Please note that subclasses may not change method signatures! Thus if we have the following:
<?php 
class Base {
public
function setDate(Date $d) { }
}

class Child extends Base {
public
function setDate(SqlDate $d) { }
}
?>

...this will work only as long as setDate() is not defined via an interface (in that case, a fatal error "Declaration of Child::setDate() must be compatible with ..." is raised). Don't rely on the fact that the checks may not become stricter in the future!
Btw, in Java, setDate(SqlDate d) and setDate(Date d) are two different methods, so this is not applicable there. You'd get an error message though if you were using the @Override annotation!

One can get around type hints be ignoring the catchable fatal errors:
<?php 
set_error_handler
(create_function('', ''), E_RECOVERABLE_ERROR);
class X {
public
static function y(X $x) { var_dump($x); }
}
X::y
(1); // This will run the y() method and print "int(1)"!
?>

Btw: RFC #0100 proposes to turn E_RECOVERABLE_ERRORs into lang.IllegalArgumentExceptions, in this case, the method will obviously not be run.

More...
We also talked about the feature of calling PHP functions during XSL transformations, e.g.:
  <xsl:value-of select="php:function('time')"/>
...or
  <xsl:value-of select="php:function('ucfirst', ./@name)"/>


For details, see RFC #0104 (implemented since 5.1.1-release).



Subscribe

You can subscribe to the XP framework's news by using RSS syndication.


Categories

News
General
PHP5
Announcements
RFCs
Further reading
Examples
Editorial
EASC
Experiments
Unittests
Databases

Related

Find related articles by a search for «Making».