IMPORTANT: Per accedir als fitxer de subversion: http://acacha.org/svn (sense password). Poc a poc s'aniran migrant els enllaços. Encara però funciona el subversion de la farga però no se sap fins quan... (usuari: prova i la paraula de pas 123456)

Requeriments

Restler 3 requereix PHP 5.3 o superior. Per versions anteriors de PHP utilitzeu Restler 2.

Instal·lació

IMPORTANT: No utilitzeu git per fer la instal·lació. Si ho feu la instal·lació és més complexe. Vegeu Restler#Retorna_tot_el_rato_un_JSON_amb_error_404_not_found

Cal activar el mòdul d'Apache mod_rewrite:

$ sudo a2enmod rewrite 
$ sudo /etc/init.d/apache2 restart

A més es configuren els fitxer .htaccess i per tant cal estar atent al paràmetre:

AllowOverride

Per defecte sol esta a

AllowOverrride none

I cal que sigui:

AllowOverrride All

Per permetre que els fitxers .htaccess puguin sobrescriure qualsevol configuració d'Apache a nivell de directori.

Cal tenir en compte que els fitxers que ens proporciona Restler són:

$ cat .htaccess
Options -MultiViews
DirectoryIndex index.php
<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteRule ^$ index.php [QSA,L]
	RewriteCond %{REQUEST_FILENAME} !-f 
	RewriteCond %{REQUEST_FILENAME} !-d
	RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
<IfModule mod_php5.c>
	php_flag display_errors Off
</IfModule>

