{"id":18750682,"url":"https://github.com/webiny/rest","last_synced_at":"2025-04-12T23:32:23.102Z","repository":{"id":20228317,"uuid":"23500172","full_name":"webiny/Rest","owner":"webiny","description":"[READ-ONLY] A simple but powerful PHP REST library that doesn't get in the way. Supports rate control, crud, versioning, cache and json output. Create RESTful APIs with ease. (master at Webiny/Framework)","archived":false,"fork":false,"pushed_at":"2017-11-26T21:25:12.000Z","size":89,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-03-26T18:06:08.001Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://www.webiny.com/","language":"PHP","has_issues":false,"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/webiny.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2014-08-30T20:31:38.000Z","updated_at":"2023-05-21T17:38:10.000Z","dependencies_parsed_at":"2022-08-25T20:40:21.284Z","dependency_job_id":null,"html_url":"https://github.com/webiny/Rest","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webiny%2FRest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webiny%2FRest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webiny%2FRest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webiny%2FRest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webiny","download_url":"https://codeload.github.com/webiny/Rest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248647257,"owners_count":21139081,"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","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-11-07T17:12:49.389Z","updated_at":"2025-04-12T23:32:20.715Z","avatar_url":"https://github.com/webiny.png","language":"PHP","readme":"REST Component\n================\n\nA simple but powerful REST library that doesn't get in the way.\n\nInstall the component\n---------------------\nThe best way to install the component is using Composer.\n\n```bash\ncomposer require webiny/rest\n```\nFor additional versions of the package, visit the [Packagist page](https://packagist.org/packages/webiny/rest).\n\n## Usage\n\nSome of the built-in features:\n- supports **GET**, **POST**, **PUT**, **PATCH** and **DELETE** requests\n- resource naming (via `@rest.url` annotation)\n- integrated version management system\n- effective Rate Control mechanism\n- services are configured using annotations\n- built in Cache using [Webiny Framework Cache component](../Cache/)\n- built in ACL using [Webiny Framework Security component](../Security/)\n- built in routing using [Webiny Framework Router component](../Router/)\n- nice debug options\n- pretty formatted JSON output (only in development mode)\n- CRUD support\n\n## Usage example\n\n```php\n// create REST instance for the given configuration and the API class\n$rest = new \\Webiny\\Component\\Rest\\Rest('InternalApi', '\\MyApp\\Services\\TestService');\n\n// process the request and send the output to browser\n$rest-\u003eprocessRequest()-\u003esendOutput();\n\n// simple as that...\n```\n\n## Configuration and dependencies\n\nThis is an example configuration:\n```yaml\nRest:\n    ExampleApi:\n\t\tCompilePath: /var/tmp\n\t\tRouter:\n            Class: \\Foo\\Bar\\MyServices\\{foo}\\{bar}\n            Path: /services/{test}/{foo}/{mock}/{bar}\n            Normalize: true\n    MiddlewareApi:\n        CompilePath: /var/tmp\n        Middleware: \\My\\Custom\\Handler\n        Router:\n            Class: \\Foo\\Bar\\MyServices\\{foo}\\{bar}\n            Path: /services/{test}/{foo}/{mock}/{bar}\n            Normalize: true\n\tSomeOtherApi:\n\t    CompilePath: /var/www/Cache/Rest\n        Cache: someCacheService\n        Security:\n            Role: ROLE_ANONYMOUS\n            Firewall: Admin\n        RateControl:\n         Limit: 60\n         Interval: 1 # in minutes\n         Penalty: 10 # in minutes\n        Environment: production\n```\n\nAs you can see, you can have multiple REST configurations. The minimum that one configuration must have is just the\ndefinition of `CompilePath`.\n\n### Configuration parameters:\n\n#### **CompilePath**\nThis is the absolute path to a folder where the REST component will store the compiled files.\n\n*If you want to know more:*\nWhen you register a class, or in API naming, a \"service\", the component will parse through that class and all its\nmethods and their annotations, which would be then evaluated based on different rules, to define the service behaviour.\nAll this is then saved an array that is actually stored in a file, that we call the compile cache file.\n\n#### **Router**\nThis is an optional setting, it tells to the Rest component how it should transform the current url to get the service\n class name. However to trigger that mechanism, the url must match the `Path` parameter. Variables in brackets will act\n as patterns to match certain parts of the url. Those matches can then be used to create the `Class` name.\n You will find more about routing in the [Routing and accessing the APIs](#routing-and-accessing-the-apis) section.\n\n#### **Cache**\nAs stated before, the component uses the [Cache component](../Cache/) from Webiny Framework. The value of the `Cache`\nshould point to a defined cache service.\n\nThe cache is used for two different operations, one is to provide a caching layer for storing results,\nand the other one is that Cache is a requirement if you wish to use the Rate Control mechanism.\n\n#### **Security**\nSecurity section provides a layer for authorization and authentication above your REST APIs. It is dependent upon the\n[Security](../Security) component.\n\nThe configuration takes two parameters:\n- `Firewall`: name of the registered firewall on the Security component configuration.\n- `Role`: this is the default role that all users need to have in order to access the API. You can overwrite the required role in the annotations. If you don't want to force the role, you can either remove this part from your configuration, or set it to `ROLE_ANONYMOUS`, which will then allow access to all users, unless it's overwritten by a method or class annotation.\n\nIf the user doesn't have access, a **403 - Forbidden** response is returned.\n\n#### **RateControl**\nRate control is a protection mechanism preventing anyone from abusing your REST API in a way the he is making too many requests in a short period of time.\n\nWith rate control you set the following parameters:\n- `Limit`: how many requests per interval a particular IP can make\n- `Interval`: after how many minutes should be reset the limit\n- `Penalty`: for how long should we block the IP if it has reached the limit\n\nIf limit is reached, and penalty is activated, the component will return **429 - Too Many Requests** response, until the\nlimit is restored.\n\nNote that the rate control mechanism requires that you have `Cache` specified on that REST configuration.\n\n#### **Environment**\nThe value of `Environment` attribute can either be 'production' or 'development'. The difference is that in development\nmode we constantly rebuild the compiled cache files, we also output special debug response headers, and JSON output uses pretty format.\n\n#### **Middleware**\nIf this parameter is set, it should point to your class that implements `MiddlewareInterface`. This gives you control over execution of your REST service method.\nEverything until and after the execution of the service is done by the component. The component passes the `RequestBag` to your middleware, and it is up to you \nhow you will execute the method, maybe perform additional checks, or some custom business logic. Whatever you do, the return value will be passed directly to \nthe REST component and will be used as a service result. \n\n## Class and method annotations\n\n```php\n/**\n * @rest.role ROLE_EDITOR\n */\nclass FooService\n{\n    /**\n     * @rest.method get\n     * @rest.default\n     * @rest.ignore\n     * @rest.cache.ttl 100\n     * @rest.header.cache.expires 3600\n     * @rest.header.status.success 200\n     * @rest.header.status.errorMessage No Author for specified id.\n     * @rest.rateControl.ignore\n     * @rest.url some/custom/url/{param1}/param2/{param2}/other/{param3}\n     *\n     * @param integer $param1 Some param.\n     * @param string $param2 Other param.\n     * @param string $param3 Other param.\n     */\n    function fooMethod($param1, $param2 = \"default\", $param3 = \"p3def\")\n    {\n        ...\n    }\n}\n```\n\nAnnotations are a way of describing certain properties of an object. With annotations you can configure the behaviour\nof your service. All the REST component annotations have a `rest` namespace.\n\nAll the annotations can be defined on a class level, making them default to methods, and on the method level you can\noverwrite them. There are no required annotations.\n\n**When one class extends another, where the child class is actually your REST API, the parent class automatically\npasses, its class and method, annotations the child class, so make sure when overwriting methods, that you also \noverwrite the annotations, if necessary.**\n\n\n### The following annotations are available:\n\n#### **@rest.method**\n\n```php\n/**\n * @rest.method get\n */\n```\n\nDefines over which HTTP method the service can be accessed. If not defined, `get` is set as default. The supported\nrequest types are **GET**, **POST**, **PUT**, **PATCH** and **DELETE**.\nThey are not case sensitive.\n\n#### **@rest.default**\n\n```php\n/**\n * @rest.default\n */\n```\n\nDefines that this method is the default method for the defined `@rest.method` request type.\nFor example if you have `@rest.method` set to `post` and the method is marked with `@rest.default` and you do a POST\nrequest just to the service name, without the method, the defined default method for POST request will be triggered.\n\n#### **@rest.ignore**\n\n```php\n/**\n * @rest.ignore\n */\n```\n\nThis flag tells to the component that it should ignore that method and that it's not part of the service.\nUsually used for some internal methods.\n\n#### **@rest.cache.ttl**\n\n```php\n/**\n * @rest.cache.ttl 100\n */\n```\n\nMarks that the returned result from this method can be cached for the specific amount of time. The time is defined in\nseconds. Note that this feature requires that you have a `Cache` service defined in your configuration.\n\n#### **@rest.header**\n\nThere are several options in the `header` section that you can control:\n- `cache.expires`: defines what ttl will be set in `Expires` header that component will send to the browser. If you don't set it, it will be set to '-1' telling the browser to always grab fresh content from the server.\n- `status.success`: what response status code should be returned if the request was successful. By default **200 - OK** is returned, with an exception of **201 - Created** for **POST** requests.\n- `status.errorMessage`: defines a custom error message that will be attached to the response status code.\n\n\n#### **@rest.role**\n\n```php\n/**\n * @rest.role ROLE_EDITOR\n */\n```\nDefines that a method can only be accessed by users that have the specific, or higher, access level.\n\nThis annotation requires that you define the `Security` section in your configuration.\n\n#### **@rest.rateControl.ignore**\n\n```php\n/**\n * @rest.rateControl.ignore\n */\n```\n\nThis flag marks that rate control will not be applied to that method.\n\n#### **@rest.url**\n\n```php\n/**\n * @rest.url some/custom/url/{param1}/param2/{param2}/other/{param3}\n */\n```\nThis annotation provides the resource naming feature by specifying a custom url that will be used in the url matching, \ninstead of the `method name`.\n**Note:** When using resource naming, you cannot use `@rest.default` annotation on that method, and also you cannot \nspecify optional parameters.\n\n\n## Routing and accessing the APIs\n\nThis is an example `Router` config. \n\n```yaml\nRest:\n    ExampleApi:\n        Router:\n            Class: \\Foo\\Bar\\MyServices\\{foo}\\{bar}\n            Path: /services/{test}/{foo}/{mock}/{bar}\n            Normalize: true\n```\n\nThe config takes the following parameters:\n\n#### Class\nThis parameter tells to the `Router` how it should implement the matching parameters from the url and the `Path` to get\n the class name used for the called Rest service.\n \n#### Path\nPath is a url pattern that the component tries to match agains the current url. If a match is made, the matched parameters are used to create \nthe `Class` name. All the patterns are inside curly brackets `{foo}` and `([\\w-]+)` regex pattern is used for matching.\n\n#### Normalize \nThis is an optional feature. It tells to the component if the matched parameters should be normalized. In this case \nunder \"normalize\" we consider transforming parameter value like this one `some-application` into this `SomeApplication`.\n\n#### Example\nLet's say you have the upper configuration example in place. The following url will produce the example class name.\n\nUrl: `http://www.hats.com/services/my-app/some-longer-name/test/pac-man`\n\nClass: `\\Foo\\Bar\\MyServices\\SomeLongerName\\PacMan`\n\n#### Some pre-requirements\nAll you need to do is set on your web server that all requests should be routed to a single file, for example `rest.php`\n On that file call the static `iniRest` method with the API name. That method returns a new Rest instance where\n you can call the `processRequest` method that triggers the service call. If the url is not matched boolean `false` is returned.\n \n```php\ntry{\n    $rest = Rest::initRest('ExampleApi');\n    if($rest){\n        $rest-\u003eprocessRequest()-\u003esendOutput();\n    }    \n}catch (RestException $e){\n    // handle the exception\n}\n```\n\n\n## Interfaces\n\nThe component provides several interfaces that you can implement on your API class to gain more control over some aspects\nof the component.\n\nAll the interfaces are under the a namespace `Webiny\\Component\\Rest\\Interfaces\\`.\n\n### Versioning and VersionInterface\n\nThe component gives you the option to version your APIs, meaning that you can have multiple active version of one API.\nThis helps a lot when you are deploying a new version, but you still need to support the old one.\n\nAlso you have two version aliases, making things even more simpler for you. The alias is nothing but a pointer to an actual version.\nThe two available aliases are `latest` and `current`. If somebody requests your API, and if he hasn't defined a version, he will be pointed\nto the `current` version, which is then mapped to an actual version.\n\nIn order to implement versioning feature, you need to implement `Webiny\\Component\\Rest\\Interfaces\\VersionInterface` on your class.\nThis looks something like this:\n\n```php\nclass FooService implements \\Webiny\\Component\\Rest\\Interfaces\\VersionInterface\n{\n\n    static public function getLatestVersion(){\n        return '2.0';\n    }\n\n    static public function getCurrentVersion(){\n        return '1.0';\n    }\n\n    static public function getAllVersions(){\n        return [\n            '1.0' =\u003e 'FooService',\n            '2.0' =\u003e 'FooServiceNew',\n            '2.1' =\u003e 'FooServiceBetaInTesting'\n        ];\n    }\n}\n```\n\nThe interface will tell you to implement the upper three methods, `getLatestVersion`, `getCurrentVersion` and `getAllVersions`.\nThe most important method is the `getAllVersions` which returns an array of supported versions where the key is the version number,\nin format X.Y, and the class name is the value. This is the class that will be used to handle the requests.\n\nNote that only the 'main' class needs to be registered with the component `$rest = new \\Webiny\\Component\\Rest\\Rest('InternalApi', 'FooService');`.\nAlso the main class is the only one that needs to implement the interface, making everything a whole lot easier to maintain.\n\n#### How to access a specific version\n\nBy default all users will be pointed to the `current` version. To make a request to a specific version you need to add a request header.\nThe request header name is `X-Webiny-Rest-Version` and the value is the version. For the version you can send a specific version\nnumber, or an alias. All requests that have this header will be mapped to that concrete version.\n\n// point the request to version 2.1\n```txt\nX-Webiny-Rest-Version: 2.1\n```\n\n### AccessInterface\n\nIf you wish to implement your own security layer, you can implement the `Webiny\\Component\\Rest\\Interfaces\\AccessInterface`.\n\n```php\nclass FooService implements \\Webiny\\Component\\Rest\\Interfaces\\AccessInterface\n{\n\n    public function hasAccess($role)\n    {\n        // do you processing here\n    }\n}\n```\n\nThe interface will ask you to define `hasAccess` method. This method takes only one parameter `$role`. This parameter\ncontains the value defined in `@rest.role` annotation. The method should return either `true` or `false`, allowing or denying access to the user.\n\nNote that you still need to define the `Security` section in your REST configuration. The configuration should only contain the default required role. Don't define the `Firewall` attribute.\n\n```yaml\nRest:\n    SomeOtherApi:\n        CompilePath: /var/www/Cache/Rest\n        Security:\n            Role: ROLE_ANONYMOUS\n```\n\n`ROLE_ANONYMOUS` allows non authenticated users to call the service. \nYou can overwrite the required role with the `@rest.role` annotation on a per-class and per-method basis.\n\n\n### CacheKeyInterface\n\nRest component, by default, creates cache keys from these parameters:\n - url path\n - query parameters\n - http method\n - post parameters\n - payload parameters\n - api version (we use the actual version number, not the aliases like current, and latest)\n\nImplement this interface to define your own method for generating a cache key.\nSome common use cases are to generate a cache key based on some cookie or token.\nNote that you should still include the url, query parameters and the http method.\nAlways take into account that generating the cache key doesn't actually take longer than getting the data without cache.\n\nThe implementation looks like this:\n\n\n```php\nclass FooService implements \\Webiny\\Component\\Rest\\Interfaces\\CacheKeyInterface\n{\n\n    public function getCacheKey($role)\n    {\n        // return your generated key\n    }\n}\n```\n\nNote that the returned key is used \"as it is\", nothing is appended to it, nor it is hashed, so make sure that you\nreturn a key with a proper size.\n\n### CrudInterface\n\nImplementing this interface you will get the basic CRUD methods and behavior described in the table below:\n\n|  Request type   |        Url         |       Mapping               |  Description                           |\n| --------------- | ------------------ | --------------------------- | -------------------------------------- |\n|      GET        |    foo-class/      |  FooClass::crudList         | Retrieve all records in a collection.  |\n|      POST       |    foo-class/      |  FooClass::crudCreate()     | Create new record.                     |\n|      DELETE     |    foo-class/{id}  |  FooClass::crudDelete($id)  | Delete a record with the given id.     |\n|      GET        |    foo-class/{id}  |  FooClass::crudGet($id)     | Retrieve a single record.              |\n|      PUT        |    foo-class/{id}  |  FooClass::crudReplace($id) | Replace a single record.               |\n|      PATCH      |    foo-class/{id}  |  FooClass::crudUpdate($id)  | Update a single record.                |\n\n\n## RestTrait\n\nIn practice you often need to use things like paging, sorting and similar,\nwhich doesn't make since to put as a parameter in your method. The best approach is to use query parameters.\nThe `RestTrait` provides you with helper functions and suggestions.\n\nIn the trait you will find the next methods:\n- `restGetPage`: returns the value of `_page` query parameter\n- `restGetPerPage`: returns the value of `_perPage` query parameter (has a built-in limit of 1.000)\n- `restGetSortField`: returns the sort field name from the `_sort` query parameter\n- `restGetSortFields`: returns the sort fields array, parsed from the `_sort` query parameter\n- `restGetSortDirection`: returns the sort direction from the `_sort` query parameter\n- `restGetFields`: returns the value of `_fields` query parameter\n\nLet's see the returned values if we would look at this url:\n`http://api.example.com/my-service/get-pages/?_page=1\u0026_perPage=10\u0026_sort=+Title\u0026_fields=id,title,author,slug`\n\nThe returned values would be as following:\n- `restGetPage`: 1\n- `restGetPerPage`: 10\n- `restGetSortField`: Title\n- `restGetSortDirection`: 1 (if we would have '-' in front of the field name, the function would return -1)\n- `restGetSortFields`: ['Title' =\u003e 1]\n- `restGetFields`: id,title,author,slug\n\n## Return values\n\nThe component returns a JSON response, like the one below:\n\n```json\n{\n    \"data\": \"this is my result\"\n}\n```\n\nYour result is always encapsulated within the `data` property.\n\nIn case of an error, the `data` property is omitted, and you will get a response containing errors, like this one:\n\n```json\n{\n    \"errorReport\": {\n        \"message\": \"This is an error.\",\n        \"description\": \"Some custom error description.\"\n    }\n}\n```\n\nYou can also add additional error entries:\n\n```json\n{\n    \"errorReport\": {\n        \"message\": \"This is an error.\",\n        \"description\": \"Some custom error description.\",\n        \"errors\": [\n            {\n                \"message\": \"This is an additional error message.\",\n                \"field\": \"This is a custom error field.\"\n            },\n            {\n                \"message\": \"Another error\",\n                \"code\": \"23a33\"\n            }\n        ]\n    }\n}\n```\n\n### Throwing errors\n\nWhen you need to throw an error, the best way is using the RestErrorException class.\n\n```php\nclass FooService\n{\n    public function testError()\n    {\n        $error = new \\Webiny\\Component\\Rest\\RestErrorException(\"This is an error.\", \"Some custom error description.\");\n        $error-\u003eaddError(['message'=\u003e'This is an additional error message.', 'field'=\u003e'This is a custom error field.']);\n        $error-\u003eaddError(['message'=\u003e'Another error', 'code'=\u003e'23a33']);\n\n        throw $error;\n    }\n}\n```\n\n## Debugging\n\nThe component will return additional debug information in the response header and in the `debug` part of the response body.\nThe additional headers are as following:\n\n- `X-Webiny-Rest-Class`: name of the used API class (useful to know which class was used based on the version)\n- `X-Webiny-Rest-ClassVersion`: actual API version\n- `X-Webiny-Rest-Method`: which HTTP request method was used\n- `X-Webiny-Rest-RateControl-Limit`: the limit of rate control (also present in the production mode)\n- `X-Webiny-Rest-RateControl-Remaining`: the remaining number of requests, until the limit is reached (also present in the production mode)\n- `X-Webiny-Rest-RateControl-Reset`: unix timestamp with the date when the rate control limit will be refreshed\n- `X-Webiny-Rest-RequestedRole`: present only if the method required some specific role\n\nHere is typical example output:\n```txt\nX-Webiny-Rest-Class:TestRestApiServiceNew\nX-Webiny-Rest-ClassVersion:2.0\nX-Webiny-Rest-Method:GET\nX-Webiny-Rest-RateControl-Limit:10\nX-Webiny-Rest-RateControl-Remaining:8\nX-Webiny-Rest-RateControl-Reset:1408414512\nX-Webiny-Rest-RequestedRole:SECRET_ROLE\n```\n\n## Compiler cache\n\nThe component reads the api classes and creates an array that contains different information about the api services \ncontained within that class. Based on the component environment, that array will be saved. If the environment is `development` \nthe cached array will be stored in memory inside a static array. If the environment is `production` the array will be written \nto the disk, using the `CompilePath`.\n\nIf you wish to create your own compiler cache class, and write, for example to a Redis database, you just need to create a \nclass and implement `\\Webiny\\Component\\Rest\\Compiler\\CacheDrivers\\CacheDriverInterface` and define the path to the class\ninside your Rest component config, like this:\n\n```yaml\nRest:\n    ExampleApi:\n        CompilerCacheDriver: '\\Vendor\\Namespace\\Class'\n```\n\nResources\n---------\n\nTo run unit tests, you need to use the following command:\n\n    $ cd path/to/Webiny/Component/Rest/\n    $ composer.phar install\n    $ phpunit\n\nMake sure that you update the configuration files inside `Test/Mocks/` folder.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebiny%2Frest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebiny%2Frest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebiny%2Frest/lists"}