Inphinit allows you to organize and isolate routes using a feature called scope. With it, you can define groups of routes that are only registered when a specific URL condition is met.
These conditions can consider different parts of the URL, such as:
- Protocol: HTTP or HTTPS.
- Domain or subdomain:
example.com,admin.example.com, and others. - Path:
/blog/,/admin/,/api/, etc.
This feature is particularly useful for applications with different areas, such as administrative panels, APIs, blogs, development environments, and multi-tenant systems.
How does a scope work?
To create a scope, we use the $app->scope() method, which takes two main arguments: the pattern used to identify the request and a callback function that defines the routes belonging to that scope.
The basic structure is:
$app->scope('/blog/', function ($app, $params) {
$app->action('GET', '/', function () {
return 'Blog homepage';
});
$app->action('GET', '/posts', function () {
return 'List of posts';
});
});
In this example, the routes will only be added when the request path matches the /blog/ scope.
Therefore, we can access:
-
/blog/— blog homepage. -
/blog/posts— list of posts.
Notice that routes declared inside the scope use paths relative to it. This allows you to organize a group of routes without having to repeat the /blog/ prefix in every declaration.
The scope also receives the $app and $params arguments. The first allows you to register new routes and share information with them. The second contains the parameters captured by the patterns used in the scope.
Next, we will see how to use this feature with different parts of the URL.
1. Isolating routes by HTTP and HTTPS protocols
One possibility is to restrict certain routes according to the protocol used in the request.
This is useful when we need to separate features that should only be available through secure connections or, in specific situations, make certain routes exclusively available over HTTP.
HTTPS only
To register routes exclusively for HTTPS, we use:
$app->scope('https://*', function ($app, $params) {
$app->action('GET', '/account', function () {
return 'Secure account area';
});
$app->action('GET', '/checkout', function () {
return 'Secure checkout';
});
});
In this case, the /account and /checkout routes will only be registered when the request uses HTTPS.
For example:
https://example.com/account
https://example.com/checkout
A request using HTTP will not activate this scope.
HTTP only
In the same way, we can restrict a group of routes to the HTTP protocol:
$app->scope('http://*', function ($app, $params) {
$app->action('GET', '/internal-status', function () {
return 'Internal application status';
});
});
In this example, the /internal-status route will only be available when the request uses HTTP.
Important: Using scope() does not automatically redirect requests between HTTP and HTTPS. It only determines the conditions under which routes are registered. If you need to redirect all HTTP traffic to HTTPS, this rule must be configured on the web server or proxy.
2. Isolating routes by domain and subdomain
Another possibility is to restrict routes to a specific domain or certain subdomains.
This is especially useful when an application serves different areas through separate addresses, such as:
-
example.com— corporate website. -
admin.example.com— administrative panel. -
api.example.com— application API. -
blog.example.com— blog.
Specific domain
To make routes available exclusively on a specific domain, we use:
$app->scope('*://example.com/', function ($app, $params) {
$app->action('GET', '/', function () {
return 'Corporate website';
});
$app->action('GET', '/about', function () {
return 'About the company';
});
});
The *:// pattern allows either HTTP or HTTPS, while example.com restricts the scope to the specified domain.
This means the routes will be registered for addresses such as:
http://example.com/
https://example.com/
https://example.com/about
The same group of routes will not be registered for admin.example.com or api.example.com.
Specific subdomain
If we want to make routes available only on a subdomain, we can use:
$app->scope('*://admin.example.com/', function ($app, $params) {
$app->action('GET', '/', function () {
return 'Administrative panel';
});
$app->action('GET', '/users', function () {
return 'User management';
});
$app->action('GET', '/settings', function () {
return 'Administrative settings';
});
});
Now, these routes will be available on the admin.example.com subdomain, regardless of whether the request uses HTTP or HTTPS.
This approach allows you to keep the administrative panel separate from the application's public routes.
Dynamically capturing the subdomain
Inphinit also allows you to use patterns to capture parts of the URL.
Imagine a multi-tenant application where each customer has their own subdomain:
company1.example.com
company2.example.com
company3.example.com
Instead of creating a separate scope for each customer, we can use a dynamic parameter:
$app->scope('*://<subdomain>.example.com/', function ($app, $params) {
$subdomain = $params['subdomain'];
$app->action('GET', '/', function () use ($subdomain) {
return 'Welcome to the environment: ' . $subdomain;
});
$app->action('GET', '/dashboard', function () use ($subdomain) {
return 'Dashboard: ' . $subdomain;
});
});
The value captured by the <subdomain> pattern will be available in $params['subdomain'].
For example, when accessing company1.example.com, the variable will contain company1. When accessing company2.example.com, it will contain company2.
This allows you to dynamically identify the customer's context and load their corresponding settings, data, and resources.
It is also possible to use the * wildcard to accept any subdomain without capturing its value:
$app->scope('*://*.example.com/', function ($app, $params) {
$app->action('GET', '/', function () {
return 'Subdomain area';
});
});
3. Isolating routes by path
In addition to working with protocols and domains, we can use scopes to organize routes by path prefixes.
This approach is particularly useful when different application modules share the same domain but need to keep their routes separate.
For example:
example.com/blog/
example.com/admin/
example.com/api/
To create a path-based scope, simply specify the desired prefix:
$app->scope('/blog/', function ($app, $params) {
$app->action('GET', '/', function () {
return 'Blog homepage';
});
$app->action('GET', '/posts', function () {
return 'List of posts';
});
$app->action('GET', '/posts/<id:num>', function ($app, $params) {
return 'Post: ' . $params['id'];
});
$app->action('POST', '/search', function () {
return 'Searching posts';
});
});
In this example, all routes belong to the /blog/ path.
The corresponding URLs will be:
/blog/
/blog/posts
/blog/posts/10
/blog/search
Notice that we do not need to repeat /blog/ in every route. The scope itself establishes this context.
Another example: Administrative panel
We can use the same strategy to separate administrative features:
$app->scope('/admin/', function ($app, $params) {
$app->action('GET', '/', function () {
return 'Dashboard home';
});
$app->action('GET', '/users', function () {
return 'User management';
});
$app->action('GET', '/reports', function () {
return 'Reports';
});
});
This way, administrative routes remain organized within a single scope, while public routes can remain in the main scope or in other groups.
4. Combining different conditions
One advantage of Inphinit's scope system is the ability to work with the complete URL, combining protocol, domain, and path.
For example, we can restrict an API so that its routes are only registered when the request uses HTTPS, targets the api.example.com domain, and starts with the /v1/ path.
$app->scope('https://api.example.com/v1/', function ($app, $params) {
$app->action('GET', '/users', function () {
return 'API user list';
});
$app->action('GET', '/products', function () {
return 'API product list';
});
});
In this case, the routes will be available at:
https://api.example.com/v1/users
https://api.example.com/v1/products
An HTTP request, a request to another domain, or a request outside the /v1/ prefix will not activate this scope.
Combining these conditions allows you to organize applications with different contexts without having to create independent projects or routing systems.
5. Important considerations when using reverse proxies and load balancing
Before using protocol- or domain-based scopes in production, there is an important detail to consider: how reverse proxies, CDNs, and load balancers handle requests.
In many architectures, requests do not reach the application directly. They first pass through an intermediary responsible for receiving connections, managing HTTPS certificates, and forwarding requests to one or more application servers.
Consider the following scenario:
Browser
|
| HTTPS
v
Proxy / Load Balancer
|
| HTTP
v
Server
In this example, the user accesses the application using HTTPS, but the proxy terminates the TLS connection and forwards the request to the Inphinit server using HTTP.
As a result, the application may identify the incoming connection as HTTP, even though the user originally accessed the application through HTTPS.
The same problem can occur with the domain. Depending on the proxy configuration, the application may receive the backend server's internal host instead of the domain originally requested by the user.
This directly affects scopes.
For example, consider:
$app->scope('https://admin.example.com/', function ($app, $params) {
$app->action('GET', '/', function () {
return 'Administrative panel';
});
});
If the application incorrectly identifies the request as http://backend.internal/, the scope will not be activated, even though the user accessed https://admin.example.com/.
To solve this problem, proxies must forward the original request information using the appropriate HTTP headers.
X-Forwarded-Proto
The X-Forwarded-Proto header tells the application which protocol the client originally used.
Example:
X-Forwarded-Proto: https
It is particularly important when TLS termination occurs at the proxy, because it allows the application to recognize that the original connection used HTTPS, even if communication between the proxy and the application server uses HTTP.
Without this information, scopes such as https://* may not match the original request.
X-Forwarded-Host
The X-Forwarded-Host header tells the application which domain was originally requested by the client.
Example:
X-Forwarded-Host: admin.example.com
This header is important when the proxy modifies the Host header while forwarding the request, replacing the public domain with an internal address.
Without it, the application may not be able to correctly identify the original domain or subdomain, affecting scopes such as:
$app->scope('*://admin.example.com/', function ($app, $params) {
// Administrative routes
});
Configuring Inphinit to recognize forwarded headers
Inphinit provides specific support for working with proxies and load balancers.
To enable this feature, define the following environment variable:
APP_FORWARDED=1
It can be configured in the .env file or directly in the server's environment variables.
Next, in the system/main.php file, configure which proxies are allowed to provide forwarded information.
For example, to trust a proxy with the IP address 10.0.0.5:
$forwarded->setKnownProxies([
'10.0.0.5'
]);
You can also trust an entire network using CIDR notation:
$forwarded->setKnownNetworks([
'10.0.0.0/24'
]);
By default, Inphinit uses the X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers.
If you need to explicitly adjust which information will be used, you can configure the flags:
use Inphinit\Experimental\Http\Forwarded;
$forwarded->setFlags(
Forwarded::IP |
Forwarded::PROTO |
Forwarded::HOST
);
In this example, we enable the reading of the forwarded original IP, protocol, and host.
If the provider uses different header names, they can also be configured through setHeaderName().
For example, some services use custom headers to identify the original protocol.
Configuring headers on the proxy
In addition to configuring Inphinit, the proxy itself must forward the headers correctly.
For example, in an Nginx server acting as a reverse proxy, a common configuration would be:
location / {
proxy_pass http://inphinit_backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
This example assumes that Nginx receives the client's original connection and that inphinit_backend represents the application server.
In architectures with multiple proxies, the original information must be preserved correctly. An intermediate proxy must not replace the public protocol with the protocol used only for internal communication.
It is also important to note that X-Forwarded-* headers can be forged by clients. Therefore, the application must only trust IP addresses or networks belonging to previously authorized proxies. Whenever possible, restrict direct access to the application server to prevent requests from bypassing the proxy.
With APP_FORWARDED enabled and trusted proxies properly configured, Inphinit can use the original forwarded information to determine values such as INPHINIT_BASE_URL and INPHINIT_PATH, which are essential for scopes to work correctly.
Conclusion
Inphinit's scope system provides a simple way to organize and isolate groups of routes using different URL criteria.
With it, we can separate features by protocol, domain, subdomain, or path, as well as combine these conditions to create more specific structures.
This flexibility is especially useful in larger applications, APIs, and multi-tenant systems, where different areas need to share the same application without mixing their routing contexts.
When using this feature in production environments with reverse proxies or load balancers, remember to correctly configure the X-Forwarded-Host and X-Forwarded-Proto headers, as well as specify which proxies are trusted. This ensures that the application can correctly identify the original URL and activate the expected scopes.
Top comments (0)