Performance Guidelines
- POSIX Extended Regular Expressions
- Perl-compatible (PCRE) Regular Expressions
- PCRE Subpattern Capture Optimization
- PCRE Backreference Caution
- Avoid Unnecessary String Replacements
- Use sprintf instead of str_replace
- Smart Substring Matching
- for() Loops
- Heredoc Strings
- One-time Use Variables
- Redundant Queries
POSIX Extended Regular Expressions
Do not use POSIX Extended regular expression functions. When you need a regular expression function, always use the Perl-compatible (PCRE) preg_
functions.
Note: NEVER USE: ereg()
, eregi()
, ereg_replace()
, eregi_replace()
, split()
, spliti()
, or sql_regcase()
Perl-compatible (PCRE) Regular Expressions
Do not use regular expression functions unless you need to.
INCORRECT:
$str = preg_replace('/foo/', 'bar', $str);
$arr = preg_split('|', $str);
$arr = preg_split('|', $str, -1, PREG_SPLIT_NO_EMPTY); // $str is '1|2|3|4|'
CORRECT:
$str = str_replace('foo', 'bar', $str);
$arr = explode('|', $str);
$arr = explode('|', trim($str, '|')); // $str is '1|2|3|4|'
PCRE Subpattern Capture Optimization
Use ?:
at the start of a subpattern if it does not need to be captured for faster execution and memory conservation.
INCORRECT:
$str = preg_replace('/xyz([0-9]+)/', 'zyx', $str); // not using the captured subpattern, so ?: should be used
CORRECT:
$str = preg_replace('/xyz(?:[0-9]+)/', 'zyx', $str); // correct use when not using the captured subpattern
$str = preg_replace('/xyz([0-9]+)/', 'zyx\\1', $str); // correct use of a captured subpattern
PCRE Backreference Caution
When using backreferences from a capture pattern combined with a parsed PHP variable, it is necessary to use dollar-sign backreferences to prevent it from looking for a backreference that doesn’t exist.
INCORRECT:
$foo = '123 Any Street';
$str = preg_replace('/(.*)/', "\\1{$foo}"); // expands to "\\1123 Any Street" and looks for backreference \\1123!
CORRECT:
$foo = '123 Any Street';
$str = preg_replace('/(.*)/', "${1}{$foo}");
Avoid Unnecessary String Replacements
Do not perform string replacements unless you need to, and know that the search string exists in the subject string. Use strpos()
to see if the replacement is necessary beforehand.
INCORRECT:
foreach ($items as $name => $value)
{
$str = str_replace($name, $value, $str);
}
CORRECT:
foreach ($items as $name => $value)
{
if (strpos($str, $name) !== FALSE)
{
$str = str_replace($name, $value, $str);
}
}
Remarkably, even if a match occurs on each loop, the additional processing overhead for the strpos()
is negligible (on a 100kb string it adds roughly 0.01 seconds for every 10,000 loops). If there are loops that do not match, this method can approach 100% greater efficiency.
Use sprintf instead of str_replace
When you need to add one or more variables to an existing string (e.g. lang values), make sure to use sprintf instead of str_replace
.
INCORRECT:
str_replace('%s', $channel, 'Currently editing the %s channel.')
CORRECT:
sprintf('Currently editing the %s channel.', $channel)
sprintf('%s is currently editing the %s channel.', $member_name, $channel)
Smart Substring Matching
When checking to see if a string has matching characters at the front of the string only, use strncmp()
and strncasecmp()
instead of substr()
. Especially on non-case sensitive checks, these functions are much faster. Never use regular expression functions for this unless you actually need a regular expression match.
INCORRECT:
if (substr($str, 0, 3) == 'foo')
if (substr(strtolower($str), 0, 3) == 'foo')
if (preg_match('/^foo/', $str)) // no need for regex match for this type of comparison
if (ereg('^foo', $str)) // AAAAAH! Never ever use ereg(), remember?
CORRECT:
if (strncmp($str, 'foo', 3) == 0)
if (strncasecmp($str, 'foo', 3) == 0)
strncmp() and strncasecmp() return < 0 if str1
is less than str2
, > 0 if str1
is greater than str2
, and 0 if they are equal.
for() Loops
Do not perform calculations in the second expression of for()
loops, as they will be executed on each iteration of the loop. Perform them either in the first expression, or before entering the loop.
INCORRECT:
for ($i = 0; $i < count($arr); $i++)
CORRECT:
for ($i = 0, $foo = $count($arr); $i < $foo; $i++)
ALTERNATIVE:
$foo = count($arr); for ($i = 0; $i < $foo; $i++)
Heredoc Strings
Avoid heredoc strings unless absolutely necessary. They are more intensive for PHP to parse than single or double-quoted strings, resulting in slower code execution and increased memory usage.
One-time Use Variables
Avoid assigning new variables for one-time use. In the example below, $foo
is never used again in the method.
INCORRECT:
$foo = 'a';
$str = $str.$foo;
CORRECT:
$str = $str.'a';
Redundant Queries
Avoid running queries in loops or running identical queries multiple times across page loads. Find a way to run such queries only once, outside of loops, by perhaps accessing all of the information your add-on will require for each iteration, storing it in a master array.
Make intelligent use of ee()->session->cache <use_of_session_cache>
so these and other “meta” queries are executed only once no matter how many times a method is called on a page load.
Note: To keep the code example simple, the values in the $ids array below are assumed to have already been validated in the code prior to what is being shown. Do not neglect to validate and escape variables before using them in queries!
INCORRECT:
foreach ($ids as $id)
{
$query = ee()->db->query("SELECT name FROM exp_pre_email_addresses WHERE id = {$id}");
if ($query->num_rows() > 0)
{
$name = $query->row('name');
// rest of the code
}
}
CORRECT:
if ( ! isset(ee()->session->cache['super_class']['names']))
{
$query = ee()->db->query('SELECT id, name FROM exp_pre_email_addresses WHERE id IN ('.implode(',', $ids).')');
if ($query->num_rows() > 0)
{
foreach ($query->result_array() as $row)
{
ee()->session->cache['super_class']['names'][$row['id']] = $row['name'];
}
}
}
$names = ee()->session->cache['super_class']['names'];
// later in the code looped queries are no longer used
foreach ($ids as $id)
{
$name = $names[$id];
// rest of the code
}