A REST API developed with Phalcon v6
The directory structure for this projects follows the recommendations of pds/skeleton
The folders contain:
bin: empty for now, we might use it later onconfig: .env configuration files for CI and exampledocs: documentation (TODO)public: entry point of the application whereindex.phplivesresources: stores database migrations and docker files for local developmentsrc: source code of the projectstorage: various storage data such as application logstests: tests
The application follows the ADR pattern where the application is split into an Action layer, the Domain layer and a Responder layer.
The folder (under src/) contains the handler that is responsible for receiving data from the Domain and injecting it to the Responder so that the response can be generated. It also collects all the Input supplied by the user and injects it into the Domain for further processing.
The Domain is organized in folders based on their use. The Components folder contains components that are essential to the operation of the application, while the Services folder contains classes that map to endpoints of the application
The application uses the Phalcon\Di\Di container with minimal components lazy loaded. Each non "core" component is also registered there (i.e. domain services, responder etc.) and all necessary dependencies are injected based on the service definitions.
Additionally there are two Providers that are also registered in the DI container for further functionality. The ErrorHandlerProvider which caters for the starting up/shut down of the application and error logging, and the very important RoutesProvider which handles registering all the routes that the application serves.
There are several enumerations present in the application. Those help with common values for tasks. For example the FlagsEnum holds the values for the co_users.usr_status_flag field. We could certainly introduce a lookup table in the database for "status" and hold the values there, joining it to the co_users table with a lookup table. However, this will introduce an extra join in our query which will inevitable reduce performance. Since the FlagsEnum can keep the various statuses, we keep everything in code instead of the database. Thorough tests for enumerations ensure that if a change is made in the future, tests will fail, so that database integrity can be kept.
The RoutesEnum holds the various routes of the application. Every route is represented by a specific element in the enumeration and the relevant prefix/suffix are calculated for each endpoint. Also, each endpoint is mapped to a particular service, registered in the DI container, so that the action handler can invoke it when the route is matched.
Finally, the RoutesEnum also holds the middleware array, which defines their execution and the "hook" they will execute in (before/after).
There are several middleware registered for this application and they are being executed one after another (order matters) before the action is executed. As a result, the application will stop executing if an error occurs, or if certain validations fail.
The middleware execution order is defined in the RoutesEnum. The available middleware is:
- NotFoundMiddleware.php
- HealthMiddleware.php
- ValidateTokenClaimsMiddleware.php
- ValidateTokenPresenceMiddleware.php
- ValidateTokenRevokedMiddleware.php
- ValidateTokenStructureMiddleware.php
- ValidateTokenUserMiddleware.php
NotFoundMiddleware
Checks if the route has been matched. If not, it will return a Resource Not Found payload
HealthMiddleware
Invoked when the /health endpoint is called and returns a OK payload
ValidateTokenPresenceMiddleware
Checks if a JWT token is present in the Authorization header. If not, an error is returned
ValidateTokenStructureMiddleware
Gets the JWT token and checks if it can be parsed. If not, an error is returned
ValidateTokenUserMiddleware
Gets the userId from the JWT token, along with other information, and tries to match it with a user in the database. If the user is not found, an error is returned
ValidateTokenClaimsMiddleware
Checks all the claims of the JWT token to ensure that it validates. For instance, this checks the token validity (expired, not before), the claims, etc. If a validation error happens, then an error is returned.
ValidateTokenRevokedMiddleware
Checks if the token has been revoked. If it has, an error is returned
The responder is responsible for constructing the response with the desired output, and emitting it back to the caller. For the moment we have only implemented a JSON response with a specified array as the payload to be sent back.
The responder receives the outcome of the Domain, by means of a Payload object. The object contains all the data necessary to inject in the response.
The application responds always with a specific JSON payload. The payload contains the following nodes:
data- contains any data that are returned back (can be empty)errors- contains any errors occurred (can be empty)meta- array of information regarding the payloadcode- the application code returnedhash- asha1hash of thedata,errorsand timestampmessage-successorerrortimestamp- the time in UTC format