PHP Warnings to Runtime Errors
In the transition from PHP 7.4 to PHP 8.2, notable changes have occurred with certain runtime errors that were previously treated as warnings by PHP. As of PHP 8.x, these errors have been elevated to runtime errors.
Automated tools like Rector can do a lot when it comes to lexical scanning but runtime (non-lexical and more closely tied to the logic and data aspects of code execution) its help is limited.
Defensive coding techniques serve as a robust shield against runtime errors, empowering you to anticipate and prevent such issues.
In our research, we've identified several of these techniques designed to assist you in addressing and rectifying potential errors.
Importance of Sugar logs
Logs are crucial to help you understand the services and customizations you develop and operate. Not only can logs help you troubleshoot problems, but they can also help you understand your customizations better, and sometimes provide you with unforeseen logic errors that your code might not expect. Even after applying defensive coding, you may end up with errors in your logs that will not propagate to the requestor, that being a UI trigger or a cron job, so it is important to keep an eye on your logs.
Some Sugar errors will not be noticeable by users but they'll be logged in the sugarcrm.log. It is your responsibility as Sugar instance's administrator to check the logs, understand the causes, and possibly provide fixes.
Enable PHP logs
The starting point to identify such errors is to enable PHP logs. This foundational step will provide the necessary data (warnings/errors) so you can implement effective defensive coding to prevent them from happening.
E_WARNING
and E_DEPRECATION
Log in to the PHP error log and test your code.-
Locate your
php.ini
file. The location can vary based on your operating system and PHP installation. Common paths include:- Windows:
C:\Program Files\PHP\php.ini
- Linux:
/etc/php/{version}/apache2/php.ini
- Windows:
-
Open the
php.ini
file in a text editor with administrative privileges. -
Search for the
error_reporting
directive. -
Update the directive to include
E_WARNING
andE_DEPRECATED
:
error_reporting = E_ALL & ~E_NOTICE | E_WARNING | E_DEPRECATED
Understanding Rector's Limitations
While Rector serves as a valuable tool for scanning code and conducting lexical analysis, it's important to note its limitations regarding Sugar expressions used in calculated fields, SugarBPM, and similar contexts.
During runtime, Sugar evaluates expressions using various methods, including PHP evaluation. This introduces the possibility of encountering exceptions similar to those in native PHP code. Therefore, developers must review their expressions to ensure they are "code safe" for Sugar interpreter, for example, by checking if a variable is not empty before applying a function to it.
Exceptions occurring during expression evaluation are logged in the sugarcrm.log
file. This log serves as a valuable resource for developers, facilitating the analysis and resolution of runtime issues within Sugar.
Applying defensive code to avoid Runtime Errors
PHP Warning: count(): Parameter must be an array or an object that implements Countable in *
- safeCount will take care of it
- For Sugar, the best practice is to use
safeCount()
instead ofcount()
!
PHP Warning: A non-numeric value encountered*
-
Occurs when invalid strings are coerced using operators expecting numbers (+ - * / ** % << >> | & ^) or their assignment equivalents.
-
Cast the values to int or float before the arithmetic operation.
- Check the values for is_numeric($value) and skip the calculation if the values are not numeric.
PHP Warning: sizeof(): Parameter must be an array or an object that implements Countable in*
- if you have code like
$a = sizeof($b) + 1;
- replace sizeof with
safeCount
PHP Warning: strlen() expects parameter 1 to be string, array given in*
Add defensive for example:
if (is_string($some_value)) { $len = strlen($some_value); } else { // throw error or deal with your data }
PHP Warning: array_key_exists(): The first argument should be either a string or an integer in*
- Add defensive for example:
if ((is_string($haystack) || is_numeric($haystack)) && array_key_exists($haystack, $myArray)) { // continue with your logic } else { // throw error or deal with your data; }
PHP Warning: strpos(): Empty needle in*
- Add defensive code, for example:
if (isset($haystack) && !empty($haystack) && isset($needle) && !empty($needle) && strpos($haystack, $needle, $offset)) { // continue with your logic } else { // throw error or deal with your data; }
PHP Warning: array_intersect(): Expected parameter 1 to be an array, null given in*
- Add defensive for example:
if (is_array($array) && is_array($arrays)) { $intersect = array_intersect($array, $arrays); } else { // throw error or deal with your data; }
PHP Warning: Creating default object from empty value*
- Add defensive for example:
// Option 1: Skip execution branch if the value is not an object $a = BeanFactory::newBean('someweirdstuff'); if (!($a instanceof SugarBean)) { return; } $a->b = 'c'; // Option 2: In cases when the variable is used without being initialized - initialize it first $a = new stdClass(); $a-> b = ‘c’;
PHP Warning: DateTime::diff() expects parameter 1 to be DateTimeInterface, null given*
PHP Warning: DateTime::setTimezone() expects parameter 1 to be DateTimeZone, string given
- Add defensive for example:
$date = new \DateTime(); if ($otherDate instanceof \DateTimeInterface) { $diff = $date->diff($otherDate); }
PHP Warning: Division by zero
- Add defensive for example:
if ($rate != 0) { $result = $amount / $rate; }
PHP Warning: Illegal offset type in isset or empty; PHP Warning: Illegal offset type
-
Array keys can only be int or string. Float, boolean and null get converted transparently (though it may be signal that something went wrong), but arrays, objects and resources don’t.
-
The solution is also defensive coding::
if (is_string($key) || is_int($key)) { $map[$key] = $value; }
PHP Warning: Illegal string offset
-
Array keys can only be int or string. Float, boolean and null get converted transparently (though it may be signal that something went wrong), but arrays, objects and resources don’t.
-
The solution is also defensive coding::
$a = ‘some_string’; if (is_array($a)) { var_dump($a[‘key’]); }
PHP Warning: Use of undefined constant SOME_NAME - assumed 'SOME_NAME'
-
This may have multiple causes, the most common is string literal, not wrapped in quotes.
-
The solution is to wrap these to quotes:
$status = $row['status']; $duration = $n . 'hours';
PHP Warning: fclose() expects parameter 1 to be resource, bool given
-
The solution is defensive coding, but in case of resources it should be applied as close as possible to the place of its initialization
$file = fopen($name, 'r'); if ($file === false) { // handle error, interrupt execution - whatever appropriate } // do something fclose($file);
PHP Warning: call_user_func() expects parameter 1 to be a valid callback, function 'my_func_name' not found or invalid function name
-
The solution is also defensive coding:
if (is_callable('my_func_name')) { call_user_func('my_func_name'); } else { // handle error }
PHP Warning: implode(): Invalid arguments passed
-
Happens when the second argument is not array:
-
The solution is also defensive coding:
if (is_array($names)) { var_dump(implode(', ', $names)); } // or $pieces = is_array($names) ? $names : []; var_dump(implode(', ', $pieces));
PHP Warning: max(): Array must contain at least one element
-
The solution is also defensive coding:
if (safeCount($items) > 0) { $best = max($items); } else { // handle error, assign some defaults, etc. }
PHP Warning: Attempt to assign property 'team_id' of non-object
-
Defensive coding - check if the object is really an instance of the class you assuming. Makes sense to perform this check as close to object initialization as possible (or at the beginning of the function if the object is passed as an argument)
if (!($a instanceof SomeClass)) { // handle error, interrupt execution, etc. } $a->b = 1;
PHP Warning: array_multisort(): Array sizes are inconsistent*
PHP Warning: array_combine(): Both parameters should have an equal number of elements
-
These warnings have similar cause and solution. Make sure, that sorted arrays are of the same size
if (safeCount($apples) !== safeCount($oranges)) { // handle error } array_multisort($apples, $oranges);
PHP Warning: array_multisort(): Argument #1 is expected to be an array or a sort flag
-
Defensive coding. Assuming here that sorting flags and order are hardcoded, otherwise, check them with is_integer
if (!is_array($apples) || !is_array($oranges)) { // handle error } array_multisort($apples, SORT_ASC, SORT_STRING, $oranges, SORT_ASC, SORT_STRING);
PHP Warning: sprintf(): Too few arguments
-
Carefully check that number of arguments to sprintf corresponds to number of placeholders in format string
echo sprintf('%s is the capital of %s', 'London'); // error - 2 placeholders but 1 argument