Giter Club home page Giter Club logo

tonic's Introduction

PHP library/framework for building Web apps while respecting the 5 principles of RESTful design.

  • Give every "thing" an ID (aka URIs)
  • Link things together (HATEOAS)
  • Use standard methods (aka the standard interface)
  • Resources with multiple representations (aka standard document formats)
  • Communicate statelessly

See the Tonic site for more info.

How it works

Everything is a resource, and a resource is defined as a PHP class. Annotations wire a URI to the resource and HTTP methods to class methods.

/**
 * This class defines an example resource that is wired into the URI /example
 * @uri /example
 */
class ExampleResource extends Tonic\Resource {
    
    /**
     * @method GET
     */
    function exampleMethod() {
        return new Response(Response::OK, 'Example response');
    }
  
}

The class method can do any logic it then requires and return a Response object, an array of status code and response body, or just a response body string.

How to get started

The best place to get started is to get the hello world example running on your system, to do this you will need a web server running PHP5.3+.

Installation

The easiest way to install Tonic is via Composer, if you are not using or familiar with Composer I recommend you go read up on it.

Add Tonic to your composer.json file and run composer install/update:

#composer.json
{
    "require": {
        "peej/tonic": "3.*"
    }
}

$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar install

Alternatively you can download Tonic from Github and manually place it within your project.

Bootstrapping

To bootstrap Tonic, use the provided web/dispatch.php script and configure your Web server to push all requests to it via the provided .htaccess file.

For development purposes you can use PHP's built in Web server by running the following command:

$ php -S 127.0.0.1:8080 vendor/bin/dispatch.php

or:

$ php -S 127.0.0.1:8080 web/dispatch.php

Once you need more, you can write your own dispatcher with your own custom behaviour.

The basic premise is to create an instance of Tonic\Application and pass it's getResource() method a Tonic\Request instance. Then an incoming request will match and load one of your resource classes, execute it, and output the response.

A very basic minimal dispatcher looks something like this:

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

$app = new Tonic\Application(array(
    'load' => 'example.php'
));
$request = new Tonic\Request();

$resource = $app->getResource($request);
$response = $resource->exec();
$response->output();

Features

URI annotations

Resources are attached to their URL by their @uri annotation:

/**
 * @uri /example
 */
class ExampleResource extends Tonic\Resource { }

As well as a straight forward URI string, you can also use a regular expression so that a resource is tied to a range of URIs:

/**
 * @uri /example/([a-z]+)
 */
class ExampleResource extends Tonic\Resource {

    /**
     * @method GET
     */
    function exampleMethod($parameter) {
        ...
    }
}

URL template and Rails route style @uri annotations are also supported:

/**
 * @uri /users/{username}
 */
class ExampleResource extends Tonic\Resource {

    /**
     * @method GET
     */
    function exampleMethod($username) {
        ...
    }
}

/**
 * @uri /users/:username
 */
class ExampleResource extends Tonic\Resource {

    /**
     * @method GET
     */
    function exampleMethod($username) {
        ...
    }
}

It is also possible for multiple resource to match the same URI or to have more than one URI for the same resource:

/**
 * @uri /example
 * @uri /example/([a-z]+)
 */
class ExampleResource extends Tonic\Resource { }

/**
 * @uri /example/apple
 * @priority 2
 */
class AnotherExampleResource extends Tonic\Resource { }

By using the @priority annotation with a number, of all the matching resources, the one with the highest postfixed number will be used.

Request object

Resource methods have access to the incoming HTTP request via the Request object.

The Request object exposes all elements of the request as public properties, including the HTTP method, request data and content type.

Request headers are accessable via public properties named afer a camelcasing of the headers name.

/**
 * @uri /example
 */
class ExampleResource extends Tonic\Resource {

    /**
     * @method GET
     */
    function exampleMethod() {
        echo $this->request->userAgent;
    }
}

Mount points

To make resources more portable, it is possible to "mount" them into your URL-space by providing a namespace name to URL-space mapping. Every resource within that namespace will in effect have the URL-space prefixed to their @uri annotation.

$app = new Tonic\Application(array(
    'mount' => array('myBlog' => '/blog')
));

Resource annotation cache

Parsing of resource annotations has a performance penalty. To remove this penalty and to remove the requirement to load all resource classes up front (and to allow opcode caching), a cache can be used to store the resource annotation data.

Passing a cache object into the Application object at construction will cause that cache to be used to read and store the resource annotation metadata rather than read it from the source code tokens. Tonic comes with two cache classes, one that stores the cache on disk and the other which uses the APC data store.

Then rather than including your resource class files explicitly, the Application object will load them for you based on the path stored in the cache and ignore the "load" configuration option.

$app = new Tonic\Application(array(
    'load' => '../resources/*.php', // look for resource classes in here
    'cache' => new Tonic\MetadataCacheFile('/tmp/tonic.cache') // use the metadata cache
));

Method conditions

