GithubHelp home page GithubHelp logo

nap's Introduction

Nap

Nap is my plaything for experimenting with a resource-centric view of building RESTful services in PHP. The aim is to be convention based, with the only required configuration being the resources themselves. I'm also playing with making it easy to plug in to existing systems, with full support for constructor dependency injection in controllers.

Resources are defined hierarchically, with a single resource only making sense in the context of its parent and children. The following is an example of the envisioned configuration of resources, stored in a separate PHP file.

<?php
use \Nap\Resource\Resource;
use \Nap\Resource\Parameter;

return array(
    new Resource("Organisation", "/organisation", new Scheme\Id(), array(
        new Resource("Teams", "/teams", new Scheme\Id(), array(
            new Resource("People", "/people")
        ))
    )),

    new Resource("Project", "/project")
)

A resource definition as above would build the following urls

/organisation/
/organisation/<id>
/organisation/<id>/teams/
/organisation/<id>/teams/<id>
/organisation/<id>/teams/<id>/people/
/project/

Each of these urls would be routed to the applicable controller (OrganisationController in the case of /organisation/ for example) and the correct method, based on the HTTP verb and number of parameters.

Basic usage example

The following example assumes you already have autoloading for the controller namespace defined

Bootstrapping

require_once(__DIR__."/../vendor/autoload.php");
define("CONTROLLER_NAMESPACE", "\Example\Controllers");

use \Nap\Resource\Resource;
use \Nap\Resource\Parameter\Scheme;

// Resources could from an external file
// ie $resources = require_once("path/to/definition.php");
$resources = array(
    new Resource("Organisation", "/organisation", new Scheme\Id(true), array(
        new Resource("Teams", "/teams", new Scheme\Id(true), array(
            new Resource("People", "/people")
        ))
    )),

    new Resource("Project", "/project", new Scheme\Id())
);

$appBuilder = new \Nap\ApplicationBuilder();
$builder->registerSerialiser("application/json", new \Nap\Serialisation\JSON());
$builder->setControllerNamespace(CONTROLLER_NAMESPACE);

$request = new \Symfony\Component\HttpFoundation\Request::createFromGlobals();
$app = $builder->build();

$app->start($request, $resources);

Controller

namespace Example\Controllers;

use Nap\Metadata\Annotations as Nap;
use Nap\Response\ActionResult;
use Nap\Response\Result\Data;
use Nap\Response\Result\HTTP\OK;

class OrganisationController implements \Nap\Controller\NapControllerInterface
{
    /**
     * Allow index to respond in either JSON or XML
     * @Nap\Accept({"application/json", "application/xml"})
     *
     * But default to JSON should the client not send an Accept header
     * @Nap\DefaultMime("application/json")
     */
    public function index(\Symfony\Component\HttpFoundation\Request $request)
    {
        /** @var Organisation[] $organisations **/
        $organisations = $this->repository->getOrganisations();

        // Return an action result with the OK status and some data
        return new ActionResult(new OK(), new Data($organisations));
    }

    // .. And so on for other methods
}

nap's People

Contributors

ap-hunt avatar galatoni avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar

Forkers

galatoni

nap's Issues

Controllers should allow for dependency injection in the constructor

Building a controller needs to allow for dependency injection in the constructor. This means Nap can never require anything of it.

Allowing DI here should probably be implemented to allow many different systems to hook in to it, for example

  • Symfony DI container (obvious)
  • Pimple
  • Laravel
  • Something proprietary

Generally, it may be better (and easier?) to allow construction to be offloaded to outside packages, specifying only the fully qualified name of the desired controller. Nap should provide controller construction by default, and for free, out of the box (ie no (additional) config) when developers don't want to use DI.

URI matching fails when two parameters have the same name

URI matching uses named patterns in regular expressions to ease retrieving parameter values. In the case where 2 or more parameters have the same name, matching fails because pattern names must be unique.

Bug fix needs to provide a way of uniquely identifying parameters and mapping that back to the parameter name. Issue #3 is related, and will probably have an impact on this issue.

Authentication mechanisms

Authentication processes\protocols like OAuth\Oauth 2 and OpenID would be great to cater for. Most likely this would involve creating interfaces and abstracts for as much of the infrastructure as possible, and allowing developers to implement and override the bits they need to fit it in to their own projects.

Example usage

Some simple examples of usage of bootstrapping and usage would be nice.

URL matching test suite

There exists a suite of URL matching tests, but at the date of writing (12th '14) it's not good and doesn't cover that many cases.

The URL matching test suite needs improving.

Individual methods should report what formats their results can be serialised to

Data can only be serialised in so many different ways (you can't turn an array in to a PDF, for example), so individual controller methods should be able to specify which formats their response can be serialized to. This could be done in configuration files or through annotations. Examples below.

Config (YAML)

resources:
    teams:
        index: ["applicaton/json", "application/xml"]
        get: ["application/pdf"]

Annotations

class TeamsController implements \Nap\Controller\NapControllerInterface
{
    /**
     * @Nap\Accept({"application/json", "application/xml"})
     */
    public function index(...)
    { }

    /**
     * @Nap\Accept({"application/pdf"})
     */
    public function get(...)
    { }
}

Resource parameters should have finer controls over when they're required

In the current implementation, a resource parameter is either required or not. This requirement is absolute; it must be present for the resource and its children. The following URIs are not valid for a resource team with a required parameter id and a child person

/team
/team/person

Parameters should really have more fine grained control over when they're required. The following states of required-ness seem applicable:

  • Optional for the resource
  • Required for the resource
  • Optional for children
  • Required for children

In that list, there are really two distinct sets (required\optional for the resource\children) which suggests the required-ness should be split in two, along the same boundary.

Take a resource team with a parameter team_id which is required for itself and its children, and a child resource (with no grandchildren) people with a parameter person_id which is required for itself. In this case, the following uris should be generated:

/team/team_id
/team/team_id/people
/team/team_id/people/person_id

Duplicate parameters in the URI should not overwrite one another

A URI with duplicate parameter names such as /organisation/<id>/teams/<id>, with the relevant resource definitions, will come out with only 1 id parameter, with the value of the last matched parameter.

This is an issue with using standard parameter schemes more than once in a resource hierarchy. It can be replicated using the following resource definitions and the uri /parent/1/child/1

new Resource("Parent", "/parent", new Scheme\Id(), array(
    new Resource("Child", "/child", new Scheme\Id())
);

Controller methods currently expect a hash of parameter values keyed on parameter names, which are defined in the parameter scheme. My current thought is this may be resolvable by creating some kind of unique name combining the resource name and the parameter name (eg Child_id). Generated names would need to be predictable and consistent, and not require any configuration at all.

An alternative could be to allow this behaviour to continue, and instead try enforce the uniqueness of parameter names in the parameter scheme. For example: new Resource("Parent", "/parent", new Scheme\Id("parent_id"));

JSON and XML serialisation

Controller actions should be able to return data agnostic of the final form (Ie XML or JSON for now). For example:

public function index()
{
    return new Data(array(...));
}

Serialized data should be stored in an output buffer and dumped at the last moment.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.