Wiki

Clone wiki

actionhandler / Home

Web Action Handler Documentation

In this documentation you'll be able to learn how framework works in background and how to properly use it. Idea behind this framework is different approach to resolving control flow of an request, instead of using one PHP class as controller for many related actions e.g UserController we split each different action per class, and inside that class we implement wanted and required interfaces to make everything work.

Here we'll go through all important and helpful stuff. So please buckle up because we are about to go deep.

Note: Every part of framework is expecting interfaces as input parameters, so it has flexibility to completely change flow of specific part of it.

Note: This documentation might and will change.


Modules

Utilities


Modules

Application

Consider following list as well

Application is hearth of framework, its used to boot everything up, which means to prepare database connections, to register all routes and to handle request with corresponding route handler.


Application::__construct(string, IRouter, IRequest, IResponse)

To instantiate Application use ObjectFactory, only required parameter that script can not determine here is first parameter which is string, that string represents path to configuration file and its required to be provided to ObjectFactor.

Example:

<?php

use \RequestHandler\Modules\Application\IApplication;
use \RequestHandler\Utils\ObjectFactory\ObjectFactory;

$app = ObjectFactory::create(IApplication::class, "/path/to/config.json");

IApplication::boot(\Closure)

Used to load configuration, connect to database, and after success connection execute callback method that provides instance of \RequestHandler\Modules\Router\IRouter used to register routes.

Example:

<?php

use \RequestHandler\Modules\Application\IApplication;
use \RequestHandler\Modules\Router\IRouter;

$app = ObjectFactory::create(IApplication::class, "/path/to/config.json");

$app->boot(function (IRouter $router) {
    /*
        This will get triggered after configuration file is loaded and
        connection to database was established
    */
    $router
        ->get('/user/:user_id', \Controllers\User\GetInfo::class)
        ->patch('/user/:user_id', \Controllers\User\UpdateInfo::class);
});

IApplication::setAttribute(string: name, mixed: value): IApplication

Sets attribute that is shared through request entire flow (middlewares, filters, validators etc...) and can be accessed using getAttribute

Example:

<?php

$app->setAttribute('server-region', 'eu');

IApplication::getAttribute(string: name[, mixed: default = null]): mixed

Retrieve value that was set through setAttribute or return $default if attribute is not set.

Example:

<?php

$reqion = $app->getAttribute('server-region', 'us');

Application Request
Request Handler a.k.a IHandle

Interface \RequestHandler\Modules\Application\ApplicationRequest\IHandle is used to tell application that given class is able to handle single request, if class that is provided to router does not implement this interface exception ApplicationException is thrown. As framework is written using PHP 7.1+ syntax required return type of handle method is IResponse.

Example:

<?php

namespace Controllers\User;

use \RequestHandler\Modules\Application\ApplicationRequest\IHandle;

class GetInfo implements IHandle
{

    public function handle(IRequest $request, IResponse $response): IResponse
    {
        $user = new User();
        // Do some logic            
        // Return response
        return $response->data(['user' => $user]);
    }
}

Request Middleware a.k.a IMiddleware

Interface \RequestHandler\Modules\Application\ApplicationRequest\IMiddleware is used to tell application that given request handler also has some middlewares that needs to be executed, after or before request handle method is executed. If any of middlewares failes, request is finished with error message what failed and all custom response errors. Middleware method gives access to IMiddlewareContainer that is used to register and execute request middlewares from application. Request middlewares must implement RequestHandler\Modules\Middleware\IMiddlewareHandler in order for application to execute them.

Example:

<?php

// app/middlewares/Authenticate.php

use RequestHandler\Modules\Middleware\IMiddlewareContainer;
use RequestHandler\Modules\Middleware\IMiddlewareHandler;
use RequestHandler\Modules\Request\IRequest;
use RequestHandler\Modules\Response\IResponse;

class CustomMiddleware implements IMiddlewareHandler
{

    public function handle(IRequest $request, IResponse $response, IMiddlewareContainer $middleware): void
    {
        // Some Logic
        if ($allGood) {

            $middleware->next();
        }
        // Can put some logic here as well

    }
}

