Lets assume I have a class called Form. This class uses the magic method __call() to add fields to itself like so:
<?php
class Form {
private $_fields = array();
public function __call($name, $args) {
// Only allow methods which begin with 'add'
if ( preg_match('/^add/', $name) ) {
// Add a new field
} else {
// PHP throw the default 'undefined method' error
}
}
}
My problem is that I can't figure out how to make PHP handle the calls to undefined methods in it's default way. Of course, the default behavior can be mimicked in many ways, for example I use the following code right now:
trigger_error('Call to undefined method ' . __CLASS__ . '::' . $function, E_USER_ERROR);
But I don't like this solution because the error itself or its level might change in the future, so is there a better way to handle this in PHP?
Update
Seems like my question is a little vague, so to clarify more... How can I make PHP throw the default error for undefined methods without the need to supply the error and it's level? The following code won't work in PHP, but it's what I'm trying to do:
// This won't work because my class is not a subclass. If it were, the parent would have
// handled the error
parent::__call($name, $args);
// or is there a PHP function like...
trigger_default_error(E_Undefined_Method);
If anyone is familiar with ruby, this can be achieved by calling the super method inside method_missing. How can I replicate that in PHP?
Use exceptions, that's what they're for
public function __call($name, $args) {
// Only allow methods which begin with 'add'
if ( preg_match('/^add/', $name) ) {
// Add a new field
} else {
throw new BadMethodCallException('Call to undefined method ' . __CLASS__ . '::' . $name);
}
}
This is then trivially easy to catch
try {
$form->foo('bar');
} catch (BadMethodCallException $e) {
// exception caught here
$message = $e->getMessage();
}
If you want to change error level, just change it when necessary or add if statement instead of E_USER_ERROR
Related
In the PHP project I'm working on there are several methods that make use of individual try/catch blocks. Here is one example:
public function getListData()
{
$clauses['filter'] = '';
$clauses['sort'] = 'CAST(propertyID AS INT) DESC';
try
{
return $this->getModel()->getListData($clauses);
}
catch (Exception $e)
{
// create an Error() object, send $e->getMessage() to it
}
}
Now, keeping in mind there are several similar methods, it would seem more efficient to write a method in the Model class that would look like this:
public function run($method)
{
try
{
return $this->$method;
}
catch (Exception $e)
{
//create an Error() object, send $e->getMessage() to it
}
}
The problem is calling it. This does not work:
public function getListData()
{
return $this->getModel()->run('getListData($clauses)');
}
The error is:
Undefined property:
classes\utility\Model::$getListData($clauses).
Is there a way to get this to work?
I'm going to assume that the first and second getListData() methods are in separate classes, otherwise you are calling a loop, since getListData would call getListData...which would call, you get it.
However, the way you are calling the method is incorrect in the run() method. It should be called using call_user_func. It is a callback to the method, not a call to the property, of the class.
You could call it statically using
public function run($method, $data)
{
try
{
return call_user_func(array($this, $method), $data);
}
catch (Exception $e)
{
//create an Error() object, send $e->getMessage() to it
}
}
public function getListData()
{
return $this->getModel()->run('getListData', $clauses);
}
There are several problems with this approach:
It prevents you from listening for custom exceptions
You can throw exceptions other than Exception, but this type of wrapper will make it much more difficult to do so.
It is difficult to follow the execution flow
When you pass method names and parameters around as strings, it becomes much harder for humans, IDEs and code analysis tools to understand what the program will do at runtime.
Try/catch blocks are cheap
The code required to catch exceptions is very simple and easy to use. This wrapper adds more complexity and more cost (an extra function call).
Consider just using try/catch blocks where needed instead of using the wrapper. If you have fifty similar methods as described in your comment above, you may gain more efficiency by eliminating the duplicate business logic and combining those methods.
You could simply convert errors to exceptions using this code:
set_error_handler(function ($errno, $errstr, $errfile, $errline)
{
if ((error_reporting() & $errno) === $errno)
throw new \Exception("$errstr ($errfile: $errline)", (int) $errno);
}, -1);
After it any error would be converted to exception.
I'm calling a method that I know could cause an error and I'm trying to handle the error by wrapping the code in a try/catch statement...
class TestController extends Zend_Controller_Action
{
public function init()
{
// Anything here happens BEFORE the View has rendered
}
public function indexAction()
{
// Anything `echo`ed here is added to the end of the View
$model = new Application_Model_Testing('Mark', 31);
$this->view->sentence = $model->test();
$this->loadDataWhichCouldCauseError();
$this->loadView($model); // this method 'forwards' the Action onto another Controller
}
private function loadDataWhichCouldCauseError()
{
try {
$test = new Application_Model_NonExistent();
} catch (Exception $e) {
echo 'Handle the error';
}
}
private function loadView($model)
{
// Let's pretend we have loads of Models that require different Views
switch (get_class($model)) {
case 'Application_Model_Testing':
// Controller's have a `_forward` method to pass the Action onto another Controller
// The following line forwards to an `indexAction` within the `BlahController`
// It also passes some data onto the `BlahController`
$this->_forward('index', 'blah', null, array('data' => 'some data'));
break;
}
}
}
...but the problem I have is that the error isn't being handled. When viewing the application I get the following error...
( ! ) Fatal error: Class 'Application_Model_NonExistent' not found in /Library/WebServer/Documents/ZendTest/application/controllers/TestController.php on line 23
Can any one explain why this is happening and how I can get it to work?
Thanks
use
if (class_exists('Application_Model_NonExistent')) {
$test = new Application_Model_NonExistent;
} else {
echo 'class not found.';
}
like #prodigitalson said you can't catch that fatal error.
An error and an exception are not the same thing. Exceptions are thrown and meant to be caught, where errors are generally unrecoverable and triggered with http://www.php.net/manual/en/function.trigger-error.php
PHP: exceptions vs errors?
Can I try/catch a warning?
If you need to do some cleanup because of an error, you can use http://www.php.net/manual/en/function.set-error-handler.php
Thats not an exception, thats a FATAL error meaning you cant catch it like that. By definition a FATAL should not be recoverable.
Exception and Error are different things. There is an Exception class, which you are using and that $e is it's object.
You want to handle errors, check error handling in php-zend framework. But here, this is a Fatal error, you must rectify it, can not be handled.
I am trying to re-architect a web application I developed to use the MVC pattern, but I'm not sure how to handle errors. For example:
class AM_Products extends AM_Object
{
public function save( $new_data = array() )
{
// Validate input
// Save input
}
}
If I pass invalid input to save, should I throw an exception like this:
class AM_Products extends AM_Object
{
public function save( $new_data = array() )
{
// Validate input
if ( ! validate( 'text', $new_data['name'] ) ) {
throw new Exception( 'Invalid data entered' );
}
// Save input
}
}
Or instead, should I add an extra function and leave it to the view/controller:
if ( $product->save( $data )->has_error() ) {
$error = $product->get_error();
}
echo '<p>' . $error . '</p>';
Don't throw an exception. Exceptions are for exceptional situations - invalid data entered into a form should not trigger an exception.
Your model should have some sort of error state set, either on the model itself or on the individual fields. The post-back should "fall through" and display the same form that was originally shown, with error messages and/or highlighted fields indicating where the error is so the user can fix it.
Throwing exceptions for validation is going to lead to a very fragile and difficult to use system. What happens if you want to simply show the user that one of the fields they supplied is invalid and give them a chance to correct it? How are you going to catch an exception and know how to display the associated record/form?
Throw an exception. Otherwise you'll have to remember always to call has_error/get_error after any operation. And you'll have a lot of duplicated code. And what if the error should be handled not by the method a() that have called save(), but by the method b() that called the method a()? you'll have to return error from a(), and b() will have to check for the error as well.
so, instead of lots of instances of
if (odbc_exec($sql))
{
}
else
{
myErrorHandlingFunction();
}
I wrap that in a function
function myOdbxExec($sql)
{
if (odbc_exec($sql))
{
}
else
{
myErrorHandlingFunction();
}
}
BUT I would like myErrorHandlingFunction() to report things like __LINE__ __FILE__ etc
Which looks like I have to pass thoses infos to every call of helper functions, e.g. myOdbxExec($sql, __FILE__, __LINE__) which makes my code look messy.
function myErrorHandlingFunction($errorTExt, $fiel, $line)
{
// error reporting code goes here
}
function myOdbxExec($sql, $file, $line)
{
if (odbc_exec($sql))
{
}
else
{
myErrorHandlingFunction();
}
}
$sql = 'select * from ... blah, blah, blah...';
myOdbxExec($sql, __FILE__, __LINE__); // <==== this is *ugly*
In C I would hide it all behind a #define, e.g. #define MY_OFBC_EXEC(sql) myOdbxExec(sql, __FILE__, __LINE__)
1) (how) can I do that in PHP
2) what else is worth outoputting? e.g. error_get_last()? but that has no meaning if odbc_exec() fails ...
To rephrase the question - what's the generic approach to PHP error handling? (especially when set_error_handler() doesn't really apply?
Edit: just to be clear - I do want to handle exceptions, programming errors, etc, but, as my example shows, I also want to handle soemthings the teh PHP interpreter might noit consider to be an error, like odbc_exec() returning false().
Both the above answers mentioning debug_backtrace are answering your _______LINE_____ / _______FILE___ question with the debug_backtrace() function. I've wanted this answer too in the past, so have put together a quick function to handle it.
function myErrorHandlingFunction($message, $type=E_USER_NOTICE) {
$backtrace = debug_backtrace();
foreach($backtrace as $entry) {
if ($entry['function'] == __FUNCTION__) {
trigger_error($entry['file'] . '#' . $entry['line'] . ' ' . $message, $type);
return true;
}
}
return false;
}
You can then call, for instance, myErrorHandlingFunction('my error message');
or myErrorHandlingFunction('my fatal error message', E_USER_ERROR);
or try { ... } catch (Exception $e) { myErrorHandlingFunction($e->getMessage()); }
First off, you might want to consider using exception handling.
If for some reason, that doesn't appeal to you, you can use debug_backtrace inside your generic error handler to figure out where the handler was called from.
EDIT In response to OP's comment:
Exceptions don't have to come from PHP built-ins. You can throw your own. Since an ODBC error generally is an exceptional condition, just throw an exception when you detect one. (And catch it at some higher level).
<?PHP
if (! odbc_exec($sql) )
throw new DatabaseException('odbc_exec returned false, that bastard!');
Use debug_backtrace to figure out where a function was called from in your error handler. Whether you invoke this error handler by manually calling it, by throwing and catching exceptions, PHPs error_handler or any other method is up to the application design and doesn't really differ that much from other languages.
I'm testing some legacy code that extends the default php exception object. This code prints out a custom HTML error message.
I would like to mock this exception object in such a way that when the tested code generates an exception it will just echo the basic message instead of giving me the whole HTML message.
I cannot figure out a way to do this. It seems like you can test for explicit exceptions, but you can't change in a general way the behavior of an exception, and you also can't mock up an object that extends a default php functionality. ( can't think of another example of this beyond exceptions... but it would seem to be the case )
I guess the problem is, where would you attach the mocked object?? It seems like you can't interfere with 'throw new' and this is the place that the object method is called....
Or if you could somehow use the existing phpunit exception functionality to change the exception behavior the way you want, in a general way for all your code... but this seems like it would be hacky and bad....
EDIT: here is some code to make things clearer:
class FooTest extends PHPUnit_Framework_TestCase{
public function testBar(){
include '/path/to/file.php'; //generates exception
$this->assertTrue($baz);
}
}
...
//overridden exception class
class Foo_Exception extends ErrorException{
...
so, my question, is there a way to deal with this overriden class, without doing it on a case by case basis? what if I'm not testing the behavior of the exception, just the code that causes the exception?
I would first write a test that captures the exception generation behavior:
include '/path/to/file.php'; //generates exception
public function testCatchFooException() {
try {
$this->assertTrue($baz);
}
catch (Exception $expected) {
$this->assertEquals('This is expected html from exception', $expected->getMessage());
return;
}
$this->fail('An expected Exception has not been raised Foo_Excpetion.');
}
Now you can do several things with this coverage test. You can either fix up the exception, or fix the code that causes the exception.
Another thing you can do is wrap the entire file.php in a class:
class FooClass {
function runFoo() {
include '/path/to/file.php'; //generates exception
}
}
Then add tests while using extract method until you isolate exception.
[EDIT]
Here is some serious procedural legacy code:
<?php
require_once 'helper.php'; //helper file
function countNewMessages($user_id) {
}
function countNewOrders() {
}
function countNewReturns() {
}
function getDB($init = NULL) {
}
function getDisplay() {
}
getDisplay();
?>
And here is the wrapped class:
<?php
require_once ''; //helper file
class Displayer {
function countNewMessages($user_id) {
}
function countNewOrders() {
}
function countNewReturns() {
}
function getDB($init = NULL) {
}
function getDisplay() {
}
}
?>
And now I can test it:
function testGetDisplay() {
$display = new Displayer();
$this->assertEquals('html code', $display->getDisplay());
}
And test the individual functions in it. And if I can further sprout methods on it.
The above test would be considered a coverage test. There may be bugs in it, but that is what it does. So as I sprout methods the get more code coverage from tests by sprouting that I can make sure I don't break the output.
The extened PHP exception object "prints" a costum HTML error page? You mean its error message is an entire HTML page? That's not very clever...
What you can do about it is to replace the default exception handler (see this function), call getMessage on the exception and parse the HTML error page to extract the message. Then you can print the error message and kill the script. Like this (in PHP 5.3):
set_exception_handler(
function (Exception $e) {
die(parse_html_error_page($e->getMessage()));
}
);
OK, I misunderstood the question. If the script you're testing catches the error and then echoes an error page, then this has nothing to do with exceptions. You can use the ob_ family:
ob_start();
include $file;
$contents = ob_get_contents();
if (result_is_error($contents))
die(extract_error_from_result($contents));
else
echo $contents;
ob_end_clean();