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:

  // 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.
      $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:
      $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:

      try {
    // source
    } catch (Throwable $e) {
    throw
    $e;
    }

    ...is the same as:
      // 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.

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

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

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

  • Advanced: Return inside catch
      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:
      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:
      $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:
  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:
  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:
  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:

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:

class Leaf extends Object {
public
function setParent(Leaf $x= NULL) { }
}

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

Please note that subclasses may not change method signatures! Thus if we have the following:
  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:
  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
5.8-SERIES
Unicode
Language
5.9-SERIES

Related

Find related articles by a search for «Making».