// app/controllers/user/GetInfo.php

namespace Controllers\User;

use \RequestHandler\Modules\Application\ApplicationRequest\IHandle;
use \RequestHandler\Modules\Application\ApplicationRequest\IMiddleware;

class GetInfo implements IHandle, IMiddleware
{

    public function handle(IRequest $request, IResponse $response): IResponse
    {
        $user = new User();
        // Do some logic            
        // Return response
        return $response->data(['user' => $user]);
    }

    public function middleware(IMiddlewareContainer $middleware): IMiddlewareContainer
    {

        return $middleware->add(new CustomMiddleware());
    }
}

Request Validator a.k.a IValidate

Interface \RequestHandler\Modules\Application\ApplicationRequest\IValidate is used to tell application that given request handler also contains request input validation, if validation failed response with errors will be returned and request handler method won't get executed

Example:

<?php

namespace Controllers\User;

use \RequestHandler\Modules\Application\ApplicationRequest\IHandle;
use \RequestHandler\Modules\Application\ApplicationRequest\IValidate;

class GetInfo implements IHandle, IValidate
{

    public function handle(IRequest $request, IResponse $response): IResponse
    {
        $user = new User();
        // Do some logic            
        // Return response
        return $response->data(['user' => $user]);
    }

    public function validate(IInputValidator $validator): IInputValidator
    {

        return $validator->validate([
            'field_name' => 'rule|another_rule:param,another_param'
        ]);
    }
}

Request Filter a.k.a IFilter

Interface \RequestHandler\Modules\Application\ApplicationRequest\IFilter is used to tell application that we want some of request input data filtered when fetching them. In following example we are telling script that when accessing user and age input data (query, form body etc) we want those values converted (filtered) into different types, we want user to be matched with user id in database and retrieved as user model, and age to be retrieved as integer.

Example:

<?php

namespace Controllers\User;

use \RequestHandler\Modules\Application\ApplicationRequest\IHandle;
use \RequestHandler\Modules\Application\ApplicationRequest\IFilter;

use \App\Models\User;

class GetInfo implements IHandle
{

    public function handle(IRequest $request, IResponse $response): IResponse
    {

        // We are sure this value is instance of "User" model
        $user = $request->get('user');

        // We are sure this value has type integer
        $age = $request->get('age');

        return $response->data(['user' => $user]);
    }

    public function filter(IRequestFilter $filter): IRequestFilter
    {

        return $filter
                ->add('user', new ModelFilter(User::class, 'id'))
                ->add('age', new IntFilter());
    }
}

Database

Database module is used to hold active connection to database, as its meant to be used only with MySQL driver It also provide easy way to prepare, bind and execute mysql queries, it has specific methods for selecting, inserting/updating and deleting queries. Each of those methods uses same logic to execute query only difference is what they return.

Database::__construct(string, string, string, string[, int = 3306])

You probably won't be needing to create new instance of database manually, but in case that you need following parameters are required. host, database, username, password and there is port which is optional.

Note: Application handles database preparation using data from config before booting anything else.

Example:

<?php

use \RequestHandler\Modules\Database\IDatabase;
use \RequestHandler\Utils\ObjectFactory\ObjectFactory;

$db = ObjectFactory::create(IDatabase::class, 'localhost', 'test_db', 'user', 'pass');

IDatabase::fetch(string, array): array

Fetch method is used to fetch single record from database and retrieve it as array. First parameter is string and it represents mysql query that needs to be executed, second parameter is array of bindings, binding are mapped one-on-one with query placeholders "?".

Example:

<?php

use \RequestHandler\Modules\Database\IDatabase;
use \RequestHandler\Utils\ObjectFactory\ObjectFactory;

// nothing provided to database constructor as we assume connection was established via framework
$db = ObjectFactory::create(IDatabase::class);

// $results would be similar to ['id' => ... 'name' => ..., 'age' => ...]
$results = $db->fetch('SELECT * FROM `users` WHERE `users`.`first_name` = ? OR `users`.`last_name` = ?;', ['Name', 'Surname']);

IDatabase::fetchAll(string, array): array

