In PHP placing an @ symbol in front of an expression (variable or function call) tells php to suppress any error messages that expression generates. I find this to be a handy piece of syntactic sugar. When used correctly the gains in code readability far outweigh the costs in terms of performance (which I benchmark below). Some people argue that suppressing errors is a mistake and can mask problems so therefore this technique should never be used. I agree with the idea that suppressing errors is bad. At the same time if I don’t care if something in a 4 level nested array is null, then suppressing PHP’s chatter is doing me a huge favor.
Let’s look at an example of where the @-operator shines. Consider trying to get a value out of a nested array, which may or may not be set such as $response[‘STATUS’][‘ERRORS’][‘ERROR_COUNT’], which is a typical thing to see in SOAP based XML responses from enterprisey APIs.
One approach might be:
if(isset($response) && isset($response['STATUS']) && isset($response['STATUS']['ERRORS']) && isset($response['STATUS']['ERRORS']['ERROR_COUNT'])) { $error_count = $response['STATUS']['ERRORS']['ERROR_COUNT']; }
Although isset() doesn’t have a problem with this shorter version either. Thank you to my friend for pointing this out!
if(isset($response['STATUS']['ERRORS']['ERROR_COUNT'])) { $error_count = $response['STATUS']['ERRORS']['ERROR_COUNT']; }
With the @-operator:
$error_count = @$response['STATUS']['ERRORS']['ERROR_COUNT'];
I like the last method because it is cleanest. I don’t care if $error_count is zero or null. The @-operator, being a somewhat lazy technique pairs well with another of PHP’s lazy at best but deeply flawed at worst ‘features’ in that NULL, “0”, 0, array(), and false are ‘falsey’ and can be used interchangeably when doing comparisons with plain ‘==’. By using three equal signs ‘===’ the types of the variables are also considered and that is generally the preferred method of comparing things, but that level of precision isn’t always required.
Notes about the @ sign in PHP:
- If you delcared a custom error handler with set_error_handler() that will still get called.
- It only works on expressions (things that give back a value). So it does not work on if/then statements, loops, and class structures, etc. This was a wise choice by the PHP community.
- The fact that it only works on expressions greatly reduces the unanticipated side effects that can result. In this sense it is nothing like ON ERROR RESUME NEXT, an infamous language feature in Visual Basic and Classic ASP, which chugs past errors. The previous error can still be checked for in a sort of poor man’s try/catch block. ON ERROR RESUME NEXT sucks and makes me want to hurl just thinking about it.
Some people really hate the @-operator:
Most of the arguments against the @-operator come down to misuse and then over reaction. The fact is inexperienced and inept programmers can take any language feature and come back with a hairball of unmaintainable code.
As I demonstrated above, the @-operator is great when digging through arrays such as complex DOM objects. This is especially true with optional keys. It should not be used when calling external resources like the file system, database, APIs, etc. In those situations, try/catch blocks should be used to make sure if something goes wrong it gets logged and cleaned up properly. The @-operator is not a substitute for a try/catch!
The second major knock against the @-operator is the alleged performance penalty. Let’s do some benchmarking:
laurence@blog $ php -v PHP 5.3.24 (cli) (built: Apr 10 2013 18:38:43) Copyright (c) 1997-2013 The PHP Group Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies laurence@blog $ cat php-at-operator-test.php
<?php error_reporting(E_ALL ^ E_NOTICE); $OPERATIONS = 100000; // test using @-operator $time_start = microtime(true); for($i=0; $i<$OPERATIONS; $i++) { $error_count = @$response['STATUS']['ERRORS']['ERROR_COUNT']; } $duration = (microtime(true) - $time_start); echo "With the @-operator:" . PHP_EOL; echo "\tTotal time:\t\t" . $duration . PHP_EOL; echo "\tTime per operation:\t" . number_format($duration / $OPERATIONS, 10) . PHP_EOL; echo PHP_EOL; // test using isset() $time_start = microtime(true); for($i=0; $i<$OPERATIONS; $i++) { if(isset($response['STATUS']['ERRORS']['ERROR_COUNT'])) { $error_count = $response['STATUS']['ERRORS']['ERROR_COUNT']; } } $duration = (microtime(true) - $time_start); echo "Using isset():" . PHP_EOL; echo "\tTotal time:\t\t" . $duration . PHP_EOL; echo "\tTime per operation:\t" . number_format($duration / $OPERATIONS, 10) . PHP_EOL; echo PHP_EOL;
laurence@blog $ php php-at-operator-test.php With the @-operator: Total time: 0.19701099395752 Time per operation: 0.0000019701 Using isset(): Total time: 0.015001058578491 Time per operation: 0.0000001500
For my limited testing with PHP 5.3.24 on a 6 core box looks like the @-operator is ~13 times slower than using isset(). That sounds like a lot, but let's look at the penalty per use, which is 0.0000018201 seconds, or ~1.82 microseconds. An application could do approximately 550 @-operator uses, and it would impact the response time by just 1 millisecond. If a single page request does 550 @-operator look-ups and every millisecond counts then you have a problem. Probably what matters more is overall memory consumption, transactionality, caching, code cleanliness, ease of maintainability, logging, unit tests, having customers, etc... Still it is good to have a solid measure when arguing the case either way. In the future as CPUs get faster and cheaper, I expect the performance penalty to shrink.
3 Responses to Correct use of PHP’s ‘at’ operator with speed benchmark