Rxn (the abbreviation for 'reaction') is a framework designed to cut out the complexity and clutter of PHP-generated views -- offloading views to whatever frontend that suits your fancy.
The philosophy behind Rxn is simple: strict backend / frontend decoupling.
Including planned features for beta (unchecked):
Rxn is released under the permissive and free MIT license.
Rxn uses a namespacing structure that explicitly matches the directory structure of the class files. While also convenient, it is mainly used to implement some pretty cool autoloading features.
Say, for example, that you created a class named OrganizationProductModelMyAwesomeModel. Just put the file in a directory structure that follows the namespace convention (e.g., {root}/organization/product/model/MyAwesomeModel.php). When you need to call the class, just invoke the class by calling it directly. There's no need to put a require anywhere.
BEFORE (not using autoloading):
<?php
require_once('/organization/product/model/MyAwesomeModel.php');
use OrganizationProductModelMyAwesomeModel;
$object = new MyAwesomeModel()
// object gets created!AFTER (using autoloading):
<?php
use OrganizationProductModelMyAwesomeModel;
$object = new MyAwesomeModel()
// object gets created!The same pattern exists for Rxn's native classes. For example, the response class (RxnFrameworkHttpResponse) is found in the {root}/rxn/api/controller directory. Autoloading is one of the many ways in which Rxn reduces overhead.
The following file extensions are supported by the autoloading feature (you may also define custom extensions in RxnFrameworkConfig):
Rxn lives, breathes, and eats exceptions. Consider the following code snippet:
try {
$result = $databse->query($sql,$bindings);
} catch (PDOException $exception) {
throw new Exception("Something went terribly wrong!",422);
}If you throw an Exception anywhere in the application, Rxn will self-terminate, roll back any in-process database transactions, and then gracefully respond using JSON:
{
"_rxn": {
"success": false,
"code": 422,
"result": "Unprocessable Entity",
"message": "Something went terribly wrong!",
//...
}
}An example API endpoint for your backend with Rxn might look like this:
https://yourapp.tld/v2.1/order/doSomething
Where:
v2.1 is the endpoint versionorder is the controllerdoSomething is the controller's action (a public method)Now if you wanted to add a GET key-value pair to the request where id=1234, in PHP you would normally do this:
BEFORE:
https://yourapp.tld/v2.1/order/someAction?id=1234
In Rxn, you can simplify this by putting the key and value in the URL using the forward slash (/) as the separator, like so:
AFTER:
https://yourapp.tld/v2.1/order/someAction/id/1234
An odd number of parameters after the version, controller, and action would result in an error.
By versioning your endpoint URLs (e.g., v1.1, v2.4, etc), you can rest easy knowing that you're not going to accidentally break your frontend whenever you alter backend endpoint behavior. Additionally, versioning also helps keep your documentation in order; frontend developers can just build to the documentation and everything will just work.
So for an endpoint with version v2.1, the first number (2) is the controller version, and the second number (1) is the action version. The example below is how we would declare controller version 2 with action version 1:
namespace OrganizationProductControllerv2;
class Order extends RxnFrameworkHttpController
{
public function doSomething_v1() {
//...
}
}This allows for maintainable, true-to-reality documentation that both frontend and backend developers can get behind.
Want to experiment and explore with your newfangled backend architecture? No problem, as long as you have a database schema, you have a suite of scaffolding APIs to toy with! Scaffolding endpoints are accessed using URIs that are similar to the following (note the api instead of the version number):
https://yourapp.tld/api/order/create
https://yourapp.tld/api/order/read/id/{id}
https://yourapp.tld/api/order/update/id/{id}
https://yourapp.tld/api/order/delete/id/{id}
https://yourapp.tld/api/order/search
Scaffolding APIs are version-less APIs, and are designed to allow frontend developers full access to the backend in the form of Create, Read, Update, and Delete (CRUD) operations and searches. Their main benefit is that you don't have to spend a ton of time manually crafting CRUD endpoints during the early phases of application development. (As it is these early phases of development when requirements are changing, and things are constantly in flux.)
Warning: Because Scaffolding APIs are version-less, they inheret all the problems associated with version-less APIs. As soon as the backend is altered, these APIs are altered as well; this can potentially break an application in unexpected or hidden ways. For this reason, it is wise to transition versionless APIs to versioned APIs as the development process nears completion.
While most people practice some form of dependency injection without even thinking about it, the fact is, manually instantiating and injecting classes with a lot of dependencies can be a pretty big hassle. The following examples should help demonstrate the benefit of automatic dependency injection via the container container.
BEFORE (manual DI):
// instantiate the dependencies
$config = new Config();
$database = new Database($config);
$registry = new Registry($config,$database);
$filecache = new Filecache($config);
$map = new Map($registry,$database,$filecache);
// call the action method
$this->doSomething_v1($registry,$database,$map);
public function doSomething_v1(Registry $registry, Database $database, Map $map) {
$customer = new Customer($registry,$database,$map);
//...
}AFTER (using the DI container container):
// call the action method
$this->doSomething_v1($app->container);
public function doSomething_v1(Container $container) {
$customer = $container->get(Customer::class);
//...
}Hopefully you can see the benefits. With Rxn, there's no need to instantiate the prerequisites every time! Use the container container to make your life easier.
Just typehint the class you need as a parameter, and poof, the DI container container will guess all of the dependencies for you and automatically load and inject them. No messy requires. You don't have to inject the dependencies manually!
BEFORE (manual instantiation):
// require the dependencies
require_once('/path/to/Config.php');
require_once('/path/to/Collector.php');
require_once('/path/to/Request.php');
public function doSomething_v1() {
// instantiate the dependencies
$config = new Config();
$collector = new Collector($config);
$request = new Request($collector,$config);
// grab the id from the request
$id = $request->collectFromGet('id');
//...
}AFTER (automatic instantiation and injection):
public function doSomething_v1(Request $request) {
// grab the id from the request
$id = $request->collectFromGet('id');
//...
}See the difference?