Conditions can be added to methods via custom annotations that map to another class method. The resource method will only match if all the conditions return without throwing a Tonic exception.

/**
 * @method GET
 * @hascookie foo
 */
function exampleMethod() {
    ...
}

function hasCookie($cookieName) {
    if (!isset($_COOKIE[$cookieName])) throw new Tonic\ConditionException;
}

There are a number of built in conditions provided by the base resource class.

@priority number    Higher priority method takes precident over other matches
@accepts mimetype   Given mimetype must match request content type
@provides mimetype  Given mimetype must be in request accept array
@lang language      Given language must be in request accept lang array
@cache seconds      Send cache header for the given number of seconds

You can also add code to a condition to be executed before and after the resource method. For example you might want to JSON decode the request input and JSON encode the response output of your resource method in a reusable way:

/**
 * @method GET
 * @json
 */
function exampleMethod() {
    ...
}

function json() {
    $this->before(function ($request) {
        if ($request->contentType == "application/json") {
            $request->data = json_decode($request->data);
        }
    });
    $this->after(function ($response) {
        $response->contentType = "application/json";
        $response->body = json_encode($response->body);
    });
}

Response exceptions

The Request object and Resource objects can throw Tonic\Exceptions when a problem occurs that the object does not want to handle and so relinquishes control back to the dispatcher.

If you don't want to handle a problem within your Resource class, you can throw your own Tonic\Exception and handle it in the dispatcher. Look at the auth example for an example of how.

Contributing

  1. Fork the code on Github.

  2. Install the dev dependencies via Composer using the --dev option (or install PHPSpec and Behat on your system yourself).

    php composer.phar --dev install

  3. Write a spec and then hack the code to make it pass.

  4. Create a pull request.

Don't fancy hacking the code? Then report your problem in the Github issue tracker.

For more information, read the code. Start with the dispatcher "web/dispatch.php" and the Hello world in the "src/Tyrell" directory.

Cookbook

Dependency injection container

You probably want a way to handle your project dependencies. Being a lightweight HTTP framework, Tonic won't handle this for you, but does make it easy to bolt in your own dependency injection container (ie. Pimple http://pimple.sensiolabs.org/).

For example, to construct a Pimple container and make it available to the loaded resource, adjust your dispatcher.php as such:

require_once '../src/Tonic/Autoloader.php';
require_once '/path/to/Pimple.php';

$app = new Tonic\Application();

// set up the container
$app->container = new Pimple();
$app->container['dsn'] = 'mysql://user:pass@localhost/my_db';
$app->container['database'] = function ($c) {
    return new DB($c['dsn']);
};
$app->container['dataStore'] = function ($c) {
    return new DataStore($c['database']);
};

$request = new Tonic\Request();
$resource = $app->getResource($request);

$response = $resource->exec();
$response->output();

Input processing

Although Tonic makes available the raw input data from the HTTP request, it does not attempt to interpret this data. If, for example, you want to process all incoming JSON data into an array, you can do the following:

require_once '../src/Tonic/Autoloader.php';

$app = new Tonic\Application();
$request = new Tonic\Request();

// decode JSON data received from HTTP request
if ($request->contentType == 'application/json') {
    $request->data = json_decode($request->data);
}

$resource = $app->getResource($request);

$response = $resource->exec();
$response->output();

We can also automatically encode the response in the same way:

$response = $resource->exec();

// encode output
if ($response->contentType == 'application/json') {
    $response->body = json_encode($response->body);
}

$response->output();

RESTful modelling

REST systems are made up of individual resources and collection resources which contain individual resources. Here is an example of an implemention of an "object" collection resource and an "object" resource to store within it:

/**
 * @uri /objects
 */
class ObjectCollection extends Tonic\Resource {

    /**
     * @method GET
     * @provides application/json
     */
    function list() {
        $ds = $this->app->container['dataStore'];
        return json_encode($ds->fetchAll());
    }

    /**
     * @method POST
     * @accepts application/json
     */
    function add() {
        $ds = $this->app->container['dataStore'];
        $data = json_decode($this->request->data);
        $ds->add($data);
        return new Tonic\Response(Tonic\Response::CREATED);
    }
}

/**
 * @uri /objects/:id
 */
class Object extends Tonic\Resource {

    /**
     * @method GET
     * @provides application/json
     */
    function display() {
        $ds = $this->app->container['dataStore'];
        return json_encode($ds->fetch($this->id));
    }

    /**
     * @method PUT
     * @accepts application/json
     * @provides application/json
     */
    function update() {
        $ds = $this->app->container['dataStore'];
        $data = json_decode($this->request->data);
        $ds->update($this->id, $data);
        return $this->display();
    }

    /**
     * @method DELETE
     */
    function remove() {
        $ds = $this->app->container['dataStore'];
        $ds->delete($this->id);
        return new Tonic\Response(Tonic\Response::NOCONTENT);
    }
}

Handling errors

When an error occurs, Tonic throws an exception that extends the Tonic\Exception class. You can amend the front controller to catch these exceptions and handle them.