This is same as fetch, only difference is that expected result is array of arrays, which mean more records from database, e.g if query returns only one record, return value of method will be list array with single assoc array instead of only assoc array.

Example:

<?php

use \RequestHandler\Modules\Database\IDatabase;
use \RequestHandler\Utils\ObjectFactory\ObjectFactory;

// nothing provided to database constructor as we assume connection was established via framework
$db = ObjectFactory::create(IDatabase::class);

// $results would be similar to [['id' => ... 'name' => ..., 'age' => ...], ['id' => ... 'name' => ..., 'age' => ...], ...]
$results = $db->fetch('SELECT * FROM `users` WHERE `users`.`age` >= ?;', [23]);

IDatabase::store(string, array): int

This method is used for saving queries (insert/update), after successful execution if primary value is available then lastInsertId is returned, if there is no primary value rowCount (number of affected rows) is returned.

Example:

<?php

use \RequestHandler\Modules\Database\IDatabase;
use \RequestHandler\Utils\ObjectFactory\ObjectFactory;

// nothing provided to database constructor as we assume connection was established via framework
$db = ObjectFactory::create(IDatabase::class);

// Returned id is e.g 3
$id = $db->store('INSERT INTO `users` SET `users`.`name` = ?, `users`.`age` = ?;', ['John', 25]);

// As we don't have primary value, number of affected rows which is 1 is returned
$affectedRows = $db->store('INSERT INTO `logs` SET `logs`.`text` = ?;', ['User created']);

IDatabase::delete(string, array): int

Works almost same as store only difference is that return value is always number of affected rows (rowCount).

Example:

<?php

use \RequestHandler\Modules\Database\IDatabase;
use \RequestHandler\Utils\ObjectFactory\ObjectFactory;

// nothing provided to database constructor as we assume connection was established via framework
$db = ObjectFactory::create(IDatabase::class);

// $deletedRecords holds number of removed items from database
$deletedRecords = $db->delete('DELETE FROM `items` WHERE `item`.`type` = ?;', ['junk']);

Middlewares


Middleware Container

Middleware Container holds all required middlewares for current user request, and provide a way of executing next middleware and checking did all middlewares finished.


MiddlewareContainer::__construct(IRequest, IResponse)

As this is not actual implementation of middleware but rather holder of all required for current request, it depends on two objects on IRequest and IResponse, IRequest is used so we can access request parameters (non-filtered nor validated) in middleware itself, and IResponse is used if we don't want to fail middleware but simply to tell framework that response holds some errors e.g $response->errors(['logger' => 'Failed to log request']);


IMiddlewareContainer::add(IMiddlewareHandler): IMiddlewareContainer

Use this method to add middleware that needs to be executed before request handler is called.

Note:

Refer to \RequestHandler\Modules\Application\ApplicationRequest\IMiddleware to see it in action.


IMiddlewareContainer::next(): void

This method will execute next middleware in queue.

Note:

Refer to IMiddlewareHandler


IMiddleware::finished(): bool

Check did all middlewares in current IMiddlewareContainer were executed.

Note: Used by Application to determine should execution of request handler be proceeded or not.


Middleware Handler

\RequestHandler\Modules\Middleware\IMiddlewareHandler interface is interface that needs to be implemented in some class in order to make it behave as middleware and be accepted by IMiddlewareContainer

Example:

<?php

use RequestHandler\Modules\Middleware\IMiddlewareContainer;
use RequestHandler\Modules\Middleware\IMiddlewareHandler;
use RequestHandler\Modules\Request\IRequest;
use RequestHandler\Modules\Response\IResponse;

class CustomMiddleware implements IMiddlewareHandler
{

    public function handle(IRequest $request, IResponse $response, IMiddlewareContainer $middleware): void
    {

        // Do some logic here

        if ($everythingIsOk) {

            $middleware->next();
        }

        // Can put some logic here as well

    }
}

Exceptions

Instead of relying on built-in exceptions, this framework uses custom exceptions for each module and utility lib. To allow easier and more readable exceptions we use BaseException as our abstract exception.

Note:

Each framework exception has unique code, that is not required its just for easier debugging


BaseException

