Reputation: 10974
I'm kind of new with PDO and currently developing the API call that returns search results. How do I set a prepare statement if there are 2 optional parameters for the search query?
$app->get('/get/search', function () {
$sql = 'SELECT * FROM user WHERE name LIKE :name AND city = :city AND gender = :gender';
try {
$stmt = cnn()->prepare($sql);
$stmt->bindParam(':name', '%'.$_GET['name'].'%', PDO::PARAM_STR);
$stmt->bindParam(':city', '%'.$_GET['city'].'%', PDO::PARAM_STR);
$stmt->bindParam(':gender', $_GET['gender'], PDO::PARAM_INT);
$stmt->execute();
if($data = $stmt->fetchAll()) {
echo json_encode($data);
} else {
echo json_encode(array('error' => 'no records found');
}
} catch(PDOException $e) {
echo json_encode(array('error' => $e->getMessage()));
}
}
The issue here, is that both $_GET['city'] and $_GET['gender'] are optional. If I try to run the code above, it will asume that any empty variable should match an empty value in the column as well; in the other hand, if I do something like this:
if($_GET['gender']) $stmt->bindParam(':gender', $_GET['gender'], PDO::PARAM_INT);
...it will return this error: "SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens"
So, what's the solution if I want to keep the prepared sql statement for optional parameters? Thanks!
Update
This is the solution based on the accepted answer and some comments (by deceze and bill-karwin):
if($_GET['name']) $where[] = 'name LIKE :name';
if($_GET['city']) $where[] = 'city LIKE :city';
if(isset($_GET['gender'])) $where[] = 'gender = :gender';
if(count($where)) {
$sql = 'SELECT * FROM user WHERE '.implode(' AND ',$where);
$stmt = cnn()->prepare($sql);
$name = '%'.$_GET['name'].'%';
if($_GET['name']) $stmt->bindValue(':name', '%'.$_GET['name'].'%', PDO::PARAM_STR);
$city = '%'.$_GET['city'].'%';
if($_GET['city']) $stmt->bindParam(':city', $city, PDO::PARAM_STR);
if(isset($_GET['gender'])) $stmt->bindParam(':gender', $_GET['gender'], PDO::PARAM_BOOL);
$stmt->execute();
if($data = $stmt->fetchAll()) {
echo json_encode($data);
}
}
Upvotes: 5
Views: 9362
Reputation: 1380
There is a nice little function which can help: tiniest query builder. No frameworks or ORMs needed to make code look like this:
public function updateUser(int $id, string $email = '', string $password = '', string $name = '') {
$sql = \App\Utils\build_query([
[ 'UPDATE "users"'],
[$email ,'SET', 'email=:email'],
[$password ,',', 'password=:password'],
[$name ,',', 'name=:name'],
[ 'WHERE "id"=:id']
]);
$stmt = $this->db->prepare($sql);
$stmt->bindValue(':id', $id, \PDO::PARAM_INT);
// Optional bindings.
$email && $stmt->bindValue(':email', $email, \PDO::PARAM_STR);
$password && $stmt->bindValue(':password', $password, \PDO::PARAM_STR);
$name && $stmt->bindValue(':name', $name, \PDO::PARAM_STR);
$stmt->execute();
}
Note how neatly query components are created, with support for optional ones of course. The &&
experssions by bindings simply check whether this parameter is given, and if it is, then appropriate bindValue
are called.
Upvotes: 2
Reputation: 522016
Some good old dynamic SQL query cobbling-together...
$sql = sprintf('SELECT * FROM user WHERE name LIKE :name %s %s',
!empty($_GET['city']) ? 'AND city = :city' : null,
!empty($_GET['gender']) ? 'AND gender = :gender' : null);
...
if (!empty($_GET['city'])) {
$stmt->bindParam(':city', '%'.$_GET['city'].'%', PDO::PARAM_STR);
}
...
You can probably express this nicer and wrap it in helper functions etc. etc, but this is the basic idea.
Upvotes: 16