Getting Started

RestFn runs your whole API through one endpoint. The client sends one JSON request, which is a tree of operations. The server validates it, runs it, and sends back the result. This page takes you from an empty project to a working endpoint.

Requirements

You need PHP 8.4+ with the json and mbstring extensions loaded.

Installation

Add RestFn to your project with Composer:

composer require arekx/restfn

Your first endpoint

Everything goes through a single PHP file. Point your web server at it and you have an API. It's one call to WebApp::createDefault():

<?php
// public/index.php

require __DIR__ . '/../vendor/autoload.php';

use ArekX\RestFn\App\WebApp;
use App\Actions\GetUserAction;

echo WebApp::createDefault([
    'config' => [
        'global' => [
            // Actions the run operation can call.
            'actions' => [
                'getUser' => GetUserAction::class,
            ],
        ],
    ],
])->run();

createDefault() builds the container and wires up the defaults: request parsing, JSON response, the evaluator, authentication services, error handling, and all built-in operations. It returns the application. You call run() on it, which reads the request body, runs it, and returns the JSON response. You echo that.

Writing an action

An action is your code. It's a class with a run method, and the run operation calls it by name. Actions are built by the container, so they can ask for whatever they need in the constructor. This one loads a user with a Doctrine DBAL connection:

use ArekX\RestFn\Parser\Contracts\ActionInterface;
use Doctrine\DBAL\Connection;

class GetUserAction implements ActionInterface
{
    public function __construct(
        protected Connection $db,
    ) {}

    public function run(mixed $data): array
    {
        // $data is whatever the client passed to `run`.
        return $this->db
            ->executeQuery('select id, email from users where id = ?', [$data])
            ->fetchAssociative() ?: [];
    }
}

The container injects that Connection. Since a DBAL connection is built a specific way, you hand the container a small factory for it:

use ArekX\RestFn\DI\Contracts\FactoryInterface;
use Doctrine\DBAL\DriverManager;

class ConnectionFactory implements FactoryInterface
{
    public function create(string $definition, array $args): mixed
    {
        return DriverManager::getConnection(['url' => getenv('DATABASE_URL')]);
    }
}

Register the action by name and point the container at the factory:

WebApp::createDefault([
    'factories' => [
        Connection::class => App\ConnectionFactory::class,
    ],
    'config' => [
        'global' => ['actions' => ['getUser' => App\Actions\GetUserAction::class]],
    ],
])->run();

That's the whole loop: register an action, ask for what it needs, and the container builds it. See Actions and Dependency Injection for more.

Sending a request

The client sends the operation tree as the JSON body of a request to your single endpoint. To get a user's email:

["get", "email", ["run", "getUser", 1]]

This reads inside out. run calls the getUser action with 1, and get pulls the email field out of the result. The response is:

"user@example.com"

All built-in operations are available by default, so there's nothing to register to start composing requests like this.

Where to go next