PHP: Classy Dynamics

16 01 2016

flickr: by JusDaFax

Devotees of PHP may worry with the debut of PHP 7 that users have lost the beloved language we’ve known over the past twenty years. In truth, the newest version generally conforms to previous ones. In fact, the advent of PHP 7 heralds a technology distinguished by greater flexibility and consistency.

Old Sturdy and Reliable

I offer the following snippet to determine whether PHP 7 has really run far afield and if so, to what extent?

<?php

function test($var){
    echo “testing $var\n”;
}

class bla {

   function foo($val){
       test($val);
   }
}

$objBla = new bla();
$objBla->foo(“someval”);

// Output: 
// testing someval

See live code

In this example, a class method calls a function which yields the given string result. The code runs perfectly fine under the wide range of PHP 4.3.0 – 7.0.2. But, an insistent, gnawing question may lurk in the back of one’s mind concerning the feasibility of achieving the same result substituting a property value for a method call.

The Call of the Callable

As long as a class property indirectly refers to a function name, code may invoke said function by appending a set of parens to the property. If in doubt, you may verify an expression’s capability for calling a function with is_callable(), as the next example illustrates:

<?php

function test($var) {
           echo "Testing: $var\n";
}

class foo {

   var $baz = "test";

   function bar( $str ) {
      if( is_callable( $this->baz )) {
		$this->baz( $str );
      }
   }
}

$foo = new foo;

try {
  $foo->bar("bla"); // fatal error
} catch (Error $e){
    echo "Whoops -- ", $e->getMessage();
    echo "\nEnd of Script :)";
}

// Output:
// Whoops -- Call to undefined method foo::baz()

See live code

Though the class property possesses a callable aspect, unfortunately the parser emits a fatal error for its inability to locate a foo::baz() method. PHP 7 allows one to trap such fatal errors, so that the script execution continues uninterrupted.

A better way to have a callable “test” entails assigning the string to an array element contained in a class property, as follows:

<?php

function test($var) {
    echo "Testing: $var\n";
}

class foo {

   var $arr = array("test");

   function bar($str) {
     if ( is_callable( $this->arr[0] ) ){ 
          echo $this->arr[0]($str); 
     }
   }
}
$foo = new foo;
$foo->bar("bla"); 

// Output: 
// Testing: bla

See live code

The code works because the parser perceives $this->arr[0] as a callable expression (see zend_language_parsyer.y). This sample code actually derives from an old bug report. Remarkably the snippet executes under PHP 4.3.0 through the latest PHP 7 version, exclusive of some antiquated versions of PHP 5 which erroneously strived to deprecate the keyword var.

One might criticize the example’s lack of clarity, since one might confusedly expect a foo::test() method, static or otherwise. So, I offer the following as a superior alternative:

<?php

class Foo {
   public $arr = array("test");
   
   function bar( $str ){
       if( is_callable( $this->arr[0],true ) ){
         self::{$this->arr[0]}( $str );
       }     
   }    

   static function test($var) {
    echo "Testing: $var\n";
   }

}

(new Foo)->bar("bla");

// Output:  PHP 5.4.0-7.0.2 
// Testing: bla

See live code

The main difference with the preceding example is that this script eliminates the function, and adds a static method to the class. Note: in PHP a static method is essentially equivalent to a global function (see PHP Internals List discussion).

Since this example uses an object only once, an opportunity presents itself to employ another PHP 7 feature, an anonymous class which enables a developer to create an object on the fly and immediately invoke an object’s method, as follows:

<?php

class Foo {
   public $arr = array("test");
   
   static function test($var) {
    echo "Testing: $var\n";
   }

}

(new class extends Foo {
      function bar( $str ){
       if( is_callable( $this->arr[0],true ) ){
         self::{$this->arr[0]}( $str );
       }     
   }    
})->bar("bla");

// Output: PHP 7.0.0 – 7.0.2
// Testing: bla
See live code

Note that for the script to execute correctly, the second parameter of is_callable() must evaluate as true for the function to verify that the name contained in the variable might be a function or method. In the next line, the object reads the value of the property, specifically the array’s zeroth element. The object’s bar method uses the resulting name to statically call Foo’s solitary method. The anonymous class serves its purpose well in this instance for testing without requiring much fuss, such as a name or documentation.

Close Encounters with Closures

Once closures became available in PHP 5.3, one could write more interesting code using a dynamic class property with the magic __call() method, as follows:

<?php

class Foo
{
    public function __call($method, $args)
    {
        if( is_callable( array( $this, $method ) ) ) {
            return call_user_func_array( $this->$method, $args );
        } else {
            throw Exception;
        } 
    } 
} 

$foo = new Foo;
$foo->fn = function( $var ) { 
              return "Testing: $var"; 
           };
try {
  echo $foo->fn('Ble'); 
} catch (Exception $e){
    echo $e->getMessage();
}

// Output: PHP 5.3.0 – PHP 7.0.0+: 
// Testing: Ble

See live code

A user at Stackoverflow in 2013 inquired about how to call a closure assigned to an object’s property directly. With PHP 7, you may choose from a couple of options.

One may assign a closure to a class property and append parens, as long as one also remembers to enclose the object and its property in parens, too, due to the way the PHP 7 parser evaluates variables. The new left-to-right parsing behavior arises from the PHP project implementing a uniform variable syntax effective with PHP 7. The following example indicates how to cope with the new parsing behavior:

<?php

$obj = new stdClass;
$obj->color = function( $item ){
                $arr = ["rose"=>"red",
                        "violet"=>"blue",
                        "grass"=>"green"];
                
                return $arr[$item]; 
};


$flowers = [0=>"rose",1=>"violet"];

foreach ($flowers as $key => $flower){
   echo ucfirst("{$flower}s are ");
   echo ($obj->color)($flower);
   echo ($key == 0)? ",\n" : " ...\n";
}

// Output: PHP 7.0.0+ 
// Roses are red,
// Violets are blue ...

See live code

If you neglect to apply the parens around the object and its color property, the parser will search in vein for a method stdClass:color(). Upon failing to find any such method, a fatal error occurs. The parens enveloping the object and property aid in evaluating the expression as one containing a closure so that the parens following it automatically cause the closure to execute.

According to the aforementioned Stackoverflow article, one may also use a new feature referred to as Closure::call in this RFC (a formal proposal) as long as you employ it with a custom class. Despite the RFC featuring its usage with a stdClass, you must avoid using an internal class in PHP (see bug report). Interestingly, the HHVM does permit one to employ an internal class (see here).

The new feature essentially adds another method to the Closure class, one that binds with an object at call time and subsequently calls the closure, too. It obviates the need to apply Closure::bind (static) or the Closure::bindTo methods. Closure::call lessens the need for temporary variables and its ease of use is most convenient. The next example, a variant of the preceding one, utilizes this new feature, as follows:

<?php

class myClass {
    private $arr =  ["rose"  => "pink",
                    "violet" => "purple",
                    "grass"  => "green"];
}

$obj = new myClass;

$flowers = [0=>"rose",1=>"violet"];

$closure = function( $item ){
                return $this->arr[ $item ]; 
};

foreach ($flowers as $key => $flower){
   $color = $closure->call($obj,$flower);
   echo ucfirst(“{$flower}s are $color”);
   echo ($key == 0)? ",\n" : " ...\n";
}
// Output: PHP 7.0.0+
// Roses are pink,
// Violets are purple …

See live code

In Sum

For those who enjoy writing dynamic code, there’s one word to describe PHP’s support for dynamic features involving classes, especially in the latest version: classy!

This work is licensed under a Creative Commons License

Advertisements

Actions

Information

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: