Polymorphic Methods in PHP5

Since PHP does not allow function/method overloading, it has become practice to define functions/methods following a scheme of function <funcname><typename>(). So I thought to myself, maybe there is a way to use that naming scheme, PHP’s variable function names together with some inflection and create the polymorpic dispatch myself.

And this is what I ended up with:

File: Polymorph.php

<?php
/**
 * Class for implementing missing polymorphic features. Ideally you would
 * extend this class, but since that is not always desired, you could just use
 * an instance of it.
 */
class Polymorph {
    /**
     * Get the names all ancestor classes of an object as an array.
     *
     * @param object $object
     * @return array
     */
    public static function getAncestors$object ) {
        $parents = array();
        $parent get_class$object );
        while ( $parent ) {
            $parents[] = $parent;
            $parent get_parent_class$parent );
        }
        return $parents;
    }
    /**
     * Get names of all interfaces a class implements.
     *
     * @param string $class
     * @return array
     */
    public static function getInterfaces$class ) {
        $reflector = new ReflectionClass$class );
        return array_keys$reflector->getInterfaces() );
    }
    /**
     * Perform name mangling of a method based on the provided type name.
     * It leaves the method name unchanged and uppercases the first letter of
     * the type name.
     *
     * @param string $method
     * @param string $type
     * @return string
     */
    protected function mangleByType$method$type ) {
        $type ucfirst$type );
        return "{$method}{$type}";
    }
    /**
     * Dispatch to passed method based on the type of the passed argument.
     *
     * @throws BadMethodCallException
     * @param string $method
     * @param mixed $var
     * @return mixed
     */
    protected function dispatchByType$method$var ) {
        $type gettype$var );
        $types = array();
        if ( $type == "object" )
            $types array_mergeself::getAncestors$var ),
                self::getInterfaces$var ) );
        else
            $types = array( $type );
        foreach ( $types as $_type ) {
            $_method $this->mangleByType$method$_type );
            if ( method_exists$this$_method ) )
                return $this->$_method$var );
        }
        // if we get here, no handler was found
        throw new BadMethodCallException(
            "Method `{$method}' for type `{$type}' not implemented." );
    }
}

File: polymorp-example.php

<?php
require_once 'Polymorph.php';
/* create some class hierarchy to polymorphically use */
interface Drawable {
}

abstract class Shape {
}

class Rectangle extends Shape implements Drawable {
}

class Triangle extends Shape implements Drawable {
}

class Circle extends Shape implements Drawable {
}

class Polygon extends Shape implements Drawable {
}

class Bitmap implements Drawable {
}

/**
 * This one will be the polymorph. Well, one method will be.
 * Note that the polymorphic implementation methods (draw<type>)
 * have to be at least `protected', they cannot be private.
 * Well, they can, but then you wouldn't be able to dispatch to
 * it.
 */
class Canvas extends Polymorph {
    public function draw$var ) {
        return $this->dispatchByType"draw"$var );
    }
    /* definitions of polymorphic functions */
    protected function drawDrawableDrawable $drawable ) {
        echo __METHOD__."\n";
        var_dump$drawable );
    }
    protected function drawShapeShape $shape ) {
        echo __METHOD__."\n";
        var_dump$shape );
    }
    protected function drawRectangleRectangle $rect ) {
        echo __METHOD__."\n";
        var_dump$rect );

    }
    protected function drawTriangleTriangle $tri ) {
        echo __METHOD__."\n";
        var_dump$tri );

    }
    protected function drawCircleCircle $circle ) {
        echo __METHOD__."\n";
        var_dump$circle );
    }
}

/* some values in an array to play with */
$values = array (
    new Rectangle(),
    new Triangle(),
    new Circle(),
    new Polygon(),
    new Bitmap(),
    120,
    "not a shape",
    true,
    false,
    null
);

$canvas = new Canvas();
foreach ( $values as $value ) {
    $canvas->draw$value );
}

Running polymorp-example.php should result in output similar to:

Canvas::drawRectangle
object(Rectangle)#1 (0) {
}
Canvas::drawTriangle
object(Triangle)#2 (0) {
}
Canvas::drawCircle
object(Circle)#3 (0) {
}
Canvas::drawShape
object(Polygon)#4 (0) {
}
Canvas::drawDrawable
object(Bitmap)#5 (0) {
}

Fatal error: Uncaught exception 'BadMethodCallException' with message 'Method
`draw' for type `integer' not implemented.' in Polymorph.php:68
<backtrace here>

`Polymorph::dispatchByType()' basically works by getting the type of a value, getting ancestor classes if the type is an object, then mangling the method name to a bit, finally calling the mangled method and passing the value to it.

Just another shortcoming of PHP, that needs to be hacked/kludged to work in some manner.

Advertisements

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: