{"id":20483468,"url":"https://github.com/tivins/database","last_synced_at":"2025-04-13T14:34:23.656Z","repository":{"id":57070286,"uuid":"336329980","full_name":"tivins/database","owner":"tivins","description":"A secure and efficient fluent PDO wrapper","archived":false,"fork":false,"pushed_at":"2023-02-25T17:36:14.000Z","size":256,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-27T05:34:19.357Z","etag":null,"topics":["database","fluent","mysql","pdo","php","prepared-statements","query","query-builder","sql","sqlinject-defense","sqlinjection","sqlite"],"latest_commit_sha":null,"homepage":"","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/tivins.png","metadata":{"files":{"readme":"README.md","changelog":"changes.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2021-02-05T16:42:03.000Z","updated_at":"2024-12-06T17:11:22.000Z","dependencies_parsed_at":"2024-11-15T05:12:04.421Z","dependency_job_id":"3f6d9146-1de4-4928-b7b2-7289d4a30fdb","html_url":"https://github.com/tivins/database","commit_stats":{"total_commits":173,"total_committers":4,"mean_commits":43.25,"dds":0.2716763005780347,"last_synced_commit":"91b8a59d7849631e2694131cada4ed928af96449"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tivins%2Fdatabase","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tivins%2Fdatabase/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tivins%2Fdatabase/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tivins%2Fdatabase/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tivins","download_url":"https://codeload.github.com/tivins/database/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248728649,"owners_count":21152264,"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":["database","fluent","mysql","pdo","php","prepared-statements","query","query-builder","sql","sqlinject-defense","sqlinjection","sqlite"],"created_at":"2024-11-15T16:17:30.960Z","updated_at":"2025-04-13T14:34:23.623Z","avatar_url":"https://github.com/tivins.png","language":"PHP","readme":"# A PDO Wrapper\n\nA Secure, fluent, lightweight, and efficient PDO wrapper.\n\nHelp to protect again SQL injections.\n\n---\n\n[![Travis CI](https://app.travis-ci.com/tivins/database.svg?branch=main)](https://app.travis-ci.com/tivins/database)\n[![Github CI](https://github.com/tivins/database/actions/workflows/php.yml/badge.svg)](https://github.com/tivins/database/actions/workflows/php.yml)\n[![Coverage Status](https://coveralls.io/repos/github/tivins/database/badge.svg?branch=main)](https://coveralls.io/github/tivins/database?branch=main)\n\n## Install\n\n### Requirements\n\n* [PHP](https://php/net) \u003e= 8.1\n* [PDO ext](https://www.php.net/pdo) \n* Optional development dependencies \n  * [PHPUnit](https://github.com/sebastianbergmann/phpunit/)\n\nSee [composer.json](/composer.json).\n\n### Install with composer\n\n```sh\ncomposer require tivins/database\n```\n\n## Quick example\n\n```php\nuse Tivins\\Database\\Database;\nuse Tivins\\Database\\Connectors\\MySQLConnector;\n\nrequire 'vendor/autoload.php';\n\n$db = new Database(new MySQLConnector('dbname', 'user', 'password', 'localhost'));\n\n$posts = $db-\u003eselect('books', 'b')\n    -\u003eleftJoin('users', 'u', 'b.author_id = u.id')\n    -\u003eaddFields('b')\n    -\u003eaddField('u', 'name', 'author_name')\n    -\u003econdition('b.year', 2010)\n    -\u003eexecute()\n    -\u003efetchAll();\n```\n\n## Summary\n\n* Usage \n  * [Connectors](#connectors)\n  * Queries :\n    * [Select](#select-query)\n    * [Insert](#insert-query)\n    * [Update](#update-query)\n    * [Create](#create-query)\n    * [Delete](#delete-query)\n    * Extended\n      * [SelectInsert](#select-insert-query) \n      * [Merge](#merge-query)\n  * Conditions and expressions\n    * [Nested conditions](#nested-conditions)\n    * [Expressions](#expressions)\n      * [Predefined expressions](#predefined-expressions) \n      * [insert expression](#insert-expressions)\n    * [Having](#having)\n  * Transversal\n    * [Order by](#order-by)\n    * [Range/Limit](#rangelimit)\n  * [Transactions](#transactions)\n  * [Error handling](#error-handling)\n* Development\n  \u003c!-- * [Insight](#insight) --\u003e \n  * [Unit tests](#unit-tests)\n\n## Usage\n\n### Connectors\n\nCreating a [`Database`][9] instance requires a valid [`Connector`][10] instance.\n\n```php\n# MySQL\n$connector = new MySQLConnector('dbname', 'user', 'password');\n# SQLite\n$connector = new SQLiteConnector('path/to/file');\n```\nor\n```php\n$db = new Database (new MySQLConnector(\n  dbname:   'my_database', \n  user:     'my_user', \n  password: 'my_encrypted_password',\n  host:     'localhost',\n  port:     3306,\n));\n```\n\n\nThen create an instance of Database, with the created connector :\n```php\n$database = new Database($connector);\n```\nA `ConnectionException` can be thrown when the `new Database()` attempt to connect the given Connector.\n\n## Using queries\n\nBoth usages below are valid :\n\n```php\n// from database object\n$query = $db-\u003eselect('users', 'u');\n// from new object\n$query = new SelectQuery($db, 'users', 'u');\n```\n\n### Select query\n\n**Basic**\n```php\n$data = $db-\u003eselect('books', 'b')\n    -\u003eaddFields('b')\n    -\u003econdition('b.reserved', 0)\n    -\u003eexecute()\n    -\u003efetchAll();\n```\n\n**Join** \n\nuse as well `innerJoin`, `leftJoin`.\n\n```php\n$db-\u003eselect('books', 'b')\n    -\u003eaddFields('b', ['id', 'title'])\n    -\u003eleftJoin('users', 'u', 'u.id = b.owner')\n    -\u003eaddField('u', 'name', 'owner_name')\n    -\u003econdition('b.reserved', 1)\n    -\u003eexecute()\n    -\u003efetchAll();\n```\n\n**Expression**\n```php\n$db-\u003eselect('books', 'b')\n    -\u003eaddField('b', 'title')\n    -\u003eaddExpression('concat(title, ?)', 'some_field', time())\n    -\u003econdition('b.reserved', 0)\n    -\u003eexecute()\n    -\u003efetchAll();\n```\n\n**Group by**\n```php\n$tagsQuery = $db-\u003eselect('tags', 't')\n    -\u003einnerJoin('book_tags', 'bt', 'bt.tag_id = t.id')\n    -\u003eaddFields('t')\n    -\u003eaddExpression('count(bt.book_id)', 'books_count')\n    -\u003egroupBy('t.id')\n    -\u003eorderBy('t.name', 'asc');\n```\n\n**Condition Expression**\n\n```php\n$db-\u003eselect('books', 'b')\n    -\u003eaddFields('b')\n    -\u003econditionExpression('concat(b.id, \"-\", ?) = b.reference', $someValue)\n    -\u003eexecute();\n```\n\n#### Range/Limit\n```php\n$query-\u003elimit(10);          # implicit start from 0.\n$query-\u003elimitFrom(0, 10);   # explicit start from 0.\n$query-\u003elimitFrom(100, 50); # will fetch 50 rows from 100th row.\n```\n\n#### Order by\n\n`orderBy()` **add** a new order statement in the query. It can be called multiple times.\n```php\n$query-\u003eorderBy('field', 'desc');\n```\nMultiple times. In the following example, the results will be sorted by `post_type`, and then, by `date`:\n```php\n$query-\u003eorderBy('post_type', 'desc')\n      -\u003eorderBy('date', 'asc');\n```\n\n### Insert query\n```php\n$db-\u003einsert('book')\n    -\u003efields([\n        'title' =\u003e 'Book title',\n        'author' =\u003e 'John Doe',\n    ])\n    -\u003eexecute();\n```\n\n#### Multiples inserts\n\n\n```php\n$db-\u003einsert('book')\n    -\u003emultipleFields([\n        ['title' =\u003e 'Book title', 'author' =\u003e 'John Doe'],\n        ['title' =\u003e 'Another book title', 'author' =\u003e 'John Doe Jr'],\n    ])\n    -\u003eexecute();\n```\nor,\n```php\n$db-\u003einsert('book')\n    -\u003emultipleFields([\n          ['Book title', 'John Doe'],\n          ['Another book title', 'John Doe Jr'],\n        ], \n        ['title', 'author'])\n    -\u003eexecute();\n```\n\n`execute()` will insert two rows in the table `book`.\n\u003cdetails\u003e\n  \u003csummary\u003eSee the build result\u003c/summary\u003e\n\n  * Query \n    ```sql\n    insert into `book` (`title`,`author`) values (?,?), (?,?);\n    ```\n  * Parameters\n    ```json\n    [\"Book title\",\"John Doe\",\"Another book title\",\"John Doe Jr\"]\n    ```\n\u003c/details\u003e\n\n#### Insert expressions\n\nExpressions can be used inside the array given to `fields()` function.\n\n\n```php\n$db-\u003einsert('geom')\n    -\u003efields([\n        'name'     =\u003e $name,\n        'position' =\u003e new InsertExpression('POINT(?,?)', $x, $y)\n    ])\n    -\u003eexecute();\n```\n\nExecute() will insert two rows in the table `book`.\n\u003cdetails\u003e\n  \u003csummary\u003eSee the build result\u003c/summary\u003e\n\n* Query\n  ```sql\n  insert into `geom` (`name`, `position`) values (?, POINT(?,?))\n  ```\n* Parameters\n  ```php\n  [$name, $x, $y]\n  ```\n\u003c/details\u003e\n\nInsertExpression are also allowed with a [MergeQuery](#merge-query).\n\n### Update query\n\n```php\n$db-\u003eupdate('book')\n    -\u003efields(['reserved' =\u003e 1])\n    -\u003econdition('id', 123)\n    -\u003eexecute();\n```\n\n### Merge query\n\n```php\n$db-\u003emerge('book')\n    -\u003ekeys(['ean' =\u003e '123456'])\n    -\u003efields(['title' =\u003e 'Book title', 'author' =\u003e 'John Doe'])\n    -\u003eexecute();\n```\n\n### Delete query\n\nPerform a `delete` query on the given table.\nAll methods of [`Conditions`][4] can be used on a [`DeleteQuery`][3] object.\n\n```php\n$db-\u003edelete('book')\n    -\u003ewhereIn('id', [3, 4, 5])\n    -\u003eexecute();\n```\n\n### Create query\n\nPerform a `create table` query on the current database.\n\n```php\n$query = $db-\u003ecreate('sample')\n    -\u003eaddAutoIncrement(name: 'id')\n    -\u003eaddInteger('counter', 0, unsigned: true, nullable: false)\n    -\u003eaddInteger('null_val', null, nullable: false)\n    -\u003eaddJSON('json_field')\n    -\u003eexecute();\n```\n\nField types :\n\n* Integers\n\n  ```php\n  $query-\u003eaddPointer('id_user'); // Shortcut to Not-null Unsigned Integer\n  ```\n\n* UnitEnum or BackedEnum\n  ```php\n  Enum Fruits { case Apple; case Banana; }\n  $query-\u003eaddEnum('fruits', Fruits::cases());\n  ```\n* Standard Enum\n  ```php\n  $query-\u003eaddStdEnum('fruits', ['apple','banana'], 'apple');\n  ```\n\n### Select-Insert Query\n\nPerform a select, then an insert if not found.\n\n```php\n$qry = $db-\u003eselectInsert('users')-\u003ematching(['name' =\u003e 'test', 'state' =\u003e 1]);\n$qry-\u003efetch()-\u003eid; // 1\n$qry-\u003egetProcessedOperation(); // MergeOperation::INSERT\n\n$qry = $db-\u003eselectInsert('users')-\u003ematching(['name' =\u003e 'test', 'state' =\u003e 1]);\n$qry-\u003efetch()-\u003eid; // 1\n$qry-\u003egetProcessedOperation(); // MergeOperation::SELECT\n```\n\nBy default, array given in `matching()` are used to insert the new record.\n\nYou can define the fields for the insert query:\n\n```php\n$matches = ['email' =\u003e 'user@example.com'];\n$obj = $db-\u003eselectInsert('users')\n    -\u003ematching($matches)\n    -\u003efields($matches + ['name' =\u003e  'user', 'created' =\u003e time()])\n    -\u003efetch();\n```\n\n\n## Expressions\n\nYou can use `SelectQuery::addExpression()` to add an expression to the selected fields.\n\nSignature : `-\u003eaddExpression(string $expression, string $alias, array $args)`.\n\n```php\n$query = $db-\u003eselect('books', 'b')\n    -\u003eaddExpression('concat(title, ?)', 'some_field', time())\n    -\u003eexecute();\n```\n\n### Predefined expressions\n\n**Count (`addCount()`)**\n```php\n$total = $db-\u003eselect('table','t')\n    -\u003eaddCount('*')\n    -\u003eexecute()\n    -\u003efetchField();\n```\n\n## Conditions\n\nSome examples :\n\n```php\n-\u003econdition('field', 2);      // eg: where field = 2\n-\u003econdition('field', 2, '\u003e'); // eg: where field \u003e 2\n-\u003econdition('field', 2, '\u003c'); // eg: where field \u003c 2\n-\u003ewhereIn('field', [2,6,8]);  // eg: where field in (2,6,8)\n-\u003elike('field', '%search%');  // eg: where field like '%search%'\n-\u003eisNull('field');            // eg: where field is null\n-\u003eisNotNull('field');         // eg: where field is not null\n```\n\n### Nested conditions\n\nConditions are available for [`SelectQuery`][1], [`UpdateQuery`][2] and [`DeleteQuery`][3].\n\n```php\n$db-\u003eselect('book', 'b')\n    -\u003efields('b', ['id', 'title', 'author'])\n    -\u003econdition(\n        $db-\u003eor()\n        -\u003econdition('id', 3, '\u003e')\n        -\u003elike('title', '%php%')\n    )\n    -\u003eexecute();\n```\nAnd below is equivalent :\n\n```php\n$db-\u003eselect('book', 'b')\n    -\u003efields('b', ['id', 'title', 'author'])\n    -\u003econdition(\n        (new Conditions(Conditions::MODE_OR))\n        -\u003econdition('id', 3, '\u003e')\n        -\u003elike('title', '%php%')\n    )\n    -\u003eexecute();\n```\n\n## Having\n\n```php\n$db-\u003eselect('maps_polygons', 'p')\n    // -\u003e...\n    -\u003ehaving($db-\u003eand()-\u003eisNotNull('geom'))\n    -\u003eexecute()\n    //...\n    ;\n```\n\n## Transactions\n\n```php\nuse Tivins\\Database{ Database, DatabaseException, MySQLConnector };\nfunction makeSomething(Database $db)\n{\n    $db-\u003etransaction()\n    try {\n        // do some stuff\n    }\n    catch (DatabaseException $exception) {\n        $db-\u003erollback();\n        // log exception...\n    }\n}\n```\n\n## Full Example\n\nSee [/tests/FullTest.php](/tests/FullTest.php)\n\n## Error handling\n\nThere are three main exception thrown by Database.\n\n* [ConnectionException][5], raised by the Database constructor, if a connection cannot be established.\n* [DatabaseException][7], thrown when a PDO exception is raised from the query execution.\n* [ConditionException][6], raised when a given operator is not allowed.\n\nAll of these exceptions has an explicit message (from PDO, essentially).\n\nUsage short example :\n\n```php\ntry {\n    $this-\u003edb = new Database($connector);\n}\ncatch (ConnectionException $exception) {\n    $this-\u003elogErrorInternally($exception-\u003egetMessage());\n    $this-\u003edisplayError(\"Cannot join the database.\");\n}\n```\n```php\ntry {\n    $this-\u003edb-\u003einsert('users')\n        -\u003efields([\n            'name' =\u003e 'DuplicateName',\n        ])\n        -\u003eexecute();  \n}\ncatch (DatabaseException $exception) {\n    $this-\u003elogErrorInternally($exception-\u003egetMessage());\n    $this-\u003edisplayError(\"Cannot create the user.\");\n}\n```\n\n## Unit tests\n\nCreate a test database, and a grant to a user on it.\nAdd a `phpunit.xml` at the root of the repository.\n\n```mysql\n/* NB: This is a quick-start example. */\ncreate database test_db;\ncreate user test_user@localhost identified by 'test_passwd';\ngrant all on test_db.* to test_user@localhost;\nflush privileges;\n```\n\n```xml\n\u003cphpunit\u003e\n    \u003cphp\u003e\n        \u003cenv name=\"DB_NAME\" value=\"test_db\"/\u003e\n        \u003cenv name=\"DB_USER\" value=\"test_user\"/\u003e\n        \u003cenv name=\"DB_PASS\" value=\"test_password\"/\u003e\n        \u003cenv name=\"DB_HOST\" value=\"localhost\"/\u003e\n    \u003c/php\u003e\n\u003c/phpunit\u003e\n```\n\nThen, run unit tests :\n\n```bash\nvendor/bin/phpunit tests/\n```\n\nTo include a coverage test, use :\n\n```bash\nmkdir -p build/logs\nvendor/bin/phpunit tests/ --coverage-clover cover.xml\n```\n\n\n\n\n## License \n\nDatabase is released under the MIT License. See the bundled [LICENSE][license] file for details. \n\nIn addition, if you are using the `--dev` mode, some parts of the project have their own licenses attached (either in the source files or in a `LICENSE` file next to them).\n\n\n## Stats\n\n[![Download Status](https://img.shields.io/packagist/dm/tivins/database.svg)](https://packagist.org/packages/tivins/database/stats)\n\n\n[1]: /src/SelectQuery.php\n[2]: /src/UpdateQuery.php\n[3]: /src/DeleteQuery.php\n[4]: /src/Conditions.php\n[5]: /src/Exceptions/ConnectionException.php\n[6]: /src/Exceptions/ConditionException.php\n[7]: /src/Exceptions/DatabaseException.php\n[8]: /src/MergeQuery.php\n[9]: /src/Database.php\n[10]: /src/Connectors/Connector.php\n[license]: https://github.com/tivins/database/blob/master/LICENSE\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftivins%2Fdatabase","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftivins%2Fdatabase","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftivins%2Fdatabase/lists"}