Title: PHPCS warnings
Author: WordPress VIP Documentation
Published: December 7, 2020
Last modified: December 31, 2025

---

 1. [PHPCS for WordPress VIP](https://docs.wpvip.com/php_codesniffer/)
 2. PHPCS warnings

#  PHPCS warnings

PHP_CodeSniffer (PHPCS) scans that are run against WordPress application code by
[the VIP Code Analysis Bot](https://docs.wpvip.com/vip-code-analysis-bot/phpcs-analysis/)—
or scans that are run manually after following the instructions to [install PHPCS for WordPress VIP](https://docs.wpvip.com/php_codesniffer/)—
will run with identical standards that include the `WordPress-VIP-Go` standard.

The PHPCS scan will generate a report that itemizes identified errors and warnings
and categorize them by severity.

Issues that are reported as VIP warnings should not be pushed to a production environment
unless a specific use case requires it. Some common issues reported as VIP warnings
are listed below.

## `*_meta` as a hit counters

Do not use meta (`post_meta`, `comment_meta`, etc.) to track counts (e.g. votes,
page views, etc.). It is not performant and will not work as expected due to caching
and race conditions on high-volume sites.

In general, avoid counting or tracking user events within WordPress. Instead, consider
using a JavaScript-based solution paired with a dedicated analytics service (such
as Google Analytics).

## Check for `is_array()`, `!empty()` or `is_wp_error()`

Before using a function that depends on an array, always check to make sure the 
arguments you are passing are arrays. If not, PHP will throw a warning.

Recommended example:

    ```lang-php
    $tags_array = get_the_terms( get_the_ID(), 'post_tag');
    //get_the_terms function returns array of term objects on success, false if there are no terms or the post does not exist, WP_Error on failure. Thus is_array is what we have to check against
    if ( is_array( $tags_array ) ) {
        $tags = wp_list_pluck( $tags_array , 'name');
    }
    ```

Not recommended:

    ```lang-php
    $tags = wp_list_pluck( get_the_terms( get_the_ID(), 'post_tag') , 'name');
    ```

Common functions/language constructs that are used without checking the parameters
beforehand: `foreach()`, `array_merge()`, `array_filter()`, `array_map()`, `array_unique()`,`
wp_list_pluck()`
Always check the values passed as parameters or cast the value 
as an array before using them.

## Commented out code, debug code or output

Application code should not contain debug code and should not output debugging information.
That includes the use of functions that provide backtrace information, such as `
wp_debug_backtrace_summary()` or `debug_backtrace()`.

The use of commented-out code should be avoided. Having code not ready for production
on production could easily lead to mistakes while reviewing, such as accidentally
publishing commented-out code.

## Cron events scheduled less than 15 minutes apart or expensive events

Overly frequent cron events (anything less than 15 minutes) can significantly impact
the site’s performance, as can cron-expensive events.

## Custom API endpoints without permissions callback

Register a valid permissions callback for custom API routes that write or read private
data to avoid data exposure or manipulation.

## Custom roles

Custom user roles and capabilities must be managed via our [helper functions](https://docs.wpvip.com/customize-user-roles/)
for the best compatibility between environments and for added security.

## Custom `wp_mail` headers

To create custom headers using user-supplied data (e.g. “FROM” header), always use
WordPress’s filters, such as [`wp_mail_from()`](https://developer.wordpress.org/reference/hooks/wp_mail_from/)
and [`wp_mail_from_name()`](https://developer.wordpress.org/reference/hooks/wp_mail_from_name/)

## Direct database queries

Thanks to the WordPress API, it is rare to need to query database tables directly.
Using WordPress APIs rather than rolling custom functions ensures compatibility 
with past and future versions of WordPress and PHP. It also makes code reviews go
more smoothly because we know we can trust the APIs. Visit our page covering [database queries](https://docs.wpvip.com/code-quality/database-queries/)
for more information.

Additionally, direct database queries bypass internal caching. If necessary, you
should evaluate the potential performance of these queries and add caching if needed.
Any queries modifying database contents may cause sync problems with the object 
cache.

## Encoding values used when creating a URL or passed to `add_query_arg()`

Values passed to `add_query_arg()` expected to be encoded with [`urlencode()` or `rawurlencode()`](https://docs.wpvip.com/code-quality/encode-values-passed-to-add_query_arg/)
to prevent parameter hijacking.

For example, passing the following code:

    ```lang-php
    $m_yurl = 'admin.php?action=delete&post_id=321';
    $my_url = add_query_arg( 'my_arg', 'somevalue&post_id=123', $my_url );
    ```

You would expect the url to be:
`admin.php?action=delete&post_id=321&somevalue%26post_id%
3D123`

But in fact it becomes:
`admin.php?action=delete&post_id=321&somevalue&post_id=123`

## `eval()` and `create_function()`

Both these functions can execute arbitrary code constructed at run time, which can
be created through difficult-to-follow execution flows. These methods can make your
site fragile because unforeseen conditions can cause syntax errors in the executed
code, which becomes dynamic. A much better alternative is an [Anonymous Function](http://php.net/manual/en/functions.anonymous.php),
which is hardcoded into the file and can never change during execution.

If there are no other options than to use this construct, pay special attention 
not to pass any user-provided data into it without properly validating it beforehand.

## Expensive 404 pages

The 404 page must be one of the fastest on the site, as it is only cached for 10
seconds. A traffic spike from a broken link can cause performance and availability
problems if the 404 page is doing expensive database lookups.

## Flash (.swf) files

Flash (.swf) files are not advisable on the WordPress VIP Platform, as they often
present security vulnerabilities that are difficult to detect and secure (due to
poor development practices or [bugs in the Flash Player](https://www.adobe.com/support/security/#flashplayer)).

## Front-end database writes

Avoid functions that write to the database on the front end. Front-end database 
writes typically will not work as expected due to page caching and can cause a significant
load on the database.

## Functions that use JOINS, taxonomy relation queries, -cat, -tax queries, subselects or API calls

Evaluate the following queries closely to avoid performance issues. They are often
expensive, causing known problems with large datasets:

 * `category__and`, `tag__and`, `tax_query` with `AND`
 * `category__not_in`, `tag__not_in`, and `tax_query` with `NOT IN`
 * `tax_query` with multiple taxonomies
 * `meta_query` with a large result set (e.g. looking for only posts with a `thumbnail_id`
   meta on a large site, looking for posts with a specific meta value on a key)

## Generating email

To prevent issues with spam, abuse, or other unwanted communications, application
code should not generate (or allow users to generate) email messages to site users
or user-supplied email addresses. To [send small amounts of email](https://docs.wpvip.com/wordpress-on-vip/email/)
to admins or a specific email address, use the built-in WordPress [`wp_mail()` function](https://developer.wordpress.org/reference/functions/wp_mail/).
To [send large amounts of email](https://docs.wpvip.com/wordpress-on-vip/email/send-from-custom-domain/)(
e.g. mailing lists, invitations to view or share content, notifications of site 
activity, or other messages generated in bulk) a third-party SMTP or ESP (Email 
Service Provider) services should be used instead.

## Including files with untrusted paths or filenames

`locate_template()`, `get_template_part()`, and sometimes `include()` or `require()`
are typically used to include templates. If your template name, file name, or path
contains non-static data or can be filtered, you must validate it against directory
traversal using `validate_file()` or by detecting the string “..”.

## Minified JavaScript files

Minified JavaScript files should be committed with changes to their unminified counterparts.
Minified files cannot be read for review and are much harder to work with when debugging
issues.

## Mobile Detection

When targeting mobile visitors, use `jetpack_is_mobile()` instead of `wp_is_mobile()`.
It is more robust and works better with full-page caching.

    ```lang-php
    function jetpack_is_mobile( $kind = 'any', $return_matched_agent = false )
    ```

Where `kind` can be:

 * smart
 * dumb
 * any

You can also use:

 * `Jetpack_User_Agent_Info::is_ipad()`
 * `Jetpack_User_Agent_Info::is_tablet()`

Views from mobile devices are cached, and to make sure things work as expected, 
please let us know before including any additional server-side logic (PHP) in your
code.

These are loaded automatically.

## No `LIMIT` queries

Using `posts_per_page` (or `numberposts`) with the value set to `-1` or an unreasonably
high number, or setting `nopaging` to `true` opens up the potential for scaling 
issues if the query ends up querying thousands of posts.

You should always fetch the lowest possible number that still gives acceptable results.
Imagine that your site grows over time to include 10,000 posts. If you specify `-
1` for `posts_per_page`, you’ll query with no limit and fetch all 10,000 posts every
time the query runs, destroying your site’s performance. If you know you’ll never
have more than 15 posts, then set `posts_per_page` to 15. If you think you might
have more than 15 but doubt it’d hit more than 100, set the limit to 100. If it 
gets much higher than that, you might need to reconsider the page architecture.

## Not using the settings API

Use the [WordPress Settings API](https://developer.wordpress.org/plugins/settings/settings-api/)
instead of handling the output of settings pages and storage yourself. It handles
much of the heavy lifting for you, including added security.

Make sure to validate and sanitize submitted values from users, using the `sanitize`
callback in the `register_setting` call.

## Prefixing functions, constants, classes, and slugs

The best practice in WordPress is to prefix everything, including less obvious things
like `post_type` and taxonomy slugs, and cron event names, and typical things like
names of functions, constants, and classes.

## Plugin registration hooks

`register_activation_hook()` and `register_deactivation_hook()` are not supported
because of the way plugins are loaded on WordPress VIP using `wpcom_vip_load_plugin()`.

## reCaptcha for share by email

Jetpack’s share by email feature (aka Sharedaddy) must be [implemented with reCaptcha](https://jetpack.com/blog/recaptcha-email-sharing-button/)
to protect against the WordPress.com network being seen as a source of email spam,
which would adversely affect VIP sites.

## Relative file includes

Including files with relative paths may lead to unintended results. It’s recommended
that all files are included with an absolute path.

Several functions are available to compose the file path, such as `__DIR__` , `dirname(
__FILE__ )` , `plugin_dir_path( __FILE__ )`, or `get_template_directory()`.

    ```lang-php
    // Don't do this:
    require_once 'file.php';

    // Do this instead:
    require_once __DIR__ . '/file.php';
    ```

## Remote calls

[Remote calls](https://docs.wpvip.com/code-quality/retrieving-remote-data/), such
as fetching information from external APIs or resources, should rely on the WordPress
HTTP API (no cURL) and be cached. Examples of remote calls that should be cached:`
wp_remote_get()`, `wp_safe_remote_get()` and `wp_oembed_get()`.

## Removing the admin bar

Removing the WordPress admin toolbar is strongly discouraged, and it must not be
removed for administrators or [`vip_support` roles](https://docs.wpvip.com/restricting-site-access/site-access-for-vip-support/).

## Return values not checked

When defining a variable through a function call, always check the function’s return
value before calling additional functions or methods using that variable.

    ```lang-php
    function wpcom_vip_meta_desc() {
        $text = wpcom_vip_get_meta_desc();
        if ( ! empty( $text ) ) {
            echo "<meta name='description' content='" . esc_attr( $text ) . "' />";
        }
    }
    ```

## Safelisting values for input/output validation

When working with user-submitted data, try to accept data only from a finite list
of known and trusted values where possible. For example:

    ```lang-php
    $possible_values = array( 'a', 1, 'good' );
    if ( ! in_array( $untrusted, $possible_values, true ) )
    die( "Don't do that!" );
    ```

## Serializing data

VIP recommends JSON for serializing data. Unserialize has known vulnerability problems
with [Object Injection](https://www.owasp.org/index.php/PHP_Object_Injection). 

## Skipping full-page caching

[Page caching](https://docs.wpvip.com/caching/page-cache/) by the [edge cache servers](https://docs.wpvip.com/vip-platform/edge-cache/)
improves performance by serving end-users a page directly from the nearest datacenter.
GET parameters are _always_ cached individually per GET parameter.  The page cache
will only respect the `Vary` headers for `X-Country-Code` and `Accept` , but **not**
for`[Cookie](https://docs.wpvip.com/caching/the-vip-cache-personalization-api/#cookies)`.

## `switch_to_blog()`

For multisite instances, `switch_to_blog()` only switches the database context, 
not the code that would run for that site (e.g. different filters).

## Taxonomy queries that do not specify `'include_children' => false`

Almost all taxonomy queries include `'include_children' => true` by default.  This
can have a very significant performance impact on code, and in some cases, queries
will time out.  We recommend adding `'include_children' => false` to all taxonomy
queries when possible.

In many instances where all posts in either a parent or child term are wanted, query
for the parent term and use a `save_post()` hook to determine whether a child term
is added and if so, enforce that its parent term is also added. A one-time WP-CLI
command might be needed to ensure previous data integrity.

## Uncached functions

Not all WordPress functions that interact with the database are cacheable. To ensure
high performance and stability, please avoid using any of the functions on our [Uncached Functions list](https://docs.wpvip.com/caching/uncached-functions/).

## Use `wp_json_encode()` over `json_encode()`

`wp_json_encode()` will ensure the string is valid UTF-8, while the regular function
will return `false` if it encounters invalid UTF-8. It also supports backward compatibility
for versions of PHP that do not accept all the parameters.

## Use `wp_parse_url()` instead of `parse_url()`

In PHP versions lower than 5.4.7, schemeless and relative URLs would not be parsed
correctly by `parse_url()`. We recommend using `wp_parse_url()` for backward compatibility.

## Use `wp_safe_redirect()` instead of `wp_redirect()`

Use `wp_safe_redirect()` along with the `allowed_redirect_hosts` filter to avoid
malicious redirects within code, and call `exit()` after a redirect to prevent executing
unwanted code.

## Using `__FILE__` for page registration

When adding menus or registering your plugins, use a unique handle or slug other
than `__FILE__` to avoid revealing system paths.

## Using `==` instead of `===`

PHP handles [type juggling](http://php.net/manual/en/types.comparisons.php). Meaning
that this:

    ```lang-php
    $var = 0;
    if ( $var == 'safe_string' ){
        return true;
    }
    ```

Will return `true`. VIP recommends using `===` over `==`.

Other interesting things that are equal are:

 * `(bool) true == 'string'`
 * `null == 0`
 * `0 == '0SQLinjection'`
 * `1 == '1XSS'`
 * `0123 == 83` (here `0123` is parsed as an octal representation)
 * `0xF == 15` (here `0xF` is parsed as a hexadecimal representation of a number)
 * `01 == '1string'`
 * `0 == 'test'`
 * `0 == ''`

## Using `$_REQUEST`

`$_REQUEST` does not identify the data location (such as `POST`, or `GET`, or a 
cookie), potentially introducing sneaky and hard-to-find bugs and causing difficulty
reviewing code.  A much better practice is to be explicit and use either `$_POST`
or `$_GET` instead.

## Using `bloginfo()` without escaping

Use `get_bloginfo()` instead of `bloginfo()` so that data can be properly late escaped
on output. `get_bloginfo()` can also return multiple data types, be used in multiple
places, and may need to be escaped with many different functions depending on the
context. For example:

    ```lang-php
    echo '<a href="' . esc_url( get_bloginfo( 'url' ) ) . '">' . esc_html( get_bloginfo( 'name' ) ) . '</a>';

    echo '<meta property="og:description" content="' . esc_attr( get_bloginfo( 'description' ) ) . '">';
    ```

## Using closing PHP tags

As part of the [WordPress Coding Standards](http://make.wordpress.org/core/handbook/coding-standards/php/#remove-trailing-spaces),
all PHP files should omit the closing PHP tag to prevent an accidental output of
whitespace and other characters, which can cause issues such as `Headers already
sent` errors.

## Using `extract()`

Never use `extract()`; it is too opaque and difficult to predict its behavior under
various inputs. It is too easy to unknowingly introduce new variables into a function’s
scope, leading to unintended and difficult debugging conflicts.

## Using page templates instead of rewrites

A common “hack” in the WordPress community when requiring a custom feature to live
at a vanity URL (e.g.,/lifestream/) is to use a Page + Page Template. This isn’t
ideal for numerous reasons:

 * It requires multiple queries to handle the lookup for the page and any additional
   manual loops.
 * The page must be manually created in each environment and new developer machine,
   which impedes development workflow.

Last updated: December 31, 2025