$app = new Tonic\Application();
$request = new Tonic\Request();
try {
    $resource = $app->getResource($request);
} catch(Tonic\NotFoundException $e) {
    $resource = new NotFoundResource($app, $request);
}
try {
    $response = $resource->exec();
} catch(Tonic\Exception $e) {
    $resource = new FatalErrorResource($app, $request);
    $response = $resource->exec();
}
$response->output();

User authentication

Need to secure a resource? Something like the following is a good pattern.

/**
 * @uri /secret
 */
class SecureResource extends Tonic\Resource {

    /**
     * @method GET
     * @secure aUser aPassword
     */
    function secret() {
        return 'My secret';
    }

    function secure($username, $password) {
        if (
            isset($_SERVER['PHP_AUTH_USER']) && $_SERVER['PHP_AUTH_USER'] == $username &&
            isset($_SERVER['PHP_AUTH_PW']) && $_SERVER['PHP_AUTH_PW'] == $password
        ) {
            return;
        }
        throw new Tonic\UnauthorizedException;
    }
}

$app = new Tonic\Application();
$request = new Tonic\Request();
$resource = $app->getResource($request);
try {
    $response = $resource->exec();
} catch(Tonic\UnauthorizedException $e) {
    $response = new Tonic\Response(401);
    $response->wwwAuthenticate = 'Basic realm="My Realm"';
}
$response->output();

If you want to secure a whole collection of resources and don't want to annotate them all, you can add the annotation to a parent class and it will be inherited to overridden child methods, or you can add the security logic to the resource's constructor so that all of its request methods are secured regardless of annotations.

/**
 * @uri /secret
 */
class SecureResource extends Tonic\Resource {

    private $username = 'aUser';
    private $password = 'aPassword';

    function setup() {
        if (
            !isset($_SERVER['PHP_AUTH_USER']) || $_SERVER['PHP_AUTH_USER'] != $this->username ||
            !isset($_SERVER['PHP_AUTH_PW']) || $_SERVER['PHP_AUTH_PW'] != $this->password
        ) {
            throw new Tonic\UnauthorizedException;
        }
    }

    /**
     * @method GET
     */
    function secret() {
        return 'My secret';
    }
}

Response templating

The use of a templating engine for generation of output is a popular way to separate views from application logic. You can easily create a method condition that adds an after filter to pass the response through a templating engine like Smarty or Twig.

/**
 * @uri /templated
 */
class Templated extends Tonic\Resource {

    /**
     * @method GET
     * @template myView.html
     */
    function pretty() {
        return new Tonic\Response(200, array(
            'title' => 'All you pretty things',
            'foo' => 'bar'
        ));
    }

    function template($templateName) {
        $this->after(function ($response) use ($templateName) {
            $smarty = $this->app->smarty;
            if (is_array($response->body)) {
                $smarty->assign($response->body);
            }
            $response->body = $smarty->fetch($templateName);
        });
    }
}

$app = new Tonic\Application();
$app->smarty = new Smarty\Smarty();
$request = new Tonic\Request();
$resource = $app->getResource($request);
$response = $resource->exec();
$response->output();

Full example

For a full project example, checkout the "example" branch which is an orphaned branch containing a Tonic project that exposes a MySQL database table.

tonic's People

Contributors

cooperaj avatar dilan avatar drkibitz avatar kishu27 avatar kontego avatar lordmortis avatar lushchick avatar nalum avatar peej avatar urkle avatar zergin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tonic's Issues

Help with examples

Hi there

I am keen to try your library but I'm having trouble getting the examples going. All I get is a blank page. Can you please give some intructions on how to access them?

Thank you,
chris

baseUri not working?

I've now tried over half an hour on getting the examples to work. I copied the folders docroot examples and lib to a subdirectory (/f/data) on my server.
But after setting the baseUri to:

$request = new Request(array(
'baseUri' => '/f/data/docroot'
));

and accessing "http://example.com/f/data/docroot/" I get:
"A resource matching URI "/f/data/docroot" was not found"

Shouldn't the baseUri be strapped from the URI?

Rails style uris broken when mixing @uri :params and regex.

I have some uri's in my system thats like

@uri /object/:objId
@uri /object/:objId/(full)

The intention is to only allow the extra operator 'full' and not any other arbitrary thing. But when used with the new param code the sequence seems to get messed up and things get overwritten. For some reason :objId becomes empty and $parameters[1] becomes what :objId should have been. I appreciate this is somewhat of an edge case.

Ideally when the uri is '/object/2/full' the $parameters array should look like:

array(0=>2, 1=>'full', objId=>2);

Thanks for running with my idea :)

How to setup tonic for use in subfolder

Hi,

I'd like to use the tonic library in a subfolder ("/sub/") and not in the doc root of my webserver. In the "/sub/dispatch.php" I added the baseUri:
$request = new Request(array(
'baseUri' => '/sub'
));

The uri in my resource looks like: "@uri /sub/user/{userId}

