{"id":14987452,"url":"https://github.com/tattersoftware/codeigniter4-roster","last_synced_at":"2025-08-25T16:18:16.593Z","repository":{"id":40660958,"uuid":"410649208","full_name":"tattersoftware/codeigniter4-roster","owner":"tattersoftware","description":"Bulk name lookup for database relations","archived":false,"fork":false,"pushed_at":"2024-01-18T04:03:09.000Z","size":75,"stargazers_count":6,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"develop","last_synced_at":"2025-08-19T17:05:40.328Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/tattersoftware.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-26T19:56:46.000Z","updated_at":"2024-06-23T21:43:22.000Z","dependencies_parsed_at":"2024-09-24T15:44:32.548Z","dependency_job_id":"a947d769-bbc8-4e5c-8839-6746cf6dc542","html_url":"https://github.com/tattersoftware/codeigniter4-roster","commit_stats":{"total_commits":14,"total_committers":3,"mean_commits":4.666666666666667,"dds":0.2142857142857143,"last_synced_commit":"451efe945c288dd127f5285ac498c07f5aafc239"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/tattersoftware/codeigniter4-roster","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tattersoftware%2Fcodeigniter4-roster","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tattersoftware%2Fcodeigniter4-roster/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tattersoftware%2Fcodeigniter4-roster/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tattersoftware%2Fcodeigniter4-roster/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tattersoftware","download_url":"https://codeload.github.com/tattersoftware/codeigniter4-roster/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tattersoftware%2Fcodeigniter4-roster/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272093922,"owners_count":24872263,"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-08-25T02:00:12.092Z","response_time":1107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","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-09-24T14:14:38.146Z","updated_at":"2025-08-25T16:18:16.543Z","avatar_url":"https://github.com/tattersoftware.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tatter\\Roster\nBulk name lookup for database relations in CodeIgniter 4\n\n[![](https://github.com/tattersoftware/codeigniter4-roster/workflows/PHPUnit/badge.svg)](https://github.com/tattersoftware/codeigniter4-roster/actions/workflows/test.yml)\n[![](https://github.com/tattersoftware/codeigniter4-roster/workflows/PHPStan/badge.svg)](https://github.com/tattersoftware/codeigniter4-roster/actions/workflows/analyze.yml)\n[![](https://github.com/tattersoftware/codeigniter4-roster/workflows/Deptrac/badge.svg)](https://github.com/tattersoftware/codeigniter4-roster/actions/workflows/inspect.yml)\n[![Coverage Status](https://coveralls.io/repos/github/tattersoftware/codeigniter4-roster/badge.svg?branch=develop)](https://coveralls.io/github/tattersoftware/codeigniter4-roster?branch=develop)\n\n## Quick Start\n\n1. Install with Composer: `\u003e composer require tatter/roster`\n2. Create a Roster class\n3. Load high-performance names: `\u003c?= service('roster')-\u003euser(1) ?\u003e`\n\n## Description\n\n`Roster` solves a common, niche problem in an elegant way: quick access to display names\nfor entity relations without requiring database lookup. An example... Your e-commerce app\nallows users to list their own products along with their username. To display the full\nproduct page, traditionally you would either need a database `JOIN` to fetch the usernames\nalong with each product, or rely on a third-party solution like Object Relation Mapping (ORM)\nto load the related information. `Roster` simplifies and optimizes this by preloading batches\nof object names and caching them for convenient on-the-fly access.\n\n## Installation\n\nInstall easily via Composer to take advantage of CodeIgniter 4's autoloading capabilities\nand always be up-to-date:\n* `\u003e composer require tatter/roster`\n\nOr, install manually by downloading the source files and adding the directory to\n`app/Config/Autoload.php`.\n\n## Usage\n\nThe `Roster` service handles locating and interacting with your Roster classes, so all you\nneed to do is create some Rosters. All Rosters must meet a few criteria to be discovered:\n* Rosters must extend the Base Roster (`Tatter\\Roster\\BaseRoster`)\n* Rosters must be located in a **Rosters** folder within a namespace (e.g. `App\\Rosters`)\n* Rosters must be named by their lookup followed \"Roster\" (e.g. \"CarRoster\")\n\n### BaseRoster\n\n`BaseRoster` defines the three methods that your class must implement:\n* `protected function key(): string;`\n* `protected function fetchAll(): array;`\n* `protected function fetch($id): ?string;`\n\n*See the `BaseRoster` file for more details.*\n\n### ModelRoster\n\nMost of the time Rosters will be fetching information from the database. In order to make this\nmore convenient and reduce repetitive code this library comes with an intermediate support\nclass, `ModelRoster`. If your Roster aligns with an existing Model then simply extend the\n`ModelRoster` class and supply these required fields:\n* `protected $modelName;`\n* `protected $field;`\n\n### Displaying\n\nOnce your Rosters are configured, use the service with the Roster name as the method and the\nID of the item as the sole parameter:\n\n```php\n$userName = service('roster')-\u003euser($userId);\n```\n\n## Example\n\nYou are developing a blog. At the bottom of every post is a comments section where logged in\nusers may post replies. Being the bright developer you are, you decide to use `Tatter\\Roster`\nto handle the display and save on expensive database joins for every page.\n\nFirst let's handle displaying the username next to each commet. You already have `UserModel`\nso we can use the `ModelRoster` to make it easier. Create **app/Rosters/UserRoster.php**:\n```php\nnamespace App\\Rosters;\n\nuse App\\Models\\UserModel;\nuse Tatter\\Roster\\ModelRoster;\n\nclass UserRoster extends ModelRoster\n{\n\tprotected $modelName = UserModel::class;\n\tprotected $field     = 'username';\n}\n```\n\nThat's it! `ModelRoster` handles retrieving the values based those properties. Now in our\ncomment HTML block we can use the Roster service to display each username:\n```php\n\u003c?php foreach ($comments as $comment): ?\u003e\n\u003cdiv class=\"comment\"\u003e\n    \u003cblockquote\u003e\u003c?= $comment-\u003econtent ?\u003e\u003c/blockquote\u003e\n    \u003cdiv class=\"comment-footer\"\u003e\n        Commented by \u003c?= service('roster')-\u003euser($comment-\u003euser_id) ?\u003e\n    \u003c/div\u003e\n\u003c/div\u003e\n\u003c?php endforeach; ?\u003e\n```\n\nLet's do our blog tags next: under the post title we want to display each tag for this post.\nUnfortunately tags are in the format \"[General] Specific\" so no single field will work for\nthe display. We can still use the `ModelRoster` but instead specifying the field we will\nprovide our own determining method. Create **app/Rosters/TagRoster.php**:\n```php\nnamespace App\\Rosters;\n\nuse App\\Models\\TagModel;\nuse Tatter\\Roster\\ModelRoster;\n\nclass TagRoster extends ModelRoster\n{\n    protected $modelName = TagModel::class;\n\n    protected function getFieldValue(array $row): string\n    {\n        // Convert the database row from TagModel into its displayable form\n        $general  = $row['general'];\n        $specific = $row['specific'];\n\n        return \"[$general] $specific\";\n    }\n}\n```\nNow our blog post header looks much cleaner:\n```php\n\u003ch1\u003e\u003c?= $post-\u003etitle ?\u003e\u003c/h1\u003e\n\u003cdiv class=\"tags\"\u003e\n    \u003c?php foreach ($post-\u003etags as $tagId): ?\u003e\n    \u003cspan class=\"tag\"\u003e\u003c?= service('roster')-\u003etag($tagId) ?\u003e\u003c/span\u003e\n    \u003c?php endforeach; ?\u003e\n\u003c/div\u003e\n```\n\nFinally, our blog is going to display a sidebar menu with post-relevant links to partners.\nThis data will come from a third-party API, which would be an expensive call to make on every\npage load so we create a Roster for it. Because the data source is not a Model we need to make\nour own extension of the Base Roster. Create **app/Rosters/LinkRoster.php**:\n```php\nnamespace App\\Rosters;\n\nuse App\\Libraries\\LinkApi;\nuse Tatter\\Roster\\BaseRoster;\n\nclass LinkRoster extends BaseRoster\n{\n    /**\n     * Returns the handler-specific identifier used for caching\n     */\n    protected function key(): string\n    {\n        return 'roster-links';\n    }\n\n    /**\n     * Loads all IDs and their names from the data source.\n     */\n    protected function fetchAll(): array\n    {\n        $results = [];\n        $links   = new LinkApi();\n\n        foreach ($links-\u003elist() as $link) {\n            $results[$link-\u003euid] = $link-\u003ehref;\n        }\n\n        return $results;\n    }\n\n    /**\n     * Loads a single ID and name from the data source.\n     */\n    protected function fetch($id): ?string\n    {\n        $links = new LinkApi();\n\n        if ($link = $links-\u003eget($id)) {\n            return $link-\u003ehref;\n        }\n\n        return null;\n    }\n}\n```\nA little bit more code, but using `BaseRoster` gives a lot more control about where the data\ncomes from and how it is formatted. You've probably already figure this part out, but let's\nfinish off our links with their HTML menu:\n```php\n\u003cnav class=\"links-menu\"\u003e\n    \u003ch3\u003eVisit our partner blogs!\u003c/h3\u003e\n    \u003cul\u003e\n        \u003c?php foreach ($post-\u003epartnerLinks as $uid): ?\u003e\n        \u003cspan class=\"tag\"\u003e\u003c?= service('roster')-\u003elink($uid) ?\u003e\u003c/span\u003e\n        \u003c?php endforeach; ?\u003e\n    \u003c/ul\u003e\n\u003c/nav\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftattersoftware%2Fcodeigniter4-roster","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftattersoftware%2Fcodeigniter4-roster","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftattersoftware%2Fcodeigniter4-roster/lists"}