Statistics
17077
14
11
13d
Badges
Tags
api
Dependencies
Keywords
nette api

Nette-Api

Nette simple api library

Build Status Dependency Status Scrutinizer Code Quality Code Coverage Latest Stable Version

SensioLabsInsight

Why Nette-Api

This library provides out-of-the box API solution for Nette framework. You can register API endpoints and connect it to specified handlers. You need only implement you custom business logic. Library provide authorisation, validation and formatting services for you api.

Installation

This library requires PHP 5.4 or later. It works also on PHP 7.0.

Recommended installation method is via Composer:

$ composer require tomaj/nette-api

Library is compliant with PSR-1, PSR-2, PSR-3 and PSR-4.

How Nette-API works

First you have register library presenter for routing. In config.neon just add this line:

application:
  mapping:
    Api: Tomaj\NetteApi\Presenters\*Presenter

And add route to you RouterFactory:

$router[] = new Route('/api/v<version>/<package>[/<apiAction>][/<params>]', 'Api:Api:default');

After that you need only register your api handlers to apiDecider ApiDecider and register ApiLink and Tomaj\NetteApi\Misc\IpDetector. This can be done also with config.neon:

services:
  - Tomaj\NetteApi\Link\ApiLink
  - Tomaj\NetteApi\Misc\IpDetector
  apiDecider:
    class: Tomaj\NetteApi\ApiDecider
      setup:
        - addApiHandler(\Tomaj\NetteApi\EndpointIdentifier('GET', 1, 'users'), \App\MyApi\v1\Handlers\UsersListingHandler(), \Tomaj\NetteApi\Authorization\NoAuthorization())
        - addApiHandler(\Tomaj\NetteApi\EndpointIdentifier('POST', 1, 'users', 'send-email'), \App\MyApi\v1\Handlers\SendEmailHandler(), \Tomaj\NetteApi\Authorization\BearerTokenAuthorization())

As you can see in example, you can register as many endpoints as you want with different configurations. Nette-Api support api versioning from the beginning. This example will prepare this api calls:

  1. http://yourapp/api/v1/users - available via GET
  2. http://yourapp/api/v1/users/send-email - available via POST

Core of the Nette-Api is handlers. For this example you need implement 2 classes:

  1. App\MyApi\v1\Handlers\UsersListingHandler
  2. App\MyApi\v1\Handlers\SendEmailHandler

This handlers implements interface ApiHandlerInterface but for easier usage you can extends your handler from BaseHandler. When someone reach your api this handlers will be triggered and handle() method will be called.

namespace App\MyApi\v1\Handlers;

use Tomaj\NetteApi\Handlers\BaseHandler;
use Tomaj\NetteApi\Response\JsonApiResponse;

class UsersListingHandler extends Basehandler
{
    private $userRepository;

    public function __construct(UsersRepository $userRepository)
    {
        parent::__construct();
        $this->userRepository = $userRepository;
    }

    public function handle($params)
    {
        $users = [];
        foreach ($this->userRepository->all() as $user) {
            $users[] = $user->toArray();
        }
        return new JsonApiResponse(200, ['status' => 'ok', 'users' => $users]);
    }
}

This simple handler is using UsersRepository that was created by Nette Container (so you have to register your App\MyApi\v1\Handlers\UsersListingHandler in config.neon).

Advanced use (with Fractal)

Nette-Api provides integration with Fractal library for formatting API responses. If you want to use it, you have to extend your handler from BaseHandler and your Fractal instance will be accessible be $this->getFractal().

Main advantage from Fractal is separation your api "view" (like transformation data to json object or xml or anything...). Also you can include transformations in other transformations to include other objects to others.

Example with fractal:

  1. You will need Formater
namespace App\MyApi\v1\Transformers;

use League\Fractal\TransformerAbstract;

class UserTransformer extends TransformerAbstract
{
    public function transform($user)
    {
        return [
            'id' => $user->id,
            'email' => $user->email,
            'name' => $user->name,
        ];
    }
}
  1. And this will be your handler:
namespace App\MyApi\v1\Handlers;

use Tomaj\NetteApi\Handlers\BaseHandler;
use Tomaj\NetteApi\Response\JsonApiResponse;

class UsersListingHandler extends Basehandler
{
    private $userTransformer;

    public function __construct(UserTransformer $userTransformer)
    {
        parent::__construct();
        $this->userTransformer = $userTransformer;
    }

    public function handle($params)
    {
        $users = $this->useRepository->all(); 

        $resource = new Collection($users, $this->userTransformer);
        $result = $this->getFractal()->createData($resource)->toArray();

        return new JsonApiResponse(200, $result);
    }
}

I have to recommend to take a look at Fractal library. There are much more information about transformers, serialisers, paginations etc. It is really nice library.

Endpoint inputs

Each handler can describe which input is required. It could be GET or POST parameters, also COOKIES, raw post json or file uploads. You have to implement method params() where you have to return array with params. This params are used in api console to generate right form.

Example with user detail:

namespace App\MyApi\v1\Handlers;

use Tomaj\NetteApi\Handlers\BaseHandler;
use Tomaj\NetteApi\Response\JsonApiResponse;
use Tomaj\NetteApi\Params\InputParam;

class UsersDetailHandler extends Basehandler
{
    private $userRepository;

    public function __construct(UsersRepository $userRepository)
    {
        parent::__construct();
        $this->userRepository = $userRepository;
    }

    public function params()
    {
        return [
            new InputParam(InputParam::TYPE_GET, 'id', InputParam::REQUIRED),
        ];
    }

    public function handle($params)
    {
        $user = $this->userRepository->find($params['id']);
        if (!$user) {
            return new JsonApiResponse(404, ['status' => 'error', 'message' => 'User not found']);
        }
        return new JsonApiResponse(200, ['status' => 'ok', 'user' => [
            'id' => $user->id,
            'email' => $user->email,
            'name' => $user->name,
        ]);
    }
}

Input Types

Nette-Api provides various InputParam types. You can send params with GET, POST, COOKIES, FILES or RAW POST data. All input types are available via test console.

This is table with support input types:

Input type Example
POST new InputParam(InputParam::TYPE_POST, 'key')
GET new InputParam(InputParam::TYPE_GET, 'key')
FILE new InputParam(InputParam::TYPE_FILE, 'key')
COOKIE new InputParam(InputParam::TYPE_COOKIE, 'key')
RAW POST new InputParam(InputParam::TYPE_POST_RAW, 'key')

Security

Protecting your api is easy with Nette-Api. You have to implement your Authorization (Tomaj\NetteApi\Authorization\ApiAuthorizationInterface) and add it as third argument to addApiHandler() method in config.neon.

For simple use, if you want to use Bearer token authorisation with few tokens, you can use StaticBearerTokenRepository (Tomaj\NetteApi\Misc\StaticBearerTokenRepository).

services:
    staticBearer: Tomaj\NetteApi\Misc\StaticBearerTokenRepository(['dasfoihwet90hidsg': '*', 'asfoihweiohgwegi': '127.0.0.1'])

