diff --git a/README.md b/README.md index a8cef90..f4bf25e 100644 --- a/README.md +++ b/README.md @@ -378,6 +378,58 @@ $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\Pdo_Wrapper::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]]); + + // Insert a new user + Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']); + $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, diff --git a/flight/database/Pdo_Wrapper.php b/flight/database/Pdo_Wrapper.php new file mode 100644 index 0000000..7628d11 --- /dev/null +++ b/flight/database/Pdo_Wrapper.php @@ -0,0 +1,148 @@ +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 + */ + 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 ]; + } +} \ No newline at end of file