<file_sep><?php

namespace Marmelab\Microrest;

use Raml\Parser;
use Silex\Application;
use Silex\ServiceProviderInterface;

class MicrorestServiceProvider implements ServiceProviderInterface
{
    public function register(Application $app)
    {
        $app['microrest.initializer'] = $app->protect(function () use ($app) {
            if (!is_readable($configFile = $app['microrest.config_file'])) {
                throw new \RuntimeException("API config file is not readable");
            }

            $apiDefinition = (new Parser())->parse($configFile);

            $app['microrest.mediaType'] = $apiDefinition->getDefaultMediaType() ?: 'application/json';

            $app['microrest.routes'] = $apiDefinition
                ->getResourcesAsUri()
                ->getRoutes()
            ;

            $baseUrl = $apiDefinition->getBaseUrl();
            $app['microrest.api_url_prefix'] = $baseUrl ? substr(parse_url($baseUrl, PHP_URL_PATH), 1) : 'api';

            $app['microrest.restController'] = $app->share(function () use ($app) {
                return new RestController($app['db']);
            });

            $app['microrest.routeBuilder'] = $app->share(function () {
                return new RouteBuilder();
            });

            $app['microrest.doc_url_prefix'] = 'doc';

            $app['microrest.description'] = function () use ($apiDefinition) {
                $resources = array();
                foreach ($apiDefinition->getResources() as $resource) {
                    $endpoints = array();
                    foreach ($resource->getMethods() as $method) {
                        $endpoints[] = [
                            'uri' => $resource->getUri(),
                            'method' => $method->getType(),
                            'description' => $method->getDescription(),
                        ];
                    }

                    foreach ($resource->getResources() as $endpoint) {
                        foreach ($endpoint->getMethods() as $method) {
                            $endpoints[] = [
                                'uri' => $endpoint->getUri(),
                                'method' => $method->getType(),
                                'description' => $method->getDescription(),
                            ];
                        }
                    }

                    $resources[] = [
                        'uri' => $resource->getUri(),
                        'name' => $resource->getDisplayName(),
                        'description' => $resource->getDescription(),
                        'endpoints' => $endpoints,
                    ];
                }

                return array(
                    'apiName' => $apiDefinition->getTitle(),
                    'resources' => $resources,
                    'baseUrl' => $apiDefinition->getBaseUrl(),
                    'version' => $apiDefinition->getVersion(),
                );
            };

            $app['twig.loader.filesystem'] = $app->share($app->extend('twig.loader.filesystem', function ($loader, $app) {
                $loader->addPath($app['microrest.templates_path'], 'Microrest');

                return $loader;
            }));

            $app['microrest.templates_path'] = function () {
                return __DIR__.'/../views';
            };
        });

        $app['microrest.builder'] = function () use ($app) {
            $app['microrest.initializer']();

            $controllers = $app['microrest.routeBuilder']
                ->build($app['controllers_factory'], $app['microrest.routes'], 'microrest.restController');
            $app['controllers']->mount($app['microrest.api_url_prefix'], $controllers);

            $controllers = $app['controllers_factory'];
            $controllers->get('/', function () use ($app) {
                return $app['twig']->render('@Microrest/doc.html.twig', $app['microrest.description']);
            });

            $app['controllers']->mount($app['microrest.doc_url_prefix'], $controllers);
        };
    }

    public function boot(Application $app)
    {
        $app['microrest.builder'];
    }
}
<file_sep><?php

if (preg_match('#^/admin#', $_SERVER["REQUEST_URI"])) {
    if ('/admin' === $_SERVER["REQUEST_URI"]) {
        header("Location: /admin/");

        exit;
    }
    return false; // serve the requested resource as-is.
}

use Symfony\Component\Debug\Debug;

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

Debug::enable();

$app = require __DIR__.'/../src/app.php';
require __DIR__.'/../config/dev.php';

$app->run();
<file_sep><?php

namespace Marmelab\Microrest;

