Using interfaces to describe processes

August 14, 2012 »best-practice

In the office, I’m currently working on something like a RESTful-API (much like the one GitHub exposes). Basically, you have multiple scripts (“API-functions” in this manner) that are called with arguments (either GET or POST), which then query the system for the specified resources and return XML/JSON results.

Of course it’s nice to have your DAO’s and utility classes OOP style, but the “API-functions” themselves are really just basic procedural code:

  1. Check the given parameters for validity
  2. Open the Database connection
  3. Query/write from/to the database
  4. Set HTTP-header and body
  5. Clean Up

The different API-functions only differ in what parameters they get and how they interact with the database. That means, a lot of code can be reused from previous API-functions. But watch out…

It’s a trap!

Since most of those API-functions are basically the same processes (as illustrated in the list above) and only differ slightly from each other, a lot of code might be copy-pasted from her to there. The danger with this is: You’re susceptible to forget something important.

I copied over some previously written code that opened a database-connection. But, as those scripts are procedural code, the “closing the database”-part came after the stuff I wanted from that script. So, I simply forgot to add it. And then, I wondered why there where more than twenty open database connections.

The sad thing with this is, that often, you’re not able to reuse code in a “write once, use often” manner, as you would do with any other code. It’s often the minimal differences between the two implementations that make it either impossible or very clumsy to outsource everything into functions.

What you can do, is using interfaces to describe similar processes and add methods for every step. That way, the compiler (or in PHP the debugger) will tell you if you forgot something important.

Important: Don’t use the provided examples in a productive environment, as they might be unsafe. Also, refer tor the last section for a word on error-handling!

Simple process interface

Here is an example of how such an interface for the above list of steps could look like:

/**
 * All these methods might return "false" to indicate, that something went
 *  horribly wrong and the other methods should not be called.
 * The "cleanUp()"-method will be called in any case!
 */
interface ApiFunctionProcess{

    /**
     * Check the given parameters
     */
    public function checkParam();

    /**
     * Open the Database connection
     */
    public function openDatabaseConnection();

    /**
     * Do what you have to do
     * (also set content and status code)
     */
    public function doWork();

    /**
     * Clean up after yourself
     * (This method is guaranteed to be called)
     */
    public function cleanUp();
}

Now, every step has it’s own method and every implementing class has to override every method of it.

The “processor”

Next, we need something to call the methods from our interface. This might be a simple class called Processor, or something you find more catchy or less irritating:

// This is an EXAMPLE IMPLEMENTATION. It might not be save to use in a productive environment!
class Processor{

    public static function execute($process){
        // Check if our interface is implemented
        // There might be a better approach to check this,
        //  see: http://stackoverflow.com/q/274360/717341
        if ($process instanceof ApiFunctionProcess){
            // The interface is implemented. Execute the methods:
            if ($process->checkParam())
                if ($process->openDatabaseConnection())
                    $process->doWork();

            $process->cleanUp();
            return; // Done.
        } else {
            echo "The given $process does not implement the ApiFunctionProcess-interface.";
        }
    }

}

Now, this code simply checks if the given $process implements our ApiFunctionProcess-interface, hence has all the methods defined in the interface, and then executes all of them in the predefined order. As a bonus, returning false from one of the methods will not call the next, but only the cleanUp()-method.

If we supply a class, which implements the interface but does not override all specified methods from the interface, the following error is issued by PHP:

Fatal error: Class Test contains 4 abstract methods and must
  therefore be declared abstract or implement the remaining methods
(
    ApiFunctionProcess::checkParam,
    ApiFunctionProcess::openDatabaseConnection,
    ApiFunctionProcess::doWork,
    ...
) on line 47

A certain part of the PHP error directly takes us to the next topic and possibility, when using this pattern. I’m talking about

[…] must therefore be declared abstract or implement the remaining methods

Abstract classes help reusing code

The next big advantage of this approach is, that by the rules of OOP, it is easy to create class which implement basic behaviour of a process, but leave the specific parts to the different processes themselves. This is possible, because we can declare a class abstract, which allows us to define abstract methods, that must be overwritten by the subclasses, very much like an interface-method.

