How to use match expression instead of switch expression - php

We can use match expression instead of switch case in PHP 8.
How to write match expression correctly for the following switch case?
switch($statusCode) {
case 200:
case 300:
$message = null;
break;
case 400:
$message = 'not found';
break;
case 500:
$message = 'server error';
break;
default:
$message = 'unknown status code';
break;
}

There is an important thing that must remember with match. It is type sensitive, not as a switch statement. Thus it's very important to cast the variable properly. In the case of HTTP codes often it is sent in string format, e.g. "400".
It may give a lot of pain during debugging when we don't know about it. If $statusCode was a string, the default option would be always invoked. My modified version of an accepted answer:
$message = match((int) $statusCode) {
200, 300 => null,
400 => 'not found',
500 => 'server error',
default => 'unknown status code',
};

Related

Why does using json_decode on this JSON string result in a null value

"op=add&item={"firstName":"test","lastName":"test","email":"test%40test.com","password":"test"}"
I'm passing the JSON body above to my PHP backend and when trying to use json_decode on it, it results in null. I've used json_last_error() and it states there is a syntax issue. Using jsonlint.com, it gives me this error:
Error: Parse error on line 1:
"op=add&item={"firstName ":"test "
---------------^
Expecting 'EOF', '}', ':', ',', ']', got 'undefined'
I've tried troubleshooting this error but haven't been able to get a successful response. Can someone describe what the issue could be?
For json_decode() to work, it requires a string.
Looking at you syntax, it seems the outer quotes aren't correct.
Yours:
"op=add&item={"firstName":"test","lastName":"test","email":"test%40test.com","password":"test"}"
As they should be:
'op=add&item={"firstName":"test","lastName":"test","email":"test%40test.com","password":"test"}'
If you use single quotes, it's a correct string. Which can be decoded.
Your "body" isn't a JSON string, only the item parameter content looks like one. Take that content and pass to the function json_decode.
Example:
json_decode($_GET['item']);
this is a useful function to debug json issues
function json_validate($string){
// decode the JSON data
$result = json_decode($string);
// switch and check possible JSON errors
switch (json_last_error()) {
case JSON_ERROR_NONE:
$error = ''; // JSON is valid // No error has occurred
break;
case JSON_ERROR_DEPTH:
$error = 'The maximum stack depth has been exceeded.';
break;
case JSON_ERROR_STATE_MISMATCH:
$error = 'Invalid or malformed JSON.';
break;
case JSON_ERROR_CTRL_CHAR:
$error = 'Control character error, possibly incorrectly encoded.';
break;
case JSON_ERROR_SYNTAX:
$error = 'Syntax error, malformed JSON.';
break;
// PHP >= 5.3.3
case JSON_ERROR_UTF8:
$error = 'Malformed UTF-8 characters, possibly incorrectly encoded.';
break;
// PHP >= 5.5.0
case JSON_ERROR_RECURSION:
$error = 'One or more recursive references in the value to be encoded.';
break;
// PHP >= 5.5.0
case JSON_ERROR_INF_OR_NAN:
$error = 'One or more NAN or INF values in the value to be encoded.';
break;
case JSON_ERROR_UNSUPPORTED_TYPE:
$error = 'A value of a type that cannot be encoded was given.';
break;
default:
$error = 'Unknown JSON error occured.';
break;
}
if ($error !== '') {
// throw the Exception or exit // or whatever :)
exit($error);
}
// everything is OK
return $result;
}

PHP switch statement always pick default or random case when using array