use Doctrine\DBAL\Connection;
use Pagerfanta\Adapter\DoctrineDbalAdapter;
use Pagerfanta\Pagerfanta;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class RestController
{
    protected $dbal;

    public function __construct(Connection $dbal)
    {
        $this->dbal = $dbal;
    }

    public function homeAction($availableRoutes)
    {
        return new JsonResponse($availableRoutes);
    }

    public function getListAction($objectType, Request $request)
    {
        $queryBuilder = $this->dbal
            ->createQueryBuilder()
            ->select('o.*')
            ->from($objectType, 'o')
        ;

        if ($sort = $request->query->get('_sort')) {
            $queryBuilder->orderBy($sort, $request->query->get('_sortDir', 'ASC'));
        }

        $countQueryBuilderModifier = function ($queryBuilder) {
            $queryBuilder
                ->select('COUNT(DISTINCT o.id) AS total_results')
                ->setMaxResults(1)
            ;
        };

        $pager = new DoctrineDbalAdapter($queryBuilder, $countQueryBuilderModifier);

        $nbResults = $pager->getNbResults();
        $results = $pager->getSlice($request->query->get('_start', 0), $request->query->get('_end', 20));

        return new JsonResponse($results, 200, array(
            'X-Total-Count' => $nbResults,
        ));
    }

    public function postListAction($objectType, Request $request)
    {
        try {
            $this->dbal->insert($objectType, $request->request->all());
        } catch (\Exception $e) {
            return new JsonResponse(array(
                'errors' => array('detail' => $e->getMessage()),
            ), 400);
        }

        $id = (integer) $this->dbal->lastInsertId();

        return $this->getObjectResponse($objectType, $id, 201);
    }

    public function getObjectAction($objectId, $objectType)
    {
        return $this->getObjectResponse($objectType, $objectId);
    }

    public function putObjectAction($objectId, $objectType, Request $request)
    {
        $data = $request->request->all();
        $request->request->remove('id');

        $result = $this->dbal->update($objectType, $data, array('id' => $objectId));
        if (0 === $result) {
            return new Response('', 404);
        }

        return $this->getObjectResponse($objectType, $objectId);
    }

    public function deleteObjectAction($objectId, $objectType)
    {
        $result = $this->dbal->delete($objectType, array('id' => $objectId));
        if (0 === $result) {
            return new Response('', 404);
        }

        return new JsonResponse('', 204);
    }

    private function getObjectResponse($name, $id, $status = 200)
    {
        $queryBuilder = $this->dbal->createQueryBuilder();
        $query = $queryBuilder
            ->select('*')
            ->from($name)
            ->where('id = '.$queryBuilder->createPositionalParameter($id))
        ;

        $result = $query->execute()->fetchObject();
        if (false === $result) {
            return new Response('', 404);
        }

        return new JsonResponse($result, $status);
    }
}
<file_sep><?php

namespace Marmelab\MicrorestTest;

use Marmelab\Microrest\MicrorestServiceProvider;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;

class MicrorestServiceProviderTest extends \PHPUnit_Extensions_Database_TestCase
{
    private $connection = null;

    /**
     * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection
     */
    public function getConnection()
    {
        $databasePath = __DIR__ . "/Fixtures/api.db";
        $config = new \Doctrine\DBAL\Configuration();
        $connectionParams = array(
            'path' => $databasePath,
            'driver' => 'pdo_sqlite',
        );
        $this->connection = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config);

