WP_Query
object. WP_Query
objects take a number of useful arguments and do things behind-the-scenes that other database access methods such as get_posts()
do not.WP_Query
object runs five queries by default, including calculating pagination and priming the term and meta caches. Each of the following arguments will remove a query:'no_found_rows' => true
: useful when pagination is not needed.'update_post_meta_cache' => false
: useful when post meta will not be utilized.'update_post_term_cache' => false
: useful when taxonomy terms will not be utilized.'fields' => 'ids'
: useful when only the post IDs are needed (less typical).posts_per_page => -1
.$wpdb
or get_posts()
unless you have good reason.get_posts()
actually calls WP_Query
, but calling get_posts()
directly bypasses a number of filters by default. Not sure whether you need these things or not? You probably don’t.no_found_rows => true
to WP_Query
.SQL_CALC_FOUND_ROWS
on the SQL query, drastically speeding up your query. SQL_CALC_FOUND_ROWS
calculates the total number of rows in your query which is required to know the total amount of “pages” for pagination.post__not_in
.cache_results => false
to WP_Query
is usually not a good idea.cache_results => true
(which is true by default if you have caching enabled and an object cache setup), WP_Query
will cache the posts found among other things. It makes sense to use cache_results => false
in rare situations (possibly WP-CLI commands).get_posts()
and WP_Query
, apart from some slight nuances, are quite similar. Both have the same performance cost (minus the implication of skipping filters): the query performed.query_posts()
, on the other hand, behaves quite differently than the other two and should almost never be used. Specifically:WP_Query
object with the parameters you specify.WP_Query
.query_posts()
isn’t meant to be used by plugins or themes. Due to replacing and possibly re-running the main query, query_posts()
is not performant and certainly not an acceptable way of changing the main query.in_array()
is not an efficient way to find if a given value is present in an array. The worst case scenario is that the whole array needs to be traversed, thus making it a function with O(n) complexity. VIP review reports in_array()
use as an error, as it’s known not to scale.in_array()
, to improve the performance slightly, you should always set the third parameter to true
to force use of strict comparison.WP_Object_Cache
, and the Transients API are great solutions for improving performance on long-running queries, complex functions, or similar.wp_options
table with an excessive amount of data. See the “Appropriate Data Storage” section for details.WP_Query
other than the main query should be cached.wp_update_comment_count
set as do_action( 'wp_update_comment_count', $post_id, $new, $old )
.$_SERVER
, $_COOKIE
or other values that are unique to a particular user.wp-admin/admin-ajax.php
. However, WordPress does not cache queries within the administration panel for obvious reasons. Therefore, if you send requests to an admin-ajax.php endpoint, you are bootstrapping WordPress and running un-cached queries. Used properly, this is totally fine. However, this can take down a website if used on the frontend.prefix_get_posts_from_other_blog()
can be called to get posts from a third-party and will handle caching internally.wp_options
table. Popular caching plugins such as Memcached place a 1MB limit on individual values stored in cache. A large options table can easily exceed this limit, severely slowing each page load.$autoload
argument to add_option()
. If your option is not going to get used often, it shouldn’t be autoloaded. As of WordPress 4.2, update_option()
supports configuring autoloading directly by passing an optional $autoload
argument. Using this third parameter is preferable to using a combination of delete_option()
and add_option()
to disable autoloading for existing options.namespace
identifier at the top of included files:Client;
Client\Package;
).use
declarations should be used for classes outside a file’s namespace. By declaring the full namespace of a class we want to use once at the top of the file, we can refer to it by just its class name, making code easier to read. It also documents a file’s dependencies for future developers.XWP
is (most likely) unique; theme
is not. A simple way to ensure uniqueness is to prefix a declaration with a unique prefix.public
. Anything intended to be private should actually be specified as protected
. There should be no private
fields or properties without well-documented and agreed-upon rationale.php
directory. All plugin code should be encapsulated in classes, and so each file in this directory should follow the pattern class-foo-bar.php
(for class Foo_Bar
) according to WordPress naming conventions.__construct
method. Doing so tightly couples the hooks to the instantiation of the class and is less flexible than registering the hooks via a separate method. Unit testing becomes much more difficult as well.page
post type. The Theme should register support for this feature using add_theme_support
,$_POST['user_id']
is validated using absint()
which ensures an integer >= 0. Without validation (or sanitization), $_POST['user_id']
could be used maliciously to inject harmful code or data into the database.update_option()
is storing in the database, the value must be sanitized (or validated). The example uses the sanitize_text_field()
function, which is appropriate for sanitizing general text fields.$wpdb->prepare()
behaves like sprintf()
and essentially calls mysqli_real_escape_string()
on each argument. mysqli_real_escape_string()
escapes characters like '
and "
which prevents many SQL injection attacks.%d
in sprintf()
, we are ensuring the argument is forced to be an integer. You might be wondering why absint()
was used since it seems redundant. It’s better to over sanitize than to miss something accidentally.$wpdb->insert()
creates a new row in the database. $post_content
is being passed into the post_content
column. The third argument lets us specify a format for our values sprintf()
style. Forcing the value to be a string using the %s
specifier prevents many SQL injection attacks. However, wp_kses_post()
still needs to be called on $post_content
as someone could inject harmful JavaScript otherwise.esc_html()
ensures output does not contain any HTML thus preventing JavaScript injection and layout breaks.sanitize_email()
ensures output is a valid email address. This is an example of validating our data. A broader escaping function like esc_attr()
could have been used, but instead sanitize_email()
was used to validate.esc_js()
ensures that whatever is returned is safe to be printed within a JavaScript string. This function is intended to be used for inline JS, inside a tag attribute (onfocus=”…”, for example).esc_js()
should never really be used. To escape strings for JS another function should be used instead, called wp_json_encode()
.wp_json_encode()
ensures that whatever is returned is safe to be printed in your JavaScript code. It returns a JSON encoded string.wp_json_encode()
includes the string-delimiting quotes for you.esc_attr()
to ensure output only contains characters appropriate for an attribute:wp_kses_*
functions can be used:wp_kses_*
functions should be used sparingly as they have bad performance due to a large number of regular expression matching attempts. If you find yourself using wp_kses_*
, it’s worth evaluating what you are doing as a whole.wp_kses_*
on the frontend, output should be cached for as long as possible.__()
function, something like esc_html__()
might be more appropriate. Instead of using the generic _e()
function, esc_html_e()
would instead be used.ID
1. To do that, you might visit this URL: https://example.com/wp-admin/post.php?post=1&action=trash
https://example.com/wp-admin/post.php?post=2&action=trash
https://example.com/wp-admin/post.php?post=1&action=trash&_wpnonce=b192fc4204
, the same nonce will not be valid in https://example.com/wp-admin/post.php?post=2&action=trash&_wpnonce=b192fc4204
.printf()
formatting codes inside the string to be translated and pass the translated version of that string to sprintf()
to fill in the values.xwp-project-name
.wp-dev-lib
will look for a phpcs.ruleset.xml
to use for the pre-commit
hook and Travis CI build. Please also be aware of the PHP_CodeSniffer-bundled tool phpcbf
(PHP Code Beautifier and Fixer) which also automatically fixes many WordPress Coding Standard violations.phpunit.xml
for plugins which facilitates writing tests in pretty much any type of Vagrant or Docker environment running WordPress. Read more about wp-dev-lib and automated testing.@see
tag to explicitly list out the method that is being tested. For instance, given a plugin Foo
that has a class Bar
in a file php/class-bar.php
:tests/test-bar.php
:wp-dev-lib
phpunit.xml
also includes a filter
configuration for restricting the list of PHP files to just those in the plugin when running the code coverage report which can be generated via:get_template_part()
function as a basic template engine. Make your template file consist mostly of HTML, with <?php ?>
tags just where you need to escape and output. The resulting file will be as readable as a heredoc/nowdoc block, but can still perform late escaping within the template itself.