{"id":18545690,"url":"https://github.com/jakubkulhan/data-access-kit","last_synced_at":"2025-10-14T12:48:58.184Z","repository":{"id":242040068,"uuid":"804884608","full_name":"jakubkulhan/data-access-kit","owner":"jakubkulhan","description":"Type-safe minimum-writing SQL repositories for PHP","archived":false,"fork":false,"pushed_at":"2025-06-26T17:34:07.000Z","size":214,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-03T22:32:08.473Z","etag":null,"topics":["dao","data-access-layer","data-access-library","data-access-object","data-mapper","object-relational-mapper","object-relational-mapping","orm","php"],"latest_commit_sha":null,"homepage":"https://github.com/jakubkulhan/data-access-kit","language":"PHP","has_issues":false,"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/jakubkulhan.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,"zenodo":null}},"created_at":"2024-05-23T13:08:08.000Z","updated_at":"2025-06-26T17:34:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"2bfe7f76-c935-4e54-9166-5fb695bc25f9","html_url":"https://github.com/jakubkulhan/data-access-kit","commit_stats":null,"previous_names":["jakubkulhan/data-access-kit"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jakubkulhan/data-access-kit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubkulhan%2Fdata-access-kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubkulhan%2Fdata-access-kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubkulhan%2Fdata-access-kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubkulhan%2Fdata-access-kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jakubkulhan","download_url":"https://codeload.github.com/jakubkulhan/data-access-kit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubkulhan%2Fdata-access-kit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279018577,"owners_count":26086579,"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-10-14T02:00:06.444Z","response_time":60,"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":["dao","data-access-layer","data-access-library","data-access-object","data-mapper","object-relational-mapper","object-relational-mapping","orm","php"],"created_at":"2024-11-06T20:21:43.136Z","updated_at":"2025-10-14T12:48:58.169Z","avatar_url":"https://github.com/jakubkulhan.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DataAccessKit\n\n\u003e Type-safe minimum-writing SQL repositories for PHP\n\n## Quick start\n\nStart by creating an object.\n\n```php\nuse DataAccessKit\\Attribute\\Table;\nuse DataAccessKit\\Attribute\\Column;\n\n#[Table]\nclass User\n{\n    public function __construct(\n        #[Column]\n        public int $id,\n        #[Column]\n        public string $firstName,\n        #[Column]\n        public string $lastName,\n    )\n    {\n    }\n}\n```\n\nThen create a repository interface.\n\n```php\nuse DataAccessKit\\Repository\\Attribute\\Repository;\n\n#[Repository(User::class)]\ninterface UserRepositoryInterface\n{\n    public function getById(int $id): User;\n}\n```\n\nBy an integration with your framework (e.g. [Symfony](https://github.com/jakubkulhan/data-access-kit-symfony#readme)), a repository implementation will be generated for you and you can use it in your services.\n\n```php\nclass UserService\n{\n    public function __construct(\n        private UserRepositoryInterface $userRepository,\n    )\n    {\n    }\n\n    public function login(int $userId): void\n    {\n        $user = $this-\u003euserRepository-\u003egetById($userId);\n        \n        // ...\n    }\n}\n```\n\n## Installation\n\n```shell\ncomposer require data-access-kit/data-access-kit@dev-main\n```\n\n### Requirements\n\n- PHP 8.3 or higher.\n\n### Supported databases\n\n- MySQL\n- MariaDB\n- PostgreSQL\n- SQLite\n\n## Object attributes\n\nDataAccessKit maps plain old PHP objects to database using [attributes](https://www.php.net/manual/en/language.attributes.overview.php).\n\n```php\nuse DataAccessKit\\Attribute\\Table;\n\n#[Table(\n    name: \"users\",\n)]\n```\n\n[Table](./src/Attribute/Table.php) attribute connects class to a database table.\n\n- `name` specifies the table name. If not provided, the table name is derived from the class name by `NameConverterInterface`. Default name converter converts CamelCase to snake_case and pluralizes the name (i.e. `User` to `users`, `UserCredential` to `user_credentials`).\n\n```php\nuse DataAccessKit\\Attribute\\Column;\n\n#[Column(\n    name: \"user_id\",\n    primary: true,\n    generated: true,\n)]\n```\n\n[Column](./src/Attribute/Column.php) attribute is supposed to be added above class property.\n\n- `name` argument, same as with Table, is optional and if omitted the column name is derived from the property name by `NameConverterInterface`. Default name converter converts CamelCase to snake_case (i.e. `userId` to `user_id`). The `primary` argument specifies whether the column is a primary key.\n- `primary` means that the column is a part of the primary key. When UPDATE or DELETE is called, values from properties annotated with `#[Column(primary: true)]` are used in WHERE clause. When INSERT is called, values from properties annotated with `#[Column(primary: true)]` are not used in the query (they are not part of the INSERT statement), but if you INSERT only one row with a single primary property, it is populated from `LAST_INSERT_ID()` (or equivalent) after the query.\n- `generated` specifies whether the column is generated by the database (e.g. auto increment, but also for [generated columns](https://dev.mysql.com/doc/refman/8.0/en/create-table-generated-columns.html)). Generated columns are only read from the database (they figure in SELECTs), but not written to it (they are not used in INSERTs, UPDATEs).\n\n## Nested objects and arrays\n\nYou may annotate with Column also nested objects and arrays. When you do so, the Column-annotated properties of nested object or array are serialized to JSON and stored in a single column.\n\n```php\nuse DataAccessKit\\Attribute\\Table;\nuse DataAccessKit\\Attribute\\Column;\n\n#[Table]\nclass User\n{\n    #[Column(primary: true, generated: true)]public int $id;\n    #[Column] public string $name;\n    #[Column] public Address $mainAddress;\n    /** @var Address[] */\n    #[Column] public array $alternativeAddresses;\n    #[Column] public object $settings;\n}\n\nclass Address\n{\n    #[Column] public string $street;\n    #[Column] public string $city;\n    #[Column] public string $zip;\n}\n```\n\nBecause Address isn't represented by a table, you don't annotate it with Table.\n\nIf you want to store arbitrary data in a column, you can use `object` type hint.\n\nPHP offers only `array` type hint without ability to specify the type of the array items. You can provide item type by `@var` annotation. Alternatively, specify the type using `#[Column(itemType: Address::class)]`. If you don't specify, the item type, array is serialized as arbitrary data.\n\n## Persistence\n\nPersistence layer is based on [Doctrine\\DBAL](https://www.doctrine-project.org/projects/dbal.html). It is a thin layer on top of it, providing type-safe object mapping from and to database based on object attributes. See [PersistenceInterface](./src/PersistenceInterface.php) for more details.\n\n## Repositories\n\nRepositories are generated from interfaces. The interface needs to be annotated with [Repository](./src/Repository/Attribute/Repository.php) attribute.\n\n```php\nuse DataAccessKit\\Repository\\Attribute\\Repository;\n\n#[Repository(\n    class: User::class,\n)]\n```\n\n- `class` specifies the class of the entity the repository is for.\n\nInterface methods are then implemented based on what attribute they are annotated with. If a method doesn't have any of the method attributes, the compiler tries to determine the method attribute based on the method name. Methods starting with `find` and `get` are considered as Find methods, methods starting with `count` are considered as Count methods. If a method attribute cannot be determined, the compiler throws an exception.\n\n### Find\n\nFind methods are used to retrieve entities from the database.\n\nThey can return a single entity or a collection of entities. Supported return types for collections are `iterable` and `array`.\n\nA single entity return type must be the class the repository is for. If the return type is non-nullable and no rows is returned from the database, the method throws an exception. Also, if multiple rows are returned from the database, an exception is thrown.\n\n```php\nuse DataAccessKit\\Repository\\Attribute\\Find;\n\n#[Find(\n    select: \"%columns(except password alias u)\", // optional, default is all columns specified by Column attributes\n    from: \"users\", // optional, default is the table the repository is for\n    alias: \"u\", // optional, default is \"t\"\n    where: \"u.id = @id\", // optional, default is AND of all equality conditions based on parameter names and arguments\n                         // to get the column names, parameter is matched with the object property and Column attribute from the property is used\n                         // e.g. for method `find(int $id, string $firstName)` the default where clause is `u.id = ? AND u.first_name = ?`\n    orderBy: \"u.first_name\", // optional, default is empty\n    limit: 1, // optional, default is no limit\n    offset: 10, // optional, default is no offset\n)]\npublic function find(int $id): User;\n```\n\n- `select` - the columns to select. [Macros and variables](#macros-and-variables) available.\n- `from` - the table to select from.\n- `alias` - the table alias.\n- `where` - the WHERE clause. [Macros and variables](#macros-and-variables) available.\n- `orderBy` - the ORDER BY clause. [Macros and variables](#macros-and-variables) available.\n- `limit` - the LIMIT clause.\n- `offset` - the OFFSET clause.\n- `for` - locking reads, see [e.g. MySQL's documentation](https://dev.mysql.com/doc/refman/8.4/en/innodb-locking-reads.html)\n\n### Count\n\nCount methods return number of rows in the database. They must return `int`.\n\n```php\nuse DataAccessKit\\Repository\\Attribute\\Count;\n\n#[Count(\n    from: \"users\", // optional, default is the table the repository is for\n    alias: \"u\", // optional, default is \"t\"\n    where: \"u.id = @id\", // optional, default is AND of all equality conditions based on parameter names and arguments\n                         // to get the column names, parameter is matched with the object property and Column attribute from the property is used\n                         // e.g. for method `count(int $id, string $firstName)` the default where clause is `u.id = ? AND u.first_name = ?`\n)]\npublic function count(int $id): int;\n```\n\n- `from` - the table to count from.\n- `alias` - the table alias.\n- `where` - the WHERE clause. [Macros and variables](#macros-and-variables) available.\n\n### SQL\n\nSQL methods execute arbitrary SQL queries. They can return a single entity, a collection of entities, a scalar value, or nothing.\n\n```php\nuse DataAccessKit\\Repository\\Attribute\\SQL;\n\n#[SQL(\n    sql: \"SELECT * FROM users u WHERE u.first_name = @firstName\", // required\n    itemType: User::class, // optional\n)]\npublic function sql(string $firstName): iterable;\n```\n\n- `sql` - the SQL query.\n- `itemType` - the type of the item if the query returns an `iterable` or `array` and the item type is different from the class the repository is for. The use case is e.g. when you want to retrieve custom aggregation of the data and map it to objects.\n\n#### Macros and variables\n\nTo reference a method argument in the SQL query, you can use `@` followed by the parameter name (e.g. `@id`). This is then replaced by a placeholder in the actual SQL query and bound to the argument in the statement.\n\nArray parameters are expanded to a list of placeholders. For example, if you have a method with an array parameter `ids`, you can use `@ids` in the SQL query, and it will be expanded to `?, ?, ?, ...` and bound to the values from array.\n\nThere are also several macros that expand to SQL fragments.\n\n- `%columns` - expands to all columns specified by Column attributes.\n  - `%columns(alias u)` - expands to all columns specified by Column attributes prefixed by the alias.\n  - `%columns(except password)` - expands to all columns specified by Column attributes except the specified columns.\n  - `%columns(except password, long_description alias u)` - combination of the previous two.\n- `%table` - expands to the table name.\n\n### SQLFile\n\nThe same as SQL attribute, but the SQL query is loaded from a file.\n\n```php\nuse DataAccessKit\\Repository\\Attribute\\SQLFile;\n\n#[SQLFile(\n    file: \"sql/find_by_first_name.sql\", // required\n    itemType: User::class, // optional\n)]\npublic function sqlFile(string $firstName): iterable;\n```\n\n### Insert, Upsert, Update, Delete\n\nTo manipulate data in the database, you can use Insert, Upsert, Update, and Delete methods.\n\n```php\nuse DataAccessKit\\Repository\\Attribute\\Insert;\nuse DataAccessKit\\Repository\\Attribute\\Upsert;\nuse DataAccessKit\\Repository\\Attribute\\Update;\nuse DataAccessKit\\Repository\\Attribute\\Delete;\n\n#[Insert]\npublic function insert(User $user): void;\n\n#[Insert]\npublic function insertAll(array $users): void;\n\n#[Upsert(\n    columns: [\"first_name\", \"last_name\"], // optional, if omitted/null all columns specified by Column attributes are updated\n)]\npublic function upsert(User $user): void;\n\n#[Upsert(\n    columns: ...,\n)]\npublic function upsertAll(array $users): void;\n\n#[Update(\n    columns: ..., // optional, if omitted/null all columns specified by Column attributes are updated\n)]\npublic function update(User $user): void;\n\n#[Delete]\npublic function delete(User $user): void;\n\n#[Delete]\npublic function deleteAll(array $users): void;\n```\n\nMethods support both single entity and array of entities signatures, except for update, which works only on a single object. Array methods issue a single SQL query with all the data.\n\nUpsert and update methods can be limited to update only specific columns in the `columns` argument of the attribute.\n\n### Delegate\n\nIf a repository method is more complex than what can be expressed by a single SQL query, you will probably want to implement it yourself.\n\n```php\nuse DataAccessKit\\Repository\\Attribute\\Delegate;\n\n#[Delegate(\n    class: UserRepositoryDelegate::class, // required\n    method: \"delegateMethodName\", // optional, default is the same name as the annotated method\n)]\npublic function delegate(string $delegatedParameter): array; \n```\n\n- `class` - the class that implements the method. It can be a class, an interface, or a trait.\n  - Classes and interfaces are then added as a constructor parameter in the generated repository class.\n  - Traits are used by an anonymous class instantiated in the generated repository's constructor. If the trait has a constructor, its parameters are added as constructor parameters in the generated repository class.\n- `method` - target method in the class. If not provided, the interface method name is used.\n\n## Contributing\n\nThis repository is automatically split from the [main repository](https://github.com/jakubkulhan/data-access-kit-src). Please open issues and pull requests there.\n\n## License\n\nLicensed under MIT license. See [LICENSE](https://github.com/jakubkulhan/data-access-kit-src/blob/main/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakubkulhan%2Fdata-access-kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjakubkulhan%2Fdata-access-kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakubkulhan%2Fdata-access-kit/lists"}