{"id":17844369,"url":"https://github.com/technicalguru/php-database","last_synced_at":"2025-03-20T06:32:19.607Z","repository":{"id":57066107,"uuid":"312377301","full_name":"technicalguru/php-database","owner":"technicalguru","description":"A PHP library for accessing databases easily","archived":false,"fork":false,"pushed_at":"2023-11-19T17:00:55.000Z","size":128,"stargazers_count":1,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-17T15:42:22.677Z","etag":null,"topics":["data","database","database-access","datamodel","datamodels","mariadb","mariadb-client","mariadb-database","mariadb-mysql","mysql","mysql-client","mysql-database"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/technicalguru.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-12T19:35:36.000Z","updated_at":"2021-10-30T10:09:58.000Z","dependencies_parsed_at":"2022-08-24T07:50:14.377Z","dependency_job_id":null,"html_url":"https://github.com/technicalguru/php-database","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/technicalguru%2Fphp-database","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/technicalguru%2Fphp-database/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/technicalguru%2Fphp-database/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/technicalguru%2Fphp-database/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/technicalguru","download_url":"https://codeload.github.com/technicalguru/php-database/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244566110,"owners_count":20473433,"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":["data","database","database-access","datamodel","datamodels","mariadb","mariadb-client","mariadb-database","mariadb-mysql","mysql","mysql-client","mysql-database"],"created_at":"2024-10-27T21:30:17.498Z","updated_at":"2025-03-20T06:32:14.588Z","avatar_url":"https://github.com/technicalguru.png","language":"PHP","readme":"# php-database\nA PHP library for accessing databases easily. This library provides a MySQL/MariaDB flavoured database object\nthat abstracts many daily task in SQL writing, such as quoting, escaping, building SQL statements, WHERE\nclauses, error handling and so on.\n\nA [Data Access Object (DAO)](#how-to-use-a-database-access-object-dao) base class is also provided to \ncater for object-relational mapping tasks. The interface will make it easier to find objects by ID, create and update \nthem, using special data objects of your own.\n\nFinally, a [Query API](#query-api) is provided to support a flexible writing of restrictions - independant of any SQL dialect.\n\nVersion 1.3 is the last major 1.x release and marks the migration release. Traditional SQL writing and Query API are supported\nsimultanously. However, many traditional SQL methods are marked as deprecated, and they will be removed in release 2.0. \nSo you shall migrate to the new Query API early.\n\n# License\nThis project is licensed under [GNU LGPL 3.0](LICENSE.md). \n\n# Installation\n\n## By Composer\n\n```sh\ncomposer require technicalguru/database\n```\n\n## By Package Download\nYou can download the source code packages from [GitHub Release Page](https://github.com/technicalguru/php-database/releases)\n\n# How to use the simple Database Layer\n\n## Creating a Database object\n\nCreate a configuration array and pass it to the constructor:\n\n```\n$config = array(\n    'host'        =\u003e 'database-hostname',\n    'port'        =\u003e 3306,\n    'dbname'      =\u003e 'my-db-name',\n    'tablePrefix' =\u003e 'test_',\n    'user'        =\u003e 'user',\n    'pass'        =\u003e 'password',\n);\n$db = \\TgDatabase\\Database($config);\n```\n\nPlease notice that we provide a table prefix. It is a common practice to prefix all your\ntablenames like `test_`. This way, you can keep several \"namespaces\" in your database.\nThe prefix will later be added to your query statements whenever you use `#__` in the\ntable name (see examples below).\n\nInstead of holding the credentials with the configuration, you could use a `TgUtils\\Auth\\CredentialsProvider`\nthat holds these data. `Database`will ignore credentials in the config array then.\n\n```\n$db = \\TgDatabase\\Database($config, $credentialsProvider);\n```\n\n## Querying objects\n\n```\n// Query a list of objects\n$arr = $db-\u003equeryList('SELECT * FROM #__devtest');\n\n// Querying a single object\n$obj = $db-\u003equerySingle('SELECT * FROM #__devtest WHERE uid='.$uid);\n```\n\nThe interface delivers `stdClass` objects by default. However, you can name\nyour own data class so the data will be populated in such a class:\n\n```\n$arr = $db-\u003equeryList('SELECT * FROM #__devtest', 'MyNamespace\\\\MyDataClass');\n$obj = $db-\u003equerySingle('SELECT * FROM #__devtest WHERE uid='.$uid, 'MyNamespace\\\\MyDataClass');\n```\n\n## Inserting, Updating and Deleting objects\n\nYou can insert your own data classes or simply use `stdClass` objects or arrays:\n\n```\n// Use a standard class object\n$obj        = new stdClass;\n$obj-\u003ename  = 'test-name';\n$obj-\u003eemail = 'test-email';\n$uid = $db-\u003einsert('#__devtest', $obj);\n\n// Use your own data class\n$obj = new MyNamespace\\MyDataClass($initialData);\n$uid = $db-\u003einsert('#__devtest', $obj);\n\n// Use an array\n$arr = array(\n   'name'  =\u003e 'test-name',\n   'email' =\u003e 'test-email'\n);\n$uid = $db-\u003einsert('#__devtest', $arr);\n```\n\nThe `Database` will automatically escape and quote strings that appear as values in your new objects.\n\nUpdating your rows is accordingly easy. You will need the table name, the new values (as object or array) and a WHERE condition:\n\n```\n// Save all object values\n$obj-\u003ename = 'Some other name';\n$db-\u003eupdate('#__devtest', $obj, 'uid='.$obj-\u003euid);\n\n// Save values from array only\n$arr = array('name' =\u003e 'Another name');\n$db-\u003eupdate('#__devtest', $arr, 'uid='.$uid);\n```\n\nIf you want to change a single object only, you also can use `updateSingle()` which can give you back the\nchanged object (as `stdClass`)\n\n```\n// Update a single row\n$updated = $db-\u003eupdateSingle('#__devtest', array('name' =\u003e 'test-value2'), 'uid='.$uid);\n```\n\nAnd finally you can delete objects. You will need the table name and the WHERE condition:\n\n```\n// Delete a single row\n$db-\u003edelete('#__devtest', 'uid='.$uid);\n```\n\n**Remark:** `update()`, `updateSingle()` and `delete()` now support the new [Query API](#query-api) for the\nWHERE condition.\n\n# How to use a Database Access Object (DAO)\n\nThe low-level `Database` abstraction makes object-relational mappings already  simple. However,\nit is still a lot of boilerplate to write, such as table names, WHERE clauses etc. A better way\nis provided by the `DAO` object. It simplifies the usage with databases a lot more.\n\n## Creating the DAO\n\nCreate a DAO by giving it the `Database` instance and the table name:\n\n```\n$dao = \\TgDatabase\\DAO($db, '#__users');\n```\n\nThe default constructor as above makes assumptions about your table:\n\n1. It always returns `stdClass` objects.\n1. It assumes that your table has an `int auto-increment` primary key that is named `uid`.\n\nHowever, you can tell `DAO` your specifics:\n\n```\n// Uses a specific class for the data\n$dao = \\TgDatabase\\DAO($db, '#__users', 'MyNamespace\\\\User`);\n\n// Uses a specific class and another primary key attribute\n$dao = \\TgDatabase\\DAO($db, '#__users', 'MyNamespace\\\\User`, 'id');\n```\n\n`DAO` can actually handle non-numeric primary keys. The usage is not recommended though as you need\nto create the primary keys yourself.\n\n## Finding objects\n\nFinding objects will be much easier now:\n\n```\n// Get user with specific ID\n$user = $dao-\u003eget(3);\n\n// Find a singe user with a specific email address\n$user = $dao-\u003efindSingle(array('email' =\u003e $email));\n\n// Find all active admin users, ordered by name and email in ascending order\n$users = $dao-\u003efind(array('group' =\u003e 'admin', 'active' =\u003e 1), array('name', 'email));\n```\n\n**Attention:** This way of describing restrictions is deprecated as of v1.3. `find()` and `findSingle()` now support \nthe new Query API. Please read the [Query API](#query-api) chapter.\n\n## Creating, Saving and Deleting objects\n\n```\n// Create a new user\n$newUser = new stdClass;\n$newUser-\u003ename     = 'John Doe';\n$newUser-\u003eemail    = 'john.doe@example.com';\n$newUser-\u003epassword = '123456';\n$newUser-\u003egroup    = 'webusers';\n$newUser-\u003eactive   = 1;\n$newId = $dao-\u003ecreate($newUser);\n\n// Update an existing user\n$user = $dao-\u003eget($newId);\n$user-\u003ename = 'Jane Doe';\n$dao-\u003esave($user);\n\n// Delete a user\n$dao-\u003edelete($user);\n// or\n$dao-\u003edelete($user-\u003euid);\n```\n\n## WHERE clauses in DAO interface\n\nThe most simple form of a WHERE clause is the condition itself:\n\n```\n$users = $dao-\u003efind('group=\\'admin\\'');\n```\n\nBut you would need to do the quoting and escaping your self. That's why you can have an array\nof all conditions that are concatenated with an `AND`:\n\n```\n$users = $dao-\u003efind(array('group' =\u003e 'admin', 'active' =\u003e 1));\n```\n\nOr, when an equals (`=`) operation is not what you need:\n\n```\n$users $dao-\u003efind(array(\n    array('group', 'admin', '!='),\n    array('active' , 1)\n));\n```\n\nThe default operator is equals (`=`), but you also can use `!=`, `\u003c=`, `\u003e=`, `\u003c`,  `\u003e`, `IN` and `NOT IN`. Latter two\nrequire arrays of values at the second position of the array:\n\n```\n$users = $dao-\u003efind(array(\n    array('group', array('admin'), 'NOT IN'),\n    array('active' , 1)\n));\n```\n**Attention:** This way of describing restrictions is deprecated as of v1.3. `find()` and `findSingle()` now support \nthe new Query API. Please read the [Query API](#query-api) chapter.\n\n## ORDER clauses in DAO interface\n\nWherever an ORDER clause can be given, there are two types:\n\n```\n// As string\n$users = $dao-\u003efind('', 'name');\n$users = $dao-\u003efind('', 'name DESC');\n$users = $dao-\u003efind('', 'name DESC, email ASC');\n\n// As array\n$users = $dao-\u003efind('', array('name'));\n$users = $dao-\u003efind('', array('name DESC'));\n$users = $dao-\u003efind('', array('name DESC', 'email ASC'));\n```\n\nDefault order sequence is ascending (`ASC`) if not specified.\n\n**Attention:** This way of describing ordering is deprecated as of v1.3. `find()` and `findSingle()` now support \nthe new Query API. Please read the [Query API](#query-api) chapter.\n\n## Extending DAO\n\nIt is a good practice not to use `DAO` class directly but derive from it in your project.\nThat way you can further abstract data access, e.g.\n\n```\nclass Users extends DAO {\n\n    public function __construct($database) {\n        parent::__construct($database, '#__users', 'MyNamespace\\\\User');\n    }\n    \n    public function findByEmail($email) {\n        return $this-\u003efindSingle(array('email' =\u003e $email));\n    }\n    \n    public function findByDepartment($department, $order = NULL) {\n        return $this-\u003efind(array('department' =\u003e $department), $order);\n    }\n}   \n```\n\n**Attention:** This way of describing restrictions is deprecated as of v1.3. `find()` and `findSingle()` now support \nthe new Query API. Please read the [Query API](#query-api) chapter.\n\n## Using Data Objects with DAOs\n\nAs mentioned above, you can use your own data classes. There are actually no\nrestrictions other than the class needs a no-argument constructor. The main\nadvantage is that this class can have additional methods that have some\nlogic. You can even define additional attributes that will not be saved in\nthe database by a DAO. These attributes start with an underscore.\n\nHere is an example:\n\n```\nclass User {\n\n    // will not be saved\n    private $_derivedAttribute;\n    \n    public function __construct() {\n        // You can initialize here\n    }\n    \n    public function getDerivedAttribute() {\n        // Have your logic for the attribute here\n        // or do something completely different\n        \n        // Return something\n        return $this-\u003e_derivedAttribute;\n    }\n}\n```\n\n# Using a DataModel\n\nFinally, we bring everything together. The last thing we need is a central location\nfor all our `DAO`s. Here comes the `DataModel`:\n\n```\n// Setup the model\n$model = new \\TgDatabase\\DataModel($database);\n$model-\u003eregister('users',    $userDAO);\n$model-\u003eregister('products', $productDAO);\n\n// And use it:\n$products = $model-\u003eget('products')-\u003efind();\n```\n\nOf course, a better idea is to encapsulate this in your own `DataModel` subclass:\n\n```\nclass MyDataModel extends \\TgDatabase\\DataModel {\n\n    public function __construct($database) {\n        parent::__construct($database);\n    }\n    \n    protected function init($database) {\n        // Optional step: call the parent method (it's empty, but could change)\n        parent::init($database);\n        \n        // No create your DAOs\n        $this-\u003eregister('users',    new UserDAO($database));\n        $this-\u003eregister('products', new ProductDAO($database));\n    }\n}\n```\n\nYou only need to implement the `init()` method. Now your final application code looks\nmuch cleaner and can be read easily:\n\n```\n// Setup...\n$database = new Database($config);\n$myModel  = new MyDataModel($database);\n\n// ...and use\n$users = $myModel-\u003eget('users')-\u003efind();\n```\n\nImagine, how much error-proned code you would have to write yourself!\n\n# Using a DaoFactory\n\nThe `DataModel` can make use of a `DaoFactory`. Such factory will create DAOs lazily when\nrequested by your application. Simply create your own instance of such a factory:\n\n```\nclass MyFactory implements DaoFactory {\n\n    // code omitted for ease of understanding...\n\n    public function createDao($name) {\n        switch ($name) {\n            case 'users':    return new UserDAO($this-\u003edatabase);\n            case 'products': return new ProductDAO($this-\u003edatabase);\n        }\n        return NULL;\n    }\n}\n```\n...and use it...\n\n```\n$model = new \\TgDatabase\\DataModel($database, $myDaoFactory);\n```\n\nNow you don't need to overwrite the `init()` method of the `DataModel`.\n\nThe use of a `DaoFactory` is recommended when you have many DAOs to manage and your application\nusually uses only a fraction of it. It also will decouple your Data Model from the DAOs.\n\n# Query API\nVersion 1.3 introduces the `Query` which gives you more freedom to express SQL conditions\nwhen searching, updating or deleting objects. It is designed using the Hibernate ORM Criteria template. So much\nof the code may appear familiar to you. \n\nThe Query API was created in addition to the Data Model and DAO API and enhances it. So you can still use\nthe v1.0 way of searching objects while already starting the Query API. However, it is planned to\nremove the old API way of describing restrictions and orderings. Watch out for deprecation warning\nmessages in your log.\n\n**Notice:** Don't worry when you were already using the v1.2 `Criteria` class. It is kept for compatibility\nin the 1.x versions (`Criteria` now inherits from `Query`). Starting with v2.0, this interface will be removed.\n\n## Creating a Query\nTwo ways exist: Creating a `Query` object from the `Database` object, or alternatively from the `DAO`\nobject:\n\n```\n// From Database object\n$query = $database-\u003ecreateQuery('#__users');\n \n// From DAO object\n$query = $userDAO-\u003ecreateQuery();\n```\n\nYou can define model classes (the objects returned from `SELECT` queries) and aliases when creating a `Query`:\n\n```\n// From Database object\n$query = $database-\u003ecreateQuery('#__users', 'MyNamespace\\\\User', 'a');\n \n// From DAO object\n$query = $userDAO-\u003ecreateQuery('a');\n```\n\nAs the DAO already knows about the model class, there is no need to mention it when creating from a `DAO`.\n\nThe alias is assigned to the underlying table name and will be added automatically when required in restrictions.\n\n## Using Restrictions\nRestrictions are expressions that can be used in `WHERE` clauses (and in `JOIN` - see below). The helper\nclass `Restrictions` is there to create them:\n\n```\n$expr1 = Restrictions::eq('name', 'myUsername');\n$expr2 = Restrictions::isNotNull('email');\n$query-\u003ewhere($expr1, $expr);\n```\n\nThe most common restrictions are provided: eq, ne, lt, gt, ge, le, like, isNull, isNotNull, between. You can also\nuse restrictions between two properties:\n\n```\n// Check for equalitity of name and email of a user.\n$expr = Restrictions::eqProperty('name', 'email');\n```\n\nAnd it is possible to combine restrictions with `and()` and `or()`:\n\n```\n$expr = Restrictions::or($expr1, $expr2, $expr3);\n```\n\n## Sorting the result\nThe `Order` class contains three static methods that produce according clauses:\n\n```\n// Ascending order\n$order1 = \\TgDatabase\\Order::asc('columnName1');\n\n// Descending order\n$order2 = \\TgDatabase\\Order::desc('columnName2');\n\n// Use plain SQL as given in argument\n$order3 = \\TgDatabase\\Order::sql('ANY_SQL_FUNCTION() ASC');\n\n```\n\n`asc()` and `desc()` will automatically respect aliases and quote the\ncolumn names, whereas `sql()` simply uses the string given. \n\nHowever, you can use another alias if required:\n\n```\n$order2 = \\TgDatabase\\Order::desc(array('b', 'columnFromJoinedTable'));\n```\n\nFinally add these objects to your query:\n\n```\n$query-\u003eorderBy($order1, $order2);\n$query-\u003eorderBy($order3);\n```\n\n## Modifying the column list: columns and projections\nQuery will return all columns of the queried table by default. However, you can modify\nthe column list:\n\n```\n// Select myColumn only\n$query-\u003eselect(Projections::property('myColumn'));\n\n// Add another column\n$query-\u003eselect(Projections::property('anotherColumn'));\n```\n\nPlease notice that the first call to `select` will remove the `*` retrieval\non the query. Any subsequent call will enhance the list. The same result can be\nachieved with:\n\n```\n$query-\u003esetSelect(Projections::property('myColumn'), Projections::property('anotherColumn'));\n```\n\nAnd there are some shortcuts:\n\n```\n// Variant 1: flexible argument list\n$query1-\u003eselect(Projections::property('myColumn'), Projections::property('anotherColumn'));\n\n// Variant 2: use #properties() method in Projections\n$query1-\u003eselect(Projections::properties('myColumn', 'anotherColumn'));\n```\n\n**Attention:** A call to `setSelect()` or `setProjection()` (deprecated alternative) will NOT remove\nthe result class definition in the query object as done before. This breaks compatibility with previous versions.\nSo you need to call `setResultClass(NULL)` to have `stdClass` returned.\n\n## Getting the result\nThat's the most easiest part:\n\n```\n$query-\u003elist();\n```\n\nYou can set restrictions on the result:\n\n```\n$query-\u003esetFirstResult(10);\n$query-\u003esetMaxResults(20);\n```\n\nOr you expect a single row only:\n\n```\n$query-\u003efirst();\n```\n\n## Using Projections\nBasic projections - the aggregation of columns of different rows - are available:\n\n```\n$proj = Projections::rowCount();\n$query-\u003eselect($proj);\n```\n\nYou will find projections for: count, distinct, sum, avg, min, max. Please notice that\nthe returned model class is not reset. You need to call `setResultClass(NULL)` to have \n`stdClass` returned when using projections.\n\n## Subqueries and JOINs\nThis is most likely the biggest advance in using the Query API. The traditional API methods\nwere not able to use subqueries when searching for objects depending on other tables.\n\nLet's assume you want to find all books in a database whose author name start with an A. \nThe main query comes from books as it is our desired model class to be returned:\n\n```\n$query = $bookDAO-\u003ecreateQuery('a');\n```\n\nNext we join the authors table and add it to the main query using the respective restriction\nto join them properly:\n\n```\n$authors     = $authorDAO-\u003ecreateQuery('b');\n$restriction = Restrictions::eq(array('a','author'), array('b','uid'));\n$query-\u003ejoin($authors, $restriction);\n```\n\nAnd finally we apply the search condition for the author:\n\n```\n$authors-\u003ewhere(Restrictions::like('name', 'A%'));\n```\n\nAnother way of adding subqueries is directly via the main `Query` object:\n\n```\n$authors = $booksDAO-\u003ecreateQuery('a');\n$authors-\u003ecreateJoin('#__authors', 'b', Restrictions::eq(array('a','author'), array('b','uid')));\n$authors-\u003ewhere(Restrictions::like('name', 'A%'));\n```\n\n## Updating and deleting multiple objects\nThe `Query` object can also update and delete objects:\n\n```\n// Update\n$restrictions = Restrictions::eq('name', 'John Doe');\n$updates      = array('comment' =\u003e 'This is an unknown author');\n$dao-\u003ecreateQuery()-\u003ewhere($restrictions)-\u003eupdate($updates);\n\n// Delete\n$restrictions = Restrictions::eq('name', 'Jane Doe');\n$dao-\u003ecreateQuery()-\u003ewhere($restrictions)-\u003edelete();\n```\n\n## GROUP BY and HAVING clauses\nThe Query API allows to define grouping result sets and restricting the returned result with the HAVING clause:\n\n```\n// List the number of books that authors published whose names begin with 'John'\n$bookQuery\n\t-\u003eselect(Projections::property('author'), Projections::rowCount('cnt'))\n\t-\u003egroupBy(Projections::property('author'))\n\t-\u003ehaving(Restrictions::like('author', 'John%'))\n\t-\u003esetResultClass(NULL)\n\t-\u003elist();\n```\n\nPlease notice that using `-\u003ecount()` on such a query might produce unexpected results. This is still an unresolved issue.\n\n## Useful methods \nYou might want to make use of some methods that will ease your code writing:\n\n```\n// Query the authors with specific name, starting with 10th row and a max of 20 authors:\n$query = $dao-\u003ecreateQuery('a', Restrictions::eq('name', 'John Doe'), Order::asc('uid'), 10, 20);\n\n// Count all records (before limiting the result):\n$count  = $query-\u003ecount(); // returns e.g. 65\n$result = $query-\u003elist();  // returns 20 objects \n\n// Count using the DAO\n$count  = $dao-\u003ecount(Restrictions::eq('name', 'John Doe'));\n```\n\n## Advantages and Limitations\nThe Query API further eases searching objects in a database and return model classes, using more\ncomplex expressions and restrictions. You are able to dynamically apply restrictions depending on\nthe requirements of your front-end users and your application. And you don't need the DAO once you \ncreated the `Query` object. It is self-contained.\n\nHowever, some limitations exist:\n\n* Query API supports basic use cases so far (searching objects with basic restrictions, updates, deleting).\n* Only MySQL / MariaDB SQL dialect is produced (but can be extended to other dialects easily when you stick to the API).\n* A few of the limitations may be ovecome by using the `SqlExpression` and `SqlProjection` classes:\n\n```\n// Use a specific restriction not supported\n$myExpr = Restrictions::sql('my-sql-fragment');\n\n// Use a specific projection not supported\n$myProj = Projections::sql('RANDOM()');\n```\n  \nBut feel free to raise an issue (see below) when you need some extension that is not yet supported.\n\n# Contribution\nReport a bug, request an enhancement or pull request at the [GitHub Issue Tracker](https://github.com/technicalguru/php-database/issues).\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechnicalguru%2Fphp-database","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftechnicalguru%2Fphp-database","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechnicalguru%2Fphp-database/lists"}