{"id":13993542,"url":"https://github.com/peej/tonic","last_synced_at":"2025-07-22T18:30:27.340Z","repository":{"id":769846,"uuid":"454508","full_name":"peej/tonic","owner":"peej","description":"RESTful PHP library/framework","archived":false,"fork":false,"pushed_at":"2022-11-03T18:39:15.000Z","size":1629,"stargazers_count":624,"open_issues_count":36,"forks_count":127,"subscribers_count":49,"default_branch":"master","last_synced_at":"2025-07-14T06:14:26.182Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://peej.github.com/tonic/","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/peej.png","metadata":{"files":{"readme":"README.markdown","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2009-12-31T16:22:27.000Z","updated_at":"2025-06-20T08:50:49.000Z","dependencies_parsed_at":"2023-01-13T16:22:06.389Z","dependency_job_id":null,"html_url":"https://github.com/peej/tonic","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/peej/tonic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peej%2Ftonic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peej%2Ftonic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peej%2Ftonic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peej%2Ftonic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/peej","download_url":"https://codeload.github.com/peej/tonic/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peej%2Ftonic/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266552231,"owners_count":23947172,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-09T14:02:25.700Z","updated_at":"2025-07-22T18:30:27.066Z","avatar_url":"https://github.com/peej.png","language":"PHP","funding_links":[],"categories":["PHP"],"sub_categories":[],"readme":"PHP library/framework for building Web apps while respecting the 5 principles\nof RESTful design.\n\n * Give every \"thing\" an ID (aka URIs)\n * Link things together (HATEOAS)\n * Use standard methods (aka the standard interface)\n * Resources with multiple representations (aka standard document formats)\n * Communicate statelessly\n\n[See the Tonic site for more info](http://peej.github.com/tonic/).\n\n\nHow it works\n============\n\nEverything is a resource, and a resource is defined as a PHP class. Annotations\nwire a URI to the resource and HTTP methods to class methods.\n\n    /**\n     * This class defines an example resource that is wired into the URI /example\n     * @uri /example\n     */\n    class ExampleResource extends Tonic\\Resource {\n        \n        /**\n         * @method GET\n         */\n        function exampleMethod() {\n            return new Response(Response::OK, 'Example response');\n        }\n      \n    }\n\nThe class method can do any logic it then requires and return a Response object,\nan array of status code and response body, or just a response body string.\n\n\nHow to get started\n==================\n\nThe best place to get started is to get the hello world example running on your\nsystem, to do this you will need a web server running PHP5.3+.\n\n\nInstallation\n------------\n\nThe easiest way to install Tonic is via [Composer](http://getcomposer.org), if you\nare not using or familiar with Composer I recommend you go read up on it.\n\nAdd Tonic to your composer.json file and run composer install/update:\n\n    #composer.json\n    {\n        \"require\": {\n            \"peej/tonic\": \"3.*\"\n        }\n    }\n\n    $ curl -sS https://getcomposer.org/installer | php\n    $ php composer.phar install\n\nAlternatively you can download Tonic from Github and manually place it within your\nproject.\n\n\nBootstrapping\n-------------\n\nTo bootstrap Tonic, use the provided web/dispatch.php script and configure your Web\nserver to push all requests to it via the provided .htaccess file.\n\nFor development purposes you can use PHP's built in Web server by running the following\ncommand:\n\n    $ php -S 127.0.0.1:8080 vendor/bin/dispatch.php\n\nor:\n\n    $ php -S 127.0.0.1:8080 web/dispatch.php\n\nOnce you need more, you can write your own dispatcher with your own custom behaviour.\n\nThe basic premise is to create an instance of Tonic\\Application and pass it's\ngetResource() method a Tonic\\Request instance. Then an incoming request will match\nand load one of your resource classes, execute it, and output the response.\n\nA very basic minimal dispatcher looks something like this:\n\n    require_once '../vendor/autoload.php';\n\n    $app = new Tonic\\Application(array(\n        'load' =\u003e 'example.php'\n    ));\n    $request = new Tonic\\Request();\n\n    $resource = $app-\u003egetResource($request);\n    $response = $resource-\u003eexec();\n    $response-\u003eoutput();\n\n\nFeatures\n========\n\n\nURI annotations\n---------------\n\nResources are attached to their URL by their @uri annotation:\n\n    /**\n     * @uri /example\n     */\n    class ExampleResource extends Tonic\\Resource { }\n\nAs well as a straight forward URI string, you can also use a regular expression\nso that a resource is tied to a range of URIs:\n\n    /**\n     * @uri /example/([a-z]+)\n     */\n    class ExampleResource extends Tonic\\Resource {\n\n        /**\n         * @method GET\n         */\n        function exampleMethod($parameter) {\n            ...\n        }\n    }\n\nURL template and Rails route style @uri annotations are also supported:\n\n    /**\n     * @uri /users/{username}\n     */\n    class ExampleResource extends Tonic\\Resource {\n\n        /**\n         * @method GET\n         */\n        function exampleMethod($username) {\n            ...\n        }\n    }\n    \n    /**\n     * @uri /users/:username\n     */\n    class ExampleResource extends Tonic\\Resource {\n\n        /**\n         * @method GET\n         */\n        function exampleMethod($username) {\n            ...\n        }\n    }\n\nIt is also possible for multiple resource to match the same URI or to have more than\none URI for the same resource:\n\n    /**\n     * @uri /example\n     * @uri /example/([a-z]+)\n     */\n    class ExampleResource extends Tonic\\Resource { }\n\n    /**\n     * @uri /example/apple\n     * @priority 2\n     */\n    class AnotherExampleResource extends Tonic\\Resource { }\n\nBy using the @priority annotation with a number, of all the matching resources,\nthe one with the highest postfixed number will be used.\n\n\nRequest object\n--------------\n\nResource methods have access to the incoming HTTP request via the Request object.\n\nThe Request object exposes all elements of the request as public properties, including\nthe HTTP method, request data and content type.\n\nRequest headers are accessable via public properties named afer a camelcasing of the\nheaders name.\n\n    /**\n     * @uri /example\n     */\n    class ExampleResource extends Tonic\\Resource {\n\n        /**\n         * @method GET\n         */\n        function exampleMethod() {\n            echo $this-\u003erequest-\u003euserAgent;\n        }\n    }\n\n\n\nMount points\n------------\n\nTo make resources more portable, it is possible to \"mount\" them into your URL-space\nby providing a namespace name to URL-space mapping. Every resource within that\nnamespace will in effect have the URL-space prefixed to their @uri annotation.\n\n    $app = new Tonic\\Application(array(\n        'mount' =\u003e array('myBlog' =\u003e '/blog')\n    ));\n\n\nResource annotation cache\n-------------------------\n\nParsing of resource annotations has a performance penalty. To remove this penalty and\nto remove the requirement to load all resource classes up front (and to allow opcode\ncaching), a cache can be used to store the resource annotation data.\n\nPassing a cache object into the Application object at construction will cause that cache to\nbe used to read and store the resource annotation metadata rather than read it from the\nsource code tokens. Tonic comes with two cache classes, one that stores the cache on disk\nand the other which uses the APC data store.\n\nThen rather than including your resource class files explicitly, the Application object\nwill load them for you based on the path stored in the cache and ignore the \"load\"\nconfiguration option.\n\n    $app = new Tonic\\Application(array(\n        'load' =\u003e '../resources/*.php', // look for resource classes in here\n        'cache' =\u003e new Tonic\\MetadataCacheFile('/tmp/tonic.cache') // use the metadata cache\n    ));\n\n\nMethod conditions\n-----------------\n\nConditions can be added to methods via custom annotations that map to another class\nmethod. The resource method will only match if all the conditions return without throwing\na Tonic exception.\n\n    /**\n     * @method GET\n     * @hascookie foo\n     */\n    function exampleMethod() {\n        ...\n    }\n\n    function hasCookie($cookieName) {\n        if (!isset($_COOKIE[$cookieName])) throw new Tonic\\ConditionException;\n    }\n\nThere are a number of built in conditions provided by the base resource class.\n\n    @priority number    Higher priority method takes precident over other matches\n    @accepts mimetype   Given mimetype must match request content type\n    @provides mimetype  Given mimetype must be in request accept array\n    @lang language      Given language must be in request accept lang array\n    @cache seconds      Send cache header for the given number of seconds\n\nYou can also add code to a condition to be executed before and after the resource method.\nFor example you might want to JSON decode the request input and JSON encode the response\noutput of your resource method in a reusable way:\n\n    /**\n     * @method GET\n     * @json\n     */\n    function exampleMethod() {\n        ...\n    }\n\n    function json() {\n        $this-\u003ebefore(function ($request) {\n            if ($request-\u003econtentType == \"application/json\") {\n                $request-\u003edata = json_decode($request-\u003edata);\n            }\n        });\n        $this-\u003eafter(function ($response) {\n            $response-\u003econtentType = \"application/json\";\n            $response-\u003ebody = json_encode($response-\u003ebody);\n        });\n    }\n\n\nResponse exceptions\n-------------------\n\nThe Request object and Resource objects can throw Tonic\\Exceptions when a problem\noccurs that the object does not want to handle and so relinquishes control back\nto the dispatcher.\n\nIf you don't want to handle a problem within your Resource class, you can throw your\nown Tonic\\Exception and handle it in the dispatcher. Look at the auth example for\nan example of how.\n\n\n\nContributing\n============\n\n1. Fork the code on Github.\n\n2. Install the dev dependencies via Composer using the --dev option (or install PHPSpec\nand Behat on your system yourself).\n\n    php composer.phar --dev install\n\n3. Write a spec and then hack the code to make it pass.\n\n4. Create a pull request.\n\nDon't fancy hacking the code? Then [report your problem in the Github issue\ntracker](https://github.com/peej/tonic/issues).\n\nFor more information, read the code. Start with the dispatcher \"web/dispatch.php\"\nand the Hello world in the \"src/Tyrell\" directory.\n\n\n\nCookbook\n========\n\n\nDependency injection container\n------------------------------\n\nYou probably want a way to handle your project dependencies. Being a lightweight\nHTTP framework, Tonic won't handle this for you, but does make it easy to bolt in\nyour own dependency injection container (ie. Pimple http://pimple.sensiolabs.org/).\n\nFor example, to construct a Pimple container and make it available to the loaded\nresource, adjust your dispatcher.php as such:\n\n    require_once '../src/Tonic/Autoloader.php';\n    require_once '/path/to/Pimple.php';\n    \n    $app = new Tonic\\Application();\n\n    // set up the container\n    $app-\u003econtainer = new Pimple();\n    $app-\u003econtainer['dsn'] = 'mysql://user:pass@localhost/my_db';\n    $app-\u003econtainer['database'] = function ($c) {\n        return new DB($c['dsn']);\n    };\n    $app-\u003econtainer['dataStore'] = function ($c) {\n        return new DataStore($c['database']);\n    };\n\n    $request = new Tonic\\Request();\n    $resource = $app-\u003egetResource($request);\n\n    $response = $resource-\u003eexec();\n    $response-\u003eoutput();\n\n\nInput processing\n----------------\n\nAlthough Tonic makes available the raw input data from the HTTP request, it does\nnot attempt to interpret this data. If, for example, you want to process all incoming\nJSON data into an array, you can do the following:\n\n    require_once '../src/Tonic/Autoloader.php';\n\n    $app = new Tonic\\Application();\n    $request = new Tonic\\Request();\n\n    // decode JSON data received from HTTP request\n    if ($request-\u003econtentType == 'application/json') {\n        $request-\u003edata = json_decode($request-\u003edata);\n    }\n\n    $resource = $app-\u003egetResource($request);\n\n    $response = $resource-\u003eexec();\n    $response-\u003eoutput();\n\nWe can also automatically encode the response in the same way:\n\n    $response = $resource-\u003eexec();\n\n    // encode output\n    if ($response-\u003econtentType == 'application/json') {\n        $response-\u003ebody = json_encode($response-\u003ebody);\n    }\n\n    $response-\u003eoutput();\n\n\nRESTful modelling\n-----------------\n\nREST systems are made up of individual resources and collection resources which contain\nindividual resources. Here is an example of an implemention of an \"object\" collection\nresource and an \"object\" resource to store within it:\n\n    /**\n     * @uri /objects\n     */\n    class ObjectCollection extends Tonic\\Resource {\n\n        /**\n         * @method GET\n         * @provides application/json\n         */\n        function list() {\n            $ds = $this-\u003eapp-\u003econtainer['dataStore'];\n            return json_encode($ds-\u003efetchAll());\n        }\n\n        /**\n         * @method POST\n         * @accepts application/json\n         */\n        function add() {\n            $ds = $this-\u003eapp-\u003econtainer['dataStore'];\n            $data = json_decode($this-\u003erequest-\u003edata);\n            $ds-\u003eadd($data);\n            return new Tonic\\Response(Tonic\\Response::CREATED);\n        }\n    }\n\n    /**\n     * @uri /objects/:id\n     */\n    class Object extends Tonic\\Resource {\n\n        /**\n         * @method GET\n         * @provides application/json\n         */\n        function display() {\n            $ds = $this-\u003eapp-\u003econtainer['dataStore'];\n            return json_encode($ds-\u003efetch($this-\u003eid));\n        }\n\n        /**\n         * @method PUT\n         * @accepts application/json\n         * @provides application/json\n         */\n        function update() {\n            $ds = $this-\u003eapp-\u003econtainer['dataStore'];\n            $data = json_decode($this-\u003erequest-\u003edata);\n            $ds-\u003eupdate($this-\u003eid, $data);\n            return $this-\u003edisplay();\n        }\n\n        /**\n         * @method DELETE\n         */\n        function remove() {\n            $ds = $this-\u003eapp-\u003econtainer['dataStore'];\n            $ds-\u003edelete($this-\u003eid);\n            return new Tonic\\Response(Tonic\\Response::NOCONTENT);\n        }\n    }\n\n\nHandling errors\n---------------\n\nWhen an error occurs, Tonic throws an exception that extends the Tonic\\Exception class. You\ncan amend the front controller to catch these exceptions and handle them.\n\n    $app = new Tonic\\Application();\n    $request = new Tonic\\Request();\n    try {\n        $resource = $app-\u003egetResource($request);\n    } catch(Tonic\\NotFoundException $e) {\n        $resource = new NotFoundResource($app, $request);\n    }\n    try {\n        $response = $resource-\u003eexec();\n    } catch(Tonic\\Exception $e) {\n        $resource = new FatalErrorResource($app, $request);\n        $response = $resource-\u003eexec();\n    }\n    $response-\u003eoutput();\n\n\nUser authentication\n-------------------\n\nNeed to secure a resource? Something like the following is a good pattern.\n\n    /**\n     * @uri /secret\n     */\n    class SecureResource extends Tonic\\Resource {\n\n        /**\n         * @method GET\n         * @secure aUser aPassword\n         */\n        function secret() {\n            return 'My secret';\n        }\n\n        function secure($username, $password) {\n            if (\n                isset($_SERVER['PHP_AUTH_USER']) \u0026\u0026 $_SERVER['PHP_AUTH_USER'] == $username \u0026\u0026\n                isset($_SERVER['PHP_AUTH_PW']) \u0026\u0026 $_SERVER['PHP_AUTH_PW'] == $password\n            ) {\n                return;\n            }\n            throw new Tonic\\UnauthorizedException;\n        }\n    }\n\n    $app = new Tonic\\Application();\n    $request = new Tonic\\Request();\n    $resource = $app-\u003egetResource($request);\n    try {\n        $response = $resource-\u003eexec();\n    } catch(Tonic\\UnauthorizedException $e) {\n        $response = new Tonic\\Response(401);\n        $response-\u003ewwwAuthenticate = 'Basic realm=\"My Realm\"';\n    }\n    $response-\u003eoutput();\n\nIf you want to secure a whole collection of resources and don't want to annotate them\nall, you can add the annotation to a parent class and it will be inherited to overridden\nchild methods, or you can add the security logic to the resource's constructor so that\nall of its request methods are secured regardless of annotations.\n\n    /**\n     * @uri /secret\n     */\n    class SecureResource extends Tonic\\Resource {\n\n        private $username = 'aUser';\n        private $password = 'aPassword';\n\n        function setup() {\n            if (\n                !isset($_SERVER['PHP_AUTH_USER']) || $_SERVER['PHP_AUTH_USER'] != $this-\u003eusername ||\n                !isset($_SERVER['PHP_AUTH_PW']) || $_SERVER['PHP_AUTH_PW'] != $this-\u003epassword\n            ) {\n                throw new Tonic\\UnauthorizedException;\n            }\n        }\n\n        /**\n         * @method GET\n         */\n        function secret() {\n            return 'My secret';\n        }\n    }\n\n\nResponse templating\n-------------------\n\nThe use of a templating engine for generation of output is a popular way to separate\nviews from application logic. You can easily create a method condition that adds an\nafter filter to pass the response through a templating engine like Smarty or Twig.\n\n    /**\n     * @uri /templated\n     */\n    class Templated extends Tonic\\Resource {\n\n        /**\n         * @method GET\n         * @template myView.html\n         */\n        function pretty() {\n            return new Tonic\\Response(200, array(\n                'title' =\u003e 'All you pretty things',\n                'foo' =\u003e 'bar'\n            ));\n        }\n\n        function template($templateName) {\n            $this-\u003eafter(function ($response) use ($templateName) {\n                $smarty = $this-\u003eapp-\u003esmarty;\n                if (is_array($response-\u003ebody)) {\n                    $smarty-\u003eassign($response-\u003ebody);\n                }\n                $response-\u003ebody = $smarty-\u003efetch($templateName);\n            });\n        }\n    }\n\n    $app = new Tonic\\Application();\n    $app-\u003esmarty = new Smarty\\Smarty();\n    $request = new Tonic\\Request();\n    $resource = $app-\u003egetResource($request);\n    $response = $resource-\u003eexec();\n    $response-\u003eoutput();\n\n\nFull example\n------------\n\nFor a full project example, checkout the \"example\" branch which is an orphaned branch\ncontaining a Tonic project that exposes a MySQL database table.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeej%2Ftonic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpeej%2Ftonic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeej%2Ftonic/lists"}