Skip to main content
Version: 1.x

Requests and Routing

Mantle provides a MVC framework on-top of WordPress. You can add a route fluently and send a response straight back without needing to work with WordPress's add_rewrite_rule() at all.

Route::get( '/example-route', function() {
return 'Welcome!';
} );

Route::get( '/hello/{who}', function( $name ) {
return "Welcome {$name}!";
} );

Routes are defined in the routes/web.php file by default and controlled by application's Route_Service_Provider located in the application

Registering Routes

Routes are registered for the application in the routes/ folder of the application. Underneath all of it, routes are a wrapper on-top of Symfony routing with a fluent-interface on top.

Closure Routes

At its most basic level, routes can be a simple anonymous function.

Route::get( '/endpoint', function() {
return 'Hello!';
} );

Controller Routes

You can use a controller to handle routes as well. In the future, resource and automatic controller routing will be added.

Route::get( '/controller-endpoint', Controller_Class::class . '@method_to_invoke' );
Route::get( '/controller-endpoint', [ Controller_Class::class, 'method_to_invoke' ] );

Generating a Controller

A controller can be generated through the CLI.

bin/mantle make:controller <name>

Invokable Controllers

A single-method controller is supported by defining a controller with an __invoke method.

use Mantle\Http\Controller;

class Invokable_Controller extends Controller {
/**
* Method to run.
*/
public function __invoke() {
return [ ... ];
}
}

Invokable controllers can be registered by passing the controller class name to the router.

Route::get( '/example-route', Invokable_Controller::class );

Invokable controllers are also generate-able through the CLI.

wp mantle make:controller --invokable

Named Routes

Naming a route provides an easy-to-reference way of generating URLs for a route.

Route::get( '/post/{slug}', function() {
//
} )->name( 'route-name' );

Routes can also pass the name to the router through as an array.

Route::get( '/posts/{slug}', [
'name' => 'named-route',
'callback' => function() { ... },
] );

Generating URLs to Named Routes

Once a route has a name assigned to it, you may use the route's name when generating URLs or redirects via the helper route function.

$url = route( 'route-name' );

Route Middleware

Middleware can be used to filter incoming requests and the response sent to the browser. Think of it like a WordPress filter on top of the request and the end response.

Example Middleware

/**
* Example_Middleware class file.
*
* @package Mantle
*/

namespace App\Middleware;

use Closure;
use Mantle\Http\Request;

/**
* Example Middleware
*/
class Example_Middleware {
/**
* Handle the request.
*
* @param Request $request Request object.
* @param Closure $next Callback to proceed.
* @return \Mantle\Http\Response
*/
public function handle( Request $request, Closure $next ) {
// Modify the request or bail early.
$request->setMethod( 'POST' );

/**
* @var Mantle\Http\Response
*/
$response = $next( $request );

// Modify the response.
$response->headers->set( 'Special-Header', 'Value' );

return $response;
}
}

Authentication Middleware

Included with Mantle, a route can check a user's capability before allowing them to view a page.

use Mantle\Facade\Route;

Route::get('/route-to-protect', function() {
// The current user can 'manage_options'.
} )->middleware( 'can:manage_options', Example_Middleware::class );

Removing Middleware

Middleware can be removed from a route by using the without_middleware method. You can pass a single middleware, an array of middleware to remove, or remove all middleware.

use Mantle\Facade\Route;

Route::get( '/route', function() {
// ...
} )->without_middleware( 'middleware_name' );

Once common use case is to remove the wrap template middleware (which will wrap your response in a WordPress header/footer). You can use the without_wrap_template() method.

use Mantle\Facade\Route;

Route::get( '/route', function() {
// ...
} )->without_wrap_template();

Route Prefix

Routes can be prefixed to make it easier to group routes together.

Route::prefix( 'prefix/to/use' )->group( function() {
// Register a route with a prefix here!
} );

Available Router Methods

The router has all HTTP request methods available:

Route::get( $uri, $callback );
Route::post( $uri, $callback );
Route::put( $uri, $callback );
Route::patch( $uri, $callback );
Route::delete( $uri, $callback );
Route::options( $uri, $callback );

Requests Pass-Through to WordPress Routing

By default, requests will pass down to WordPress if there is no match in Mantle. That can be changed inside of Route_Service_Provider. If the request doesn't have a match, the request will 404 and terminate before going through WordPress' require rules. REST API requests will always pass through to WordPress and bypass Mantle routing.

/**
* Route_Service_Provider class file.
*
* @package Mantle
*/

namespace App\Providers;

use Mantle\Facade\Request;
use Mantle\Framework\Providers\Route_Service_Provider as Service_Provider;

/**
* Route Service Provider
*/
class Route_Service_Provider extends Service_Provider {
/**
* Bootstrap any application services.
*/
public function boot() {
parent::boot();

$this->allow_pass_through_requests();
}
}

Model Routing

Models can have their permalinks and routing handled automatically. The underlying WordPress object will use the Mantle-generated URL for the model, too. The application will generate an archive route for post models and singular routes for post and term models.

tip

Models that are automatically registered will have their routing automatically registered. Read more about model registration here.

Model Routing Controller

Similar to a resource controller, the router will invoke a single controller for the 'resource' (the model).

MethodCallback
Archive /product/{slug}/Product_Controller::index()
Singular /product/{slug}/Product_Controller::show()

Registering a Model for Routing

Route models can be registered like any other route and also supports prefixes, middleware, etc.

Route::model( Product::class, Product_Controller::class );

In the above example the Product model will have /product/{slug} and /product routes registered.

use Mantle\Database\Model\Post;
use Mantle\Database\Model\Concerns\Custom_Post_Permalink;

class Product extends Post {
use Custom_Post_Permalink;

public function get_route(): ?string {
return '/route/{slug}';
}
}

Route Model Binding

Routes support model binding that will automatically resolve a model based on a route parameter and a type-hint on the route method. This supports implicit and explicit binding from a service provider.

Implicit Binding

Mantle will automatically resolve models that are type-hinted for the route's method. For example:

Route::get( 'users/{user}', function ( App\User $user ) {
return $user->title;
} );

Since the $user variable is type-hinted as App\User model and the variable name matches the {user} segment, Mantle will automatically inject the model instance that has an ID matching the corresponding value from the request URI. If a matching model instance is not found in the database, a 404 HTTP response will automatically be generated.

Customizing The Default Key Name

If you would like model binding to use a default database column other than id when retrieving a given model class, you may override the get_route_key_name method on the model:

/**
* Get the route key for the model.
*
* @return string
*/
public function get_route_key_name(): string {
return 'slug';
}

Explicit Binding

To register an explicit binding, use the router's model method to specify the class for a given parameter. You should define your explicit model bindings in the boot method of the Route_Service_Provider class:

public function boot() {
parent::boot();

Route::bind_model( 'user', App\User::class );
}

Next, define a route that contains a {user} parameter:

Route::get( 'profile/{user}', function ( App\User $user ) {
//
} );

Since we have bound all {user} parameters to the App\User model, a User instance will be injected into the route. So, for example, a request to profile/1 will inject the User instance from the database which has an ID of 1.

If a matching model instance is not found in the database, a 404 HTTP response will be automatically generated.

Customizing The Resolution Logic

If you wish to use your own resolution logic, you may use the Route::bind method. The Closure you pass to the bind method will receive the value of the URI segment and should return the instance of the class that should be injected into the route:

/**
* Bootstrap any application services.
*
* @return void
*/
public function boot() {
parent::boot();

Route::bind( 'user', function ( $value ) {
return App\User::where( 'name', $value )->firstOrFail();
} );
}

Alternatively, you may override the resolve_route_binding method on your model. This method will receive the value of the URI segment and should return the instance of the class that should be injected into the route:

/**
* Retrieve the model for a bound value.
*
* @param mixed $value
* @param string|null $field
* @return static|null
*/
public function resolve_route_binding( $value, $field = null ) {
return $this->where( 'name', $value )->firstOrFail();
}

Responses

Responses for routed requests can come in all shapes and sizes. Underneath all of it, the response will always come out to be a Symfony\Component\HttpFoundation\Response object. The response() helper exists to help with returning responses.

Strings & Arrays

use Mantle\Facade\Route;

Route::get( '/', function () {
return 'Hello World';
} );

Route::get( '/', function () {
return [ 1, 2, 3 ];
} );

Views

WordPress template parts can be returned for a route.

Route::get( '/', function () {
return response()->view( 'template-parts/block', [ 'variable' => '123' ] );
} );

View Location

By default WordPress will only load a template part from the active theme and parent theme if applicable. Mantle supports loading views from a dynamic set of locations. Mantle support automatically register the current theme and parent theme as view locations.

Default View Locations
  • Active Theme
  • Parent of Active Theme
  • {root of mantle site}/views

For more information about views, read the 'Templating' documentation.

Redirect to Endpoint and Route

Redirects can be generated using the response() helper.

use Mantle\Facade\Route;

Route::get( '/logout', function() {
return response()->redirect_to( '/home' );
} );

Route::get( '/old-page', function() {
return response()->redirect_to( '/home', 301 );
} );

Route::get( '/oh-no', function() {
return response()->redirect_to_route( 'route_name' );
} );

REST API Routing

Mantle supports registering to the WordPress REST API directly. REST API Routes are registered underneath with native core functions and does not use the Symfony-based routing that web requests pass through.

Registering Routes

Registering a REST API route requires a different function call if you do not wish to use a closure.

use Mantle\Facade\Route;

Route::rest_api( 'namespace/v1', '/route-to-use', function() {
return [ 1, 2, 3 ];
} );

Routes can also be registered using the same HTTP verbs web routes use with some minor differences.

use Mantle\Facade\Route;
use WP_REST_Request;

Route::rest_api(
'namespace/v1',
function() {
Route::get(
'/example-group-get',
function() {
return [ 1, 2, 3 ];
}
);

Route::get(
'/example-with-param/(?P<slug>[a-z\-]+)',
function( WP_REST_Request $request) {
return $request['slug'];
}
);
}
);

REST API routes can also be registered using the same arguments you would pass to register_rest_route().

use Mantle\Facade\Route;

Route::rest_api(
'namespace/v1',
function() {
Route::get(
'/example-endpoint',
function() {
// This callback can be omitted, too.
},
[
'permission_callback' => function() {
// ...
}
]
);
}
);

Using Controllers

REST API routes can also use controllers to handle the request. You can use a specific method on a controller or make the controller invokeable to handle the request.

use Mantle\Facade\Route;
use WP_REST_Request;

Route::rest_api(
'namespace/v1',
function() {
Route::get( '/example-endpoint', Example_Controller::class );
Route::get( '/another-endpoint', [ Another_Example_Controller::class, 'method' ] );
}
);

class Example_Controller {
public function __invoke( WP_REST_Request $request ) {
return [ 1, 2, 3 ];
}
}

class Another_Example_Controller {
public function method( WP_REST_Request $request ) {
return [ 1, 2, 3 ];
}
}

Using Middleware

REST API routes also support the same Route Middleware that web requests use. The only difference is that web requests are passed a Mantle Request object while REST API requests are passed a WP_REST_Request one.

use Mantle\Facade\Route;

Route::middleware( Example_Middleware::class )->group(
function() {
Route::rest_api( 'namespace/v1', '/example-route', function() { /* ... */ } );
}
)

Events

Mantle-powered routes for both web and REST API will fire the Route_Matched event when a route has been matched. It will always include the current request object. For web routes it will include the Route object as the route property. For REST API requests it will include an array of information about the current matched route.

use Mantle\Http\Routing\Events\Route_Matched;

Event::listen(
Route_Matched::class,
function( Route_Matched $event ) {
var_dump( $event->route );
var_dump( $event->request->ip() );
}
);

New Relic

Using the Route_Matched event Mantle will automatically fill in transaction information for New Relic if the extension is loaded. All requests will have the transaction name properly formatted instead of relying on New Relic to fill in the blanks.