But all I get is a 404 error if I open the link "http://localhost:8080/sub/user/222". What am I doing wrong here? Do I need to change the "/sub/.htaccess" file? Because I also get a 404 when I try to open "http://localhost:8080/sub/phpinfo.php".

If I set this up in the doc root, everythign works fine!

I hope you can help me with this!

Kind regards,
Michael

How to use optional query parameters

I'm building a web service and want to have a uri that looks like this:

/**
 * @uri /1/leaderboards/wp7/{$appKey}?anid={anid}&period={period}&table={table}&count={count}
 */
class Leaderboards_1_WP7 extends Resource
{
  function get($request, $appKey, $anid, $period, $table, $count)
  {
    $response = new Response($request);
    $response->body = "Handling a get request for $appKey! anid: $anid, period: $period, table: $table, count: $count";
    return $response;
  }
}

but when I try to invoke the resource, I just get a 404. If I take out the ? on from the URI and remove the associated parameters of the get method it works fine but obviously that's not ideal. How does one work with optional query parameters in Tonic?

Help: Versioning

Hi,

I am a bit new to the REST interface.
I have managed to get Tonic working how I want it to except for one issue. My idea is to have different versions of classes stored in separate folders. This is mainly for legacy code where if I make changes/additions older calls will still function.

ie
http://mywebsite.com/v0/search/blah/blah/blah
+
http://mywebsite.com/v1/search/blah/blah/blah

Then each of these will will include different class files
eg

D:\tonic\example\v0\example.php
+
D:\tonic\example\v1\example.php

I have tried using an extended class in the D:\tonic\example folder to do this but am at a bit of a loss.

Any help would be greatly appreciated.

Richard

A resource matching URI "/dispatch" was not found

I am getting following issue after placed all file in my htdocs dir.

Deprecated: Assigning the return value of new by reference is deprecated in C:\xampp\php\PEAR\Auth.php on line 469
A resource matching URI "/dispatch" was not found

Please help me .

I am using xampp to run my php file

Arbitrary depth nested resources?

Is it possible to write a url pattern to handle nested resources of an unknown depth?

For instance:

/parent/child/child/child/child/

Trying to achieve a parent/child resource relationship where the user can build any kind of tree they wish and it would be represented in the url.

URI regular expressions with '|' (Pipe) in them do not work.

I spent a bit of time debugging it but I'm just not able to wrap my head around the intricacies of PHP escape character manipulation. It appears that the str_replace function at line 405 puts in two \ (backslashes) and preg doesn't then match.

My log files are also full of PHP Warning: preg_match(): Delimiter must not be alphanumeric or backslash in $PATH on line 406.

In the end I gave up and changed the delimiter to # (hash) and removed the str_replace code.

404 handling

It would be useful to be able to map Tonic's 404 or other error document handling to a resource controller.

Handle multiple representations

How can I know what representation wants the client?

For example:

  • XML
  • PLAIN
  • JSON
  • ...

I haven't seen any example about this.

Thanks!!

Autoloading resource classes

I am not sure if tonic is able to do this already, I've found an autoload routine in the constructor for Request but I am having a hard time figuring out how to get this to work. Is there any documentation on autoloading classes or what the best practice is for tonic to load resource classes? It seems a little impractical to have to include every resource class as you make them.

Autoload broken

Autoload does not work for me:

in Tonic.php:658/333

"class_exists" call triggers the autoload function defined to dynamicaly load resource classes. Need to add FALSE as the second parameter to prevent firing.

in Tonic:471

The __toString is itended to display loaded resources. So, only loaded resources must be filtered. Ad a conditionnal on loaded value

Here is a patch:

Index: tonic.php

--- tonic.php (revision 19)
+++ tonic.php (working copy)
@@ -330,7 +330,7 @@
}

     // load definitions of already loaded resource classes
  •    $resourceClassName = class_exists('Tonic\Resource') ? 'Tonic\Resource' : 'Resource';
    
  •    $resourceClassName = class_exists('Tonic\Resource', FALSE) ? 'Tonic\Resource' : 'Resource';
     foreach (get_declared_classes() as $className) {
         if (is_subclass_of($className, $resourceClassName)) {
    

@@ -469,12 +469,14 @@
}
$str .= 'Loaded Resources:'."\n";
foreach ($this->resources as $uri => $resource) {

  •        $str .= "\t".$uri."\n";
    
  •        if (isset($resource['namespace']) && $resource['namespace']) $str .= "\t\tNamespace: ".$resource['namespace']."\n";
    
  •        $str .= "\t\tClass: ".$resource['class']."\n";
    
  •        $str .= "\t\tFile: ".$resource['filename'];
    
  •        if (isset($resource['line']) && $resource['line']) $str .= '#'.$resource['line'];
    
  •        $str .= "\n";
    
  •       if ($resource['loaded']) {
    
  •           $str .= "\t".$uri."\n";
    
  •           if (isset($resource['namespace']) && $resource['namespace']) $str .= "\t\tNamespace: ".$resource['namespace']."\n";
    
  •           $str .= "\t\tClass: ".$resource['class']."\n";
    
  •           $str .= "\t\tFile: ".$resource['filename'];
    
  •           if (isset($resource['line']) && $resource['line']) $str .= '#'.$resource['line'];
    
  •           $str .= "\n";
    
  •      }
     }
     return $str;
    

    }
    @@ -653,7 +655,7 @@
    $parameters
    );

  •        $responseClassName = class_exists('Tonic\\Response') ? 'Tonic\\Response' : 'Response';
    
  •        $responseClassName = class_exists('Tonic\Response', FALSE) ? 'Tonic\Response' : 'Response';
         if (!$response || !($response instanceof $responseClassName)) {
             throw new Exception('Method '.$request->method.' of '.get_class($this).' did not return a Response object');
         }
    

