-
Notifications
You must be signed in to change notification settings - Fork 1
SqlBuilder: Easily Create Secure SQL Statements
Ryan Fischbach edited this page May 28, 2017
·
2 revisions
Given the following SQL statement, how would one convert it to use the SqlBuilder class?
"SELECT pack_test_id, text from {$this->tnPackageTests} pt JOIN {$this->tnTests} t on pt.test_id = t.test_id WHERE package_id = '$package_id' && stopped = 0 && acronym in (3, 5, 7, 9, 14, 16, 17)"
//Knowing how to write SQL statements with PHP's PDO classes, especially using
// parametrized queries, will help you greatly understand the SqlBuilder class.
// First, we create a new instance and map this model to it so that queries will
// use the correct database connection to execute the query.
// Most of these methods can be chained together but will not be done so here so
// that notes about the statement can be placed above each method.
$theSql = SqlBuilder::withModel($this);
//supply the parameters the SQL will likely be using (can be an object or an array)
$theSql->obtainParamsFrom(array(
'package_id' => $package_id,
'acronym' => array(3, 5, 7, 9, 14, 16, 17),
));
//NOTE: deliberately leaving "stopped" out as an example of a missing-but-required parameter.
//Once we have the object instance, start building our query.
$theSql->startWith('SELECT');
//add the field list desired for the statement
$theSql->add('pack_test_id, text');
//do not hardcode the table name, use the table name variable instead. this allows us
// to easily change the database name used in the database connection as well as
// let the website admin change the table prefix being used without having to change
// any code. Being able to change the database name and/or table prefix is a
// requirement to be able to install websites into 3rd party hosted data servers
// which might utilize shared databases.
$theSql->add('FROM')->add($this->tnPackageTests);
$theSql->add('JOIN')->add($this->tnTests)->add('USING (test_id)');
//tell SqlBuilder that we are starting to construct the WHERE clause; this allows us
// to treat NULL parameters correctly so that instead of the usual "field=:field"
// string generation which will not work if the value is NULL in the WHERE clause.
// Since SqlBuilder knows we are connected to a MySQL database, once we call
// startWhereClause(), the string generated would be "field IS NULL" which will
// make MySQL happy. While not strictly _needed_ here, it is best to get into the
// habit now so that if other quirks with other database types come up, we can easily
// accomodate them in SqlBuilder and your code will already be able to handle it.
$theSql->startWhereClause();
//use parameterized queries whenever you have a "variable value" and ESPECIALLY if
// that value is supplied by a webform in order to prevent SQL Injection attacks.
$theSql->mustAddParam('package_id');
//how should the next paramater in the WHERE clause be added? AND? OR? etc.
// the string will not be appended to the SQL statement unless a mustAddParam(),
// addParam(), or similar method actually appends a parameter to the SQL string
// being built. This allows us to make some parameters optional and only get
// appended to the SQL string correctly if there is data in the parameter.
// NOTE: spaces are important here and must be supplied as they will not
// be automatically added to the SQL statement.
$theSql->setParamPrefix(' AND ');
//if a required parameter is missing from the array (or object) passed into
// obtainParamsFrom(), we can specify a default value.
$theSql->mustAddParam('stopped', 0);
//once a prefix is set, no need to set it again unless you want to change it
//$theSql->setParamPrefix(' AND ');
//a parameter can be an array of values -- SqlBuilder will detect it and convert it
// to be an IN (...) clause automatically.
$theSql->mustAddParam('acronym');
//once done with the WHERE clause, tell SqlBuilder.
$theSql->endWhereClause();
//now that we're done building the SQL statement, it is now time to execute the query.
// place the execute method inside a try/catch block.
try {
$thePDOStatement = $theSql->query();
//do whatever else you need to here...
}
catch (\PDOException $pdoe)
//catching the PDOException and then re-throwing it via newDbException() will
// toss the exception expected by other aspects of the framework as well as gets
// automatically logged into the Strings::errorLog() mechanism.
{ throw $theSql->newDbException(__METHOD__, $pdoe); }