views:

34

answers:

2

In a lot of my PHP classes, I have this code:

private $strError = "";
private $intErrorCode = NULL;
private $blnError = FALSE;


public function isError() {
    return $this->blnError;
}

public function getErrorCode() {
    return $this->intErrorCode;
}

private function setError( $strError, $intErrorCode = NULL ) {
    $this->blnError = TRUE;
    $this->intErrorCode = $intErrorCode;
    $this->strError = $strError;
}

The point is so that outside code can know if an object has an error state, what the string of the error is, etc. But to have this exact code in a bunch of different classes is repetitious!

I'd love to have a dual-extension where I could do

class childClass extends parentClass, error {
    ...
}

And have those properties and methods inborn, But PHP doesn't support multiple inheritances. What I'm thinking about doing is creating an error class that exists inside each class. If I make it public, I can call it directly through the object

if ( $myObject->error->isError() ) {...}

but wouldn't that also make its error status settable from outside the containing class,

$myObject->error->setError("I shouldn't be doing this here");

which I would rather avoid?

Or I could write 'gateway' functions in the containing class, which do the appropriate calls on the error object, and prevent setting the error status from outside,

class childClass extends parentClass {

    private $error;

    public function __construct(...) {
        ...
        $error = & new error();
        ...
    }

    public function isError() {...}
    public function getError() {...}
    public function getErrorCode() {...}
    private function setError() {...}

    ...
}

but that leads to (some of) the code duplication that I'm trying to avoid.

What's the optimal solution here? I'm trying to have functionality for error statuses for a number of objects, so that the outside world can see their error state, with minimal repetition.

+2  A: 

Use composition instead of inheritance.

class Errors {

    private $strError = "";
    private $intErrorCode = NULL;
    private $blnError = FALSE;


    public function isError() {
        return $this->blnError;
    }

    public function getErrorCode() {
        return $this->intErrorCode;
    }

    private function setError( $strError, $intErrorCode = NULL ) {
        $this->blnError = TRUE;
        $this->intErrorCode = $intErrorCode;
        $this->strError = $strError;
    }

}

And now use a private instance variable to refer to it:

class childClass extends parentClass {
    private $errors = new Errors();
    ...
}

The private visibility prevents you from referencing $errors outside of the class.

There's also no need to create isError(), getError(), etc. inside childClass (and therefore no need to worry about code duplication). Simply call $this->errors->isError(), $this->errors->getError(), etc. If you still wanted to require those methods to be implemented though, as suggested below, you could specify an interface.

Edward Mazur
And let's add an `interface` to `childClass` that expects `setErrors` and `getErrors` to be implemented
Gordon
On the other hand, if you had error class extend parent class and all child classes extend error class you would have no need for the one liner "private $errors = new Errors();" in all your child classes. (Not saying a better solution, just saying.)
Chris
Am I totally confused? Isn't the whole point *to* call `errors` from outside the class? I want to see from outside if the class has an error, and $errors is just a way of keeping track of it. The *only* thing I don't want to reference from outside the class is `setError()`. Am I right? I mean, it's a great way for a class to keep track of its errors internally, but at some point, I do want to communicate that to the outside world, and that was the whole point in the first place -- to find out if `$myObject` is in an error state.
@user An instance of childClass has all the Errors class methods available to it.
Chris
@user In that case, do as Gordon suggested and also implement an interface, say `ErrorCheckable`, that exposes only the methods you want. I'd prefer this small amount of duplication over bending the inheritance hierarchy in ways it probably shouldn't go.
Edward Mazur
@user having adapter or proxy methods isnt bad, but if collecting Errors is a concern all or many of your classes have, then you are duplicating code all over the place just because you have to implement the methods that naturally are the responsibility of the ErrorCollector. With an interface defining setErrorCollector method you are enabling setter injection, which allows you to decouple the collector from the aggregating classes and with getErrorCollector you simply get the instance and work with it. That's as clean and reusable as can be.
Gordon
+1  A: 

You could also abuse the __call magic method to do the same thing:

public function __call($name, array $arguments) {
    $name = strtolower($name);
    if (isset($this->methods[$name])) {
        array_unshift($arguments, $this);
        return call_user_func_array($this->methods[$name], $arguments);
    }
    throw new BadMethodCallException('Method does not exist');
}

Note that I said abuse... Ideally, I'd think of a different architecture rather than having all these "common methods" everywhere. Why not use an exception instead of checking $foo->isError? If that's not appropriate, why not decorate a class?

class Errors 
    protected $object = null;
    public function __construct($object) {
        $this->object = $object;
    }
    public function __call($method, array $arguments) {
        $callback = array($this->object, $method);
        if (is_callable($callback)) {
                return call_user_func_array($callback, $arguments);
        }
        throw new BadMethodCallException('Method does not exist');
    }
    public function __get($name) { return $this->object->$name; }
    public function __set($name, $value) { $this->object->$name = $value; }
    //  Your methods here
    public function isInstance($name) { return $this->object instanceof $name; }
}

Then just "wrap" your existing object in that class:

$obj = new Errors($obj);
$obj->foo();
ircmaxell
That is a really interesting approach to this problem, I might just try and implement that in my next project. I love it!
Chris
It has some really big drawbacks. First, you can't use interfaces naively, since it won't implement your interface (Hence the reason for the `isInstance` method). Second, debugging can be VERY difficult, since you're not sure the exact chain or order of the "decorators" or dynamic methods... Third, it can make for some very interesting "Where was this method defined" issues. It's definitely useful in some situations (which is why I posted it). But be very careful with how you implement it...
ircmaxell