Architecture
RestFn is built from a few small parts that each do one job. This page explains what they are and how a request flows through them.
The big picture
A request flows through these layers, top to bottom:
WebApp bootstraps everything and runs the request
Runner parses the request, runs middleware, returns the response
Parser validates and evaluates the operation tree
Ops the operations (get, map, run, ...) that do the work
Actions your code, called by the run/list operations
The dependency injection container creates and wires all of it together.
State that belongs to a single request lives in a Context that's passed along the
way.
The parts
Container
The container creates objects and injects their dependencies. Every part of RestFn is resolved from it, which is what makes everything swappable. You declare what you need in a constructor and the container fills it in. You give it the configuration and the interface-to-implementation bindings (aliases) when it's built. See Dependency Injection.
WebApp
WebApp is the entry point. WebApp::createDefault() takes your config, merges it
over the default bindings, builds the container, and resolves the application, which
it returns. You call run() on it. The application itself is small: it gets the
Runner injected and runs it. You can replace the whole application by binding
ApplicationInterface to your own class.
Runner
The Runner drives one request. It asks the request parser for the request, wraps
the handling in your middleware, validates and evaluates the request body, and
passes the result to the response. See Runner.
Parser and operations
The Parser is the evaluator. It takes an operation tree and walks it. Each node is
an operation: the first element of the array is the operation name, and the rest are
its parameters. The parser looks up the operation, validates it, and evaluates it.
Operations can contain other operations, so the parser is recursive. See
Operations.
There are two passes. Validate checks the whole tree before anything runs and returns errors if the request is malformed. Evaluate runs the tree and produces the result.
Context
The Context holds the state for a single request: the variables set by the var
operation, and the current nesting depth that guards against overly deep requests.
The parser itself is stateless, so the same parser and operations are reused for the
whole request. Nothing leaks between requests because each request gets its own
Context.
Actions
Actions are your code. An action implements ActionInterface (or
ListActionInterface for paginated lists) and is called by the run (or list)
operation by name. This is where you talk to your database, call other services, and
do the real work. See Actions.
Request and Response
The Runner reads the request through a RequestParserInterface. The default
JsonRequestParser reads the JSON body. The result is turned into output by a
ResponseInterface, and the default JsonResponse encodes it as JSON. Both are
swappable.
Authentication
Authentication is a set of services and a middleware. The middleware reads the bearer token, verifies it, resolves an identity, and stores it. Operations that run actions check whether an action needs authentication. See Authentication.
Request lifecycle
Putting it together, a single request goes like this:
- The web server calls your
index.php, which builds the app withWebApp::createDefault()and callsrun()on it. - The
Runnerreads the request body and turns it into aRequest. - Middleware run on the way in. By default error handling wraps everything, and authentication establishes the identity from a bearer token.
- The parser validates the operation tree. If it's invalid, the request is rejected.
- The parser evaluates the tree. Operations call your actions as needed.
- Middleware run on the way out.
- The result is turned into a response (JSON by default) and returned. If anything threw along the way, the error middleware turns it into a JSON error instead.
Everything is swappable
Each piece sits behind an interface and is bound to a default in
WebApp::DEFAULT_ALIASES. To replace one, bind its interface to your own class when
you call createDefault():
WebApp::createDefault([
'aliases' => [
ResponseInterface::class => App\XmlResponse::class,
],
'config' => ['global' => [/* ... */]],
])->run();
The same goes for the request parser, the evaluator, the response, the authenticator, the token parser, and the application itself.