Jump to content

PHP Method Chaining Tutorial ($this->foo()->bar())

- - - - -

  • Please log in to reply
2 replies to this topic

#1
Alexander

Alexander

    It's Science!

  • Moderators
  • 4,124 posts
  • Location:Vancouver, Eh! Cleverness: 200
This article assumes basic understandings of PHP 5 classes and exception handling, if these two concepts are still new to you, CodeCall has tutorials of which will be of benefit to you:

PHP 5 OOP: http://forum.codecal...-php-5-oop.html
PHP 5 Exception Handling: (to be written after this)



Method chaining in software engineering is often formally described as a fluent interface, of which can in effect chain similar methods in a readable way that does not require the object to be referenced in name for multiple calls its methods.

A sample PHP 5 class will be used in this article, that we will work on and improve using this method of object oriented programming:
<?php
class myMailClass {
  private $message;
  private $subject;
  private $recipients;
  
  public function __construct() {
    $this->message = "Default message";
    $this->subject = "Default subject";
    $this->recipients = array();
  }
  
  public function setMessage( $message, $subject ) {
    $this->message = $message;
  }
  
  public function setRecipients( array $recipients ) {
    $this->recipients = $recipients;
  }
  
  public function sendMessage() {
    return mail(implode($this->recipients, ", "), $this->subject, $this->message);
  }
}
Now we have the proper functions set up, and we could manually verify each parameter and then send the email:

//sample calling
$myMessage = new myMailClass;
$myBody = "This is the body";
$mySubject = "Welcome to our example";
$recipients = array("you@you.com", "me@me.com");

if(isset($myBody) && isset($mySubject) && is_array($recipients)) {
  $myMessage->setMessage($myBody, $mySubject);
    $myMessage->setRecipients($recipients);
    if($myMessage->sendMessage()) {
      print "Successful";
    } else {
      print "Failure";
    }
  }
} else {
  print "Please check your parmeters";
}
The chaining version will look like this:
$myMessage->setMessage($myBody, $mySubject)
          ->setRecipients($recipients)
          ->sendMessage();
This looks a lot more improved, how does it work?

We simply return $this in each object method:
  public function setMessage( $message, $subject ) {
    $this->message = $message;
    return $this;
  }
This will allow us to access the `$this` object much as we could access the normal scalar return values if it were to return something. This raises more than one problem however:


  • How can one know if there is an error in one of the chained methods?
  • How would we stop the other functions from running if a previous one has failed?

One way would be to return false (as in our example of sendMessage() if mail() had failed) however this will not return `$this`, and it will cause an error when attempting to access it as an object for the next function breaking each function after it.

We can utilize exceptions in each method to fix this problem, including our own custom exceptions if required.

...
  if( !isset($message) ... ) {
    throw new MessageEmptyFailure("Message or subject is empty, please check parameters.");
  } else {
    $this->message = $message;
  }
  return $this;
...
  if( !mail(..., ...) ) {
    throw new MessageDeliveryException("Message could not be delivered, check port 25.");
  }
  return $this;
And now we can catch each custom exception, and maintain our simple to read and follow chaining method:

<?php
//Errors can break methods too, we can convert in to an exception
set_error_handler('gen_error_handler');

class GenericPhpError extends Exception {}
class MessageDeliveryFailure extends Exception {}
class MessageEmptyFailure extends Exception {}

function gen_error_handler($errno, $errstr, $errfile, $errline) {
  throw new GenericPhpError(...);
}


?>
...
<?php
$myMessage = new myMailClass;
$myBody = "This is the body";
$mySubject = "Welcome to our example";
$recipients = array("you@you.com", "me@me.com");

try {
  $myMessage->setMessage($myBody, $mySubject)
            ->setRecipients($recipients)
            ->sendMessage();
} catch( MessageEmptyFailure $ex ) {
    print $ex->getMessage();
} catch( MessageDeliveryFailure $ex ) {
    file_put_contents("fatal.txt", "Delivery failure", FILE_APPEND);
    print $ex->getMessage();
} catch( GenericPhpError $ex ) {
    print "A generic exception has occurred: " . $ex->getMessage();
}
?>
The fixes provided over the original code can increase the quality and readability of your code greatly, however you must take caution in how you ensure each exception is caught.

Edited by Alexander, 05 June 2011 - 01:46 PM.

Be sure to read the updated FAQ! || Health is achieved through the same 10,000 steps.
If a suggested code/method fails, informing us is less important than telling us why or what errors occurred.

#2
John

John

    Writes binary right handed and hex left handed

  • Moderators
  • 6,321 posts
  • Location:New York, NY
Interesting. You actually put commas after each method call? Is that to let PHP know there is a method chained on the next line?

#3
Alexander

Alexander

    It's Science!

  • Moderators
  • 4,124 posts
  • Location:Vancouver, Eh! Cleverness: 200
Good catch John! An unfortunately added trailing comma by my IDE. The member operator is not affected by appropriate whitespace (newlines, spaces) and those can be freely added for clarity as in my example.
Be sure to read the updated FAQ! || Health is achieved through the same 10,000 steps.
If a suggested code/method fails, informing us is less important than telling us why or what errors occurred.




1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users