• Stars
    star
    736
  • Rank 61,564 (Top 2 %)
  • Language
    PHP
  • License
    MIT License
  • Created over 9 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Easy-to-use PDO wrapper for PHP projects.

EasyDB - Simple Database Abstraction Layer

Build Status Latest Stable Version Latest Unstable Version License Downloads

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

EasyDB was created by Paragon Initiative Enterprises as part of our effort to encourage better application security practices.

Check out our other open source projects too.

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

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.

Installing EasyDB

First, get Composer, if you don't already use it.

Next, run the following command:

/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.

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.

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.

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.

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):

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):

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:

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:

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

This is wrong:

$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:

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

This is wrong:

$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.

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.

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.

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.

$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.

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

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:

$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.

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.

vendor/bin/psalm --taint-analysis

This will expose where you're passing tainted data to EasyDB in a potentially unsafe way.

Troubleshooting Common Issues

Only one-dimensional arrays are allowed

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

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

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

<?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:

<?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.

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.

More Repositories

1

random_compat

PHP 5.x support for random_bytes() and random_int()
PHP
8,170
star
2

awesome-appsec

A curated list of resources for learning about application security
PHP
6,305
star
3

paseto

Platform-Agnostic Security Tokens
PHP
3,248
star
4

halite

High-level cryptography interface powered by libsodium
PHP
1,124
star
5

sodium_compat

Pure PHP polyfill for ext/sodium
PHP
894
star
6

constant_time_encoding

Constant-Time Character Encoding in PHP Projects
PHP
816
star
7

csp-builder

Build Content-Security-Policy headers from a JSON file (or build them programmatically)
PHP
543
star
8

chronicle

Public append-only ledger microservice built with Slim Framework
PHP
469
star
9

ciphersweet

Fast, searchable field-level encryption for PHP projects
PHP
436
star
10

airship

Secure Content Management for the Modern Web - "The sky is only the beginning"
PHP
418
star
11

sapient

Secure API Toolkit
PHP
314
star
12

anti-csrf

Full-Featured Anti-CSRF Library
PHP
297
star
13

certainty

Automated cacert.pem management for PHP projects
PHP
262
star
14

EasyRSA

Simple and Secure Wrapper for phpseclib
PHP
198
star
15

password_lock

Wraps Bcrypt-SHA2 in Authenticated Encryption
PHP
190
star
16

sodium-plus

Developer-friendly libsodium interface
JavaScript
171
star
17

multi_factor

Vendor-Agnostic Two-Factor Authentication
PHP
142
star
18

gpg-mailer

GnuPG-encrypted emails made easy
PHP
96
star
19

pecl-libsodium-doc

Free Online Documentation for the Libsodium PHP Extension
88
star
20

typed-arrays

Userland typed array implementation
PHP
74
star
21

ciphersweet-js

Searchable Encryption for Node.js projects
JavaScript
65
star
22

hidden-string

The HiddenString class extracted from Halite.
PHP
64
star
23

corner

Exceptions and Errors made more user-friendly
PHP
62
star
24

paseto-io

Paseto Website
Twig
58
star
25

iaso

Powerful JSON Toolkit, includes a JSON parser immune to Hash-DoS attacks
PHP
51
star
26

easy-ecc

High-Level Usability Wrapper for PHPECC
PHP
44
star
27

hpkp-builder

Build HTTP Public-Key-Pinning headers from a JSON file (or build them programmatically)
PHP
41
star
28

seedspring

Seeded, Deterministic PRNG (based on AES-CTR instead of LCG)
PHP
39
star
29

passwdqc

Password/passphrase strength checking and enforcement (PHP port)
PHP
39
star
30

pharaoh

Utility to quickly and effectively diff two PHP Archives
PHP
36
star
31

libgossamer

Public Key Infrastructure without Certificate Authorities, for WordPress and Packagist
PHP
33
star
32

ionizer

Input Filter System for PHP Software
PHP
32
star
33

easydb-cache

EasyDB with Prepared Statement Caching
PHP
30
star
34

argon2-refiner

Generate Parameter Recommendations for Argon2id in PHP 7.3+
PHP
25
star
35

bsidesorl-2017

Supplementary Material for Building Defensible Solutions to Weird Problems
PHP
23
star
36

blakechain

Hash chains built with BLAKE2b
PHP
22
star
37

quill

Library for quickly and easily writing data to a Chronicle instance
PHP
21
star
38

stern

Stern lets you built type-safe PHP projects, even if your project's users aren't writing type-safe code
PHP
21
star
39

herd

Hash-Ensured Replicated Database
PHP
16
star
40

eloquent-ciphersweet

Bridge library between Eloquent ORM and CipherSweet
PHP
14
star
41

discretion

On-demand and reusable contact forms that only send GnuPG-encrypted messages to your inbox.
PHP
13
star
42

xchacha20-js

JavaScript implementation of ChaCha20, HChaCha20, and XChaCha20
JavaScript
12
star
43

pco_prototype

PCO - PHP Crypto Objects
PHP
12
star
44

paserk-php

PHP Implementation of PASERK
PHP
12
star
45

slim-sapient

Slim Framework Adapter for Sapient
PHP
10
star
46

airship-docs

Documentation for CMS Airship
Nginx
10
star
47

php-jwt-guard

Security Defense for Firebase's PHP-JWT Library
PHP
9
star
48

zend-diactoros-sapient

Zend Diactoros Adapter for Sapient
PHP
8
star
49

phone-to-pick

Whitelist Your Incoming Phone Calls (for Android)
Java
7
star
50

gossamer-server

Standalone Gossamer server
PHP
5
star
51

poly1305-js

JavaScript implementation of the Poly1305 one-time authenticator
JavaScript
5
star
52

paseto-browser.js

PASETO in the Web Browser
JavaScript
4
star
53

airship-barge

Build Gadgets for Airship projects (Command Line Interface)
PHP
3
star
54

monolog-quill

A Monolog Handler for writing to a Chronicle instance
PHP
3
star
55

sodium-jvm

Pure-Java implementation of the Sodium cryptography library.
3
star
56

blogpost-translations

Translations of Paragon Initiative Enterprise blog posts
2
star
57

ward-docs

Online Documentation for Ward (Web Application Realtime Defender)
2
star
58

php71_crypto

Pluggable Cryptography Interface for PHP 7.1
2
star
59

ristretto-php

Implements a type-safe API for working with the Ristretto Group in PHP projects.
PHP
2
star
60

node-halite

High-level cryptography interface powered by node-sodium
1
star
61

pie-hosted.com

Source code for the pie-hosted.com website
PHP
1
star
62

halite-legacy

Legacy versions of Halite to facilitate migrations from older ciphersuites to the latest supported version
PHP
1
star
63

certainty-js

Certainty-js: Automated CACert.pem Management for Node.js Software
JavaScript
1
star