tereta / route
Requires
- php: >=8.4
- tereta/core: ^1.0
- tereta/db: ^1.0.0
- tereta/di: ^1.0
- tereta/utilities: ^1.0.3
Suggests
- tereta/application: Required to register Route as a module via Application's scanner (uses Module.php). Without it, Route works as a standalone routing library.
- tereta/cli: Required to use the route:site CLI command (Commands/Site.php). Without it, sites are managed programmatically via Services\Site.
README
π Π ΡΡΡΠΊΠΈΠΉ | English
Table of Contents
- Overview
- Dependencies
- Architecture
- Multi-Site Support
- Routing
- Declaring a Controller
- Generating URLs by Alias
- Route Model: Two Roles
- Request Lifecycle
- Helper Services
- CLI Commands
- Author and License
Overview
Routing module for the Tereta framework. It turns an HTTP request into a controller invocation: resolves the current domain and site, resolves the route, instantiates the controller, and returns a Response. Supports a multi-site architecture β a single project can serve multiple sites across different domains, with HTTPS, subpaths, and non-standard ports.
The main entry point is Services\Route::singleton()->getByUrl(): it reads the current URL and HTTP method from $_SERVER and returns a Models\Request with the resolved Domain, Site, and Route. For reverse resolution (generating URLs by alias), use Services\Url::alias().
Dependencies
tereta/coreβSingleton,Factory,Chaintraits, the baseValuemodel, base exceptionstereta/diβ resolves controllers and chain links through the DI containertereta/dbβ ORM baseCores\Model,ModelFactory,Connection::transaction()tereta/cli(suggest) β required for theroute:siteCLI commandtereta/application(suggest) β auto-registers the module and scans controller attributes
Architecture
| Layer | Role |
|---|---|
Module.php | Registers the site, route, domain tables via SetupContract |
Services/ | Business services: Route, Domain, Site, Url, Helper, Get, Post, Files |
Models/ | DTOs Request, Response and ORM models Route, Site, Domain |
Factories/ | Assemble resolution chains (Router, Router\Alias) and instantiate controllers (Controller, Controller\Attribute) |
Chains/ | Route resolution links: Route\Attribute, Route\Db, Route\Attribute\Alias |
Attributes/ | PHP attributes: #[Controller] for controllers, #[Route] for chain links |
Interfaces/ | Contracts: Controller, Chains\Route |
Commands/ | CLI: route:site (list / create / delete) |
Exceptions/ | NotFoundException, Core, Domain |
Services and factories use traits from tereta/core: Singleton for global access, Factory for parameterised DI construction.
Multi-Site Support
Siteβ a logical unit with anameandidentifier; not tied to a specific host.Domainβ a physical host bound to a site. Fields:domainβ host with optional port (e.g.example.com:8080)secureβ HTTPS flagpathβ subpath within the host (e.g./admin)site_idβ reference toSitegeneralβ flag marking the site's primary domain (used in reverse resolution)
A single Site can have multiple Domain records. Domain resolution (Services\Domain::getByUrl()) sorts matches by path length in descending order β specific paths (/admin) intercept requests before generic ones (/). Host extraction correctly handles non-standard ports.
Routing
Forward resolution (URL β controller) is performed in Services\Route::getByUrl() via a chain built by Factories\Router::createChain() and ordered by #[RouteChainAttribute(priority)]. The current configuration:
Chains\Route\Attribute(priority 100) β matches the URI against the regex from#[Controller(match: β¦)]and looks up the controller in memory viaFactories\Controller\Attribute::getClass(). Does not touch the database.Chains\Route\Dbβ if the previous link returnednull, looks up a record in theroutetable bypath = $uri.
If both links return null, an Exceptions\NotFoundException is thrown. The route table (schema: Resources/db/route.xml) is used when routes must be created dynamically (CMS, user-defined URLs) and cannot be declared as attributes at compile time.
Supported HTTP methods (constants on Services\Route): METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_DELETE, METHOD_PATCH, METHOD_OPTIONS, METHOD_HEAD, METHOD_ANY. An unknown method raises Exceptions\Core with code 405.
Declaring a Controller
π Π ΡΡΡΠΊΠΈΠΉ | English
A controller implements Interfaces\Controller and declares one or more routes via the #[Controller] attribute (which is marked IS_REPEATABLE):
use Tereta\Route\Attributes\Controller;
use Tereta\Route\Interfaces\Controller as ControllerContract;
use Tereta\Route\Models\Request as RequestModel;
use Tereta\Route\Models\Response as ResponseModel;
use Tereta\Core\Data\Value;
#[Controller(
alias: 'user.profile',
match: '^user/(\d+)/profile$',
uri: 'user/%s/profile',
method: 'GET',
)]
class UserProfileController implements ControllerContract
{
public function __construct(
private RequestModel $requestModel,
) {}
public function handle(): ResponseModel
{
return ResponseModel::factory()->create([
'handler' => 'view/user/profile',
'data' => Value::factory()->create([
'data' => ['url' => $this->requestModel->getUrl()],
]),
]);
}
}
Fields of the #[Controller] attribute:
| Field | Purpose |
|---|---|
match | Regular expression for forward resolution (URL β controller) |
alias | Name used for reverse resolution (alias β URL) |
uri | sprintf URL template with %s placeholders for reverse resolution |
method | HTTP method (GET, POST, β¦) or null for any |
RequestModel is injected into the controller's constructor automatically: Factories\Controller::create() passes it to the tereta/di container, which fills the matching parameter.
Generating URLs by Alias
use Tereta\Route\Services\Url;
use Tereta\Route\Services\Route as RouteService;
$urlService = Url::factory()->create(['siteModel' => $siteModel]);
// Base site URL: https://example.com
echo $urlService->get();
// URL with an extra URI: https://example.com/some/path
echo $urlService->get('some/path');
// URL by alias with parameters: https://example.com/user/42/profile
echo $urlService->alias('user.profile', [42], RouteService::METHOD_GET);
Url::alias() delegates to Services\Route::getByAlias(), which uses a separate chain from Factories\Router\Alias consisting only of Chains\Route\Attribute\Alias. The DB link does not participate in this chain β see below for the reason.
If the alias template contains sprintf placeholders but parameters are missing (or fewer than expected), Url::alias() throws an ArgumentCountError with the alias name and the template.
Route Model: Two Roles
The Tereta\Route\Models\Route class is annotated with #[Model(table: 'route')], which formally makes it an ORM entity for the route table. However, in the current implementation it is used in two distinct roles:
ORM entity β when resolving a route via
Chains\Route\Dband when working with theroutetable directly. In this role, thepathfield (the concrete request URI) is populated, matching the column of the same name in the schema (Resources/db/route.xml).In-memory DTO for reverse resolution (alias β URL) β
Chains\Route\Attribute\Aliascreates aRouteModelinstance bypassing the database and stores aurifield in it β asprintftemplate with%splaceholders, taken from the#[Controller(uri: β¦)]attribute. This field does not exist in the table schema and lives only in the memory of that specific instance.
Difference between path and uri
pathβ the concrete request URI, e.g.user/42/profile. Stored in the database, read byModels\Request::getUrl().uriβ a template with placeholders for URL generation, e.g.user/%s/profile. Used only byServices\Url::alias()to substitute parameters viasprintf. Not persisted to the database.
Why it works this way
Reverse resolution (Url::alias()) is by design intended to work only with routes declared via controller attributes β because only a controller carries a sprintf template. A DB route's path is already concrete, and there is conceptually nothing to generate "with parameters" from it. Therefore Chains\Route\Db does not participate in the alias chain, and the alias resolver and the DB resolver do not overlap.
Request Lifecycle
HTTP Request ($_SERVER, $_GET, $_POST, $_FILES)
β
βΌ
Services\Helper::getUrl() β current URL + scheme from $_SERVER
β
βΌ
Services\Route::getByUrl()
β
βΌ
Services\Domain::getByUrl() β resolves Domain β Site
β
βΌ
Factories\Router::createChain() β chain: Attribute β Db
β
βΌ
Models\Request β DTO: Domain + Site + Route + method
β
βΌ
Factories\Controller::create() β instantiation via tereta/di
β
βΌ
Interfaces\Controller::handle() β controller returns a Response
β
βΌ
Models\Response β handler, data, status, meta, headers
Models\Response extends Tereta\Core\Data\Value and stores the handler (template/view name), body (data), HTTP status (defaults to 200), meta, and headers. Delivering this object to the client is the responsibility of the calling layer (e.g. the Application module).
Helper Services
Singleton wrappers around PHP superglobals, extending Tereta\Core\Data\Value:
| Service | Source |
|---|---|
Services\Get | $_GET |
Services\Post | $_POST |
Services\Files | $_FILES |
Services\Helper | Builds the current URL from $_SERVER['HTTP_HOST'] / REQUEST_URI / HTTPS / HTTP_X_FORWARDED_PROTO |
use Tereta\Route\Services\Get;
use Tereta\Route\Services\Post;
use Tereta\Route\Services\Helper;
$page = Get::singleton()->get('page'); // GET parameter
$name = Post::singleton()->get('name'); // POST parameter
$url = Helper::singleton()->getUrl(); // Current URL
CLI Commands
Site management:
./cli.php route:site list # List sites
./cli.php route:site create <identifier> "<name>" "<url>" # Create a site
./cli.php route:site delete <identifier> # Delete a site
Example:
./cli.php route:site create default "My Site" "https://example.com"
create creates a Site and its primary Domain in a single transaction; delete removes the site along with its associated domains.
Author and License
Author: Tereta Alexander
Website: tereta.dev
License: Apache License 2.0. See LICENSE.
www.ββββββββββββββββββββββββ βββββββββββββββββ ββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββ
βββ ββββββ ββββββββββββββ βββ ββββββββ
βββ ββββββ ββββββββββββββ βββ ββββββββ
βββ βββββββββββ βββββββββββ βββ βββ βββ
βββ βββββββββββ βββββββββββ βββ βββ βββ
.dev
Copyright (c) 2024-2026 Tereta Alexander