I'm using switch to read value with specific key from an array. the array have 2 kind of value, int (0-9) and hex (0-e). when i try to read the int, my switch always run default. when i try to return said data, it return 1 and i have case 1 but the switch run default instead.
i have tried changing to if-else with 0 error but still not working. i tried to force it to string using (string)$paramapi and change the case value to case '1' and still not working. i also have try to pass it to integer with (integer)$paramapi and (int)$paramapi while case 1 and still not working correctly.
please note that my swich variable is untouched on every ...
$paramapi = trim($parraya[1]);
$paramid = trim($parraya[2]);
$param1 = trim($parraya[3]);
...
$paramapi=settype($paramapi,"string");
switch($paramapi){
case '1':
$param1 = $param1."%";
$db = new DbOperation();
$result = $db->addLogin(
$paramid,
$param1
);
if($result){
$response['error'] = false;
$response['message'] = 'login success';
}else{
$response['error'] = true;
$response['message'] = 'login failed';
}
break;
case '2':
...
break;
case '3':
...
break;
case '4':
...
break;
case '5':
...
break;
case '6':
...
break;
case '7':
...
break;
default:
$response['error'] = true;
$response['message'] = 'Invalid API Call'.' '.$aparraya[1];
}
i got "Invalid API Call 1" and want to get "login success" or "login failed"
i change $paramapi=settype($paramapi,"string"); to settype($paramapi,"string"); this fix some new error which i found when forcing my value to string.
i also change
default:
$response['error'] = true;
$response['message'] = 'Invalid API Call'.' '.$aparraya[1];
}
to
default:
$response['error'] = true;
$response['message'] = 'Invalid API Call'.' '.$paramapi;
}
and still got "invalid API call 1"
my code works now. turn out my error is because i forgot to save it.
The function settype returns a boolean, so $paramapi will always be true or false. You do not need to put the result of settype back into $paramapi. It does that for you by just calling the function.
So change $paramapi=settype($paramapi,"string"); into settype($paramapi,"string"); and it should work.
(https://www.php.net/manual/en/function.settype.php)

Unit testing of json encoding/decoding against errors

So basically I have the code which is giving me the message from json_last_error():
$msg = 'Unknown error';
switch (json_last_error()) {
case JSON_ERROR_NONE:
$msg = null;
break;
case JSON_ERROR_DEPTH:
$msg = 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
$msg = 'Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
}
return $msg;
For testing purposes I want to raise all the errors from this list to have 100% coverage, but I can't raise JSON_ERROR_STATE_MISMATCH.
Can anyone help giving me the example with either encoding or decoding, with any parameters, which can produce this error?
$j = '{"j": 1 ] }';
json_decode($j);
var_dump(json_last_error() === JSON_ERROR_STATE_MISMATCH); // true
How I found it: just checked the source code :-)

Can I catch exit() and die() messages?