To achieve easier and readable exceptions handling, with combining both static and custom messages we use (extend) \RequestHandler\Modules\Exception\BaseException class, it provide us with easy defining error messages for error codes, all it need its simple map.

Example:

<?php

// app/exceptions/CustomException.php

use \RequestHandler\Modules\Exception\BaseException;

class ModelException extends BaseException
{

    const ERROR_INVALID_FIELD = 40001;
    const ERROR_MISSING_PRIMARY = 40002;

    protected $_errors = [
        ModelException::ERROR_INVALID_FIELD => 'Field is not valid',
        ModelException::ERROR_MISSING_PRIMARY => 'Primary key is required'
    ];

}

// app/modules/CustomModel.php

use \RequestHandler\Modules\Model\IModel;

class CustomModel implements IModel
{

    private $_fields = ['created_at', 'last_update'];

    public function validateField(string $fieldName): void
    {

        if (false === in_array($fieldName, $this->_fields)) {

            // Imagine this method was called with parameter $fieldName equals to 'name' that is not defined in field list
            // Error message of this exception would be something like
            // Field is not valid: name
            // It will also contain name of exception as well as error code
            throw new ModelException(ModelException::ERROR_INVALID_FIELD, $fieldName);
        }

    }
}

Request


Request::__construct()

Initialize request object.


Request::method(): string

Retrieve type of request method (e.g GET, POST)

<?php

use RequestHandler\Utils\ObjectFactory\ObjectFactory;
use RequestHandler\Modules\Request\IRequest;

/** @var IRequest $request */
$request = ObjectFactory::create(IRequest::class);

var_dump($request->method()); // e.g string(3) "GET"

Request::query(string $key, $default = null, ?IDataFilter $dataFilter = null): mixed

Retrieves query (GET) parameter matching given key.

<?php

use RequestHandler\Utils\ObjectFactory\ObjectFactory;
use RequestHandler\Modules\Request\IRequest;
use RequestHandler\Utils\DataFilter\Filters\IntFilter;

/** @var IRequest $request */
$request = ObjectFactory::create(IRequest::class);