    apiDecider:
        class: Tomaj\NetteApi\ApiDecider
        setup:
            - addApiHandler(\Tomaj\NetteApi\EndpointIdentifier('GET', 1, 'users'), \App\MyApi\v1\Handlers\UsersListingHandler(), \Tomaj\NetteApi\Authorization\BearerTokenAuthorization(@staticBearer))

With this registration you will have api /api/v1/users that will be accessible from anywhere with Authorisation HTTP header Bearer dasfoihwet90hidsg or from 127.0.0.1 with Bearer asfoihweiohgwegi. In Nette-Api if you would like to specify IP restrictions for tokens you can use this patterns:

IP Pattern Access
* accessible from anywhere
127.0.0.1 accessible from single IP
127.0.0.1,127.0.02 accessible from multiple IP, separator could be new line or space
127.0.0.1/32 accessible from ip range
false token is disabled, cannot access

But it is very easy to implement your own Authorisation for API.

Javascript ajax calls (CORS - preflight OPTIONS calls)

If you need to call API via javascript ajax from other domains, you will need to prepare API for preflight calls with OPTIONS method. Nette-api is ready for this situation and you can choose if you want to enable pre-flight calls globally or you can register prepared prefligt handlers.

Globally enabled - every api endpoint will be available for preflight OPTIONS call:

services:
    apiDecider:
        class: Tomaj\NetteApi\ApiDecider
        setup:
            - enableGlobalPreflight()
            - addApiHandler(\Tomaj\NetteApi\EndpointIdentifier('GET', 1, 'users'), \App\MyApi\v1\Handlers\UsersListingHandler(), Tomaj\NetteApi\Authorization\NoAuthorization())

Or you can register custom OPTIONS endpoints:

services:
    apiDecider:
        class: Tomaj\NetteApi\ApiDecider
        setup:
            - addApiHandler(\Tomaj\NetteApi\EndpointIdentifier('OPTIONS', 1, 'users'), \Tomaj\NetteApi\Handlers\CorsPreflightHandler(), Tomaj\NetteApi\Authorization\NoAuthorization())
            - addApiHandler(\Tomaj\NetteApi\EndpointIdentifier('GET', 1, 'users'), \App\MyApi\v1\Handlers\UsersListingHandler(), Tomaj\NetteApi\Authorization\NoAuthorization())
            

Logging

It is good practice to log you api access if you provide valuable information with your API. To enable logging you need to implement class with interface ApiLoggerInterface (Tomaj\NetteApi\Logger\ApiLoggerInterface) and register it as service in config.neon. It will be automatically wired and called after execution of all api requests.

CORS Security

If you need to iteract with your API with Javascript you will need to send correct CORS headers. ApiPresenter has property to set this headers. By default api will send header 'Access-Control-Allow-Origin' with value ''*. If you need to change it you can set property $corsHeader to values:

  1. 'auto' - send back header Access-Control-Allow-Origin with domain that made request. It is not secure, but you can acces this api from other domains via AJAX
  2. ''* - send header with '*' - this will work fine if you dont need to send cookies via ajax calls to api with jquery $.ajax with xhrFields: { withCredentials: true } settings
  3. 'off' - will not send any CORS header
  4. other - any other value will be send in Access-Control-Allow-Origin header

You can set this property in config.neon if you register ApiPresenter:

services:
  -
    class: Tomaj\NetteApi\Presenters\ApiPresenter
    setup:
      - setCorsHeader('auto')

or if you extend ApiPresenter, than you can set it on your own presenter.

WEB console - API tester

Nette-Api contains 2 UI controls that can be used to validate you api. It will generate listing with all API calls and also auto generate form with all api params.

All components generate bootstrap html and can be styled with bootstrap css:

You have to create components in your controller:

use Nette\Application\UI\Presenter;
use Tomaj\NetteApi\ApiDecider;
use Tomaj\NetteApi\Component\ApiConsoleControl;
use Tomaj\NetteApi\Component\ApiListingControl;

class MyPresenter extends Presenter
{
    private $apiDecider;

    public function __construct(ApiDecider $apiDecider)
    {
        parent::__construct();
        $this->apiDecider = $apiDecider;
    }

    public function renderShow($method, $version, $package, $apiAction)
    {
    }

    protected function createComponentApiListing()
    {
        $apiListing = new ApiListingControl($this, 'apiListingControl', $this->apiDecider);
        $apiListing->onClick(function ($method, $version, $package, $apiAction) {
            $this->redirect('show', $method, $version, $package, $apiAction);
        });
        return $apiListing;
    }