The interesting part is, that an abstract class that implements an interface can decide to not override methods from that interface and leave it to it’s subclasses to do so:

/**
 * This abstract class implements a basic database open/close functionality
 */
abstract class BasicDatabaseProcess implements ApiFunctionProcess{

    protected $db;

    // Override for basic database opening:
    public function openDatabaseConnection(){
        // Do the basic database work
        $this->db = new mysqli('localhost', 'my_user', 'my_password', 'my_db');
        return true;
    }

    // Override for basic database closing:
    public function cleanUp(){
        // Close the database:
        if ($this->db) $this->db->close();
        return true;
    }

    // We intentionally left out "checkParam()" and "doWork()", because we really
    //  can't provide any "basic" implementations for those steps.
    // Since this class is declared "abstract", that's cool.
}

This abstract BasicProcess implements the basic database open/close operations. Every process that needs to do anything with the database can subclass this class, override the two left out methods from the interface and use the already provided database connection.

Extend the “basic” implementations

Sometimes, there are a few more steps required to get a certain part of the process done. Maybe, you want to hook up the database to a DAO object. You want to do this in the openDatabaseConnection()-method, as it was defined to do this kind of work. But you want to keep the basic implementation (which creates the MySQLi-object) because you need that anyways.

It comes in handy, that you can also override (and effectively extend) those pre-implemented methods:

/**
 * Also hooks the database-connection up to the DAO object
 */
abstract class BasicDaoProcess extends BasicDatabaseProcess{

    protected $dao;

    /**
     * Also hook the DAO up to the database connection
     */
    public function openDatabaseConnection(){
        // Execute the basic code from "BasicDatabaseProcess" to get the MySQLi connection:
        parent::openDatabaseConnection();
        // Hook it up to the DAO:
        $this->dao = new SomeDAO($this->db);
        return true;
    }

    // We won't override the "cleanUp()"-method here, for the sake of readability.
    // Just imagine the DAO-object does not need to be closed :-)

}

Now we have a process that opens a database-connection, hooks it up to a DAO and offers that basic functionality to every subclass (e.g. every process that needs the DAO). We produced readable and reusable code and kept everything OOP. Neat.

Example

Consider the following example API-function, that checks if the required parameters are given to the script, uses the DAO to load some data-sets (according to the given parameters) and returns the results as a JSON object:

class ReadUserProfile extends BasicDaoProcess{

    private $user_id;

    /**
     * Check if we got the mandatory parameters
     */
    public function checkParam(){
        if (!empty($_POST['user_id'])){
            // We have the parameter. Store it:
            $this->user_id = $_POST['user_id'];
        } else {
            // We don't have it, the function can not proceed.
            // Return false to abort the execution of the process.
            return false;
        }
        return true;
    }

    /**
     * Query the database and return the profile as JSON
     */
    public function doWork(){
        $profile = $this->dao->getUserProfileById($this->user_id);
        // Set response header and body
        header('HTTP/1.0 200 OK', true, 200);
        header('Content-type: application/json');
        echo json_encode($profile);
    }

}

This function uses the basic logic provided by the BasicDaoProcess-class to just load the user-profile data from the database and return it as a JSON-object (given that the getUserProfileById() returns an object that holds these information).

To execute it, only one line is necessary:

Processor::execute(new ReadUserProfile());

Following this approach, you’re not that error-prone when reusing code than you where before.

A quick word on error-handling

The above examples are in no way meant to be working, fully functional implementations! In the above approach, there is no (nice) way to handle errors in any of the called methods. Although you can log them when they occur, you’ll most likely want to tell the API client, that the function could not be executed probably (due to a missing parameter for example).

An idea of how this could be implemented would be to return an error-code (as a integer) from every function (in case of error), or to return 0 or true when the function executed probably. The Processor would then check that return-value and if it’s not indicating success, call a handleError($error_code)-method to handle the error appropriately.

Conclusion

  • Create interfaces to describe processes step by step
  • Then, create a “processor”, which executes those steps in the appropriate order
  • Use abstract classes to implement “basic” functionality that should be reusable
  • Extend those “basic” implementations to do more specific tasks that should be reusable

Posted by Lukas Knuth

Comments

comments powered by Disqus