2017 © Pedro Peláez
 

library easydb

Easy-to-use database abstraction

image

paragonie/easydb

Easy-to-use database abstraction

  • Sunday, July 15, 2018
  • by paragonie-scott
  • Repository
  • 30 Watchers
  • 360 Stars
  • 12,536 Installations
  • PHP
  • 12 Dependents
  • 0 Suggesters
  • 47 Forks
  • 4 Open issues
  • 35 Versions
  • 34 % Grown

The README.md

EasyDB - Simple Database Abstraction Layer

Build Status Latest Stable Version Latest Unstable Version License Downloads, (*1)

PDO lacks brevity and simplicity; EasyDB makes separating data from instructions easy (and aesthetically pleasing)., (*2)

EasyDB was created by Paragon Initiative Enterprises as part of our effort to encourage better application security practices., (*3)

Check out our other open source projects too., (*4)

If you're looking for a full-fledged query builder, check out Latitude and Aura.SqlQuery, which can be used with EasyDB., (*5)

If you'd like to use EasyDB but cache prepared statements in memory for multiple queries (i.e. to reduce database round-trips), check out our EasyDB-Cache wrapper class., (*6)

Installing EasyDB

First, get Composer, if you don't already use it., (*7)

Next, run the following command:, (*8)

/path/to/your/local/composer.phar require paragonie/easydb:^3

If you've installed Composer in /usr/bin, you can replace /path/to/your/local/composer.phar with just composer., (*9)

Why Use EasyDB? Because it's cleaner!

Let's refactor a dangerous PHP snippet that previously used string concatenation to pass user input instead of prepared statements. For example, imagine something that just dropped {$_GET['blogpostid']} into the middle of a mysql_query() statement. Let's make it secure., (*10)

The PDO Way

$db = new \PDO(
    'mysql:host=localhost;dbname=something',
    'username',
    'putastrongpasswordhere'
);

$statement = $db->prepare('SELECT * FROM comments WHERE blogpostid = ? ORDER BY created ASC');
$exec = $statement->execute([$_GET['blogpostid']]);
$rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
foreach ($rows as $row) {
    $template_engine->render('comment', $row);
}

That's a little wordy for such a simple task. If we do this in multiple places, we end up repeating ourselves a lot., (*11)

The EasyDB Solution

$db = \ParagonIE\EasyDB\Factory::fromArray([
    'mysql:host=localhost;dbname=something',
    'username',
    'putastrongpasswordhere'
]);

$rows = $db->run('SELECT * FROM comments WHERE blogpostid = ? ORDER BY created ASC', $_GET['blogpostid']);
foreach ($rows as $row) {
    $template_engine->render('comment', $row);
}

We made it a one-liner., (*12)

What else can EasyDB do quickly?

Insert a row into a database table

$db->insert('comments', [
    'blogpostid' => $_POST['blogpost'],
    'userid' => $_SESSION['user'],
    'comment' => $_POST['body'],
    'parent' => isset($_POST['replyTo']) ? $_POST['replyTo'] : null
]);

This is equivalent to the following SQL query (assuming $_POST['blogpostid'] is equal to 123, $_SESSION['user'] is equal to 234, $_POST['body'] is equal to test, and $_POST['replyTo'] is equal to 3456):, (*13)

INSERT INTO comments (blogpostid, userid, comment, parent) VALUES (
    123,
    234,
    'test',
    3456
);

Build an insert without executing

$sql = $db->buildInsertQuery('comments', [
    'blogpostid',
    'userid',
    'comment'
]);

// INSERT INTO comments (blogpostid, userid, comment) VALUES (?, ?, ?)

$result = $db->q(
    $sql,
    $values,
    \PDO::FETCH_BOTH,
    true
);

Update a row from a database table

$db->update('comments', [
    'column' => 'foo',
    'otherColumn' => 123456,
    'approved' => true
], [
    'commentid' => $_POST['comment']
]);

This is equivalent to the following SQL query (assuming $_POST['comment'] is equal to 789):, (*14)

UPDATE comments
SET 
  column = 'foo',
  otherColumn = 123456,
  approved = TRUE
WHERE commentid = 789

Delete a row from a database table

// Delete all of this user's comments
$db->delete('comments', [
    'userid' => 3
]);

This is equivalent to the following SQL query:, (*15)

DELETE FROM comments WHERE userid = 3

Fetch a single row from a table

$userData = $db->row(
    "SELECT * FROM users WHERE userid = ?",
    $_GET['userid']
);

Note: This expects a variadic list of arguments, not an array. If you have multiple parameters, stack them like this:, (*16)