I'd like to be able to catch die() and exit() messages. Is this possible? I'm hoping for something similar to set_error_handler and set_exception_handler. I've looked at register_shutdown_function() but it seems to contain no context for the offending die() and exit() calls.
I realize that die() and exit() are bad ways to handle errors. I am not looking to be told not to do this. :) I am creating a generic system and want to be able to gracefully log exit() and die() if for some reason someone (not me) decides this is a good idea to do.
Yes you can, but you need ob_start, ob_get_contents, ob_end_clean and register_shutdown_function
function onDie(){
$message = ob_get_contents(); // Capture 'Doh'
ob_end_clean(); // Cleans output buffer
callWhateverYouWant();
}
register_shutdown_function('onDie');
//...
ob_start(); // You need this to turn on output buffering before using die/exit
#$dumbVar = 1000/0 or die('Doh'); // "#" prevent warning/error from php
//...
ob_end_clean(); // Remember clean your buffer before you need to use echo/print
According to the PHP manual, shutdown functions should still be notified when die() or exit() is called.
Shutdown functions and object destructors will always be executed even if exit() is called.
It doesn't seem to be possible to get the status sent in exit($status). Unless you can use output buffering to capture it, but I'm not sure how you'd know when to call ob_start().
Maybe override_function() could be interesting, if APD is available
As best as I can tell this is not really possible. Some of the solutions posted here may work but they require a lot of additional work or many dependencies. There is no way to easily and reliable trap the die() and exit() messages.
Why do not use custom error handling instead? If not, you could always use LD_PRELOAD and C Code injection to catch it :) Or recompile php with your customizations :P
If you use the single point of entry method. (index.php) I can recommend this for your error handling:
Short version:
ob_start();
register_shutdown_function('shutdownHandler');
include('something');
define(CLEAN_EXIT, true);
function shutdownHandler() {
if(!defined("CLEAN_EXIT") || !CLEAN_EXIT) {
$msg = "Script stopped unexpectedly: ".ob_get_contents();
//Handle premature die()/exit() here
}
}
Additional steps and more detailed:
Roughly my way of doing it. I have even more going on than I show here (handling database transactions/rollback/sending e-mails/writing logs/displaying friendly error messages/user error reporting/etc), but this is the basic idea behind all of it).
Hope it helps someone.
<?php
//Some initialization
//starting output buffering. (fatalErrorHandler is optional, but I recommend using it)
ob_start('fatalErrorHandler');
//Execute code right at the end. Catch exit() and die() here. But also all other terminations inside PHPs control
register_shutdown_function('shutdownHandler');
//handling other errors: Also optional
set_error_handler('errorHandler');
try {
//Include of offensive code
include(...);
}
catch (Exception $ex) {
//Handling exception. Be careful to not raise exceptions here again. As you can end up in a cycle.
}
//Code reached this point, so it was a clean exit.
define(CLEAN_EXIT, true);
//Gets called when the script engine shuts down.
function shutdownHandler() {
$status = connection_status();
$statusText = "";
switch ($status) {
case 0:
if (!defined("CLEAN_EXIT") || !CLEAN_EXIT) {
$msg = "Script stopped unexpectedly: ".ob_get_contents();
//Handle premature die()/exit() here
}
else {
//Clean exit. Just return
return;
}
case 1: $statusText = "ABORTED (1)"; break;
case 2: $statusText = "TIMEOUT (2)"; break;
case 3: $statusText = "ABORTED & TIMEOUT (3)"; break;
default : $statusText = "UNKNOWN ($status)"; break;
}
//Handle other exit variants saved in $statusText here ob_get_contents() can have additional useful information here
}
// error handler function (This is optional in your case)
function errorHandler($errno, $errstr, $errfile, $errline) {
$msg = "[$errno] $errstr\nOn line $errline in file $errfile";
switch ($errno) {
case E_ERROR: $msg = "[E_ERROR] ".$msg; break;
case E_WARNING: $msg = "[E_WARNING] ".$msg; break;
case E_PARSE: $msg = "[E_PARSE] ".$msg; break;
case E_NOTICE: $msg = "[E_NOTICE] ".$msg; break;
case E_CORE_ERROR: $msg = "[E_CORE_ERROR] ".$msg; break;
case E_CORE_WARNING: $msg = "[E_CORE_WARNING] ".$msg; break;
case E_COMPILE_ERROR: $msg = "[E_COMPILE_ERROR] ".$msg; break;
case E_COMPILE_WARNING: $msg = "[E_COMPILE_WARNING] ".$msg; break;
case E_USER_ERROR: $msg = "[E_USER_ERROR] ".$msg; break;
case E_USER_WARNING: $msg = "[E_USER_WARNING] ".$msg; break;
case E_USER_NOTICE: $msg = "[E_USER_NOTICE] ".$msg; break;
case E_STRICT: $msg = "[E_STRICT] ".$msg; break;
case E_RECOVERABLE_ERROR: $msg = "[E_RECOVERABLE_ERROR] ".$msg; break;
case E_DEPRECATED: $msg = "[E_DEPRECIATED] ".$msg; break;
case E_USER_DEPRICIATED: $msg = "[E_USER_DEPRICIATED] ".$msg; break;
default: $msg = "[UNKNOWN] ".$msg; break;
}
//Handle Normal error/notice/warning here.
$handled = ...
if ($handled)
return true; //handled. Proceed execution
else
throw Exception($msg); //Be careful. this might quickly become cyclic. Be sure to have code that catches and handles exceptions. Else die() here after logging/reporting the error.
}
function fatalErrorHandler(&$buffer) {
$matches = null;
//Checking if the output contains a fatal error
if (preg_match('/<br \/>\s*<b>([^<>].*)error<\/b>:(.*)<br \/>$/', $buffer, $matches) ) {
$msg = preg_replace('/<.*?>/','',$matches[2]);
//Handle Fatal error here
return "There was an unexpected situation that resulted in an error. We have been informed and will look into it."
}
//No fatal exception. Return buffer and continue
return $buffer;
}
Catching exits is useful in automated tests. The way I do it is I throw a special runtime exception instead of calling exit directly.
<?php
class CatchableExit extends RuntimeException
{
}
class Quitter
{
public function run($i)
{
echo "Quitter called with \$i = $i \n";
throw new CatchableExit();
}
}
class Runner
{
public function run()
{
trigger_error('I am a harmless warning', E_USER_WARNING);
trigger_error('And I am a notice', E_USER_NOTICE);
for ($i = 0; $i < 10; $i++) {
$q = new Quitter();
try {
$q->run($i);
} catch (CatchableExit $e) {
}
}
}
}
function exception_handler(Throwable $exception)
{
if ($exception instanceof CatchableExit) {
exit();
}
}
set_exception_handler('exception_handler');
$runner = new Runner();
$runner->run();
yes: write a function and use that instead.
function kill($msg){
// Do your logging..
exit($msg);
}

Header use in PHP

