Both bindings here are fine:
$q->where(DB::raw("CONCAT(`firstname`, ' ', `lastname`)"), 'LIKE', "%".$query."%")
->orwhere('email', 'LIKE', "%{$query}%");
As Eloquent's ->where() will bind the string as a parameter and will pass it to PDO as a prepared statement parameter.
The issue would be if you where doing something like this:
$q->whereRaw("CONCAT(`firstname`, ' ', `lastname`) LIKE '%{$query}% '")
where you tell the query builder: "hey don't touch this expression, I know what I am doing!" While concatenating the $query parameter manually.
->whereRaw() accepts a bindings array, which I prefer when adding conditions like yours:
$q->whereRaw("CONCAT(`firstname`, ' ', `lastname`) LIKE ?", ['%' . $query . '%'])
Note that one thing is escaping against SQL Injection attacks, this is properly done by PDO when using prepared statements, and is the case on your code samples and on this last one.
The other is escaping against wildcard usage.
For example: If a user provides this search string: John%Doe, they will be able to add a SQL wildcard character in the middle of their search string on their will.
If you want to prevent this too, you need to escape the search string against its wildcard characters manually. See this example from a package that does that: