A quick look into WP Emerge’s new route query API
UPDATE: The routing API has been slightly updated since this article was published. Click here for more information.
WP Emerge v0.11.0 comes with a new Route::query()
API for routes which replaces the old Route::rewrite()
one which, as you probably guessed, was based on WordPress’ Rewrite API. This change was done due to several reasons:
rewrite()
did little more than just calladd_rewrite_rule()
.rewrite()
only allowed us to declare direct mapping from one url to another.query()
works without having to refresh permalinks.query()
gives us imperative control over the rewriting – this allows us to do things like filter our parameters, add conditional logic etc.query()
gives us full access to the currentWP_Query
arguments so we can filter them as needed and event use arguments that are not publicly queryable under normal circumstances. Be careful with the last part though.
Setup
In case you don’t have WP Emerge installed or you are not using the WP Emerge Starter Theme, here are the quickstart steps to have WP Emerge running in your theme:
- Make sure you cover the requirements listed in the documentation.
- Open your terminal of choice and run the following in your theme directory:
composer require htmlburger/wpemerge
- Make sure you’ve included the generated
autoload.php
file inside yourfunctions.php
filerequire_once( 'vendor/autoload.php' );
- Add the following immediately after:
add_action( 'after_setup_theme', function() { WPEmerge::boot(); // All code references in the examples section would go here. // For organization purposes, you may want to create a // separate file and require it here instead. } );
Breaking it down
Here’s the full example, as seen in the article thumbnail:
Router::get( '/custom/{taxonomy}/url/for/{term}/', 'Controller@index' )
->query( function ( $query_vars, $taxonomy, $term ) {
$taxonomy = get_taxonomy( sanitize_text_field( $taxonomy ) );
if ( ! $taxonomy || ! $taxonomy->publicly_queryable ) {
return $query_vars;
}
return [
$taxonomy->query_var => sanitize_text_field( $term ),
];
} );
Let’s break it down and explore what each part does:
Router::get( ... )
Our first step in declaring a new route is to specify the HTTP method we want to match, The Router class comes with a number of tools to match against HTTP methods which are shown here. For simplicity’s sake we will go with GET
.
'/custom/{taxonomy}/url/for/{term}/'
Next, we specify the URL pattern we wish to match. The special {taxonomy}
and {term}
parameter tokens specify that we wish to accept any user input in these segments – we’ll get to use their values later on.
'Controller@index'
One of the core concepts in WP Emerge are Controllers (the same controllers as in the MVC design pattern) – they handle the actual response when a route matches. Most commonly, we will specify a controller class name and a method of that class which WP Emerge should use to handle the request – this comes in the CONTROLLER_CLASS@METHOD_NAME
format as seen in the example. Check out the Controllers and Route Handlers sections of the documentation for more information.
If we do not have any special handling logic we can omit this argument and let normal behavior resume:
Router::get( '/custom/{taxonomy}/url/for/{term}/' )
.
->query( function ( $query_vars, $taxonomy, $term ) { ... } );
Here we call query()
to signify we wish to filter the main WordPress query and pass an anonymous function that will do the filtering. The first argument passed to our function holds the current WordPress query args and subsequent arguments are the values of the special parameter tokens we used in the URL pattern earlier.
$taxonomy = get_taxonomy( sanitize_text_field( $taxonomy ) );
if ( ! $taxonomy || ! $taxonomy->publicly_queryable ) {
return $query_vars;
}
return [
$taxonomy->query_var => sanitize_text_field( $term ),
];
Finally, due to our example’s needs, we throw away any query vars that WordPress intended to use and just return an array of custom query arguments that we want to use instead. Don’t forget to sanitize user input!
More examples
Show a landing page to anonymous users instead of the normal homepage
Match:
/
Route:
Router::get( '/' )
->query( function ( $query_vars ) {
if ( ! is_user_logged_in() ) {
return [
'post_type' => 'page',
'name' => 'landing-page',
];
}
return $query_vars;
} );
Clean URL search filter based on taxonomies
Match:
/cars/BMW/7-series/2019/
Route:
Router::get( '/cars/{make}/{model}/{year}/' )
->query( function ( $query_vars, $make, $model, $year ) {
return [
'car_make' => sanitize_text_field( $make ),
'car_model' => sanitize_text_field( $model ),
'car_year' => sanitize_text_field( $year ),
];
} );
Custom endpoint to existing post type
Match:
/files/2018-yearly-report/download/csv/
/files/2018-yearly-report/download/xls/
Route:
Router::get( '/files/{file}/download/{format:(csv|xls)}/', 'FileController@single' )
->query( function ( $query_vars, $file, $format ) {
// Assuming we handle the download format logic in our controller, we
// only want to force WordPress to render the single page of the
// file post even though we have extra parameters added to
// the URL.
return [
'post_type' => 'file',
'name' => sanitize_text_field( $file ),
];
} );
Reach out
Let me know your thoughts about the new query API in the comment section and feel free to join me for a chat in the Gitter Lobby!