i està pensat per a que l'API estigui a l'arrel. Si no és així (per exemple per tal d'executar els exemples) cal utilitzar RewriteBase:

$ cat .htaccess
RewriteBase /restler/examples/_001_helloworld
Options -MultiViews
DirectoryIndex index.php
<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteRule ^$ index.php [QSA,L]
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteCond %{REQUEST_FILENAME} !-d 
	RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
<IfModule mod_php5.c>
	php_flag display_errors Off
</IfModule>

Recursos:

Exemple concret instal·lació a /usr/share

Restler s'instal·la a:

/usr/share/restler

utilitzant:

https://codeload.github.com/Luracast/Restler/zip/v3rc3-Stable

La configuració d'Apache és:

$ cat /etc/apache2/conf.d/restler.conf
# Include GOsa to your web service
Alias /restler /usr/share/restler/public

<Directory /usr/share/restler/public>
	AllowOverride All
</Directory> 

La web és accesible amb:

http://localhost/restler/

Per exemple, l'exemple 1, el Hello World:

http://localhost/restler/examples/_001_helloworld/readme.html

http://localhost/restler/examples/_001_helloworld/say/hello

Els fitxers .htaccés es modifiquen per afegir RewriteBase:

$ cat .htaccess
RewriteBase /restler/examples/_001_helloworld
Options -MultiViews
DirectoryIndex index.php
<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteRule ^$ index.php [QSA,L]
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteCond %{REQUEST_FILENAME} !-d 
	RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
<IfModule mod_php5.c> 
	php_flag display_errors Off
</IfModule>

Exemples

Formats suportats

Per defecte treballa amb Json, però es poden especificar altres formats com XML:

$r = new Restler();
$r->setSupportedFormats('JsonFormat', 'XmlFormat');

El primer format és el format per defecte quan el client no especifica res. Com pot el clients especificar el format desitjat?

Exemples extensions:

http://localhost/restler/examples/_003_multiformat/index.php/bmi.json  -> JSON
http://localhost/restler/examples/_003_multiformat/index.php/bmi.xml  -> XML

Al utilitzar un navegador normalment es mostrarà el format XML per que és un dels formats demanats per defecte a les HTTP Accept Header per parts dels navegadors. En canvi en peticions AJAX o CURL retornarà JSON.

Fixeu-vos que només cal:

$ cat BMI.php 
<?php
class BMI
{
    function index($height = 162.6, $weight = 84)
    {
        $result = new stdClass();

        //	1 pound = 0.45359237 kilograms
        //	1 meter = 3.2808399  feet
        //	1 meter = 39.3700787 inches
        //	1 meter = 100		 cms

        // assume height is given in centimeters
        $cm = $height;
        // assume weight is given in kilograms
        $kg = $weight;

        $meter = $cm / 100;
        $inches = $meter * 39.3700787;
        $feet = round($inches / 12);
        $inches = $inches % 12;

        $result->bmi = round($kg / ($meter * $meter), 2);
        $lb = round($kg / 0.45359237, 2);

        if ($result->bmi < 18.5) {
            $result->message = 'Underweight';
        } elseif ($result->bmi <= 24.9) {
            $result->message = 'Normal weight';
        } elseif ($result->bmi <= 29.9) {
            $result->message = 'Overweight';
        } else {
            $result->message = 'Obesity';
        }
        $result->metric = array(
            'height' => "$cm centimeter",
            'weight' => "$weight kilograms"
        );
        $result->imperial = array(
            'height' => "$feet feet $inches inches",
            'weight' => "$lb pounds"
        );
        return $result;
    }
}

Observeu l'ús de classes PHP predefinides:

        $result = new stdClass();

stdClass:

{[nota|el mètode és diu index per tal de que el rounting sigui automàtic. és a dir si no s'indica el mètode que es vol executar s'executa este}}

Recursos:

Autenticació

La clau està en el mètode:

$r->addAuthenticationClass('SimpleAuth');

On SimpleAuth és una classe que implementa l'autenticació. Un exemple molt bàsic:

$ cat SimpleAuth.php 
 <?php
 use Luracast\Restler\iAuthenticate;
 
 class SimpleAuth implements iAuthenticate
 {
     const KEY = 'rEsTlEr2'; 
 
    function __isAllowed()
    {
        return isset($_GET['key']) && $_GET['key'] == SimpleAuth::KEY ? TRUE : FALSE;
    }
 
    function key()
    {
        return SimpleAuth::KEY;
    }
 }

Com podeu veure implementa la interfície iAuthenticate:

/usr/share/restler/vendor/Luracast/Restler$ cat iAuthenticate.php 
<?php
namespace Luracast\Restler;

/**
 * Interface for creating authentication classes
 *
 * @category   Framework
 * @package    Restler
 * @subpackage auth
 * @author     R.Arul Kumaran <[email protected]>
 * @copyright  2010 Luracast
 * @license    http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link       http://luracast.com/products/restler/
 * @version    3.0.0rc3
 */
interface iAuthenticate extends iFilter
{
}

Que deriva de Ifilter:

<?php namespace Luracast\Restler;

/**
 * Interface for creating classes that perform authentication/access
 * verification
 *
 * @category   Framework
 * @package    Restler
 * @subpackage auth
 * @author     R.Arul Kumaran <[email protected]>
 * @copyright  2010 Luracast
 * @license    http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link       http://luracast.com/products/restler/
 * @version    3.0.0rc3
 */
interface iFilter
{
    /**
     * Access verification method.
     *
     * API access will be denied when this method returns false
     *
     * @abstract
     * @return boolean true when api access is allowed false otherwise
     */
    public function __isAllowed();

}

Els mètodes que estan protegits són els:

  • Marcats com protected:
protected function restricted() {
		return 'protected method';
	}

Amb un PHP Doc:

	/**
	 * @access protected
	 */
	function restricted2(){
		return 'protected by comment';
	}

O afegir:

Add @access protected comment to the class to protect all methods of that class

A totes les classes

Recursos:

CRUD

Recursos:

Com utilitzar REST Console

Per fer funcionar l'exemple de fer un POST d'un objecte, cal posar

A la secció Target:

Al a secció Body:

  • Content-type: application/json
  • RAW Body:
{"name": "Anothersdadsadsa", "email": "[email protected]"}

i fer click al botó POST

Lo que en CURL seria:

$ curl -X POST http://localhost/restler/examples/_007_crud/index.php/authors -H "Content-Type: application/json" -d '{"name": "Anothersdadsadsa", "email": "[email protected]"}'

Com utilitzar Restler Explorer

El explorador està molt bé no només com a documentació sinó que també permet provar les operacions GET, POST, PUT, etc.

MySQL.php

Extret de: http://localhost/restler/examples/_007_crud/readme.html

Vegeu també PHP i Mysql

<?php
/**
 * MySQL DB. All data is stored in data_pdo_mysql database
 * Create an empty MySQL database and set the dbname, username
 * and password below
 *
 * This class will create the table with sample data
 * automatically on first `get` or `get($id)` request
 */
use Luracast\Restler\RestException;
class DB_PDO_MySQL
{
    private $db;
    function __construct()
    {
        try {
            $options = array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8');
            $this->db = new PDO(
                'mysql:host=localhost;dbname=data_pdo_mysql',
                'username',
                'password',
                $options
            );
            $this->db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,
                PDO::FETCH_ASSOC);
        } catch (PDOException $e) {
            throw new RestException(501, 'MySQL: ' . $e->getMessage());
        }
    }
    function get($id, $installTableOnFailure = FALSE)
    {
        $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        try {
            $sql = $this->db->prepare('SELECT * FROM authors WHERE id = :id');
            $sql->execute(array(':id' => $id));
            return $this->id2int($sql->fetch());
        } catch (PDOException $e) {
            if (!$installTableOnFailure && $e->getCode() == '42S02') {
                $this->install();
                return $this->get($id, TRUE);
            }
            throw new RestException(501, 'MySQL: ' . $e->getMessage());
        }
    }
    function getAll($installTableOnFailure = FALSE)
    {
        $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        try {
            $stmt = $this->db->query('SELECT * FROM authors');
            return $this->id2int($stmt->fetchAll());
        } catch (PDOException $e) {
            if (!$installTableOnFailure && $e->getCode() == '42S02') {
                $this->install();
                return $this->getAll(TRUE);
            }
            throw new RestException(501, 'MySQL: ' . $e->getMessage());
        }
    }
    function insert($rec)
    {
        $sql = $this->db->prepare("INSERT INTO authors (name, email) VALUES (:name, :email)");
        if (!$sql->execute(array(':name' => $rec['name'], ':email' => $rec['email'])))
            return FALSE;
        return $this->get($this->db->lastInsertId());
    }
    function update($id, $rec)
    {
        $sql = $this->db->prepare("UPDATE authors SET name = :name, email = :email WHERE id = :id");
        if (!$sql->execute(array(':id' => $id, ':name' => $rec['name'], ':email' => $rec['email'])))
            return FALSE;
        return $this->get($id);
    }
    function delete($id)
    {
        $r = $this->get($id);
        if (!$r || !$this->db->prepare('DELETE FROM authors WHERE id = ?')->execute(array($id)))
            return FALSE;
        return $r;
    }
    private function id2int($r)
    {
        if (is_array($r)) {
            if (isset($r['id'])) {
                $r['id'] = intval($r['id']);
            } else {
                foreach ($r as &$r0) {
                    $r0['id'] = intval($r0['id']);
                }
            }
        }
        return $r;
    }
    private function install()
    {
        $this->db->exec(
            "CREATE TABLE authors (
                id INT AUTO_INCREMENT PRIMARY KEY ,
                name TEXT NOT NULL ,
                email TEXT NOT NULL
            ) DEFAULT CHARSET=utf8;"
        );
        $this->db->exec(
            "INSERT INTO authors (name, email) VALUES ('Jac  Wright', '[email protected]');
             INSERT INTO authors (name, email) VALUES ('Arul Kumaran', '[email protected]');"
        );
    }
}

