{"id":13462536,"url":"https://github.com/paragonie/easydb","last_synced_at":"2025-05-15T01:06:05.021Z","repository":{"id":32522386,"uuid":"36103696","full_name":"paragonie/easydb","owner":"paragonie","description":"Easy-to-use PDO wrapper for PHP projects.","archived":false,"fork":false,"pushed_at":"2024-08-07T12:45:31.000Z","size":584,"stargazers_count":739,"open_issues_count":6,"forks_count":86,"subscribers_count":34,"default_branch":"master","last_synced_at":"2025-05-14T12:14:24.338Z","etag":null,"topics":["database","databases","mysql","pdo","php","postgresql","prepared-statements","secure-by-default","sqlite"],"latest_commit_sha":null,"homepage":"https://paragonie.com/projects","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/paragonie.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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":"2015-05-23T02:16:41.000Z","updated_at":"2025-03-27T03:54:15.000Z","dependencies_parsed_at":"2024-12-25T18:15:57.614Z","dependency_job_id":null,"html_url":"https://github.com/paragonie/easydb","commit_stats":{"total_commits":344,"total_committers":21,"mean_commits":16.38095238095238,"dds":0.6918604651162791,"last_synced_commit":"79760589dbc2d77c83825dcd565f070dabfa23e2"},"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paragonie%2Feasydb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paragonie%2Feasydb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paragonie%2Feasydb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paragonie%2Feasydb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paragonie","download_url":"https://codeload.github.com/paragonie/easydb/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254254040,"owners_count":22039792,"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","databases","mysql","pdo","php","postgresql","prepared-statements","secure-by-default","sqlite"],"created_at":"2024-07-31T12:00:51.164Z","updated_at":"2025-05-15T01:06:04.957Z","avatar_url":"https://github.com/paragonie.png","language":"PHP","funding_links":[],"categories":["Uncategorized","PHP","数据库( Database )"],"sub_categories":["Uncategorized"],"readme":"# EasyDB - Simple Database Abstraction Layer\n\n[![Build Status](https://github.com/paragonie/easydb/actions/workflows/ci.yml/badge.svg)](https://github.com/paragonie/easydb/actions)\n[![Latest Stable Version](https://poser.pugx.org/paragonie/easydb/v/stable)](https://packagist.org/packages/paragonie/easydb)\n[![Latest Unstable Version](https://poser.pugx.org/paragonie/easydb/v/unstable)](https://packagist.org/packages/paragonie/easydb)\n[![License](https://poser.pugx.org/paragonie/easydb/license)](https://packagist.org/packages/paragonie/easydb)\n[![Downloads](https://img.shields.io/packagist/dt/paragonie/easydb.svg)](https://packagist.org/packages/paragonie/easydb)\n\nPDO lacks brevity and simplicity; EasyDB makes separating data from instructions\neasy (and aesthetically pleasing).\n\nEasyDB was created by [Paragon Initiative Enterprises](https://paragonie.com)\nas part of our effort to encourage better [application security](https://paragonie.com/service/appsec) practices.\n\nCheck out our other [open source projects](https://paragonie.com/projects) too.\n\nIf you're looking for a full-fledged query builder, check out\n[Latitude](https://github.com/shadowhand/latitude) and [Aura.SqlQuery](https://github.com/auraphp/Aura.SqlQuery),\nwhich can be used with EasyDB.\n\nIf you'd like to use EasyDB but cache prepared statements in memory for\nmultiple queries (i.e. to reduce database round-trips), check out our\n[EasyDB-Cache](https://github.com/paragonie/easydb-cache) wrapper class.\n\n## Installing EasyDB\n\nFirst, [get Composer](https://getcomposer.org/download/), if you don't already use it.\n\nNext, run the following command:\n\n```bash\n/path/to/your/local/composer.phar require paragonie/easydb:^3\n```\n\nIf you've installed Composer in `/usr/bin`, you can replace\n`/path/to/your/local/composer.phar` with just `composer`.\n\n## Why Use EasyDB? Because it's cleaner!\n\nLet's refactor a dangerous PHP snippet that previously used string concatenation to pass user input\ninstead of prepared statements. For example, imagine something that just dropped `{$_GET['blogpostid']}` into the\nmiddle of a `mysql_query()` statement. Let's make it secure.\n\n### The PDO Way\n\n```php\n$db = new \\PDO(\n    'mysql:host=localhost;dbname=something',\n    'username',\n    'putastrongpasswordhere'\n);\n\n$statement = $db-\u003eprepare('SELECT * FROM comments WHERE blogpostid = ? ORDER BY created ASC');\n$exec = $statement-\u003eexecute([$_GET['blogpostid']]);\n$rows = $statement-\u003efetchAll(\\PDO::FETCH_ASSOC);\nforeach ($rows as $row) {\n    $template_engine-\u003erender('comment', $row);\n}\n```\n\nThat's a little wordy for such a simple task. If we do this in multiple places,\nwe end up repeating ourselves a lot.\n\n### The EasyDB Solution\n\n```php\n$db = \\ParagonIE\\EasyDB\\Factory::fromArray([\n    'mysql:host=localhost;dbname=something',\n    'username',\n    'putastrongpasswordhere'\n]);\n\n$rows = $db-\u003erun('SELECT * FROM comments WHERE blogpostid = ? ORDER BY created ASC', $_GET['blogpostid']);\nforeach ($rows as $row) {\n    $template_engine-\u003erender('comment', $row);\n}\n```\n\nWe made it a one-liner.\n\n## What else can EasyDB do quickly?\n\n### Insert a row into a database table\n\n```php\n$db-\u003einsert('comments', [\n    'blogpostid' =\u003e $_POST['blogpost'],\n    'userid' =\u003e $_SESSION['user'],\n    'comment' =\u003e $_POST['body'],\n    'parent' =\u003e isset($_POST['replyTo']) ? $_POST['replyTo'] : null\n]);\n```\n\nThis is equivalent to the following SQL query (assuming `$_POST['blogpostid']`\nis equal to `123`, `$_SESSION['user']` is equal to `234`, `$_POST['body']` is\nequal to `test`, and `$_POST['replyTo']` is equal to `3456`):\n\n```sql\nINSERT INTO comments (blogpostid, userid, comment, parent) VALUES (\n    123,\n    234,\n    'test',\n    3456\n);\n```\n\n#### Build an insert without executing\n\n```php\n$sql = $db-\u003ebuildInsertQuery('comments', [\n    'blogpostid',\n    'userid',\n    'comment'\n]);\n\n// INSERT INTO comments (blogpostid, userid, comment) VALUES (?, ?, ?)\n\n$result = $db-\u003eq(\n    $sql,\n    $values,\n    \\PDO::FETCH_BOTH,\n    true\n);\n```\n\n### Update a row from a database table\n\n```php\n$db-\u003eupdate('comments', [\n    'column' =\u003e 'foo',\n    'otherColumn' =\u003e 123456,\n    'approved' =\u003e true\n], [\n    'commentid' =\u003e $_POST['comment']\n]);\n```\n\nThis is equivalent to the following SQL query\n(assuming `$_POST['comment']` is equal to `789`):\n\n```sql\nUPDATE comments\nSET \n  column = 'foo',\n  otherColumn = 123456,\n  approved = TRUE\nWHERE commentid = 789\n```\n\n### Delete a row from a database table\n\n```php\n// Delete all of this user's comments\n$db-\u003edelete('comments', [\n    'userid' =\u003e 3\n]);\n```\n\nThis is equivalent to the following SQL query:\n\n```sql\nDELETE FROM comments WHERE userid = 3\n```\n\n### Fetch a single row from a table\n\n```php\n$userData = $db-\u003erow(\n    \"SELECT * FROM users WHERE userid = ?\",\n    $_GET['userid']\n);\n```\n\nNote: This expects a variadic list of arguments, not an array. If you have\nmultiple parameters, stack them like this:\n\n```php\n$userData = $db-\u003erow(\n    \"SELECT * FROM users WHERE userid = ? AND other = ?\",\n    $_GET['userid'],\n    $_GET['other']\n);\n```\n\nThis is **wrong**:\n\n```php\n$userData = $db-\u003erow(\n    \"SELECT * FROM users WHERE userid = ? AND other = ?\",\n    array($userid, $other) // WRONG, should not be in an array\n);\n```\n\n### Fetch a single column from a single row from a table\n\n```php\n$exists = $db-\u003ecell(\n    \"SELECT count(id) FROM users WHERE email = ?\",\n    $_POST['email']\n);\n\n/* OR YOU CAN CALL IT THIS WAY: */\n$exists = $db-\u003esingle(\n    \"SELECT count(id) FROM users WHERE email = ?\",\n    array(\n        $_POST['email']\n    )\n);\n```\n\nNote: `cell()` expects a variadic list of arguments, not an array. If you have\nmultiple parameters, stack them like this:\n\n```php\n$exists = $db-\u003ecell(\n    \"SELECT count(id) FROM users WHERE email = ? AND username = ?\",\n    $_POST['email'],\n    $_POST['usenrame']\n);\n```\n\nThis is **wrong**:\n\n```php\n$exists = $db-\u003ecell(\n    \"SELECT count(id) FROM users WHERE email = ? AND username = ?\",\n    array($email, $username) // WRONG, should not be in an array\n);\n```\n\nAlternatively, you can use `single()` instead of `cell()` if you really \nwant to pass an array.\n\n### Try to perform a transaction\n```php\n$save = function (EasyDB $db) use ($userData, $query) : int {\n    $db-\u003esafeQuery($query, [$userData['userId']]);\n    return \\Some\\Other\\Package::CleanUpTable($db);\n};\n// auto starts, commits and rolls back a transaction as necessary\n$returnedInt = $db-\u003etryFlatTransaction($save);\n```\n\n### Generate dynamic query conditions\n\n```php\n$statement = EasyStatement::open()\n    -\u003ewith('last_login IS NOT NULL');\n\nif (strpos($_POST['search'], '@') !== false) {\n    // Perform a username search\n    $statement-\u003eorWith('username LIKE ?', '%' . $db-\u003eescapeLikeValue($_POST['search']) . '%');\n} else {\n    // Perform an email search\n    $statement-\u003eorWith('email = ?', $_POST['search']);\n}\n\n// The statement can compile itself to a string with placeholders:\necho $statement; /* last_login IS NOT NULL OR username LIKE ? */\n\n// All the values passed to the statement are captured and can be used for querying:\n$user = $db-\u003esingle(\"SELECT * FROM users WHERE $statement\", $statement-\u003evalues());\n```\n\n_**Note**: Passing values with conditions is entirely optional but recommended._\n\n#### Variable number of \"IN\" arguments\n\n```php\n// Statements also handle translation for IN conditions with variable arguments,\n// using a special ?* placeholder:\n$roles = [1];\nif ($_GET['with_managers']) {\n    $roles[] = 2;\n}\n\n$statement = EasyStatement::open()-\u003ein('role IN (?*)', $roles);\n\n// The ?* placeholder is replaced by the correct number of ? placeholders:\necho $statement; /* role IN (?, ?) */\n\n// And the values will be unpacked accordingly:\nprint_r($statement-\u003evalues()); /* [1, 2] */\n```\n\n#### Grouping of conditions\n\n```php\n// Statements can also be grouped when necessary:\n$statement = EasyStatement::open()\n    -\u003egroup()\n        -\u003ewith('subtotal \u003e ?')\n        -\u003eandWith('taxes \u003e ?')\n    -\u003eend()\n    -\u003eorGroup()\n        -\u003ewith('cost \u003e ?')\n        -\u003eandWith('cancelled = 1')\n    -\u003eend();\n\necho $statement; /* (subtotal \u003e ? AND taxes \u003e ?) OR (cost \u003e ? AND cancelled = 1) */\n```\n\n### Insert and Update with custom placeholder\n\nSince Version 2.12.0, EasyDB supports placeholders for calling stored procedures and SQL functions\nwhen inserting or updating data.\n\nThe `EasyPlaceholder` class is constructed in the same fashion as other EasyDB methods: The first\nargument, the \"mask\", must be a string. The mask may contain `?` placeholders, and any subsequent \narguments will fill in for the `?` placeholders when the query is executed.\n\n```php\n$db-\u003einsert('user_auth', [\n    'user_id' =\u003e 1,\n    'timestamp' =\u003e new EasyPlaceholder('NOW()'),\n    'expired' =\u003e new EasyPlaceholder('TIMESTAMPADD(HOUR, 2, NOW())'),\n    'location' =\u003e new EasyPlaceholder(\n        \"ST_GeomFromText(CONCAT('POINT(', ?, ' ', ?, ')'))\",\n        50.4019514,\n        30.3926105\n    )\n]);\n\n$db-\u003eupdate(\n    'user_auth', \n    [\n        'last_update' =\u003e new EasyPlaceholder('NOW()'),\n    ], \n    [\n        'user_id' =\u003e 1\n    ]\n);\n```\n\n\u003e Security warning: Do not concatenate user input into the first parameter.\n\n`EasyPlaceholder` can be used in `insert()`, `insertIgnore()`, `insertOnDuplicateKeyUpdate()`,\nand `update()`.\n\n## What if I need PDO for something specific?\n\n```php\n$pdo = $db-\u003egetPdo();\n```\n\n## Can I create an EasyDB wrapper for an existing PDO instance?\n\n**Yes!** It's as simple as doing this:\n\n```php\n$easy = new \\ParagonIE\\EasyDB\\EasyDB($pdo, 'mysql');\n```\n\n## How do I run tests ?\n\n```sh\nvendor/bin/phpunit\n```\n\n## Using Psalm's Security Analysis with EasyDB\n\nFirst, make sure you've [read the Psalm documentation](https://psalm.dev/docs/security_analysis/).\n\nEasyDB's API exposes several taint sinks. Next, run the following command on your codebase\nthat uses EasyDB to identify sources of SQL injection risk.\n\n```terminal\nvendor/bin/psalm --taint-analysis\n```\n\nThis will expose where you're passing tainted data to EasyDB in a potentially unsafe way.\n\n## Troubleshooting Common Issues\n\n### Only one-dimensional arrays are allowed\n\nThis comes up a lot when trying to pass an array of parameters to `run()`.\n\n`EasyDB::run()` expects a query string, then any number of optional parameters.\nIt does **NOT** expect an array of all the parameters.\n\nIf you want to use an API that looks like `$obj-\u003emethod($string, $array)`,\nuse `safeQuery()` instead of `run()`.\n\n```diff\n\u003c?php\n/**\n * @var EasyDB $db\n * @var string $query\n * @var array $params \n */\n- $rows = $db-\u003erun($query, $params);\n+ $rows = $db-\u003esafeQuery($query, $params);\n```\n\nAlternatively, you can flatten your array with the [splat operator](https://secure.php.net/manual/en/migration56.new-features.php#migration56.new-features.splat):\n\n```diff\n\u003c?php\n/**\n * @var EasyDB $db\n * @var string $query\n * @var array $params \n */\n- $rows = $db-\u003erun($query, $params);\n+ $rows = $db-\u003erun($query, ...$params);\n```\n\nEasyDB's `run()` method is a variadic wrapper for `safeQuery()`, so either\nsolution is correct.\n\n## Support Contracts\n\nIf your company uses this library in their products or services, you may be\ninterested in [purchasing a support contract from Paragon Initiative Enterprises](https://paragonie.com/enterprise).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparagonie%2Feasydb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fparagonie%2Feasydb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparagonie%2Feasydb/lists"}