In my PHP layer I'm receiving error codes as strings ("NOT_FOUND", "EXPIRED", etc). It's a small list of possible strings, perhaps a dozen.
What's the most efficient way of dealing with these? Using switch-case against string constants stored in a class, or should I parse them to numbers (or something) first? Or is PHP smart enough so it doesn't really matter?
You might want to consider using constants? Let's say you have a class Error and define all error codes there like this:
class Error {
const NOT_FOUND = 0;
const EXPIRED = 1;
// and so forth
}
And then you can use them in your code by accessing them like Error::NOT_FOUND and a switch statement wouldn't need to compare strings but has plain ints without downgrading readability.
It really depends on what you want to do with the strings. Do you want to output error messages? Then instead of a case statement you could use a lookup table like this:
$messages = array(
'NOT_FOUND' => 'The file was not found',
'EXPIRED' => 'The cookie expired'
// ETC
);
echo empty($messages[$error]) ? "Unknown error" : $messages[$error];
With PHP 5.3 you could also store code in the array to handle the error situations:
$handlers = array(
'NOT_FOUND' => function() { /* Error handling code here */ },
'EXPIRED' => function() { /* Other error handling code */ }
);
if(!empty($handlers[$error])) {
$handler = $handlers[$error];
$handler();
}
else {
echo "Could not handle error!"; die();
}
With a technique like this you avoid case statements that go over several pages.
With PHP < 5.3 you might look into call_user_func for dynamic dispatching of error handling functions.
Strings can't be recognised as class constants. But the answer on your question is that it doesnt really matter if you do it like this:
switch (className::{$errorCode}) { // $errorCode == name of the constant, like NOT_FOUND
// Cases here.
}
Related
I know that an E_WARNING is generated by PHP
PHP Warning: Unknown: Input variables exceeded 1000
But how can I detect this in my script?
A "close enough" method would be to check if( count($_POST, COUNT_RECURSIVE) == ini_get("max_input_vars"))
This will cause a false positive if the number of POST vars happens to be exactly on the limit, but considering the default limit is 1000 it's unlikely to ever be a concern.
count($_POST, COUNT_RECURSIVE) is not accurate because it counts all nodes in the array tree whereas input_vars are only the terminal nodes. For example, $_POST['a']['b'] = 'c' has 1 input_var but using COUNT_RECURSIVE will return 3.
php://input cannot be used with enctype="multipart/form-data". http://php.net/manual/en/wrappers.php.php
Since this issue only arises with PHP >= 5.3.9, we can use anonymous functions. The following recursively counts the terminals in an array.
function count_terminals($a) {
return is_array($a)
? array_reduce($a, function($carry, $item) {return $carry + count_terminals($item);}, 0)
: 1;
}
What works for me is this. Firstly, I put this at the top of my script/handler/front controller. This is where the error will be saved (or $e0 will be null, which is OK).
$e0 = error_get_last();
Then I run a bunch of other processing, bootstrapping my application, registering plugins, establishing sessions, checking database state - lots of things - that I can accomplish regardless of exceeding this condition.. Then I check this $e0 state. If it's not null, we have an error so I bail out (assume that App is a big class with lots of your magic in it)
if (null != $e0) {
ob_end_clean(); // Purge the outputted Warning
App::bail($e0); // Spew the warning in a friendly way
}
Tweak and tune error handlers for your own state.
Registering an error handler won't catch this condition because it exists before your error handler is registered.
Checking input var count to equal the maximum is not reliable.
The above $e0 will be an array, with type => 8, and line => 0; the message will explicitly mention input_vars so you could regex match to create a very narrow condition and ensure positive identification of the specific case.
Also note, according to the PHP specs this is a Warning not an Error.
function checkMaxInputVars()
{
$max_input_vars = ini_get('max_input_vars');
# Value of the configuration option as a string, or an empty string for null values, or FALSE if the configuration option doesn't exist
if($max_input_vars == FALSE)
return FALSE;
$php_input = substr_count(file_get_contents('php://input'), '&');
$post = count($_POST, COUNT_RECURSIVE);
echo $php_input, $post, $max_input_vars;
return $php_input > $post;
}
echo checkMaxInputVars() ? 'POST has been truncated.': 'POST is not truncated.';
Call error_get_last() as soon as possible in your script (before you have a chance to cause errors, as they will obscure this one.) In my testing, the max_input_vars warning will be there if applicable.
Here is my test script with max_input_vars set to 100:
<?php
if (($error = error_get_last()) !== null) {
echo 'got error:';
var_dump($error);
return;
}
unset($error);
if (isset($_POST['0'])) {
echo 'Got ',count($_POST),' vars';
return;
}
?>
<form method="post">
<?php
for ($i = 0; $i < 200; $i++) {
echo '<input name="',$i,'" value="foo" type="hidden">';
}
?>
<input type="submit">
</form>
Output when var limit is hit:
got error:
array
'type' => int 2
'message' => string 'Unknown: Input variables exceeded 100. To increase the limit change max_input_vars in php.ini.' (length=94)
'file' => string 'Unknown' (length=7)
'line' => int 0
Tested on Ubuntu with PHP 5.3.10 and Apache 2.2.22.
I would be hesitant to check explicitly for this error string, for stability (they could change it) and general PHP good practice. I prefer to turn all PHP errors into exceptions, like this (separate subclasses may be overkill, but I like this example because it allows # error suppression.) It would be a little different coming from error_get_last() but should be pretty easy to adapt.
I don't know if there are other pre-execution errors that could get caught by this method.
What about something like that:
$num_vars = count( explode( '###', http_build_query($array, '', '###') ) );
You can repeat it both for $_POST, $_GET, $_COOKIE, whatever.
Still cant be considered 100% accurate, but I guess it get pretty close to it.
I hope the title isn't too confusing, I'll try to explain better below.
Suppose I have a function in a separate file, functions.php:
function divide($num1, $num2) {
if ($num1 == 0 || $num2 == 0) {
trigger_error("Cannot divide by 0", E_USER_ERROR);
} else {
return ($num1 / $num2);
}
}
And another file that calls it:
include "functions.php";
echo divide(10, 0);
My error is
Fatal error: Cannot divide by 0 in
C:\Users\Derek\Desktop\projects\functions.php on line 5
My question is, how do I make that error instead point to the location of the error in the main code, so I instead get:
Fatal error: Cannot divide by 0 in
C:\Users\Derek\Desktop\projects\main.php on line 3
The particular reason I want this is because I have a function called load_class that simply finds a PHP file and instantiates the object inside, but if given an incorrect file name, it reports an error from inside load_class, which is technically true, but it's not particularly helpful if I don't remember where I called load_class in the first place. I would like the error to point to the file that called load_class incorrectly.
Also, I would like to write a function error() (something like below) that when given a message as a parameter would throw more "meaningful" error messages, but when done that way, the error always says it comes from error(), not from where the error actually came from!
For example, in an error.php:
/**
* error()
*
* Throws an error of a certain type
*
* #param string $type The type of error. "Fatal", "warning," or "notice"
* #param string $message A description of the error
* #return void
*/
function error($type, $message) {
switch (strtolower($type)) {
case 'fatal':
trigger_error($message, E_USER_ERROR);
break;
case 'notice':
trigger_error($message, E_USER_NOTICE);
default:
trigger_error($message, E_USER_WARNING);
break;
}
}
And in an index.php
error("fatal", "A sample warning!");
My error given is:
Fatal error: A sample warning! in
C:\Users\Derek\Desktop\projects\synthesis\sys\Error.php on line 45
But the error didn't occur in error.php, it happened in index.php! How can I make it show where it really came from?
The debug_backtrace function allows you to obtain the stacktrace as an array. You can pick the original location from there.
Next to that you need to slip into the error message to make this look-alike. Example:
function divide($num1, $num2) {
if ($num1 == 0 || $num2 == 0) {
trigger_error_original("Cannot divide by 0", E_USER_ERROR);
} else {
return ($num1 / $num2);
}
}
function trigger_error_original($message, $type) {
$trace = debug_backtrace(FALSE);
list($location) = array_slice($trace, 1, 1) + array('file' => 'unknown', 'line' => 'unknown');
$message .= sprintf(" in %s on line %d\nTriggered", $location['file'], $location['line']);
trigger_error($message, $type);
}
divide(1, 0);
The error message than shows something like:
> php test-pad.php
Fatal error: Cannot divide by 0 in test-pad.php on line 18
Triggered in test-pad.php on line 15
The downside of this is, that you need to change your code to have this "feature". If you need this for debugging your own code, it's much better that you enable backtraces in your logs. The Xdebug extension does this for you, or you can write your own error handler that takes care of that.
See as well the related question Caller function in PHP 5?. I used array_slice so that you could create an additional parameter to define the number of steps you want to go "up" in the backtrace.
Use debug_backtrace(), and debug_print_backtrace() for a full call stack. These are especially effective when using Xdebug, which will override the function to colorize the output.
I have this same problem...
#1: while 10/0 = ERROR, 0/10 = 0 is perfectly legal, you shouldn't have an exception for that.
#2: when you include a file, it effectively becomes part of this new file, so perhaps you might have to toy a little bit with things like __FILE__ and see if you can make it point it to the file before it gets included in the other file..
You can use xdebug - it will show you the stacktrace or you can register your own error handndler and display the stacktrace. Just check the example in php.net for set_error_handler().
Maybe exceptions are better to use in your case. You get the full stacktrace and can locate where the function was called without relying on some tricky code :)
First let me say sorry for the amount of code I'm posting below I'm going to try and keep it as short as possible but this is built on top of my MVC (lightweight-mvc)
Ok So my Problem is that for some reason php is throwing a fatal error on code that should not be being used in my current code,
So how this works I have my MVC witch used the first 2 parts of the url to know what its loading, the problem is I'm building a Moulder CMS into my MVC so it's boot strapping twice,
So here is my Problem,
http://{domain}/admin/control/addon/uploader/method/uploadCheck/
I'm using the above now let me explain a little into that the /admin/control are for the main MVC System it auto-loads the Admin controller then fires the controlAction method from the controller much the same as most MVC's,
The next part are URL paramters that build an array the same as GET or POST would
array('addon'=>'uploader', 'method'=>'uploadCheck')
So from that my control action will auto load as is the code below
public function controlAction(){
global $_URL;
if(cleanData::URL("addon")){
$addonName = "addon_".cleanData::URL("addon");
$methodName = (cleanData::URL("method"))? cleanData::URL("method")."Action" : "indexAction";
echo $methodName;
$addon = new $addonName();
$addon->$methodName();
return;
}else{
$this->loadView("CMS/controll");
}
}
cleanData::URL is an abstract method that just returns the value of the key provided though addSlashes()
So as you can see from the code below it will then use the autoloader to load the module(AKA addon)
Just so you can follow the auto loader works in a simpler version of the Zend Frame work autoloader _ so you have class name addon_admin that would be inside file admin.php that is in the folder addon so the autoloader will load addon/admin.php
So As above with my URL and controlAction it's loading addon/uploader.php and as such this is the contents
<?php
class addon_uploader extends Addons{
public function uploadCheckAction(){
echo 0;
}
public function uploaderAction(){
if (!empty($_FILES)) {
$tmpFile = $_FILES['Filedata']["tmp_name"];
$newLock = "../uploads/".end(explode('/', $tmpFile).$_FILES['Filedata']['name']);
move_uploaded_file($tmpFileName, $newLock);
$POSTback = array(
'name' => $_FILES['Filedata']['name'],
'type' => $_FILES['Filedata']['type'],
'tmp_name' => $newLock,
'error' => $_FILES['Filedata']['error'],
'size' => $_FILES['Filedata']['size']
);
echo json_enocde($POSTback);
}
}
}
?>
But as you can see from my URL its using the uploadCheckAction method witch for debugging i have set so it always says false (AKA 0),
But i seem to get this error:
Fatal error: Only variables can be passed by reference in C:\xampp\htdocs\FallenFate\addon\uploader.php on line 11
But line 11 is $newLock = "../uploads/".end(explode('/', $tmpFile).$_FILES['Filedata']['name']); witch should not be being used could any one provide any help into why this would occur and how i could fix it
end() PHP Manual needs an expression of a single variable (more precisely an array), but not a function return value or any other type of expression.
I think that's basically the cause of your error, more specifically with your code:
$newLock = "../uploads/".end(explode('/', $tmpFile).$_FILES['Filedata']['name']);
you're even driving this far beyond any level of treatment PHP can cope with:
you concatenate an array with a string (which results in a string).
you run end() on that string - not on a variable, not even an array.
I have no clue what you try to do with the code, but I can try:
$filename = $_FILES['Filedata']['name'];
$parts = explode('/', $tmpFile);
$last = end($parts);
$newLock = "../uploads/". $last . $filename;
Or probably even only this:
$filename = $_FILES['Filedata']['name'];
$newLock = "../uploads/". $filename;
Is there any available solution for (re-)generating PHP code from the Parser Tokens returned by token_get_all? Other solutions for generating PHP code are welcome as well, preferably with the associated lexer/parser (if any).
From my comment:
Does anyone see a potential problem,
if I simply write a large switch
statement to convert tokens back to
their string representations (i.e.
T_DO to 'do'), map that over the
tokens, join with spaces, and look for
some sort of PHP code pretty-printing
solution?
After some looking, I found a PHP homemade solution in this question, that actually uses the PHP Tokenizer interface, as well as some PHP code formatting tools which are more configurable (but would require the solution as described above).
These could be used to quickly realize a solution. I'll post back here when I find some time to cook this up.
Solution with PHP_Beautifier
This is the quick solution I cooked up, I'll leave it here as part of the question. Note that it requires you to break open the PHP_Beautifier class, by changing everything (probably not everything, but this is easier) that is private to protected, to allow you to actually use the internal workings of PHP_Beautifier (otherwise it was impossible to reuse the functionality of PHP_Beautifier without reimplementing half their code).
An example usage of the class would be:
file: main.php
<?php
// read some PHP code (the file itself will do)
$phpCode = file_get_contents(__FILE__);
// create a new instance of PHP2PHP
$php2php = new PHP2PHP();
// tokenize the code (forwards to token_get_all)
$phpCode = $php2php->php2token($phpCode);
// print the tokens, in some way
echo join(' ', array_map(function($token) {
return (is_array($token))
? ($token[0] === T_WHITESPACE)
? ($token[1] === "\n")
? "\n"
: ''
: token_name($token[0])
: $token;
}, $phpCode));
// transform the tokens back into legible PHP code
$phpCode = $php2php->token2php($phpCode);
?>
As PHP2PHP extends PHP_Beautifier, it allows for the same fine-tuning under the same API that PHP_Beautifier uses. The class itself is:
file: PHP2PHP.php
class PHP2PHP extends PHP_Beautifier {
function php2token($phpCode) {
return token_get_all($phpCode);
}
function token2php(array $phpToken) {
// prepare properties
$this->resetProperties();
$this->aTokens = $phpToken;
$iTotal = count($this->aTokens);
$iPrevAssoc = false;
// send a signal to the filter, announcing the init of the processing of a file
foreach($this->aFilters as $oFilter)
$oFilter->preProcess();
for ($this->iCount = 0;
$this->iCount < $iTotal;
$this->iCount++) {
$aCurrentToken = $this->aTokens[$this->iCount];
if (is_string($aCurrentToken))
$aCurrentToken = array(
0 => $aCurrentToken,
1 => $aCurrentToken
);
// ArrayNested->off();
$sTextLog = PHP_Beautifier_Common::wsToString($aCurrentToken[1]);
// ArrayNested->on();
$sTokenName = (is_numeric($aCurrentToken[0])) ? token_name($aCurrentToken[0]) : '';
$this->oLog->log("Token:" . $sTokenName . "[" . $sTextLog . "]", PEAR_LOG_DEBUG);
$this->controlToken($aCurrentToken);
$iFirstOut = count($this->aOut); //5
$bError = false;
$this->aCurrentToken = $aCurrentToken;
if ($this->bBeautify) {
foreach($this->aFilters as $oFilter) {
$bError = true;
if ($oFilter->handleToken($this->aCurrentToken) !== FALSE) {
$this->oLog->log('Filter:' . $oFilter->getName() , PEAR_LOG_DEBUG);
$bError = false;
break;
}
}
} else {
$this->add($aCurrentToken[1]);
}
$this->controlTokenPost($aCurrentToken);
$iLastOut = count($this->aOut);
// set the assoc
if (($iLastOut-$iFirstOut) > 0) {
$this->aAssocs[$this->iCount] = array(
'offset' => $iFirstOut
);
if ($iPrevAssoc !== FALSE)
$this->aAssocs[$iPrevAssoc]['length'] = $iFirstOut-$this->aAssocs[$iPrevAssoc]['offset'];
$iPrevAssoc = $this->iCount;
}
if ($bError)
throw new Exception("Can'process token: " . var_dump($aCurrentToken));
} // ~for
// generate the last assoc
if (count($this->aOut) == 0)
throw new Exception("Nothing on output!");
$this->aAssocs[$iPrevAssoc]['length'] = (count($this->aOut) -1) - $this->aAssocs[$iPrevAssoc]['offset'];
// post-processing
foreach($this->aFilters as $oFilter)
$oFilter->postProcess();
return $this->get();
}
}
?>
In the category of "other solutions", you could try PHP Parser.
The parser turns PHP source code into an abstract syntax tree....Additionally, you can convert a syntax tree back to PHP code.
If I'm not mistaken http://pear.php.net/package/PHP_Beautifier uses token_get_all() and then rewrites the stream. It uses heaps of methods like t_else and t_close_brace to output each token. Maybe you can hijack this for simplicity.
See our PHP Front End. It is a full PHP parser, automatically building ASTs, and a matching prettyprinter that regenerates compilable PHP code complete with the original commments. (EDIT 12/2011:
See this SO answer for more details on what it takes to prettyprint from ASTs, which are just an organized version of the tokens: https://stackoverflow.com/a/5834775/120163)
The front end is built on top of our DMS Software Reengineering Toolkit, enabling the analysis and transformation of PHP ASTs (and then via the prettyprinter code).
What's the best practice to handle errors if using objects?
A) Before the method of object is called and not even getting to execute method if there's some error, or
B) Just pass parameters and perform error checking in method itself, returning error code or something.
Please pick your option and short description, why?
Thanks orlandu63, it is good practice, but what about non-fatal errors, such as user should provide a title for something, and he/she didn't?
class Sample {
var $err_no_title = 1;
function createNewRecord ($title) {
if (!$title) return $this->err_no_title;
}
}
Or use exceptions for these kind of errors also?
If you're using OO, you might as well use Exceptions. My answer is a mix of both A and B:
class DatabaseConnectionException extends Exception {}
class Database {
public function connect($user, $pass, $db) {
//Connection stuff.
if($baduser) {
throw new DatabaseConnectionException('Username (' . $user. ') is invalid.')
}
if($badpass) {
//''
}
}
}
$db = new Database;
try {
$db->connect($user, $pass, $db);
catch (DatabaseConnectionException $e) {
die('I cannot connect to the database:' . $e);
}
What are the advantages to this? I don't know, but it seems right.
You can read more on it on http://php.net/exceptions and google.
Regarding your second part,
First of all your example will treat it more of an error than a "warning" since you exit the function and thus don't create a record if you have no title. This shows that method B is flawed. So method A all the way.
To choose from options you offer it would be B, but don't use error codes and throw exceptions instead. All the logic (even validation of inputs) should be encapsulated in the function.
The reasons are:
The function may change and so may change requirements for inputs.
User of your function may not always know, what the inputs should be like.
You surely don't want to repeat the validation code everywhere you use the function.
Be careful though, as exceptions are raised by object oriented code only. For example this code does not fire an exception:
<?php
$number = $number / 0;
?>
Your example would be like:
<?php
class Sample {
function createNewRecord ($title) {
if (!$title) throw new Exception('Title required');
}
}
...
try {
$mysample->createNewRecord($title);
} catch ($ex) {
echo "Could not create record. Please try again. (Reason: $ex)";
}
...
?>
In the first place, this sort of thing should be validated in the user interface. So it would be A, but B needs to be there to. So final verdict would be: both.