A query built by joining strings together mixes your instructions with someone else's data. The database cannot tell which is which - so a value chosen carefully stops being a value and becomes part of the question.

// wrong, and it is wrong even if you escape
$sql = "SELECT * FROM users WHERE email = '" . $email . "'";

The fix is not escaping

Escaping tries to make dangerous data safe. Prepared statements make the data ARRIVE SEPARATELY, so it can never be read as instructions at all. The database is told the question first and the values afterwards.

$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
Escaping can be got wrong by a character set, by a nested quote, by a value that has already been through another layer. A prepared statement cannot be got wrong in those ways, because the value never touches the SQL text.

What a placeholder cannot be

A placeholder stands for a VALUE. It cannot stand for a table name, a column name or a direction - those are part of the question. If they must come from input, check them against a list you wrote.

$allowed = ['created_at', 'total', 'status'];
$sort = in_array($in, $allowed, true) ? $in : 'created_at';
$sql = "SELECT * FROM orders ORDER BY $sort DESC";

Find the ones you have

grep -rn "SELECT.*\$_\(GET\|POST\|REQUEST\)" --include="*.php" . | head

Any query with a superglobal inside the string is the pattern. There are usually a handful and they are usually old.

And the second line

Give the application user only what it needs - four verbs on one database. A successful injection through an account that cannot drop a table does far less than one through an account that can.

An ORM parameterises for you until you use its raw-query helper. That helper is where injections live in modern code.