1. Go to this page and download the library: Download snicco/better-wpdb library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
snicco / better-wpdb example snippets
❌ // This is not a prepared query
$wpdb->get_results(
$wpdb->prepare('select * from `test_table` where `test_int` = %d and `test_string` = %s', [1, 'foo'])
);
✅ // This is a "real" prepared query
$better_wpdb->preparedQuery('select * from `wp_users` where `id` = ?' and `test_string` = ?, [1, 'foo']);
❌ // This is what you typically see in WordPress code
$result = $wpdb->query('apparently not so valid sql');
if($result === false) {
throw new Exception($wpdb->last_error);
}
✅ // This is how it should be
$result = $better_wpdb->preparedQuery('apparently not so valid sql');
// You will never ever get here.
var_dump($e->getMessage()) // You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'apparently not a valid SQL statement' at line 1
// Query: [apparently not a valid SQL statement]
// Bindings: []
❌
$result = $wpdb->insert('test_table', [
// the limit is 10, we insert 11 chars
'test_string' => str_repeat('X', 11)
])
var_dump($result) // (bool) false
var_dump($wpdb->last_error); // WordPress database error: Processing the value for the following field failed: test_string.
// The supplied value may be too long or contains invalid data.
// Notice that there is no mention of the invalid query or what type of data was inserted
// As a little benefit you also have nothing in your error log.
✅
$result = $better_wpdb->insert('test_table', [
'test_string' => str_repeat('X', 11)
])
// You will never ever get here.
var_dump($e->getMessage()) // Data too long for column 'test_string' at row 1
// Query: [insert into test_table (test_string) values(?)]
// Bindings: ['XXXXXXXXXXX']
// This exception message is automatically logged.
❌
$result = $wpdb->insert('test_table', [
'test_string' => 'bar'
'test_int' => -10
])
var_dump($result) // (bool) true
var_dump($wpdb->last_error); // ''
// Congratulations. Your database now contains invalid data and you will never know about it.
✅
$result = $better_wpdb->insert('test_table', [
'test_string' => 'bar'
'test_int' => -10
])
// You will never ever get here.
var_dump($e->getMessage()) // Out of range value for column 'test_int' at row 1
// Query: [insert into test_table (test_string, test_int) values (?,?)]
// Bindings: ['bar', -10]
// This exception message is automatically logged.
❌ // The abbreviated phpdoc of wpdb::get_row
// This method has 4 different return types? Also, what is return void?
/**
*
* @param string|null $query
* @param string $output
* @param int $y
* @return array|object|null|void Database query result in format specified by $output or null on failure.
*/
public function get_row($query = null, $output = OBJECT, $y = 0) {
//
}
✅ // Your favorite static analysis tool will thank you.
/**
* @param non-empty-string $sql
* @param array<scalar|null> $bindings
*
* @return array<string, string|int|float|null>
*
* @throws NoMatchingRowFound
* @throws QueryException
* @throws InvalidArgumentException
*/
public function selectRow(string $sql, array $bindings): array {
//
}
// se Snicco\Component\BetterWPDB\BetterWPDB;
$better_wpdb = BetterWPDB::fromWpdb();
// se Snicco\Component\BetterWPDB\BetterWPDB;
$mysqli = /* ... */
$better_wpdb = new BetterWPDB($mysqli);
use Snicco\Component\BetterWPDB\BetterWPDB;
$mysqli = /* ... */
$better_wpdb = new BetterWPDB($mysqli);
// Only for select queries.
$auto_restore_error_handling = false;
// stmt is an instance of mysqli_stmt
$stmt = $better_wpdb->preparedQuery(
'select * from test_table where test_string = ? or test_int = ?',
['foo', 1],
$auto_restore_error_handling
);
var_dump($stmt->num_rows);
var_dump($stmt->affected_rows);
$better_wpdb->restoreErrorHandling();
❌❌❌ // NEVER EVER DO THIS. You will get hacked.
$order_by = $_GET['order'];
$better_wpdb->preparedQuery(
'select * from test_table where test_string = ? order by ?',
[$_GET['test_string'], $order_by]
)
✅ // Use a whitelist approach
$order_by = 'desc';
$_get_order_by = strtolower($_GET['order_by']);
if('asc' === $_get_order_by) {
$order_by = 'asc';
}
$better_wpdb->preparedQuery(
'select * from test_table where test_string = ? order by ?',
[$_GET['test_string'], $order_by]
)
/** @var mysqli_result $result */
$result = $better_wpdb->select('select * from test_table where test_string = ?', ['foo']);
echo $result->num_rows
while($row = $result->fetch_array()) {
// Do stuff with $row
}
/** @var array<array> $result */
$rows = $better_wpdb->selectAll('select * from test_table where test_string = ?', ['foo']);
foreach ($rows as $row) {
echo $row['test_string'];
echo $row['test_int'];
echo $row['test_bool'];
echo $row['test_float'];
}
❌ // you just loaded 100k rows into memory
$orders = $better_wpdb->selectAll('select * from orders where created_at <= ?', [$date]);
✅ // You load 1 row at a time. But only when you start looping over the result.
/** @var Generator<array> $orders */
$orders = $better_wpdb->selectLazy('select * from orders where created_at <= ?', [$date]);
// You have not made any db queries yet.
foreach ($orders as $order) {
// One order is fetched at a time.
// You only make one db query. But thanks to the generator you only have one order in memory
// process order
}
use Snicco\Component\BetterWPDB\BetterWPDB;
use Snicco\Component\BetterWPDB\KeysetPagination\Lock;
use Snicco\Component\BetterWPDB\KeysetPagination\Query;
// This should be a simple SQL query. It can contain where clauses with placeholder ("?").
// But make sure that the (static) conditions have the proper indexes, otherwise this method will not perform well.
// DON'T add any LIMIT or ORDER BY clauses.
$sql = 'select ID from wp_users';
// With a (static) condition:
// $sql = 'select ID form wp_posts where post_type = ?'
// The combination of the sorting columns must be a unique record.
// This is typically done by using the tables primary key. However, compound primary keys are supported.
// (http://mysql.rjweb.org/doc.php/deletebig#iterating_through_a_compound_key)
$deterministic_sorting_columns = ['ID' => 'asc'];
// The batch size is the number of records that are passed to the callback function at once.
$batch_size = 500;
// Optional: The values for static conditions (if any are used).
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// THIS IS THE ONLY PLACE WHERE USER INPUT IS ALLOWED.
$static_column_bindings = [];
//$static_column_bindings = ['some-post-type']
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
$query = new Query($sql, ['ID' => 'asc'], $deterministic_sorting_columns, $batch_size, $static_column_bindings);
// (Optional) database lock. If a lock is passed as a third argument to the batchProcess method
// each batch of records will be executed in a database transaction using the provided lock type.
$lock = Lock::forReadWrite();
// Your user-defined callback function.
// $records is an array containing $batch_size database records.
// You can return arbitrary values from this method, they will be returned as an array.
$callback = function(array $records) :array {
$failed = [];
foreach ($records as $record) {
$id = $record['ID'];
try {
$user_login = get_user_by('id', $ID)->user_login;
retrieve_password($user_login);
} catch (Throwable $e) {
$failed[] = $id;
}
}
return $failed;
}
/** @var BetterWPDB $db */
$db = /* */
/** @var array<int[]> $failed_records */
$failed_records = $db->batchProcess($query, $callback, $lock);
try {
/** @var array $row */
$row = $better_wpdb->selectRow('select * from test_table where test_string = ? limit 1', ['foo']);
echo $row['test_string'];
echo $row['test_int'];
echo $row['test_bool'];
echo $row['test_float'];
}catch (NoMatchingRowFound $e) {
// don't catch this exception. Just a demo.
}
try {
/** @var int $row */
$count = $better_wpdb->selectValue('select count(*) from test_table where test_string = ?', ['foo']);
}catch (NoMatchingRowFound $e) {
// don't catch this exception. Just a demo.
}
❌ // This is awful. What happens if a customer and an order is created but creating the payment fails?
my_plugin_create_customer();
my_plugin_create_create();
my_plugin_create_payment();
✅ // wrap these calls inside a database transaction
$better_wpdb->transactional(function () {
my_plugin_create_customer();
my_plugin_create_create();
my_plugin_create_payment(); // If this fails, customer and order will not be created.
});
use Snicco\Component\BetterWPDB\BetterWPDB;use Snicco\Component\BetterWPDB\QueryInfo;use Snicco\Component\BetterWPDB\QueryLogger;
class NewRelicLogger implements QueryLogger {
public function log(QueryInfo $info) :void {
$sql = $info->sql;
$duration = $info->duration_in_ms;
$start_time = $info->start;
$end_time = $info->end
// log to new relic
}
}
$better_wpdb = BetterWPDB::fromWpdb(new NewRelicLogger());
// Now, all queries, including the sql and duration are logged automatically
$better_wpdb->insert('test_table' , ['test_string' => 'foo']);
❌ // You don't need a query builder. The query is always the same. Only the input changes.
$query = SomeQueryBuilder::table('table')
->select([['col1', 'col1']])
->where('condition_1' = ?)
->andWhere('condition2' = ?)
->orWhere('condition3' = ?)
->limit(1)
->orderBy('desc')
✅ // As plain sql.
$query = 'select col1, col1
from table
where ( condition_1 = ? and condition2 = ? )
or condition3 = ?
limit 1
order by desc'
$result = $better_wpdb->selectAll($query, ['foo', 'bar', 'baz']);
use Latitude\QueryBuilder\Engine\CommonEngine;
use Latitude\QueryBuilder\QueryFactory;
use function Latitude\QueryBuilder\field;
$factory = new QueryFactory(new CommonEngine());
$query = $factory
->select('id', 'username')
->from('users')
->where(field('id')->eq(5))
->compile();
$sql = $query->sql(); // SELECT "id" FROM "users" WHERE "id" = ?
$bindings = $query->params(); // [5]
$results = $better_wpdb->selectRow($sql, $bindings);
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.