// url ...?firstName=John&lastName=Doe&phone=0641234567
var_dump($request->get('firstName')); // e.g string(4) "John"
var_dump($request->get('lastName')); // e.g string(4) "Doe"
var_dump($request->get('middlename', 'none')); // e.g string(4) "none"
var_dump($request->get('phone', null, ObjectFactory::create(IntFilter::class)); // e.g int(641234567)

Note: If requested key is not defined value of "$default" will be filtered and returned If "$dataFilter" is present, retuned value of this method will be result value of data filter (transformer)


Request::parameter(string $key, $default = null, ?IDataFilter $dataFilter = null)

Retrieve specific parameter value from url

<?php

use RequestHandler\Utils\ObjectFactory\ObjectFactory;
use RequestHandler\Modules\Request\IRequest;
use RequestHandler\Utils\DataFilter\Filters\IntFilter;

$request = ObjectFactory::create(IRequest::class);

/**
 * route: /user/:id/profile
 * actual: /user/2/profile
 */
var_dump($request->parameter('id')); // e.g string(1) "2"
var_dump($request->parameter('id', null, ObjectFactory::create(IntFilter::class))); // e.g int(2)

Note: If requested key is not defined value of "$default" will be filtered and returned If "$dataFilter" is present, retuned value of this method will be result value of data filter (transformer)


Request::data(string $key, $default = null, ?IDataFilter $dataFilter = null)

Retrieves body data (POST) parameter matching given key

<?php

use RequestHandler\Utils\ObjectFactory\ObjectFactory;
use RequestHandler\Modules\Request\IRequest;
use RequestHandler\Utils\DataFilter\Filters\IntFilter;

$request = ObjectFactory::create(IRequest::class);

/**
 * Form Data:
 *  - name=Test
 *  - gender=M
 */
var_dump($request->data('gender')); // e.g string(1) "M"
var_dump($request->data('test', 'none'); // e.g string(4) "none"

Note: If requested key is not defined value of "$default" will be filtered and returned If "$dataFilter" is present, retuned value of this method will be result value of data filter (transformer)


Request::get(string $key, $default = null, ?IDataFilter $dataFilter = null)

Retrieves parameter from either body, query or parameter. It follows following order: * 1. POST * 2. If not in POST look GET * 3. If not in none of above look in URL Parameters

<?php

use RequestHandler\Utils\ObjectFactory\ObjectFactory;
use RequestHandler\Modules\Request\IRequest;
use RequestHandler\Utils\DataFilter\Filters\BoolFilter;

$request = ObjectFactory::create(IRequest::class);

/**
 * route: /user/:action
 * actual: /user/create?ignoreError=false
 * Form Data:
 *  - name=John
 */
var_dump($request->get('name')); // e.g string(4) "John"
var_dump($request->get('action')); // e.g string(6) "create"
var_dump($request->get('ignoreError')); // e.g string(5) "false"
var_dump($request->get('invalid', 'none')); // e.g string(4) "none"
var_dump($request->get('ignoreError', null, ObjectFactory::create(BoolFilter::class)); // e.g bool(false)

Note: If requested key is not defined value of "$default" will be filtered and returned If "$dataFilter" is present, retuned value of this method will be result value of data filter (transformer)


Request::getData(): array

Retrieve all body (form) data from request

<?php

use RequestHandler\Utils\ObjectFactory\ObjectFactory;
use RequestHandler\Modules\Request\IRequest;

$request = ObjectFactory::create(IRequest::class);

/**
 * Form Data:
 *  - id=1
 *  - title=Test Title
 */
var_dump($request->getData()); // e.g array(2) ['id' => 1, 'title' => 'Test Title']

Request::getParameters(): array

Retrieve all url paraneters from request

<?php

use RequestHandler\Utils\ObjectFactory\ObjectFactory;
use RequestHandler\Modules\Request\IRequest;

$request = ObjectFactory::create(IRequest::class);

/**
 * route: /user/:action/:subAction
 * actual: /user/create/social
 */
var_dump($request->getParameters()); // e.g array(2) ['action' => 'create', 'subAction' => 'social']

Request::getQuery(): array

Retrieve all query parameter from request

<?php

use RequestHandler\Utils\ObjectFactory\ObjectFactory;
use RequestHandler\Modules\Request\IRequest;

$request = ObjectFactory::create(IRequest::class);

/**
 * /user/create?ignoreErrors=true&useFacebook=false
 */
var_dump($request->getQuery()); // e.g array(2) ['ignoreErrors' => true, 'useFacebook' => false]

Request::getAll(): array

Get all key => value pairs from any input POST, GET, URL Parameter In case that there are multiple keys with same name on different arrays (e.g "id" in both URL and Form Body) Following system of prioritizing is applied 1. POST 2. If not in POST look GET 3. If not in none of above look in URL Parameters

<?php

use RequestHandler\Utils\ObjectFactory\ObjectFactory;
use RequestHandler\Modules\Request\IRequest;

$request = ObjectFactory::create(IRequest::class);

/**
 * /user/create/social?ignoreErrors=true&useFacebook=false
 * Form Data:
 * - name=Test
 */
var_dump($request->getAll()); // e.g array(2) ['action' => 'create', 'subAction' => 'social', 'ignoreErrors' => true, 'useFacebook' => false, 'name' => 'test']

Request::setFilter(IRequestFilter $filter): IRequest

Set request input filter used to transform values to desire type/value

<?php

use RequestHandler\Utils\ObjectFactory\ObjectFactory;
use RequestHandler\Modules\Request\IRequest;
use RequestHandler\Utils\DataFilter\Filters\IntFilter;
use RequestHandler\Modules\Request\RequestFilter\IRequestFilter;

$requestFilter = ObjectFactory::create(IRequestFilter::class);

$request = ObjectFactory::create(IRequest::class);
$request->setFilter($requestFilter);

/**
 * route: /user/:id/profile
 * actual: /user/1/profile
 */
var_dump($request->get('id')); // e.g string(1) "1"

$requestFilter->add('id', ObjectFactory::create(IntFilter::class));

var_dump($request->get('id')); // e.g int(1)

Updated