$userData = $db->row(
    "SELECT * FROM users WHERE userid = ? AND other = ?",
    $_GET['userid'],
    $_GET['other']
);

This is wrong:, (*17)

$userData = $db->row(
    "SELECT * FROM users WHERE userid = ? AND other = ?",
    array($userid, $other) // WRONG, should not be in an array
);

Fetch a single column from a single row from a table

$exists = $db->cell(
    "SELECT count(id) FROM users WHERE email = ?",
    $_POST['email']
);

/* OR YOU CAN CALL IT THIS WAY: */
$exists = $db->single(
    "SELECT count(id) FROM users WHERE email = ?",
    array(
        $_POST['email']
    )
);

Note: cell() expects a variadic list of arguments, not an array. If you have multiple parameters, stack them like this:, (*18)

$exists = $db->cell(
    "SELECT count(id) FROM users WHERE email = ? AND username = ?",
    $_POST['email'],
    $_POST['usenrame']
);

This is wrong:, (*19)

$exists = $db->cell(
    "SELECT count(id) FROM users WHERE email = ? AND username = ?",
    array($email, $username) // WRONG, should not be in an array
);

Alternatively, you can use single() instead of cell() if you really want to pass an array., (*20)

Try to perform a transaction

$save = function (EasyDB $db) use ($userData, $query) : int {
    $db->safeQuery($query, [$userData['userId']]);
    return \Some\Other\Package::CleanUpTable($db);
};
// auto starts, commits and rolls back a transaction as necessary
$returnedInt = $db->tryFlatTransaction($save);

Generate dynamic query conditions

$statement = EasyStatement::open()
    ->with('last_login IS NOT NULL');

if (strpos($_POST['search'], '@') !== false) {
    // Perform a username search
    $statement->orWith('username LIKE ?', '%' . $db->escapeLikeValue($_POST['search']) . '%');
} else {
    // Perform an email search
    $statement->orWith('email = ?', $_POST['search']);
}

// The statement can compile itself to a string with placeholders:
echo $statement; /* last_login IS NOT NULL OR username LIKE ? */

// All the values passed to the statement are captured and can be used for querying:
$user = $db->single("SELECT * FROM users WHERE $statement", $statement->values());

Note: Passing values with conditions is entirely optional but recommended., (*21)

Variable number of "IN" arguments

// Statements also handle translation for IN conditions with variable arguments,
// using a special ?* placeholder:
$roles = [1];
if ($_GET['with_managers']) {
    $roles[] = 2;
}

$statement = EasyStatement::open()->in('role IN (?*)', $roles);

// The ?* placeholder is replaced by the correct number of ? placeholders:
echo $statement; /* role IN (?, ?) */

// And the values will be unpacked accordingly:
print_r($statement->values()); /* [1, 2] */

Grouping of conditions

// Statements can also be grouped when necessary:
$statement = EasyStatement::open()
    ->group()
        ->with('subtotal > ?')
        ->andWith('taxes > ?')
    ->end()
    ->orGroup()
        ->with('cost > ?')
        ->andWith('cancelled = 1')
    ->end();

echo $statement; /* (subtotal > ? AND taxes > ?) OR (cost > ? AND cancelled = 1) */

Insert and Update with custom placeholder

Since Version 2.12.0, EasyDB supports placeholders for calling stored procedures and SQL functions when inserting or updating data., (*22)

The EasyPlaceholder class is constructed in the same fashion as other EasyDB methods: The first argument, the "mask", must be a string. The mask may contain ? placeholders, and any subsequent arguments will fill in for the ? placeholders when the query is executed., (*23)

$db->insert('user_auth', [
    'user_id' => 1,
    'timestamp' => new EasyPlaceholder('NOW()'),
    'expired' => new EasyPlaceholder('TIMESTAMPADD(HOUR, 2, NOW())'),
    'location' => new EasyPlaceholder(
        "ST_GeomFromText(CONCAT('POINT(', ?, ' ', ?, ')'))",
        50.4019514,
        30.3926105
    )
]);

$db->update(
    'user_auth', 
    [
        'last_update' => new EasyPlaceholder('NOW()'),
    ], 
    [
        'user_id' => 1
    ]
);

Security warning: Do not concatenate user input into the first parameter., (*24)

EasyPlaceholder can be used in insert(), insertIgnore(), insertOnDuplicateKeyUpdate(), and update()., (*25)

What if I need PDO for something specific?

$pdo = $db->getPdo();

Can I create an EasyDB wrapper for an existing PDO instance?

Yes! It's as simple as doing this:, (*26)

$easy = new \ParagonIE\EasyDB\EasyDB($pdo, 'mysql');

How do I run tests ?