Documentació de l'API. SwaggerUI

Un exemple de com queda la documentació, basat en l'exemple 7 CRUD:

Un altre exemple és el exemple 8 que podeu trobar en local:

http://localhost/restler/examples/_008_documentation/explorer/index.html  TODO????

Vegeu Swagger

Producció

Al crear l'objecte:

$r = new Restler();

Es pot crear amb les opcions:

$r = new Restler(true);

Per indicar que activem el cache o mode de producció. Per depurar pot ser útil:

 $r = new Restler(true,true);

Per fer proves i que el fitxer routers.php es crei cada cop que es fa una petició

$ cat cache/routes.php

La carpeta cache ha de tenir permisos per ser escrita per l'usuari www-data:

/usr/share/restler1/public/examples/_001_helloworld$ ls -la
total 60
drwxr-xr-x  3 www-data www-data  4096 mai  7 15:57 .
drwxr-xr-x 15 root     root      4096 mai  7 12:39 ..
drwxr-xr-x  2 www-data www-data  4096 mai  7 15:46 cache
-rwxr-xr-x  1 www-data www-data   292 mai  7 15:34 .htaccess
-rwxr-xr-x  1 root     root       354 mai  7 14:20 .htaccess~
-rwxr-xr-x  1 www-data www-data   308 mai  7 13:01 .htaccess.old
-rwxr-xr-x  1 www-data www-data   845 mai  7 15:57 index.php
-rwxr-xr-x  1 root     root       854 mai  7 15:56 index.php~
-rw-r--r--  1 www-data www-data 19208 mai  7 12:39 readme.html
-rw-r--r--  1 www-data www-data  3628 mai  7 12:39 readme.md
-rwxr-xr-x  1 www-data www-data   120 mai  7 12:39 Say.php


