Bind SQL values and allowlist dynamic column names
A search endpoint accepts a term and a sort column. Both are strings from the request, but they occupy different positions in SQL. A bound parameter represents a value; it does not safely turn an arbitrary string into a column identifier.
Map the sort choice
<?php
$allowed = ['name' => 'name', 'newest' => 'created_at'];
$requested = 'newest';
$column = $allowed[$requested] ?? 'name';
assert($column === 'created_at');
// With an existing PDO connection:
// $statement = $pdo->prepare("SELECT id, name FROM products WHERE name = :name ORDER BY $column");
// $statement->execute(['name' => $searchTerm]);
Only the fixed allowlisted value is interpolated as an identifier. The search term is bound separately. Do the same for sort direction rather than appending an arbitrary request string after the column name.
Query safety does not establish data access
A perfectly parameterized query can still return another tenant's records. Include the authenticated tenant boundary in the query and enforce the application's policy. Parameter binding prevents one category of query construction error, not every data exposure.
For LIKE searches, decide whether % and _ should act as wildcards or literal characters. Binding preserves the value safely but does not change LIKE semantics. Escape those characters according to the database and intended search behavior when literal matching is required.
Test an unknown sort name, a malicious-looking sort string, and a search value containing quotes. Also test an identifier owned by a different account. The last assertion checks access control rather than SQL syntax and belongs in the endpoint's integration suite.
Reference
Official documentation. The examples here illustrate the stated boundary; adapt them to your application and test its failure paths.