        return $this->createDefaultDBConnection($this->connection->getWrappedConnection(), 'api');
    }

    /**
     * @return PHPUnit_Extensions_Database_DataSet_IDataSet
     */
    public function getDataSet()
    {
        return new \PHPUnit_Extensions_Database_DataSet_YamlDataSet(
            dirname(__FILE__)."/Fixtures/data.yml"
        );
    }

    public function testSimpleRamlApiFile()
    {
        $app = new Application();
        $app['debug'] = true;

        $app->register(new \Silex\Provider\DoctrineServiceProvider(), array(
            'db.options' => array(
                'driver' => 'pdo_sqlite',
                'path' => __DIR__ . "/Fixtures/api.db",
            ),
        ));
        $app->register(new \Silex\Provider\ServiceControllerServiceProvider());
        $app->register(new \Silex\Provider\TwigServiceProvider());
        $app->register(new MicrorestServiceProvider(), array(
            'microrest.config_file' => __DIR__.'/Fixtures/api.rml',
        ));

        $request = Request::create('/api/artists', 'GET');
        $response = $app->handle($request);

        $this->assertEquals('[{"id":"1","name":"<NAME>","description":"French electronic music duo consisting of musicians <NAME> and <NAME>","image_url":"http:\/\/travelhymns.com\/wp-content\/uploads\/2013\/06\/random-access-memories1.jpg","nationality":"France"},{"id":"2","name":"<NAME>","description":"English rock band that achieved international acclaim with their progressive and psychedelic music.","image_url":"http:\/\/www.billboard.com\/files\/styles\/promo_650\/public\/stylus\/1251869-pink-floyd-reunions-617-409.jpg","nationality":"England"},{"id":"3","name":"Radiohead","description":"English rock band from Abingdon, Oxfordshire, formed in 1985","image_url":"http:\/\/www.billboard.com\/files\/styles\/promo_650\/public\/stylus\/1251869-pink-floyd-reunions-617-409.jpg","nationality":"England"}]', $response->getContent());
    }
}
<file_sep><?php

use JDesrosiers\Silex\Provider\CorsServiceProvider;
use Marmelab\Microrest\MicrorestServiceProvider;
use Silex\Application;
use Silex\Provider\ServiceControllerServiceProvider;
use Silex\Provider\TwigServiceProvider;
use Silex\Provider\UrlGeneratorServiceProvider;
use Silex\Provider\ValidatorServiceProvider;

$app = new Application();
$app->register(new UrlGeneratorServiceProvider());
$app->register(new ValidatorServiceProvider());
$app->register(new ServiceControllerServiceProvider());

$app->register(new MicrorestServiceProvider(), array(
    'microrest.config_file' => __DIR__ . '/../config/api/api.raml',
));

$app->register(new TwigServiceProvider(), array(
    'twig.options' => array('cache' => __DIR__.'/../var/cache/twig'),
));

$app->register(new CorsServiceProvider(), array(
    'cors.allowOrigin' => 'http://localhost:8000',
    'cors.exposeHeaders' => 'X-Total-Count',
));

$app->after($app['cors']);

return $app;
<file_sep>Microrest.php demo
==================

The demo use ng-admin backend adminstration application.

Installation
------------

    make install-demo
    make run-demo<file_sep><?php