Pretty simple question: which one of these two PHP (version 5+) header call is the "best"?
header('Not Modified', true, 304);
header('HTTP/1.1 304 Not Modified');
I'm pretty sure the first one is the most polyvalent one, but just curious if PHP would "fix" the second one if under HTTP 1.0...
Thanks!
Edit: One of these header crashes PHP on my Web host. Follow-up question at:
PHP header() call "crashing" script with HTTP 500 error
I would use this one:
header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified', true, 304);
$_SERVER['SERVER_PROTOCOL'] contains the protocol used in the request like HTTP/1.0 or HTTP/1.1.
Edit    I have to admit that my suggestion is senseless. After a few tests I noticed that if the first parameter is a valid HTTP status line, PHP will use that status line regardless if and what second status code was given with the third parameter. And the second parameter (documentation names it replace) is useless too as there can not be multiple status lines.
So the second and third parameter in this call are just redundant:
header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified', true, 304);
Use just this instead:
header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');
There are two things about the behaviour of the first header call that are worth pointing out:
If you provide the 3rd argument, PHP will ignore the first string argument and send the correct response for the given number. This might make the first method less prone to programmer errors.
PHP seems to respond with a HTTP/1.1 response even when the request was made with HTTP/1.0
I'd go with the second one, as the http response code argument is only supported >= PHP 4.3.0 (which could affect code portability).
I've done this many times and not come across any client that doesn't support HTTP/1.1, so unless you have a special case I shouldn't think that would ever be a problem.
I would normally go with the second example - however, when recently benchmarking an application using apachebench, we noticed ab hanging often.
After debugging, it was identified that the header in this style:
header('HTTP/1.1 304 Not Modified')
Was the culprit (Yeah, I have no idea) and after changing it to,
header('Not Modified', true, 304);
Believe it or not ab started working. Very strange but something to think about. I'll probably use the second method going forward.
For future reference the http_response_code() function is coming in 5.4:
http://www.php.net/manual/en/function.http-response-code.php
An alternative is:
if (!function_exists('http_response_code')) {
function http_response_code($code = NULL) {
if ($code !== NULL) {
switch ($code) {
case 100: $text = 'Continue'; break;
case 101: $text = 'Switching Protocols'; break;
case 200: $text = 'OK'; break;
case 201: $text = 'Created'; break;
case 202: $text = 'Accepted'; break;
case 203: $text = 'Non-Authoritative Information'; break;
case 204: $text = 'No Content'; break;
case 205: $text = 'Reset Content'; break;
case 206: $text = 'Partial Content'; break;
case 300: $text = 'Multiple Choices'; break;
case 301: $text = 'Moved Permanently'; break;
case 302: $text = 'Moved Temporarily'; break;
case 303: $text = 'See Other'; break;
case 304: $text = 'Not Modified'; break;
case 305: $text = 'Use Proxy'; break;
case 400: $text = 'Bad Request'; break;
case 401: $text = 'Unauthorized'; break;
case 402: $text = 'Payment Required'; break;
case 403: $text = 'Forbidden'; break;
case 404: $text = 'Not Found'; break;
case 405: $text = 'Method Not Allowed'; break;
case 406: $text = 'Not Acceptable'; break;
case 407: $text = 'Proxy Authentication Required'; break;
case 408: $text = 'Request Time-out'; break;
case 409: $text = 'Conflict'; break;
case 410: $text = 'Gone'; break;
case 411: $text = 'Length Required'; break;
case 412: $text = 'Precondition Failed'; break;
case 413: $text = 'Request Entity Too Large'; break;
case 414: $text = 'Request-URI Too Large'; break;
case 415: $text = 'Unsupported Media Type'; break;
case 500: $text = 'Internal Server Error'; break;
case 501: $text = 'Not Implemented'; break;
case 502: $text = 'Bad Gateway'; break;
case 503: $text = 'Service Unavailable'; break;
case 504: $text = 'Gateway Time-out'; break;
case 505: $text = 'HTTP Version not supported'; break;
default:
exit('Unknown http status code "' . htmlentities($code) . '"');
break;
}
$protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
header($protocol . ' ' . $code . ' ' . $text);
$GLOBALS['http_response_code'] = $code;
} else {
$code = (isset($GLOBALS['http_response_code']) ? $GLOBALS['http_response_code'] : 200);
}
return $code;
}
}
In this example I am using $GLOBALS, but you can use whatever storage mechanism you like... I don't think there is a way to return the current status code:
https://bugs.php.net/bug.php?id=52555
For reference the error codes I got from PHP's source code:
http://lxr.php.net/opengrok/xref/PHP_5_4/sapi/cgi/cgi_main.c#354
And how the current http header is sent, with the variables it uses:
http://lxr.php.net/opengrok/xref/PHP_5_4/main/SAPI.c#856
I think Gumbo's answer is the most sensible so far. However try this:
<?php
header('Gobbledy Gook', true, 304);
?>
If the first string is not a proper header it is discarded. If iy does look like a valid header it is appended to the headers - try this:
<?php
header('Cache-Control: max-age=10', true, 304);
?>
The manual for header() and note the special cases - in general I think its not advisable to rely on such built-in heuristics.
However, I'm guessing that your actually interested in getting the content cached well by proxies/browsers. In most instances, latency is far more of a problem of a problem than bandwidth. Next consider how a browser behaves when cached content is stale - in the absence of updated caching information, it keeps making repeated requests to the server to see if the content is still stale.
I.e. in most cases ignoring the conditional part of requests (or even better stripping them on the webserver) actually improves performance.

Categories