{"id":34939003,"url":"https://github.com/phpnomad/datastore","last_synced_at":"2026-05-20T20:11:03.933Z","repository":{"id":244274159,"uuid":"708763921","full_name":"phpnomad/datastore","owner":"phpnomad","description":"Storage-agnostic data access pattern — separate business interfaces from concrete storage implementations","archived":false,"fork":false,"pushed_at":"2026-04-10T02:10:03.000Z","size":82,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-10T02:36:01.663Z","etag":null,"topics":["data-access","datastore","framework","php","phpnomad","platform-agnostic","repository-pattern"],"latest_commit_sha":null,"homepage":"https://phpnomad.com","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/phpnomad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-10-23T10:48:22.000Z","updated_at":"2026-04-10T02:10:07.000Z","dependencies_parsed_at":"2024-06-13T19:56:14.976Z","dependency_job_id":"dd9ef18f-676a-471e-a471-46bfb8a377d2","html_url":"https://github.com/phpnomad/datastore","commit_stats":null,"previous_names":["phpnomad/datastore"],"tags_count":4,"template":false,"template_full_name":"phpnomad/repository-template","purl":"pkg:github/phpnomad/datastore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpnomad%2Fdatastore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpnomad%2Fdatastore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpnomad%2Fdatastore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpnomad%2Fdatastore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phpnomad","download_url":"https://codeload.github.com/phpnomad/datastore/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpnomad%2Fdatastore/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33273744,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-20T15:12:43.734Z","status":"ssl_error","status_checked_at":"2026-05-20T15:12:42.300Z","response_time":356,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["data-access","datastore","framework","php","phpnomad","platform-agnostic","repository-pattern"],"created_at":"2025-12-26T18:49:37.448Z","updated_at":"2026-05-20T20:11:03.928Z","avatar_url":"https://github.com/phpnomad.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# phpnomad/datastore\n\n[![Latest Version](https://img.shields.io/packagist/v/phpnomad/datastore.svg)](https://packagist.org/packages/phpnomad/datastore) [![Total Downloads](https://img.shields.io/packagist/dt/phpnomad/datastore.svg)](https://packagist.org/packages/phpnomad/datastore) [![PHP Version](https://img.shields.io/packagist/php-v/phpnomad/datastore.svg)](https://packagist.org/packages/phpnomad/datastore) [![License](https://img.shields.io/packagist/l/phpnomad/datastore.svg)](https://packagist.org/packages/phpnomad/datastore)\n\n`phpnomad/datastore` is the storage-agnostic data access pattern at the heart of [PHPNomad](https://phpnomad.com). It ships a small set of interfaces, traits, models, and events that let your business logic describe what data operations it needs without committing to where the data lives. It contains no concrete storage code of its own.\n\nThe pattern splits into two layers. Your Core layer holds the `Datastore` interface your application code depends on, the `DatastoreHandler` interface that storage implementations fulfill, and the domain models that flow between them. Your Service layer holds concrete handlers that talk to real storage, whether that's a SQL database, a REST API, a GraphQL endpoint, or an in-memory array for tests. `phpnomad/db` is one Service-layer implementation against SQL, and PHPNomad has been running this pattern in production for years, powering [Siren](https://sirenaffiliates.com), several MCP servers, and other client systems.\n\n## Installation\n\n```bash\ncomposer require phpnomad/datastore\n```\n\n## Quick Start\n\nThe pattern centers on three files: a `Datastore` interface (your public API), a `DatastoreHandler` interface (the storage contract), and a Core implementation that wires them together with decorator traits.\n\nDeclare your public API by extending the base interfaces you need and adding any custom business methods:\n\n```php\n\u003c?php\n\nnamespace MyApp\\Core\\Datastores\\Post\\Interfaces;\n\nuse MyApp\\Core\\Models\\Post;\nuse PHPNomad\\Datastore\\Interfaces\\Datastore;\nuse PHPNomad\\Datastore\\Interfaces\\DatastoreHasPrimaryKey;\nuse PHPNomad\\Datastore\\Interfaces\\DatastoreHasWhere;\n\ninterface PostDatastore extends Datastore, DatastoreHasPrimaryKey, DatastoreHasWhere\n{\n    /**\n     * @return Post[]\n     */\n    public function getPublishedPosts(): array;\n}\n```\n\nDeclare the storage contract as a sibling interface. It extends the same base interfaces but stays free of business-specific methods, so any backend can implement it:\n\n```php\n\u003c?php\n\nnamespace MyApp\\Core\\Datastores\\Post\\Interfaces;\n\nuse PHPNomad\\Datastore\\Interfaces\\Datastore;\nuse PHPNomad\\Datastore\\Interfaces\\DatastoreHasPrimaryKey;\nuse PHPNomad\\Datastore\\Interfaces\\DatastoreHasWhere;\n\ninterface PostDatastoreHandler extends Datastore, DatastoreHasPrimaryKey, DatastoreHasWhere\n{\n}\n```\n\nWrite the Core implementation once. Decorator traits delegate every standard method to the injected handler, so you only implement the custom business method:\n\n```php\n\u003c?php\n\nnamespace MyApp\\Core\\Datastores\\Post;\n\nuse MyApp\\Core\\Datastores\\Post\\Interfaces\\PostDatastore as PostDatastoreInterface;\nuse MyApp\\Core\\Datastores\\Post\\Interfaces\\PostDatastoreHandler;\nuse PHPNomad\\Datastore\\Interfaces\\Datastore;\nuse PHPNomad\\Datastore\\Traits\\WithDatastoreDecorator;\nuse PHPNomad\\Datastore\\Traits\\WithDatastorePrimaryKeyDecorator;\nuse PHPNomad\\Datastore\\Traits\\WithDatastoreWhereDecorator;\n\nclass PostDatastore implements PostDatastoreInterface\n{\n    use WithDatastoreDecorator;\n    use WithDatastorePrimaryKeyDecorator;\n    use WithDatastoreWhereDecorator;\n\n    protected Datastore $datastoreHandler;\n\n    public function __construct(PostDatastoreHandler $datastoreHandler)\n    {\n        $this-\u003edatastoreHandler = $datastoreHandler;\n    }\n\n    public function getPublishedPosts(): array\n    {\n        return $this-\u003edatastoreHandler-\u003ewhere([\n            [\n                'type' =\u003e 'AND',\n                'clauses' =\u003e [\n                    ['column' =\u003e 'status', 'operator' =\u003e '=', 'value' =\u003e 'published'],\n                ],\n            ],\n        ]);\n    }\n}\n```\n\nA Service-layer class like `PostDatabaseDatastoreHandler` implements `PostDatastoreHandler` against an actual backend. You bind the handler interface to that concrete class in your DI container at bootstrap time. Swapping storage later is a one-line change to the binding. Nothing in `PostDatastore` or its callers has to move.\n\n## Key Concepts\n\n- `Datastore` is your public API. `DatastoreHandler` is the storage contract. Both extend the same base interfaces, but only the `Datastore` adds custom business methods.\n- Base interfaces are composable. Mix `Datastore`, `DatastoreHasPrimaryKey`, `DatastoreHasWhere`, and `DatastoreHasCounts` to describe exactly the operations your entity supports, no more.\n- Decorator traits (`WithDatastoreDecorator`, `WithDatastorePrimaryKeyDecorator`, `WithDatastoreWhereDecorator`, `WithDatastoreCountDecorator`) delegate the standard methods to your handler so implementations stay lean.\n- `DataModel` and `ModelAdapter` keep domain entities independent of storage format. Adapters translate between raw arrays and model objects on read and write.\n- `RecordCreated`, `RecordUpdated`, and `RecordDeleted` events let handler implementations broadcast state changes through `phpnomad/event` without coupling consumers to any specific storage backend.\n\n## Documentation\n\nFull documentation lives at [phpnomad.com](https://phpnomad.com), including the datastore conceptual overview, the Core datastore layer reference, model and adapter guidance, and the `phpnomad/db` Service-layer implementation for SQL-backed storage.\n\n## License\n\nMIT, see [LICENSE.txt](LICENSE.txt) for the full text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphpnomad%2Fdatastore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphpnomad%2Fdatastore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphpnomad%2Fdatastore/lists"}