namespace Marmelab\Microrest;

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class RouteBuilder
{
    private static $validMethods = array('GET', 'POST', 'PUT', 'PATCH', 'DELETE');

    public function build($controllers, array $routes, $controllerService)
    {
        $availableRoutes = array();
        $beforeMiddleware = function (Request $request, Application $app) {
            if (0 === strpos($request->headers->get('Content-Type'), $app['microrest.mediaType'])) {
                $data = json_decode($request->getContent(), true);
                $request->request->replace(is_array($data) ? $data : array());
            }
        };
        $afterMiddleware = function (Request $request, Response $response, Application $app) {
            $response->headers->set('Content-Type', $app['microrest.mediaType']);
        };

        foreach ($routes as $index => $route) {
            $route['method'] = $route['type'];
            unset($route['type']);

            if (!in_array($route['method'], self::$validMethods)) {
                continue;
            }

            $availableRoutes[] = $index;

            if (preg_match('/{[\w-]+}/', $route['path'], $identifier)) {
                $route['type'] = 'Object';
                $route['objectType'] = strtolower(str_replace(array('/', $identifier[0]), '', $route['path']));
                $route['path'] = str_replace($identifier[0], '{objectId}', $route['path']);
            } else {
                $route['type'] = 'List';
                $route['objectType'] = strtolower(str_replace('/', '', $route['path']));
            }

            $action = $controllerService.':'.strtolower($route['method']).$route['type'].'Action';
            $name = 'microrest.'.strtolower($route['method']).ucfirst($route['objectType']).$route['type'];

            $controllers
                ->match($route['path'], $action)
                ->method($route['method'])
                ->setDefault('objectType', $route['objectType'])
                ->bind($name)
                ->before($beforeMiddleware)
                ->after($afterMiddleware)
            ;
        }

        $controllers->match('/', $controllerService.':homeAction')
            ->method('GET')
            ->setDefault('availableRoutes', $availableRoutes)
            ->bind('microrest.home')
            ->after($afterMiddleware)
        ;

        return $controllers;
    }
}
<file_sep><table>
        <tr>
            <td><img width="20" src="https://cdnjs.cloudflare.com/ajax/libs/octicons/8.5.0/svg/archive.svg" alt="archived" /></td>
            <td><strong>Archived Repository</strong><br />
            This code is no longer maintained. Feel free to fork it, but use it at your own risks.
	    </td>
        </tr>
</table>

# Marmelab Microrest

Microrest is a Silex provider to setting up a REST API on top of a relational database, based on a YAML (RAML) configuration file.

Check out the [launch post](http://marmelab.com/blog/2015/01/05/introducing-microrest-raml-api-in-silex.html).

## What is RAML ?

[RESTful API Modeling Language (RAML)](http://raml.org/) is a simple and succinct way of describing practically-RESTful APIs. It encourages reuse, enables discovery and pattern-sharing, and aims for merit-based emergence of best practices.   

You can easily set up a RAML file from [API Designer](http://api-portal.anypoint.mulesoft.com/raml/api-designer).     

## Installation

To install microrest.php library, run the command below and you will get the latest version:

```bash
composer require marmelab/microrest "~1.0@dev"
```

Enable `ServiceController`, `Doctrine` and `Microrest` service providers in your application:

```php
$app->register(new Silex\Provider\ServiceControllerServiceProvider());
$app->register(new Silex\Provider\DoctrineServiceProvider(), array(
    'db.options' => array(
        'driver'   => 'pdo_sqlite',
        'path'     => __DIR__.'/app.db',
    ),
));
$app->register(new Marmelab\Microrest\MicrorestServiceProvider(), array(
    'microrest.config_file' => __DIR__ . '/api.raml',
));
```
  
You need to give the path to the `RAML` file describing your API. You can find an example into the `tests/fixtures` directory.

Then, browse your new API REST on the url defined in the `baseUrl` configuration of your `RAML` api file.

## Tests

Run the tests suite with the following commands:

```bash
make install
make test
```

## Demo

You can find a complete demo application in `examples/ng-admin`. You just need 2 commands to install and run it:

```bash
make install-demo
make run-demo
```

Play with the Silex demo API at the url: `http://localhost:8888/api`

Explore the API using [ng-admin](https://github.com/marmelab/ng-admin) backend administration at the url: `http://localhost:8888/admin`

## License

microrest.php is licensed under the [MIT License](LICENSE), courtesy of [marmelab](http://marmelab.com).
<file_sep><?php

$app->register(new Silex\Provider\DoctrineServiceProvider(), array(
    'db.options' => array(
        'driver'   => 'pdo_sqlite',
        'path'     => __DIR__.'/../app.db',
    ),
));<file_sep>.PHONY: test

install-demo:
	composer -d=examples/ng-admin install
	bower --config.cwd=./examples/ng-admin/web/admin install
	cp examples/ng-admin/app.db-dist examples/ng-admin/app.db

run-demo:
	composer -d=examples/ng-admin run

install:
	composer install --dev --prefer-source

test:
	composer test
