Defining a route can be done with just a couple lines of code. To add a route to your Concrete CMS application, first open application/bootstrap/app.php
. Then, get a copy of the application router instance from the Concrete\Core\Application\Application
object, which is already defined as $app
in the local scope of the file.
$router = $app->make(‘router’);
Next, define your route. In our example before, we wanted to define a route at /api/current_user
that would return a simple JSON representation of the currently logged-in user. Let’s define that route.
$router->get(‘/api/current_user’, function() {
return ‘Return a simple string.’;
}
Then, when you visit http://www.mysite.com/api/current_user
you’ll receive a simple “Return a simple string.” on a white screen.
The most basic routes accept a URI and a Closure
, as in the example above.
HTTP Verb Support
Notice the get()
method that we used above? This method specifies that the route in question will respond to the HTTP GET request. There are HTTP-specific verbs for all routes
- $router->get($endpoint, $callback)
- $router->post($endpoint, $callback)
- $router->put($endpoint, $callback)
- $router->patch($endpoint, $callback)
- $router->delete($endpoint, $callback)
- $router->options($endpoint, $callback)
Want to make a route respond to all verbs? Use the
all()
method. - $router->all($endpoint, $callback)
Now, let’s move onto something a little less contrived.
Route Parameters
If you need to capture parameters from the route, you can do with required parameters. Here’s an example of a route that requires an ID.
$router->get(‘/api/customer/{customerId}, function($customerId) {
return ‘The customer ID is: ‘ . $customerId;
});
Routes can take multiple parameters. They are passed to the callback in order:
$router->get(‘/api/customers/{country}/{province}, function($country, $province) {
return ‘We are searching for customers in ‘ . $country . ‘ and in ‘ . $province;
});
Parameter Formats
You can force routes to take parameters that conform to a particular format by using setRequirements
on the Router object when defining a route. Pass in a regular expression to set the format. For example, this forces an alphanumeric string for the particular parameter:
$router->get('/rss/{identifier}', function() {
// Callback here.
})
->setRequirements(['identifier' => '[A-Za-z0-9_/.]+']);
Named Routes
If you’d like you can name a particular route. This information will be passed through the route, and you can always retrieve a route from the system route collection by name, which can be useful in certain contexts.
$router->get('/rss/{identifier}', function() {
// Callback here.
})
->setName(‘rss’)
->setRequirements(['identifier' => '[A-Za-z0-9_/.]+']);
Note: The Router Object is Chainable
As you can see above, all methods when working with route generation are chainable, which results in a simpler, more expressive syntax.
Accessing the Request from within a Route Callback
Accessing the Request object from within a route callback is no different than elsewhere – just use the createFromGlobals
method on the Request object:
$router->put(‘/api/customer/update/{customerId}, function($customerId) {
$request = \Concrete\Core\Http\Request::createFromGlobals();
$email = $request->request->get(‘email’);
// Now we have the customerId, and we have the email address from the POST that we want to update it to.
});
Responses
Returning strings makes routing extremely simple, but this is a bit contrived: strings don’t give you the ability to set HTTP Response codes, and they also require you to work with your own JSON formats. Thankfully, the Concrete router works very well with the Concrete\Core\Http\Response
object (which itself is just a subclass of Symfony\Component\HttpFoundation\Response
. Return any response object from your route, and it will be handled automatically.
Simple Response
Here’s our first example, using the Response object.
use Symfony\Component\HttpFoundation\Response;
$router->get(‘/api/current_user’, function() {
$response = new Response(‘Return a simple string.’);
return $response;
}
But it gets better when you want something more advanced. Let’s implement our current_user
endpoint functionality.
JSON Response
use Symfony\Component\HttpFoundation\JsonResponse;
$router->get(‘/api/current_user’, function() {
$u = new \User();
if ($u->isRegistered()) {
$data = [];
$data[‘user_id’] = $u->getUserID();
$data[‘username’] = $u->getUserName();
return new JsonResponse($data);
} else {
return new JsonResponse([], 400);
}
});
What’s going on here? Well, if the user is registered, we create a PHP array with their user ID and username populated, and return it inside the JsonResponse. If they’re not logged in, we return an empty object, along with the HTTP 400 error code.
Redirect Responses
Returning a redirect response from a route works as well.
use Symfony\Component\HttpFoundation\RedirectResponse;
$router->get(‘/api/current_user’, function() {
return new RedirectResponse(‘/api/new_location’, 301);
});
Error Handling in Routes
The Concrete\Core\Error\ErrorList\ErrorList
object is a handy object for tracking errors and formatting them. This object works well in a route context as well. Let’s refactor our code above to use the error list object, and use its builtin methods for error response.
use Symfony\Component\HttpFoundation\JsonResponse;
use Concrete\Core\Error\ErrorList\ErrorList;
$router->get(‘/api/current_user’, function() {
$u = new \User();
$errors = new ErrorList;
if ($u->isRegistered()) {
// This code is the same.
} else {
$errors->add(‘You must be logged in to get the current user.’);
return $errors->createResponse(400);
}
});
The ErrorList object keeps track of all errors added to it. Running createResponse
from it returns a standardized JSON error object, with all error strings, using the HTTP error code passed to it.
Middleware
In Concrete any route can be attached to middleware. Middleware is a reusable routine that filters HTTP requests. A middleware might be responsible for adding certain HTTP headers on every request, or logging all requests.
Example Middleware
If you want to make an API route only accessible to authenticated API calls, just add the Concrete\Core\Http\Middleware\OAuthAuthenticationMiddleware
to it:
$router->get(‘/api/current_user’, function() {
//snip
})->addMiddleware(OAuthAuthenticationMiddleware::class);
Going Beyond Closures
This is all it takes to define routes in Concrete – just define these routes in application/bootstrap/app.php
as you need. However, you may not want to define all your route logic within a \Closure
. Instead, you should wire your routes to controllers. Read on for more.