Smarty

Hi,

Is smarty needed. The latest package uses require_once 'smarty/Smarty.class.php in one of the examples and this class is not present so I disabled this line....

Resource exec does not limit available methods

Resource exec should limit the available methods to the default http methods and allow the developer to override them with whatever custom functions he or she wants to allow. Currently it allows any function in a resource class to be called by a request which unintentionally allows methods within a resource to be called even though it may not be the intention of the application to allow it.

Line 567: if (method_exists($this, $request->method)) {

Here's an example using one of the provided examples (filesystem):

telnet 127.0.0.1 80
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
turnUriIntoFilePath /filesystem http/1.1
host: rest.work

HTTP/1.1 200 OK
Date: Mon, 04 Jul 2011 21:58:22 GMT
Server: Apache
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Pragma: no-cache
Content-Length: 385
Content-Type: text/html


Fatal error: Uncaught exception 'Exception' with message 'Method TURNURIINTOFILEPATH of FilesystemResource did not return a Response object' in /usr/share/php/tonic/lib/tonic.php:590 Stack trace: #0 /usr/share/php/tonic/docroot/dispatch.php(13): Resource->exec(Object(Request)) #1 {main}

thrown in /usr/share/php/tonic/lib/tonic.php on line 590

Connection closed by foreign host.

Same resource class to handle two URIs

Is it possible to set up a resource class to handle two uris? For example I'd like a company listing resource to handle both:

* @uri /business/{current_company_id}/

And

* @uri /business/{current_company_id}/{current_company_nicename}

Not sure what the best way of achieving this is, hoping I don't have to duplicate the resource class.

Issues with Apache configuration

The default VirtualHost on debian/ubuntu has the following options:

Options Indexes FollowSymLinks MultiViews

This causes the following error when accessing a resource in a file named after itself (e.g. if you have an AppleResource class in a file in the doc root called apple.php and make a request to /apple):

Fatal error: Class 'Resource' not found

I think the problem is with MultiViews but Indexes on its own does cause Apache to return "403 Forbidden".

I've tried to force these options to off by adding the following to your .htaccess:

Options -Indexes -FollowSymLinks -MultiViews

Unfortunately this doesn't seem to work. It may be worth adding something about this to your README as I think a fair few debian/ubuntu users may get caught out by this if they're used to just copying and tweaking a standard VirtualHost entry.

How to reliably detect requested HTTP_ACCEPT mime-type?

I need to be able to determine if a request is for JSON or HTML so I can issue a JSON or HTML response. The thing is it appears different browsers send different headers so using the $request->accept array is somewhat useless. IE8 for instance appears to send accept headers as "application/x-shockwave-flash, image/gif, image/jpeg, image/pjpeg, image/pjpeg, /" for HTML. Not useful.

Feature Request: Have query string AND body $request->data payloads

My api use a shared secret key passed as a query string. This allows simple GET requests to be really, well, simple.

But my POST, PUT and DELETE requests also need a data payload (I don't want to clutter the query string with Base64 encoded json).

I'll code in some extra handling for this and post the patch if I can.

POST Request body not being mapped in Tonic V2?

Paul,

Love what you're doing here... Using Tonic to make a quick RESTful framework for a small RIA project, and it's nice and simple and very well thought out. Kudos.

NOW... I'm having an issue with being able to POST data to a Resource because the Request object doesn't appear to actually parse the $_POST parameters. I saw in the Tonic V1 documentation that Request used to have a "body" member but no longer has this in V2 and the "data" member appears to only be populated from the URL query string.

Is this an oversite? Or am I missing something. Namely what I'm trying to do is use a post to pass a value to the Resource to trigger it to initialize a database, and I want to send it a parameter called "init" as either 0 or 1 to tell it to load the DB only if init is set to 1.

Anyway, I have a work around right now, but would love to see some examples of using all of the verbs in Tonic, just to make more sense of their intended use.

Thanks,
Etienne

Change from REQUEST_URI to SCRIPT_NAME breaks framework

The change made 3 days ago that switches from REQUEST_URI to SCRIPT_NAME breaks the framework.

Accessing any url causes the current code to return 'dispatch' as the resource. Presumably this would change to the whatever the name of the bootstrap file was.

Using REQUEST_URI is also broken when there are query strings involved since the code does not strip them when attempting to discover the requested resource.

running examples on windows

Not really an issue, but for anyone else out there that just wants to run the examples and see what the framework is like, I had to make a couple of changes.

After setting my doc root to the tonic docroot folder (i.e. DocumentRoot C:/wamp/www/tonic/docroot), I then had to change the regular expression to avoid looking for the newline in the preg_match call. So to work on windows change line 28 in examples.php to:

preg_match('|/**\s__\s_(.+?)_/|s', file_get_contents($readme), $match);

No API documentation

There is no API documentation on the web.

Run PHPDoctor over the code and push the results into the gh-pages branch.

uriRegex improperly replaces non-rails pattern when using non-capturing subpattern

$uriRegex improperly replaces non-rails pattern when using non-capturing subpattern. Here's an example of a pattern I'm using for the autoloader:

/(?:services?)?

If I try to use that pattern, I get an error saying my regex cannot be compiled. Further investigation shows that this line is to blame:

$uriRegex = preg_replace('#(:[^(/]+|{[^0-9][^}]*})#', '(.+)', $uri);

When replace is run, it outputs this:

/(?(.+)(?:/([A-Za-z]+))?)?

This is obviously wrong as it is an incorrect regular expression and it clobbers by non-capturing subpattern command. So the fix is to use a negative look behind assertion:

$uriRegex = preg_replace('#((?<!?):[^(/]+|{[^0-9][^}]*})#', '(.+)', $uri);

Which Version?

Paul,

I started working with the Tonic V1 Framework two weeks ago and while running into an issue with POST requests stumbled upon the version 2 Framework.

It seems that you have very much restructured the framework and I am a bit hesitant to switch over. So I have few questions.

  1. You seem to have moved completely away from the concept of Adapters, (I have not read the code in detail, I have only skimmed it). The biggest plus for me of Version 1 is the MySQL adapter, how difficult a job will it be for me to port those over, meaning can I just copy them or is the framework so different it won't work?
  2. The Smarty Resource: I see the smarty resource in the example section will it work like the existing Smarty Resource?
  3. Since I am not deep into the project which Framework do you recommend overall and what mental shifts will I need to make if I move to Version 2?

Thanks Paul,
Daniel Carter

HEAD requests should not echo the response

Although I could set the body to null for head requests, I think this should really be handled by tonic. The echo should not even be called for a HEAD request.

/**
 * Output the response
 * @codeCoverageIgnore
 */
function output() {

    if (php_sapi_name() != 'cli' && !headers_sent()) {

        header('HTTP/1.1 '.$this->code);
        foreach ($this->headers as $header => $value) {
            header($header.': '.$value);
        }
    }
    if (strtoupper($this->request->method) !== 'HEAD') {
        echo $this->body;
    }

}

Can't get uri params to passthrough to methods...

peej,

When I'm doing a uri pattern like:
/*

  • @uri /some/resource/:resourceId
    */

class SomeResource extends Resource {
function get($request, $resourceId){
error_log("The resourceId: ".$resourceId);
}
}

And I hit the url as:

http://localhost/some/resource/10

I'm getting the following in the output log:

The resourceId:

Big zippo!

Am I interpretting the docs wrong? I'd try to run the test scripts, but I'm missing the "simpletest/..." includes.

About to give up and start using CodeIgniter for my webservice needs at this point... please help.

Thanks,
Etienne

keep getting

I put all code here: htdocs/library/tonic
dispatch.php
docroot
examples
features
lib
LICENSE
phpdoctor.ini
README.markdown
tests

So when i run http://www.mydomain.com/library/tonic/helloworld, i should get a response, but instead i get:
A resource matching URI "/library/tonic/helloworld" was not found

I really want to use this but i think im calling it wrong. Please help.
Thx,
Clay

White spaces after ?>

I installed the framework and set my apache DocumentRoot to tonic/docroot and expected it to work fine.
However, I got a 500 internal server error and went to look at the source code of dispatch.php.

After the ending php delimter, there was whitespace, so I just removed the ?> tag from that file and the other example files and it worked great.

So if anyone is running into the same problem, please try this solution.

Not so restful URI configuration

Hello,

thanks for this nice REST API.

IMHO URI configuration doesn't allow for basic REST resource management. For instance I'd like to have the following mapping for a photos resource:

GET /photos/:photoId => get an existing photo
POST /photos => create a new photo

with some code like :

/**

  • @uri /photos/:photoId
    */
    class PhotoResource extends Resource {

    public function get($request,$photoId){
    ......
    }

    public function post($request){
    .......
    }

But the following code gives "A resource matching URI "/photos_service/photos" was not found" when trying to POST to /photos since it seems to require a photoId URL parameter for all access to this resource.

Maybe I missed something since this approach is possible with Rails or in JSR 311 like below.

@path(“/customers”)
public class CustomersResource {

@get
@path(“/{email}”)
public Customer getCustomer(@PathParam(“email”) String emailAddress)
{…}

In both rails and JSR311 ways it requires to have lattitude to configure URL at the method/verb level.

Do you have an idea ?

Regards,

Ismaël

Fatal Error: Class Resource not Found

Could you please confirm what exactly are the changes that i need to make in order to set this up. I am getting the Fatal error stating that the class resource is not found e.g. "Fatal error: Class 'Resource' not found in C:\wamp\www\webservice\peej-tonic-f42f161\examples\helloworld\helloworld.php on line 13"

Not sure what I am doing wrong here.

Thanks,
Baran

Missing Location Redirect

When I call a resource defined in tonic, I get the following from the python client (python-rest-client) I have consuming it.

RedirectMissingLocation: Redirected but the response is missing a Location: header.

This doesn't occur when I hit it with curl, or when I consume non tonic resources with the python client. Any ideas?

Resources with & without extra params

Hi!

Great work with Tonic! Amazing how much time it saved for us already!

Here's what I believe is a quickie for you guys, which I cannot get my head around though.

When creating a resource which has an extra param, like below (taken from your readme), I cannot access the example resource without passing the param.

The error " A resource matching URI "/example/1" was not found" is thrown on the URL http://localhost/example, while http://localhost/example/1 is working like intended.

My resource:

/**
 * @uri /example/([a-z]+)
 */
class ExampleResource extends Resource {
    function get($request, $parameter = FALSE) {
        ...
    }
}

Do I really need to make two resources to handle what I believe is a really basic REST pattern?

Thanks

Regex appearing not to satisfy regex requirements

We have a specific form of id we use in our systems: 9999-xxxxxxxx-yyyyyy. I originally was specifying the uri's as

@uri /events/([-0-9a-z]+)

which was not strict enough and caused request problems downstream. I tried to tighten up the regex to properly specify the constraints.

@uri /events/(\d+-[0-9a-f]{8}-[0-9a-f]{6})

However the mandatory lengths of segment 2 and 3 (8 and 6 characters respectively) does not seem to be checked. I am matching on resources with a request of "/events/1234-56-78" when these are malformed. I looked at the tonic code but it was not obvious to me what I am doing incorrectly that is making this not work. Any help would be appreciated.

Image uploading

I am getting the below fatal error , while uploading the image

"Fatal error: Uncaught exception 'Exception' with message 'Method POST of test did not return a Response object' in C:\xampp\htdocs\lib\tonic.php:647 Stack trace: #0"

Please help me and how to upload image using tonic

Tonic should always add the http Allow header (especially with 405 responses)

Tonic should always add the http Allow header especially when it sends a 405 Method not allowed since it's required in this situation as documented here:

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7

Proposed fix:

--- tonic.php 2011-07-19 14:56:00.000000000 -0500
+++ tonic.php 2011-07-27 23:57:44.478494752 -0500
@@ -579,7 +579,8 @@
* @throws ResponseException If the HTTP method is not allowed on the resource, a 405 exception is thrown
*/

function exec($request) {

  •    $allowed_methods = array_intersect(array_map('strtoupper', get_class_methods($this)), 
    
  •        $request->HTTPMethods);
     if (
         in_array(strtoupper($request->method), $request->HTTPMethods) &&
         method_exists($this, $request->method)
    

    @@ -603,6 +604,7 @@
    array($this, $request->method),
    $parameters
    );

  •        $response->addHeader('Allow', implode(', ', $allowed_methods));
    
         $responseClassName = class_exists('Tonic\\Response') ? 'Tonic\\Response' : 'Response';
         if (!$response || !is_a($response, $responseClassName)) {
    

    @@ -614,7 +616,9 @@
    // send 405 method not allowed
    throw new ResponseException(
    'The HTTP method "'.$request->method.'" is not allowed for the resource "'.$request->uri.'".',

  •            Response::METHODNOTALLOWED
    
  •            Response::METHODNOTALLOWED,
    
  •            null,
    
  •            $allowed_methods
         );
    
     }
    

    @@ -773,7 +777,25 @@

    • @namespace Tonic\Lib
      */
      class ResponseException extends Exception {
  • /**

  • \* Allowed resource methods
    
  • \* @var str[]
    
  • */
    
  • public $allowed_methods = array();

  • /**
  • \* Construct
    
  • *
    
  • \* @param mixed $error error message
    
  • \* @param integer $code error code
    
  • \* @param array $allowed_methods allowed methods for the resource
    
  • */
    
  • function __construct($message, $code, Exception $previous = null, array $allowed_methods = array())
  • {
  •    $this->allowed_methods = $allowed_methods;
    
  •    parent::__construct($message, $code, $previous);
    
  • }

/**

  • Generate a default response for this exception
  • @param Request request
    @@ -781,6 +803,9 @@
    */
    function response($request) {
    $response = new Response($request);
  •    if (count($this->allowed_methods)) {
    
  •        $response->addHeader('Allow', implode(', ', $this->allowed_methods));
    
  •    }
     $response->code = $this->code;
     $response->body = $this->message;
     return $response;
    

Current Behavior:

telnet rets.work 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
OPTIONS / http/1.1
host: some.domain

HTTP/1.1 405 Method Not Allowed
Date: Thu, 28 Jul 2011 04:13:07 GMT
Server: Apache
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Pragma: no-cache
Content-Length: 62
Content-Type: text/html

The HTTP method "OPTIONS" is not allowed for the resource "/".

Problem with namespaces

Request is a very generic name. It will probably conflict with other class in a big system like it happend in one of my projects. Adding namespaces works but only after changing 3 lines of hardcoded strings.

public $noResource = 'NoResource';
if (is_subclass_of($className, 'Resource')) {
if (!$response || !is_a($response, 'Response')) {

My solution was to prepand the namespace in front of the strings. Maybe you should use other functions insesitive to namespace ?

URI case sensitivity

I've noticed that uri's are case sensitive, I fixed this by adding the following line to the Request constructor around the part where it's working out the requesting uri

$this->uri = strtolower($this->uri);

I am not sure if the HTTP request APIs are supposed to be case sensitive but I can't see a time when I'd want case sensitive resource responses.

Authentification - Digest and/or Basic?

Hi again Paul,

I've seen your auth-example in the tarball and done some testing with it. Seems like a good and simple way to get up and running.

However with the lack of SSL (I know, it's a shame) I am not too keen of having to ship usernames and passwords to my REST API along with my requests. I've done some research and found out about the HTTP Digest method of securing things a bit harder, but I have absolutely no idea of how to implement it with Tonic.

I actually found this website of yours (http://www.peej.co.uk/projects/phphttpdigest) showing and explaining Digest authentication in a way that even I could understand, but as mentioned - I have a hard time to understand how to make it work alongside with Tonic.

Is this a valid concern of mine or should I go with BASIC Authentication only?

Bug: Accept extacted from url

$key = array_search($parts[0], $this->mimetypes);
should be in fact
$key = array_search($parts[0], array_flip($this->mimetypes));

Oddness with AcceptEncoding and binary truncated data.

This may just be because I have a screwy php cgi implementation (though I don't think so) but if I manually set the acceptEncoding to "" (i.e. blank) to force not compressing the response (debugging things is harder otherwise) then every other request to a resource comes back mangled.

It seems that my web browser is making a favicon request and getting a 404 message returned via Tonic. But that somehow the Content-Length that is returned is too short. The next request for the resource (not the favicon) has the final bytes of the favicon 404 prepended to it breaking it horribly. i.e. I get some binary then a full http request as plain text - so all the headers and then the body content.

Reading http://redmine.lighttpd.net/boards/2/topics/3886 tells a similar story. The fix there was to stop messing with Content-Length headers since invariably they'd be wrong. Mod-PHP on Apache recalculates the correct value and changes it before sending it out which is why I don't think it's been noticed before now.

Commenting out the line where the content length is set fixes this. But I'm not sure it's the correct solution. Perhaps not doing encoding is a way forward? Most frameworks/CMS's/applications leave it up to your PHP or webserver (mod_deflate, mod_compress etc) anyway.

Incorrect Rails route style @uri annotations processing

Or may be correct, but strange.

I wanna use urls like:
/myres/param1:value/param2:value
where params are optional

my regexp looks like:
@uri /myres(?:/param1:([^/]*))?(?:/param2([^/]*))?

in tonic lib at 454 line it converts to the
/myres(?:/param1(.+)/]*))?(?:/param2(.+)/]*))?

as result "Compilation failed: unmatched parentheses at offset" error issued

changing regexp at 454 to
$uriRegex = preg_replace('#(:[^(/]+|{[^}]+})#', '(.+)', $uri);

solves my problem (added opening parenthesis in first negotiation character class), but may be we need more strict checking in case of placeholder? Smth like [a-z-_]?

Zend style classnames?

How amenable are you to having Zend style classnames? It makes life quite a lot easier when using the Zend Loader you see :).

I've gone through my copy and Added 'Tonic_' where necessary as well as splitting into separate files.

Can't get it up.

Hi,

Sorry, but I can't get it working.
I always receive : Oops! This link appears to be broken.

What I did:

  • docroot/dispatch.php >> htdocs/dispatch.php (with modified path's)
  • docroot/.htaccess >> htdocs/.htaccess

Now this should work: localhost/helloworld
But it gives broken link.

thanks for your help.

Adding response uri should not add vary headers

I'm not aware of any specification that says that a vary header must be added when adding a content-location header to the response:

Content-Location:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.14

Vary:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44

I propose that these two lines be removed in the tonic response class __construct:

$this->addVary('Accept');
$this->addVary('Accept-Language');

The vary header is only necessary to add if server-driven content negotiation takes place regardless of the presence of the Content-Location header.

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.