-
Notifications
You must be signed in to change notification settings - Fork 407
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #505 from flightphp/database-class
Database class
- Loading branch information
Showing
5 changed files
with
316 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -453,6 +453,60 @@ $new = Flight::db(false); | |
Keep in mind that mapped methods have precedence over registered classes. If you | ||
declare both using the same name, only the mapped method will be invoked. | ||
|
||
## PDO Helper Class | ||
|
||
Flight comes with a helper class for PDO. It allows you to easily query your database | ||
with all the prepared/execute/fetchAll() wackiness. It greatly simplifies how you can | ||
query your database. | ||
|
||
```php | ||
// Register the PDO helper class | ||
Flight::register('db', \flight\database\PdoWrapper::class, ['mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [ | ||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'', | ||
PDO::ATTR_EMULATE_PREPARES => false, | ||
PDO::ATTR_STRINGIFY_FETCHES => false, | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC | ||
] | ||
]); | ||
|
||
Flight::route('/users', function () { | ||
// Get all users | ||
$users = Flight::db()->fetchAll('SELECT * FROM users'); | ||
|
||
// Stream all users | ||
$statement = Flight::db()->runQuery('SELECT * FROM users'); | ||
while ($user = $statement->fetch()) { | ||
echo $user['name']; | ||
} | ||
|
||
// Get a single user | ||
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]); | ||
|
||
// Get a single value | ||
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users'); | ||
|
||
// Special IN() syntax to help out (make sure IN is in caps) | ||
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]); | ||
// you could also do this | ||
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [ '1,2,3,4,5']); | ||
|
||
// Insert a new user | ||
Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', '[email protected]']); | ||
$insert_id = $Flight::db()->lastInsertId(); | ||
|
||
// Update a user | ||
Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]); | ||
|
||
// Delete a user | ||
Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]); | ||
|
||
// Get the number of affected rows | ||
$statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']); | ||
$affected_rows = $statement->rowCount(); | ||
|
||
}); | ||
``` | ||
|
||
# Overriding | ||
|
||
Flight allows you to override its default functionality to suit your own needs, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
<?php | ||
|
||
namespace flight\database; | ||
|
||
use PDO; | ||
use PDOStatement; | ||
|
||
class PdoWrapper extends PDO { | ||
|
||
/** | ||
* How you create the connection for the database | ||
* | ||
* @param string $dsn - Ex: 'mysql:host=localhost;port=3306;dbname=testdb;charset=utf8mb4' | ||
* @param string $username - Ex: 'root' | ||
* @param string $password - Ex: 'password' | ||
* @param array $options - PDO options you can pass in | ||
*/ | ||
public function __construct(string $dsn, ?string $username = null, ?string $password = null, array $options = []) { | ||
parent::__construct($dsn, $username, $password, $options); | ||
} | ||
|
||
/** | ||
* Use this for INSERTS, UPDATES, or if you plan on using a SELECT in a while loop | ||
* | ||
* Ex: $statement = $db->runQuery("SELECT * FROM table WHERE something = ?", [ $something ]); | ||
* while($row = $statement->fetch()) { | ||
* // ... | ||
* } | ||
* | ||
* $db->runQuery("INSERT INTO table (name) VALUES (?)", [ $name ]); | ||
* $db->runQuery("UPDATE table SET name = ? WHERE id = ?", [ $name, $id ]); | ||
* | ||
* @param string $sql - Ex: "SELECT * FROM table WHERE something = ?" | ||
* @param array $params - Ex: [ $something ] | ||
* @return PDOStatement | ||
*/ | ||
public function runQuery(string $sql, array $params = []): PDOStatement { | ||
$processed_sql_data = $this->processInStatementSql($sql, $params); | ||
$sql = $processed_sql_data['sql']; | ||
$params = $processed_sql_data['params']; | ||
$statement = $this->prepare($sql); | ||
$statement->execute($params); | ||
return $statement; | ||
} | ||
|
||
/** | ||
* Pulls one field from the query | ||
* | ||
* Ex: $id = $db->fetchField("SELECT id FROM table WHERE something = ?", [ $something ]); | ||
* | ||
* @param string $sql - Ex: "SELECT id FROM table WHERE something = ?" | ||
* @param array $params - Ex: [ $something ] | ||
* @return mixed | ||
*/ | ||
public function fetchField(string $sql, array $params = []) { | ||
$data = $this->fetchRow($sql, $params); | ||
return is_array($data) ? reset($data) : null; | ||
} | ||
|
||
/** | ||
* Pulls one row from the query | ||
* | ||
* Ex: $row = $db->fetchRow("SELECT * FROM table WHERE something = ?", [ $something ]); | ||
* | ||
* @param string $sql - Ex: "SELECT * FROM table WHERE something = ?" | ||
* @param array $params - Ex: [ $something ] | ||
* @return array | ||
*/ | ||
public function fetchRow(string $sql, array $params = []): array { | ||
$sql .= stripos($sql, 'LIMIT') === false ? ' LIMIT 1' : ''; | ||
$result = $this->fetchAll($sql, $params); | ||
return is_array($result) && count($result) ? $result[0] : []; | ||
} | ||
|
||
/** | ||
* Pulls all rows from the query | ||
* | ||
* Ex: $rows = $db->fetchAll("SELECT * FROM table WHERE something = ?", [ $something ]); | ||
* foreach($rows as $row) { | ||
* // ... | ||
* } | ||
* | ||
* @param string $sql - Ex: "SELECT * FROM table WHERE something = ?" | ||
* @param array $params - Ex: [ $something ] | ||
* @return array<int,array> | ||
*/ | ||
public function fetchAll(string $sql, array $params = []): array { | ||
$processed_sql_data = $this->processInStatementSql($sql, $params); | ||
$sql = $processed_sql_data['sql']; | ||
$params = $processed_sql_data['params']; | ||
$statement = $this->prepare($sql); | ||
$statement->execute($params); | ||
$result = $statement->fetchAll(); | ||
return is_array($result) ? $result : []; | ||
} | ||
|
||
/** | ||
* Don't worry about this guy. Converts stuff for IN statements | ||
* | ||
* Ex: $row = $db->fetchAll("SELECT * FROM table WHERE id = ? AND something IN(?), [ $id, [1,2,3] ]); | ||
* Converts this to "SELECT * FROM table WHERE id = ? AND something IN(?,?,?)" | ||
* | ||
* @param string $sql the sql statement | ||
* @param array $params the params for the sql statement | ||
* @return array{sql:string,params:array} | ||
*/ | ||
protected function processInStatementSql(string $sql, array $params = []): array { | ||
|
||
// Handle "IN(?)". This is to be used with a comma delimited string, but can also be used with an array. | ||
// Remove the spaces in variations of "IN ( ? )" where the space after IN is optional, and any number of spaces before and after the question mark is optional. | ||
// Then loop through each "IN(?)" in the query and replace the single question mark with the correct number of question marks. | ||
$sql = preg_replace('/IN\s*\(\s*\?\s*\)/i', 'IN(?)', $sql); | ||
$current_index = 0; | ||
while(($current_index = strpos($sql, 'IN(?)', $current_index)) !== false) { | ||
$preceeding_count = substr_count($sql, '?', 0, $current_index - 1); | ||
|
||
$param = $params[$preceeding_count]; | ||
$question_marks = '?'; | ||
|
||
// If param is a string, explode it and replace the question mark with the correct number of question marks | ||
if(is_string($param) || is_array($param)) { | ||
|
||
$params_to_use = $param; | ||
if(is_string($param)) { | ||
$params_to_use = explode(',', $param); | ||
} | ||
|
||
foreach($params_to_use as $key => $value) { | ||
if(is_string($value)) { | ||
$params_to_use[$key] = trim($value); | ||
} | ||
} | ||
|
||
// Replace the single question mark with the appropriate number of question marks. | ||
$question_marks = join(',', array_fill(0, count($params_to_use), '?')); | ||
$sql = substr_replace($sql, $question_marks, $current_index + 3, 1); | ||
|
||
// Insert the new params into the params array. | ||
array_splice($params, $preceeding_count, 1, $params_to_use); | ||
} | ||
|
||
// Increment by the length of the question marks and accounting for the length of "IN()" | ||
$current_index += strlen($question_marks) + 4; | ||
} | ||
|
||
return [ 'sql' => $sql, 'params' => $params ]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
<?php | ||
|
||
use flight\database\PdoWrapper; | ||
|
||
/** | ||
* Flight: An extensible micro-framework. | ||
* | ||
* @copyright Copyright (c) 2012, Mike Cao <[email protected]> | ||
* @license MIT, http://flightphp.com/license | ||
*/ | ||
|
||
|
||
|
||
class PdoWrapperTest extends PHPUnit\Framework\TestCase | ||
{ | ||
/** | ||
* @var Pdo_Wrapper | ||
*/ | ||
private $pdo_wrapper; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->pdo_wrapper = new PdoWrapper('sqlite::memory:'); | ||
// create a test table and insert 3 rows of data | ||
$this->pdo_wrapper->exec('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)'); | ||
$this->pdo_wrapper->exec('INSERT INTO test (name) VALUES ("one")'); | ||
$this->pdo_wrapper->exec('INSERT INTO test (name) VALUES ("two")'); | ||
$this->pdo_wrapper->exec('INSERT INTO test (name) VALUES ("three")'); | ||
} | ||
|
||
protected function tearDown(): void | ||
{ | ||
// delete the test table | ||
$this->pdo_wrapper->exec('DROP TABLE test'); | ||
} | ||
|
||
public function testRunQuerySelectAllStatement() { | ||
$statement = $this->pdo_wrapper->runQuery('SELECT * FROM test'); | ||
$this->assertInstanceOf(PDOStatement::class, $statement); | ||
$this->assertCount(3, $statement->fetchAll()); | ||
} | ||
|
||
public function testRunQuerySelectOneStatement() { | ||
$statement = $this->pdo_wrapper->runQuery('SELECT * FROM test WHERE id = 1'); | ||
$this->assertInstanceOf(PDOStatement::class, $statement); | ||
$this->assertCount(1, $statement->fetchAll()); | ||
} | ||
|
||
public function testRunQueryInsertStatement() { | ||
$statement = $this->pdo_wrapper->runQuery('INSERT INTO test (name) VALUES ("four")'); | ||
$this->assertInstanceOf(PDOStatement::class, $statement); | ||
$this->assertEquals(1, $statement->rowCount()); | ||
} | ||
|
||
public function testRunQueryUpdateStatement() { | ||
$statement = $this->pdo_wrapper->runQuery('UPDATE test SET name = "something" WHERE name LIKE ?', ['%t%']); | ||
$this->assertInstanceOf(PDOStatement::class, $statement); | ||
$this->assertEquals(2, $statement->rowCount()); | ||
} | ||
|
||
public function testRunQueryDeleteStatement() { | ||
$statement = $this->pdo_wrapper->runQuery('DELETE FROM test WHERE name LIKE ?', ['%t%']); | ||
$this->assertInstanceOf(PDOStatement::class, $statement); | ||
$this->assertEquals(2, $statement->rowCount()); | ||
} | ||
|
||
public function testFetchField() { | ||
$id = $this->pdo_wrapper->fetchField('SELECT id FROM test WHERE name = ?', ['two']); | ||
$this->assertEquals(2, $id); | ||
} | ||
|
||
public function testFetchRow() { | ||
$row = $this->pdo_wrapper->fetchRow('SELECT * FROM test WHERE name = ?', ['two']); | ||
$this->assertEquals(2, $row['id']); | ||
$this->assertEquals('two', $row['name']); | ||
} | ||
|
||
public function testFetchAll() { | ||
$rows = $this->pdo_wrapper->fetchAll('SELECT * FROM test'); | ||
$this->assertCount(3, $rows); | ||
$this->assertEquals(1, $rows[0]['id']); | ||
$this->assertEquals('one', $rows[0]['name']); | ||
$this->assertEquals(2, $rows[1]['id']); | ||
$this->assertEquals('two', $rows[1]['name']); | ||
$this->assertEquals(3, $rows[2]['id']); | ||
$this->assertEquals('three', $rows[2]['name']); | ||
} | ||
|
||
public function testFetchAllWithNamedParams() { | ||
$rows = $this->pdo_wrapper->fetchAll('SELECT * FROM test WHERE name = :name', [ 'name' => 'two']); | ||
$this->assertCount(1, $rows); | ||
$this->assertEquals(2, $rows[0]['id']); | ||
$this->assertEquals('two', $rows[0]['name']); | ||
} | ||
|
||
public function testFetchAllWithInInt() { | ||
$rows = $this->pdo_wrapper->fetchAll('SELECT id FROM test WHERE id IN(?)', [ [1,2 ]]); | ||
$this->assertEquals(2, count($rows)); | ||
} | ||
|
||
public function testFetchAllWithInString() { | ||
$rows = $this->pdo_wrapper->fetchAll('SELECT id FROM test WHERE name IN(?)', [ ['one','two' ]]); | ||
$this->assertEquals(2, count($rows)); | ||
} | ||
|
||
public function testFetchAllWithInStringCommas() { | ||
$rows = $this->pdo_wrapper->fetchAll('SELECT id FROM test WHERE id > ? AND name IN(?)', [ 0, 'one,two' ]); | ||
$this->assertEquals(2, count($rows)); | ||
} | ||
|
||
} |