vendor/bin/phpunit

Using Psalm's Security Analysis with EasyDB

First, make sure you've read the Psalm documentation., (*27)

EasyDB's API exposes several taint sinks. Next, run the following command on your codebase that uses EasyDB to identify sources of SQL injection risk., (*28)

vendor/bin/psalm --taint-analysis

This will expose where you're passing tainted data to EasyDB in a potentially unsafe way., (*29)

Troubleshooting Common Issues

Only one-dimensional arrays are allowed

This comes up a lot when trying to pass an array of parameters to run()., (*30)

EasyDB::run() expects a query string, then any number of optional parameters. It does NOT expect an array of all the parameters., (*31)

If you want to use an API that looks like $obj->method($string, $array), use safeQuery() instead of run()., (*32)

<?php
/**
 * @var EasyDB $db
 * @var string $query
 * @var array $params 
 */
- $rows = $db->run($query, $params);
+ $rows = $db->safeQuery($query, $params);

Alternatively, you can flatten your array with the splat operator:, (*33)

<?php
/**
 * @var EasyDB $db
 * @var string $query
 * @var array $params 
 */
- $rows = $db->run($query, $params);
+ $rows = $db->run($query, ...$params);

EasyDB's run() method is a variadic wrapper for safeQuery(), so either solution is correct., (*34)

Support Contracts

If your company uses this library in their products or services, you may be interested in purchasing a support contract from Paragon Initiative Enterprises., (*35)

The Versions

15/07 2018

dev-master

9999999-dev

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *
  • php ^7

 

The Development Requires

database sql security pdo

14/06 2018

dev-v1.x-legacy

dev-v1.x-legacy

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *
  • php ^5.5|^7

 

The Development Requires

database sql security pdo

14/06 2018

v1.6.2

1.6.2.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • php ^5.5|^7
  • ext-pdo *

 

The Development Requires

database sql security pdo

14/06 2018

v2.6.2

2.6.2.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • php ^7
  • ext-pdo *

 

The Development Requires

database sql security pdo

01/05 2018

v1.6.1

1.6.1.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • php ^5.5|^7
  • ext-pdo *

 

The Development Requires

database sql security pdo

01/05 2018

v2.6.1

2.6.1.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • php ^7
  • ext-pdo *

 

The Development Requires

database sql security pdo

10/03 2018

v2.6.0

2.6.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

27/02 2018

dev-easystatement-update

dev-easystatement-update

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

27/12 2017

v2.5.1

2.5.1.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

18/12 2017

v2.5.0

2.5.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

17/11 2017

v2.4.0

2.4.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

27/06 2017

dev-psalm

dev-psalm

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

10/05 2017

v1.3.1

1.3.1.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

10/05 2017

v2.3.1

2.3.1.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

24/01 2017

dev-feature/v3-split-escape

dev-feature/v3-split-escape

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

24/01 2017

dev-feature/enhanced-escaping

dev-feature/enhanced-escaping

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

12/01 2017

v1.3.0

1.3.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

12/01 2017

dev-feature/version-3-0

dev-feature/version-3-0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

12/01 2017

v2.3.0

2.3.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

23/11 2016

v1.2.1

1.2.1.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

23/11 2016

v2.2.1

2.2.1.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

04/11 2016

v1.2.0

1.2.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

04/11 2016

v2.2.0

2.2.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

01/11 2016

v1.1.1

1.1.1.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

01/11 2016

v2.1.1

2.1.1.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

The Development Requires

database sql security pdo

31/10 2016

v1.1.0

1.1.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • php ^7.0
  • ext-pdo *

 

The Development Requires

database sql security pdo

22/10 2016

v2.1.0

2.1.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • php ^7.0
  • ext-pdo *

 

The Development Requires

database sql security pdo

19/10 2016

v2.0.1

2.0.1.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

database sql security pdo

19/10 2016

v2.0.0

2.0.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

The Requires

  • ext-pdo *

 

database sql security pdo

23/04 2016

v1.0.0

1.0.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

database sql security pdo

02/02 2016

v0.2.4

0.2.4.0

Easy-to-use database abstraction

  Sources   Download

MIT

database sql security pdo

19/01 2016

v0.2.3

0.2.3.0

Easy-to-use database abstraction

  Sources   Download

MIT

database sql security pdo

19/01 2016

v0.2.2

0.2.2.0

Easy-to-use database abstraction

  Sources   Download

MIT

database sql security pdo

30/05 2015

0.2.0

0.2.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

database sql security pdo

23/05 2015

0.1.0

0.1.0.0

Easy-to-use database abstraction

  Sources   Download

MIT

database sql security pdo