NOTA: production mode writes human readable cache file for the routes in the cache directory by default. So make sure cache folder has necessary write permission.

Behat

Feu:

$ cd /usr/share/restler

i

$ sudo make composer-install

i

$ ls -la bin/
total 8
drwxr-xr-x 2 root root 4096 mai  7 16:43 .
drwxr-xr-x 8 root root 4096 mai  7 16:43 ..
lrwxrwxrwx 1 root root   31 mai  7 16:43 behat -> ../vendor/behat/behat/bin/behat

Permet fer tests de comportament de l'aplicació,primer:

$ sudo joe behat.yml

Canvieu la base_url. En el meu cas:

base_url: http://localhost/restler

i podeu executar un test exemple amb:

$ bin/behat  features/examples/_001_helloworld.feature
@example1 @helloworld
Feature: Testing Helloworld Example

 Scenario: Saying Hello world                           # features/examples/_001_helloworld.feature:4
   When I request "/examples/_001_helloworld/say/hello" # RestContext::iRequest()
   Then the response status code should be 200          # RestContext::theResponseStatusCodeShouldBe()
   And the response is JSON                             # RestContext::theResponseIsJson()
   And the type is "string"                             # RestContext::theTypeIs()
   And the value equals "Hello world!"                  # RestContext::theValueEquals()

 Scenario: Saying Hello Restler                              # features/examples/_001_helloworld.feature:11
   Given that "to" is set to "Restler"                       # RestContext::thatItsStringPropertyIs()
   When I request "/examples/_001_helloworld/say/hello{?to}" # RestContext::iRequest()
   Then the response status code should be 200               # RestContext::theResponseStatusCodeShouldBe()
   And the response is JSON                                  # RestContext::theResponseIsJson()
   And the type is "string"                                  # RestContext::theTypeIs()
   And the value equals "Hello Restler!"                     # RestContext::theValueEquals()

 Scenario: Saying                                            # features/examples/_001_helloworld.feature:19
   When I request "/examples/_001_helloworld/say"            # RestContext::iRequest()
   Then the response status code should be 404               # RestContext::theResponseStatusCodeShouldBe()
   And the response is JSON                                  # RestContext::theResponseIsJson()
   And the type is "array"                                   # RestContext::theTypeIs()

 Scenario: Saying Hi                                         # features/examples/_001_helloworld.feature:25
   When I request "/examples/_001_helloworld/say/hi"         # RestContext::iRequest()
   Then the response status code should be 404               # RestContext::theResponseStatusCodeShouldBe()
   And the response is JSON                                  # RestContext::theResponseIsJson()
   And the type is "array"                                   # RestContext::theTypeIs()

 Scenario: Saying Hi Arul                                    # features/examples/_001_helloworld.feature:31
   Given that "to" is set to "Arul"                          # RestContext::thatItsStringPropertyIs()
   When I request "/examples/_001_helloworld/say/hi/{to}"    # RestContext::iRequest()
   Then the response status code should be 200               # RestContext::theResponseStatusCodeShouldBe()
   And the response is JSON                                  # RestContext::theResponseIsJson()
   And the type is "string"                                  # RestContext::theTypeIs()
   And the value equals "Hi Arul!"                           # RestContext::theValueEquals()

5 escenarios (5 exitosos)
25 pasos (25 exitosos)
0m0.089s


I executeu els passos de l'apartat:

 instal·lació

Vegeu Behat

Troubleshooting. Resol·lució de problemes

Retorna tot el rato un JSON amb error 404 not found

En el meu cas el problema era utilitzar un git clone en comptes de baixar la versió estable de la rc 3.0. A l'arrel hi ha un gitignore i sembla que no s'ignoren certs fitxers com:

vendor/autoload.php

Exemple 2

Cal descomentar la línia:

use Luracast\Restler\Restler;  

Del fitxer index.php. Sinó dona errors:

$ sudo tail -f /var/log/apache2/error.log
[Tue May 07 16:54:21 2013] [error] [client 127.0.0.1] PHP Warning:  realpath() expects parameter 1 to be string, array given in /usr/share/restler/vendor/Luracast/Restler/AutoLoader.php on line 161, referer: http://localhost/restler/examples/_002_minimal/readme.html

404 : Not Found ../resources.json al mostra la documentació de l'API: Restler Explorer

