{"id":23359793,"url":"https://github.com/firehed/api","last_synced_at":"2025-08-21T23:20:01.052Z","repository":{"id":47670725,"uuid":"42483583","full_name":"Firehed/api","owner":"Firehed","description":"A PHP API toolkit","archived":false,"fork":false,"pushed_at":"2023-04-20T13:58:31.000Z","size":270,"stargazers_count":1,"open_issues_count":20,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-13T22:27:00.196Z","etag":null,"topics":["api","framework","php","php-framework"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Firehed.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-09-14T23:48:25.000Z","updated_at":"2021-08-18T18:01:21.000Z","dependencies_parsed_at":"2024-12-21T11:12:09.298Z","dependency_job_id":"bf5706d4-8355-4c38-978f-284b92f33564","html_url":"https://github.com/Firehed/api","commit_stats":{"total_commits":136,"total_committers":2,"mean_commits":68.0,"dds":"0.42647058823529416","last_synced_commit":"d16e8887355b684acc973f46d52e89b54304c1fd"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Firehed%2Fapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Firehed%2Fapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Firehed%2Fapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Firehed%2Fapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Firehed","download_url":"https://codeload.github.com/Firehed/api/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247729478,"owners_count":20986392,"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":["api","framework","php","php-framework"],"created_at":"2024-12-21T11:11:58.497Z","updated_at":"2025-04-07T20:46:38.169Z","avatar_url":"https://github.com/Firehed.png","language":"PHP","readme":"# API Framework\n\n[![Build Status](https://github.com/Firehed/api/workflows/Test/badge.svg?branch=master)](https://github.com/Firehed/api/actions?query=workflow%3ATest+branch%3Amaster)\n[![codecov](https://codecov.io/gh/Firehed/api/branch/master/graph/badge.svg)](https://codecov.io/gh/Firehed/api)\n\n\n## Installation\n\nAPI is available via composer:\n\n`composer require firehed/api`\n\n## Usage\n\nSet up an `.apiconfig` file, which contains JSON-formatted settings for the framework.\nYou may run `vendor/bin/api generate:config` to do this.\nSee configuration below for additional information.\n\nGenerate a default front-controller:\n\n`vendor/bin/api generate:frontController`\n\nAfter creating, modifying, or deleting endpoints, run the compiler:\n\n`vendor/bin/api compile:all`\n\nThis step is _not_ optional: the framework depends on the generated files, rather than ever attempting to perform the same step at runtime.\nIt is expected that you will rebuild the files on every build/deployment with the above command.\nSee the section on best practices below.\n\n## Testing\n\nFor convenience, a trait is included that includes tests for the description methods of your endpoints.\nIn your test case class (which typically extends `PHPUnit\\Framework\\TestCase`, use the trait:\n\n`\\Firehed\\API\\Traits\\EndpointTestCases`\n\nAnd add a `getEndpoint` method that returns an instance of the endpoint under test.\n\n### Example\n\n```php\n\u003c?php\n\nnamespace MyApp\\API\\Endpoints\\User;\n\nuse Firehed\\API\\Traits\\EndpointTestCases;\n\n/**\n * @covers MyApp\\API\\Endpoints\\User\\Create\n */\nclass CreateTest extends \\PHPUnit\\Framework\\TestCase\n{\n\n    use EndpointTestCases;\n\n    protected function getEndpoint()\n    {\n        return new Create();\n    }\n}\n```\n\n## Configuration\n\nPlace a file named `.apiconfig` in your project root.\nIt uses JSON for the format.\nThere is a console command included to walk you through configuration, which can be invoked by running `vendor/bin/api generate:config`.\n\n### Options\n\n`source`: **required** *string*\n\nThe source code directory to scan for API endpoints.\nMost commonly `src`.\n\n`namespace`: **required** *string*\n\nA namespace to filter on when searching for endpoints.\n\n`webroot`: **required** *string*\n\nThe directory to place a generated front controller.\nThe value should be relative to the project root.\n\n`container`: **optional** *string*\n\nThe path to a file which returns a `PSR-11`-compliant container for config values.\n\n### Container\n\nIf you set a `container` value in `.apiconfig`, the API will be made aware of the container (if you do not use the generated front controller, you may also do this manually).\nThis is how to configure API endpoints at runtime.\nBy convention, if the container `has()` an endpoint's fully-qualified class name, the dispatcher will `get()` and use that value when the route is dispatched.\nIf no container is configured, or the container does not have a configuration for the routed endpoint, the routed endpoint will simply be instantiated via `new $routedEndpointClassName`.\n\nOther auto-detected container entries:\n\n| Key | Usage | Detected |\n|---|---|---|\n| Psr\\Log\\LoggerInterface | Internal logging | generated front controller |\n| Firehed\\API\\Authentication\\ProviderInterface | Authentication Provider | Always if an AuthorizationProvider is set |\n| Firehed\\API\\Authorization\\ProviderInterface | Authorization Provider | Always if an AuthenticationProvider is set |\n| Firehed\\API\\Errors\\HandlerInterface | Error Handler | Always |\n\n\n### Example\n\n`.apiconfig`:\n\n```json\n{\n    \"webroot\": \"public\",\n    \"namespace\": \"Your\\\\Application\",\n    \"source\": \"src\",\n    \"container\": \"config.php\"\n}\n```\n\n`config.php`:\n\n```php\n\u003c?php\nuse Firehed\\API;\nuse Psr\\Log\\LoggerInterface;\nuse Your\\Application\\Endpoints;\n\n$container = new Pimple\\Container();\n// Endpoint config\n$container[Endpoints\\UserPost::class] = function ($c) {\n    return new Endpoints\\UserPost($c['some-dependency']);\n};\n\n// Other services\n$container[API\\Authentication\\ProviderInterface::class] = function ($c) {\n    // return your provider\n};\n$container[API\\Authorization\\ProviderInterface::class] = function ($c) {\n    // return your provider\n};\n$container[LoggerInterface::class] = function ($c) {\n    return new Monolog\\Logger('your-application');\n};\n\n// ...\nreturn new Pimple\\Psr11\\Container($container);\n```\n\nIn this example, when your `UserPost` endpoint is routed, it will use the endpoint defined in the container - this allows for endpoints with required constructor arguments or other configuration.\n\nIf you have e.g. a `UserGet` endpoint which is _not_ in the container, the dispatcher will automatically attempt to instantiate it with `new`.\nIf that endpoint has no constructor arguments, this will be fine.\nHowever, this means your application will crash at runtime if it does - so any endpoints with required constructors **must** be configured in the container.\n\n## Authentication and Authorization\n\nThere are two interfaces defined for the processes of authentication (who is performing the request) and authorization (whether they are allowed to perform the request), respectively named `Authentication\\ProviderInterface` and `Authorization\\ProviderInterface`.\nBoth interfaces will be autodetected in a container, and both must be provided.\nIf both of these are not provided, **no authentication or authorization will ever be performed using the application-wide handlers**.\n\nAny endpoint that implements `Interfaces\\AuthenticatedEndpointInterface` will have these processes performed prior to execution, and the container returned by the Authentication provider will be made available to it.\nIf an endpoint does not implement `Interfaces\\AuthenticatedEndpointInterface` (i.e. it only implements `Interfaces\\EndpointInterface`), **application-wide auth will be skipped**.\nEndpoints may, of course, choose to implement their own auth protocols in their `execute()` method, but this is discouraged, with the exception of login-type pages (see below).\n\nGenerally speaking, implementations for the above interfaces should be looking for authentication data present in (almost) every request: cookies, OAuth Bearer tokens, HTTP basic auth, etc., and validating their authenticity.\nEndpoints that are used to obtain auth data (e.g. OAuth grant) typically will NOT be authenticated themselves, but will set or return the data to be used to authenticate other requests.\n\nExample provider, which implements both interfaces:\n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Your\\Project;\n\nuse Firehed\\API\\Authentication\\ProviderInterface as AuthnProvider;\nuse Firehed\\API\\Authorization\\Exception as AuthException;\nuse Firehed\\API\\Authorization\\ProviderInterface as AuthzProvider;\nuse Firehed\\API\\Authorization\\Ok;\nuse Firehed\\API\\Container;\nuse Firehed\\API\\Interfaces\\AuthenticatedEndpointInterface;\nuse Psr\\Container\\ContainerInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\n\n// This elides a lot of details and error handling for simplicity\nclass AuthProvider implements AuthnProvider, AuthzProvider\n{\n    public function authenticate(ServerRequestInterface $request): ContainerInterface\n    {\n        list($_, $token) = explode(' ', $request-\u003egetHeaderLine('Authorization'), 2);\n        // Find a user, app, etc. from the token string\n        return new Container([\n            App::class =\u003e $app,\n            User::class =\u003e $user,\n            // ...\n            'oauth_scopes' =\u003e $scopes,\n        ]);\n    }\n\n    public function authorize(AuthenticatedEndpointInterface $endpoint, ContainerInterface $container): Ok\n    {\n        $scopes = $container-\u003eget('oauth_scopes');\n        if (!$endpoint instanceof YourInternalScopeInterface) {\n            throw new \\LogicException('Endpoint is invalid');\n        }\n        // This is a method in YourInternalScopeInterface\n        $neededScopes = $endpoint-\u003egetRequiredScopes();\n        foreach ($neededScopes as $scope) {\n            if (!in_array($scope, $scopes)) {\n                throw new AuthException(sprintf('Missing scope %s', $scope));\n            }\n        }\n        return new Ok();\n    }\n}\n```\n\n## Error Handling\n\nIt is strongly discouraged to handle most exceptions that are thrown in an Endpoint's `execute()` method.\nInstead, prefer to write services that fail loudly by throwing exceptions and endpoints that expect the success case.\nThis is not an absolute rule, but helps avoid deeply-nested `try`/`catch` blocks and other complexity around error handling.\n\nThe API framework is responsible for catching all exceptions thrown during an Endpoint's `execute()` method, and will provide them to dedicated exception handlers.\n\nIt is highly recommended to create and provide a default error handler, `Firehed\\API\\Errors\\HandlerInterface`, via the container (see table above).\nAll unhandled exceptions will be sent to that handler, along with the request (so that responses can be formatted according to `Accept` headers, etc).\n\nAll endpoints that implement `Firehed\\API\\Interfaces\\HandlesOwnErrorsInterface` (which is a part of `EndpointInterface` prior to v4.0.0) will have their `handleException()` method called with the thrown exception.\nThis handler will be called _before_ the default error handler.\nThis method _may_ choose to ignore certain exception classes (by rethrowing them), but must return a PSR `ResponseInterface` when opting to handle an exception.\n\nFinally, a global fallback handler is configured by default, which will log the exception and return a generic 500 error.\n\n## Best Practices\n\n### Source Control\n\nUse source control, of course.\n\nThe following patterns should be added to your source control's ignored files, to exclude generated files:\n\n- `__*__.*`\n\n### Build Automation\n\nIt is highly recommended (for any modern PHP application) to use automated builds.\n\nThis framework relies on compilation in order to improve performance.\nYou **must** run the compilation process prior to deployment, and **should** do so during your automated build:\n\n`vendor/bin/api compile:all`.\n\n### Docker\n\nThere are no special requirements to run in Docker, beyond what is noted in the above build automation section.\n\nThis means you should have the following line in your Dockerfile at any point after installing dependencies with Composer:\n\n```Dockerfile\nRUN vendor/bin/api compile:all\n```\n\nYou **should** also add all of the source control ignore files to your `.dockerignore`.\n\n## Compatibility\n\nThis framework tries to strictly follow the rules of Semantic Versioning.\nIn summary, this means that given a release named `X.Y.Z`:\n\n- Breaking changes will only be introduced when `X` is incremented\n- New features will only be introduced either when `Y` is incremented or when `X` is incremented and `Y` is reset to `0`\n- Bugfixes may be introduced in any version increment\n\nThe term \"breaking changes\" should be interpreted to mean:\n\n- Additional required parameters being added to methods\n- Additional methods being added to interfaces\n- Tightening the typehints of a method or function parameter\n- Loosening the return type of a method or function\n- Deletion of any public method (except on classes marked as internal)\n- Additional system requirements (PHP version, extensions, etc.)\n- Substantial, non-optional behavior changes\n- Required modifications to documented build steps (e.g. `vendor/bin/api compile:all`)\n\nBreaking changes DO NOT include:\n\n- Removal of a dependency (if you are implicitly relying on a dependency of this framework, you should explicitly add it into your own `composer.json`)\n- Removal of a class or method that is clearly marked internal\n- Format or content changes to any files that are intended to be generated during the compilation process, including adding or removing files entirely\n\nWhenever possible, deprecated functionality will be marked as such by `trigger_error(string, E_USER_DEPRECATED)` (in addition to release notes).\nNote that depending on your PHP settings, this may result in an `ErrorException` being thrown.\nSince that is a configurable behavior, it is NOT considered to be a BC break.\n\nAdditionally, the entire `Firehed\\API` namespace should be considered reserved for the purposes of PSR-11 Container auto-detection.\nThat is to say, if you use a key starting with `Firehed\\API` in your container, you should expect that key may be retrieved and used without explicitly opting-in to the behavior it provides.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffirehed%2Fapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffirehed%2Fapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffirehed%2Fapi/lists"}