$sql = "SELECT * FROM users WHERE email = '$email'";
Someone submits ' OR '1'='1 and the query becomes WHERE email = '' OR '1'='1', true for every row. With a login, that is a login as the first user in the table, usually the administrator.
Escaping quotes by hand fails on charset edge cases, on numeric contexts, and on the day someone forgets. The fix is structural: send the query and the data SEPARATELY, so the data can never be read as SQL.
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);Now ' OR '1'='1 is just a string that matches no email. It is not cleverly escaped. It is never parsed as SQL at all.
They bind VALUES, not identifiers. A table or column name cannot be a placeholder. If a sort column comes from the URL, validate it against an allow-list:
$allowed = ['name','price','created_at']; $sort = in_array($_GET['sort'] ?? '', $allowed, true) ? $_GET['sort'] : 'name';
Turn off emulated prepares, ATTR_EMULATE_PREPARES => false, so the database does the parameterising rather than the driver doing string interpolation on your behalf.