{"id":18736657,"url":"https://github.com/radiergummi/shore","last_synced_at":"2025-06-28T11:06:42.697Z","repository":{"id":88617356,"uuid":"151399000","full_name":"Radiergummi/shore","owner":"Radiergummi","description":"Shore is a lean PHP framework for APIs.","archived":false,"fork":false,"pushed_at":"2018-10-08T22:51:21.000Z","size":154,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-07T07:40:58.248Z","etag":null,"topics":["api","api-server","framework-php","middleware","php","php-router","router"],"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/Radiergummi.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2018-10-03T10:58:06.000Z","updated_at":"2020-11-03T02:24:53.000Z","dependencies_parsed_at":null,"dependency_job_id":"0af04a50-7a29-416f-bbdc-38dc3f15ce05","html_url":"https://github.com/Radiergummi/shore","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Radiergummi/shore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Radiergummi%2Fshore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Radiergummi%2Fshore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Radiergummi%2Fshore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Radiergummi%2Fshore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Radiergummi","download_url":"https://codeload.github.com/Radiergummi/shore/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Radiergummi%2Fshore/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262419807,"owners_count":23308100,"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","api-server","framework-php","middleware","php","php-router","router"],"created_at":"2024-11-07T15:22:00.738Z","updated_at":"2025-06-28T11:06:42.680Z","avatar_url":"https://github.com/Radiergummi.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Shore\nA lean framework for PHP-driven APIs\n\n--------\n\nShore is an opinionated framework for building APIs. It get's out of your way mostly, as it only takes care of handling \nrequests and responses.  \nAs any other web framework, Shore lets you define _routes_ that can be hooked to closures or controllers. Route handlers\nwill always be called in context of the application container, which holds the services you registered.  \nOf course, there's support for middleware, too. \n\n**Beware, though: Shore does _not_ implement PSR-7 HTTP messages. If that's a killer for you, I'm sorry.** The \nreasoning being, I find the responses way too bloated. I don't want to use streams as response bodies, but that prevents\nme from implementing PSR-7 all together, as well as the PSR-15 middleware standard - since that relies on PSR-7.\n\nSo if you're looking for a small alternative to Laravel, Symfony at al to build your system upon however, you've come to \nthe right place.\n\n## Features\n - Shore is _fast_: Currently, the whole framework stack takes around 0.01s to execute, including setup, route matching\n    and sending the responses.\n - A framework you can understand: Take a look at the code, hack it, whatever. Everything's simple, thoroughly \n   documented and open for exploration.\n - No fixed layout whatsoever: There are three constants in the [index.php](./public/index.php) that define where\n   the code for your application is to be found. Just add some routes and build up the layout you desire.\n - Real-world way to do things: No, Shore won't let you inject five different dispatchers to send XML/JSON/RPC/HTML. \n   Sorry. It sends JSON or strings, just like that.\n\n## Getting started\nClone this repository at the root of your new project, add routes in the main route file and you're ready. To follow the\nflow of requests, start at [`public/index.php`](./public/index.php). Comments will guide you.\n\n## Defining routes\nAll routing has to happen in the main routes file which is `require_once`'d in [`app/bootstrap`](./app/bootstrap.php).  \nLet's look at an example:  \n\n```php\n\u003c?php\nuse \\Shore\\Framework\\Facades\\Router;\n\n// That route will just call your callable with request and response objects.\nRouter::get('/welcome', function($req, $res) {\n    return 'hi there!';\n});\n\n// That route will call `index` on your `BooksController`.\nRouter::get('/books', '\\Shore\\App\\Controllers\\BooksController@index');\n\n// That route expects your controller to be callable, that is, having an `__invoke` method.\nRouter::get('/ping', \\Shore\\App\\Controllers\\PingController::class);\n```\n\nYou're free to use each of the three handler definition types as you wish. Of course, there's also support for \nplaceholders:\n\n```php\n\u003c?php\nuse \\Shore\\Framework\\Facades\\Router;\n\n// Your controller will receive the title as its third argument, or via $request-\u003eget('title')\nRouter::get('/books/{title}', '\\Shore\\App\\Controllers\\BooksController@byTitle');\n\n// Separated by a colon, you can pass arbitrary regular expression constraints for your placeholders\nRouter::get('/books/{id:\\d+}', '\\Shore\\App\\Controllers\\BooksController@byId');\n\n// Add a new route group. The group URI will be prefixed on all routes within the callback. Infinitely nestable!\nRouter::group('/authors', function() {\n    \n    // This route will be available at /authors/search, as it's grouped\n    Router::get('/search', '\\Shore\\App\\Controllers\\AuthorsController@search');\n});\n```\n\n## Creating route handlers\nRoute handlers are callables or controller methods that can handle requests. They can either return the response \ndirectly (anything _not_ a string will trigger the response to be sent as JSON automatically) or modify the response \nobject and return that.\n\n```php\n\u003c?php\n$handler = function(\\Shore\\Framework\\Http\\Request $request, Shore\\Framework\\Http\\Response $response) {\n    return $response-\u003ewithBody('Hello world');\n};\n```\n\nRequests and responses both implement most of the PSR-7 style messages, except the cloning and stream parts.\n\n## Creating and using middleware\nMiddlewares are layers any request or response has to pass through. Each middleware can decide whether to send a \nresponse or pass on to the next one. This allows for neat use cases such as authentication, CSRF or trailing slash \nredirection. Shore has first-class middleware support, in fact, the whole route handling happens inside the \n[Kernel middleware](./lib/Http/Kernel.php).  \nMiddleware can be supplied as classes implementing the `MiddlewareInterface` or simply callables. To return a response,\nthey'll need to return the response object. To pass on, they need to call the `$handler-\u003enext($request);` method. Easy.\n\n```php\n\u003c?php\nuse \\Shore\\Framework\\Facades\\Response;\n\n$middleware = function(\\Shore\\Framework\\Http\\Request $request, \\Shore\\Framework\\RequestHandlerInterface $handler) {\n    if ($request-\u003eget('token')) {\n        return $handler-\u003enext($request);\n    }\n    \n    return Response::error('Token missing from request', Response::STATUS_UNAUTHORIZED);\n};\n```\n\nTo load middleware into your stack, add the `middleware` key to your application config array and insert middleware \ninstances into that:\n\n```php\n\u003c?php\nreturn [\n    'middleware' =\u003e [\n        new MyFirstMiddleware(),\n        new MySecondMiddleware($withConfiguration)\n    ]\n];\n```\n\nThey will be loaded in order of occurrence.\n\n## Dependency injection\nAt the core of Shore sits the `Application` instance which implements the PSR-11 container interface. It holds all your\nservices and is set as the context of any route handler. What is a service, you ask? Well, just anything, identified by\na string. You can use that string to retrieve services throughout the lifecycle of your app.  \nTo stick with the DI paradigm, it's often a good idea to use the class path of an interface as the service ID: \n`DatabaseInterface::class`, for example. Should you switch out the database later on, you'll only need to do so in one \nplace, without risking missing it somewhere.\n\nTo add your own services, add the `services` key to your application config array and insert your services into that:\n\n```php\n\u003c?php\nreturn [\n    'services' =\u003e [\n        'database' =\u003e new DatabaseConnection($dbConfig),\n        MessageQueueInterface::class =\u003e new RedisQueue($redisConfig)\n    ]\n];\n```\n\nThey will be registered with the name you passed as the key, so inside any route handler, you can use the following to \naccess your service:\n\n```php\n    // ...\n    $db = $this-\u003eget('database'); // Wham! There is your configured instance.\n```\n\n## Facades\nMaybe you've spotted the `Facade` classes already. These are special classes that allow accessing instance methods on \nyour services via static calls, which makes certain aspects easier to handle - without giving up on dependency \ninjection. Custom facades only need to implement a single call that returns the name of the service they wish to \nprovide, everything else is handled automatically.  \nYou don't have to use the facades feature, but it will make your life easier. Let's create a fully functional facade for\nthe database service from the previous section:\n\n```php\n\u003c?php\n\n/**\n* @method static array query(string $sql)\n */\nclass Database extends \\Shore\\Framework\\Facade {\n    public static function getServiceId(): string {\n        return 'database';\n    }\n}\n\n// Use like:\n$results = Database::query('SELECT * from BORING_EXAMPLES');\n```\n\nYup. That's it. Pro tip: Add annotations for your methods in the doc block. That way, your IDE can even autocomplete \nfacade methods! Awesome!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fradiergummi%2Fshore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fradiergummi%2Fshore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fradiergummi%2Fshore/lists"}