Apareix l'error al mirar de consultar la documentació dels exemples en local:

http://localhost/restler/examples/_008_documentation/explorer/index.html

A la web de Restler el fitxer apareix ok:

http://restler3.luracast.com/examples/_008_documentation/resources.json
{
 "apiVersion": "1",
 "swaggerVersion": "1.1",
 "basePath": "http://restler3.luracast.com/examples/_008_documentation",
 "apis": [
   {
     "path": "/resources/authors-v1.{format}",
     "description": ""
   }
 ]
}

Segons la documentació (https://github.com/Luracast/Restler-API-Explorer) l'error es degut a no afegir la línia:

$r->addAPIClass('Luracast\\Restler\\Resources'); //this creates resources.json at API Root

Quedant de la següent manera:

$ tail index.php
 //$r = new Restler();
 // comment the line above and uncomment the line below for production mode
 $r = new Restler();
 
 $r->addAPIClass('Luracast\\Restler\\Resources'); //this creates resources.json at API Root
 
 $r->addAPIClass('improved\\Authors');
 $r->addAPIClass('Resources');

Cal tenir en compte que no es tracta d'un fitxer que existeixi realment al sistema de fitxers. Aquest fitxer es crea automàticament per PHP (es pot veure pel navegador però no pel sistema de fitxers). Per que funcioni cal tenir correctament el .htaccess (és a dir amb el RewriteBase correcte). Això fa que la petició http://localhost/restler/examples/_008_documentation/resources.json s'envii al fitxer index.html qui dona la resposta resources.json de forma adequada:

/usr/share/restler/public/examples/_008_documentation$ cat .htaccess
RewriteBase /restler/examples/_008_documentation
DirectoryIndex index.php
<IfModule mod_rewrite.c> 
	RewriteEngine On
	RewriteRule ^$ index.php [QSA,L]
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteCond %{REQUEST_FILENAME} !-d
	RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
<IfModule mod_php5.c>
	php_flag display_errors Off
</IfModule>

Recursos:

cache should exist with write permission

Apareix l'error al log d'Apache:

$ sudo tail -f /var/log/apache2/error.log
[Wed May 08 11:24:56 2013] [error] [client 127.0.0.1] PHP Fatal error:  Uncaught exception 'Exception' with message 'The cache directory `/usr/share/restler/public/examples/_008_documentation/cache` should exist with write  
permission.' in /usr/share/restler/vendor/Luracast/Restler/HumanReadableCache.php:128\nStack trace:\n#0 /usr/share/restler/vendor/Luracast/Restler/HumanReadableCache.php(75): Luracast\\Restler
\\HumanReadableCache->throwException()\n#1 /usr/share/restler/vendor/Luracast/Restler/Restler.php(222): Luracast\\Restler\\HumanReadableCache->set('routes', Array)\n#2 [internal function]: Luracast\\Restler\\Restler->__destruct()\n#3 
{main}\n  thrown in /usr/share/restler/vendor/Luracast/Restler/HumanReadableCache.php on line 128

La solució es crear la carpeta amb permisos:

$ sudo mkdir cache
$ sudo chown www-data:www-data cache

Això permet crear el fitxer routes.php:

$ head -n 30 cache/routes.php 
<?php $o = array();

// ** THIS IS AN AUTO GENERATED FILE. DO NOT EDIT MANUALLY ** 

//==================== GET ====================

$o['GET'] = array();

//==== GET v1/resources/{id}-v{version} ====

$o['GET']['v1/resources/{id}-v{version}'] = array (
   'className' => 'Resources',
   'path' => 'v1/resources',
   'methodName' => 'get',
   'arguments' => 
   array (
       'version' => 0,
       'id' => 1,
   ),
   'defaults' => 
   array (
       0 => NULL,
       1 => ,
   ),
   'metadata' => 
   array (
       'description' => ,
       'longDescription' => ,
       'access' => 'hybrid',
       'param' =>

TODO

; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI.  PHP's
; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok
; what PATH_INFO is.  For more information on PATH_INFO, see the cgi specs.  Setting
; this to 1 will cause PHP CGI to fix its paths to conform to the spec.  A setting
; of zero causes PHP to behave as before.  Default is 1.  You should fix your scripts
; to use SCRIPT_FILENAME rather than PATH_TRANSLATED.
; http://php.net/cgi.fix-pathinfo
;cgi.fix_pathinfo=1

Vegeu també

Enllaços externs