    protected function createComponentApiConsole()
    {
        $api = $this->apiDecider->getApiHandler($this->params['method'], $this->params['version'], $this->params['package'], isset($this->params['apiAction']) ? $this->params['apiAction'] : null);
        $apiConsole = new ApiConsoleControl($this->getHttpRequest(), $api['endpoint'], $api['handler'], $api['authorization']);
        return $apiConsole;
    }
}

Troubleshooting

If your apache server is runing on CGI or fastCGI script, $_SERVER['HTTP_AUTHORIZATION'] is empty. You'll need to do some mod_rewrite wizardry to get your headers past the CGI barrier, like so:

RewriteEngine on
RewriteRule .? - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]`

Change log

Please see CHANGELOG for more information what has changed recently.

Testing

$ composer test

Contributing

Please see CONTRIBUTING and CONDUCT for details.

Security

If you discover any security related issues, please email tomasmajer@gmail.com instead of using the issue tracker.

License

The MIT License (MIT). Please see License File for more information

  • 1.10.0 Added X-Requested-With to Cors handler

    • Added X-Requested-With to Cors handler
  • 1.9.1 Added possibility to send empty fields via console request

    • Added possibility to send empty fields via console request
  • 1.11.0 Custom OPTIONS handler

    • ability to set custom handlers for OPTIONS request
  • 1.8.1 1.8.1

    • Updated league/fractal library to 0.14.0
  • 1.9.0 1.9.0

    • PUT type for InputType
  • 1.8.0 1.8.0

    • Added cors headers - Access-Control-Allow-Headers and Access-Control-Allow-Methods
  • 1.7.0 1.7.0

    • Added cors preflight headers for javascript requests
  • 1.6.2 1.6.2

    • InputParam validation of multi params, if available values are defined
  • 1.6.1 1.6.1

    • Fixed Nette 2.4 compatibility
    • End of support for php < 5.6
    • Service of type Tomaj\NetteApi\Misc\IpDetectorInterface is no more required if service of type Tomaj\NetteApi\Logger\ApiLoggerInterface is not configured and used
    • Service of type Tomaj\NetteApi\Logger\ApiLoggerInterface need not be named "apiLogger"
  • 1.6.0 1.6.0

    • Added Content-Length header to Json and Xml responses
  • 1.5.0 1.5.0

    • ApiPresenter: getRequestDomain() returns port as well
    • Updated JsonApiResponse - separate charset from content type as changed in nette/application 2.3.12
    • Also change nette/application minimal version to 2.3.12
  • 1.4.0 1.4.0

    • changed rendering in API console when available values are set
    • fixed negative values for response time in API web console
  • 1.3.0 Error reporting with Tracy

    • added tracy for error logging from ApiPresenter
    • added tests for ApiPresenter
  • 1.2.0 New Input Types

    New ability to send new input types:

    • send RAW POST data
    • send COOKIES
    • send FILEs

    more info in readme section Inputs
    all new types are available in test console for easy testing api calls

  • 1.1.0 Added CORS Support

    • Added CORS support to ApiPresenter. Available options:
      • 'auto' - send back header Access-Control-Allow-Origin with domain that made request
      • '*' - send header with '*' - this will work fine if you dont need to send cookies via ajax calls to api with jquery $.ajax with xhrFields: { withCredentials: true } settings
      • 'off' - will not send any CORS header
      • other - any other value will be send in Access-Control-Allow-Origin header
    • Rewritten few internal functions
  • 1.0.1 Fixed array parsing

    Fixed parsing array variables from GET and POST when using multi InputParam

  • 1.0.0 First stable version

    First version that can be used for api. Contains

    1. Authorization
    2. Handling api request
    3. Logging
    4. UI Control for api listing
    5. UI Control for web console to test api endpoints
    6. Api versioning

Is this addon outdated? Did you find an issue? Please report it.

Componette Componette admin@componette.com