Safely checking if a model class exists (without exploding)

You are viewing revision #3 of this wiki article.
This is the latest version of this article.
You may want to see the changes made in this revision.

« previous (#2)

  1. When is this needed?
  2. Suggested solution

When is this needed?

While rewriting the PcReportContent extension I'm maintaining I've bumped to a challenge. This wiki was born out of it.

This PcReportContent extension is about reporting inappropriate content by users, to some predefined email address. This works using a widget that is plant-able in any location on the website, and which gets a 'model name' and 'model id' when rendered. It plants this information in the report form so when the user submits the form, it reaches the server side with this information, along of course with the user text explaining what's wrong with the content its reporting of. This allows for several such 'report content' boxes (=widget instances) to live (happily ever after) one along side each other on the same page. Each contains different form values (meaning model name and id), in accordance to where it was 'rendered'.

Here's the problem: When processed on server side, the server side counterparts needs to validate the client side data, as any data from client side is never to be trusted, right? Ok, so my initial validation on the model name was a 'class_exists()' firing. Problem is, if the class does not exists, without rather complex changes to Yii's auto loader or to PHP's environment (silencing PHP errors - another thing you don't want to operate under - you want to be aware of problems in your code/application) a PHP Error (WARNING) will be triggered and the application operation will abort. I wanted to catch this situation and return nicely to client side rather then this abortion.

Just to complete the picture regarding webite security, with the input extension deployed, I think I'm pretty safe with regard to malicious user tweaking the form values.

Suggested solution

Since the script will end with a PHP error, not with a PHP Exception being thrown, a simple try-catch using is not an option.

Also note that I do want to check the presence of the model class using Yii's auto loader so using _classexists(MyClass, false) (as suggested here) was not an option as well.

Solution: The solution was to temporarily provide an error handler method that is 'operative' just when the validation of the model class takes place. Since what will happen is that Yii will attempt to load the class, using 'include()' at the end of the process, if the class name does not exists, a PHP warning will be thrown. PHP WARNINGs are 'catch-able' by custom error handler methods. So, it all boils down to this setup:

// validate we have modelName, modelId
set_error_handler(array($this, 'tempErrorHandler'), error_reporting());
if ((!class_exists($_POST['model_name'])) || (empty($_POST['model_id']))) {
    // if this block is executed its since the model_id was problematic.
    // log and return with an error to client side
    Yii::log(...);
    echo "Error occurred";
    Yii::app()->end();
}
// purge the temporary error handler
restore_error_handler();

// and down the same class (its a controller class) we have the custom error handler method
public function tempErrorHandler($errno, $errstr)
{
    Yii::log(...);
    echo "Error occurred";
    Yii::app()->end();
}