Optional Route Sub-patterns ​
Optional Route Sub-patterns allow parts of a route to be, well, optional! This means a user can visit different variations of the same URL, and your app will still respond correctly.
For example, you could have a route like /post/{id}(/edit)?
, where {id}
is required, but /edit
is optional. This allows both /post/1 and /post/1/edit to be valid routes. The optional part is denoted by the ?
character.
app()->get('/post/{id}(/edit)?', function () {
echo 'Hello this is a post';
});
While this might seem simple, it's important to note that the optional part should be inside the sub-pattern itself. The leading /
of the sub-pattern should be inside the sub-pattern otherwise, you might end up with unexpected results.
Using Regular Expressions ​
The example above showed /post/{id}(/edit)?
, but you can also use regular expressions to define optional sub-patterns. For example, you could have /post/(\d+)(/edit)?
to match /post/1
and /post/1/edit
. You can also use more complex regular expressions to match more complex URLs. Let's look at an example:
app()->get('/blog(/\d+)?(/\d+)?(/\d+)?(/[a-z0-9_-]+)?', function ($year = null, $month = null, $day = null, $slug = null) {
if (!$year) {
echo 'Blog overview';
return;
}
if (!$month) {
echo 'Blog year overview';
return;
}
if (!$day) {
echo 'Blog month overview';
return;
}
if (!$slug) {
echo 'Blog day overview';
return;
}
echo 'Blogpost ' . htmlentities($slug) . ' detail';
});
With this example, we can respond to URLs like /blog
, /blog/{year}
, /blog/{year}/{month}
, /blog/{year}/{month}/{day}
, and /blog/{year}/{month}/{day}/slug
all with a single route. This is a powerful feature that can significantly reduce the number of routes you need to define, however, it has the downside of being harder to read and understand compared to defining separate routes.
Successive Optional Sub-patterns ​
This scary-sounding term means that you can have multiple optional sub-patterns in a row. This is done by nesting the optional sub-patterns inside each other. This is important because it ensures that the optional parts are correctly matched. For example, the example above which is /blog(/\d+)?(/\d+)?(/\d+)?(/[a-z0-9_-]+)?
can respond to /blog/somecrazystring
which is not what we want. To fix this, we can nest the optional sub-patterns like this:
app()->get('/blog(/\d+(/\d+(/\d+(/[a-z0-9_-]+)?)?)?)?', function ($year = null, $month = null, $day = null, $slug = null) {
// ...
});
What we've done here is place the sub-patterns inside each other, instead of leaving them next to each other. This ensures that the optional parts are correctly matched and that the route only responds to the correct URLs. Now accessing /blog/somecrazystring
will not match this route.
Quantifiers ​
In the examples above, we used \d+
to match one or more digits. You can use quantifiers to require a specific number of digits in the URL. For example, you could use \d{4}
to match exactly 4 digits. This can be useful when you want to ensure that the URL matches a specific format. You can read more about quantifiers in the PHP documentation. Let's update our example to require exactly 4 digits for the year, 2 digits for the month, and 2 digits for the day:
app()->get('/blog(/\d{4}(/\d{2}(/\d{2}(/[a-z0-9_-]+)?)?)?)?', function ($year = null, $month = null, $day = null, $slug = null) {
// ...
});
This ensures that the route only responds to URLs like /blog/2021/01/01/slug
and not /blog/17819090091/01/01/slug
.