Laravel how to get query with bindings? - laravel

I have some query that I need to pass to another query using query builder
$query = DB::table('table')->whereIn('some_field', [1,2,30])->toSql();
Model::join(DB::raw("({$query}) as table"), function($join) {
$join->on('model.id', '=', 'table.id');
})
which should results with
Select * from model join (select * from table where some_field in (1,2,30)) as table on model.id = table.id
but the bindings are not passed, which force me to do
$query = DB::table('table')->whereRaw('some_field in ('. join(',', [1,2,30]) .')')->toSql();
what can be unsafe at times. How can I get the query with bindings?

Check out the getBindings() method on the Builder class
getBindings()
$query = DB::table('table')->whereIn('some_field', [1,2,30]);
$sql = $query->toSql();
$bindings = $query->getBindings();

Laravel now offers debugging directly on your Builder!!!
https://laravel.com/docs/queries#debugging
\App\User::where('age', '18')->dump();
\App\User::where('age', '18')->dd();
Outputs
"select * from `users` where `age` = ?"
[
0 => "18"
]

public static function getQueries(Builder $builder)
{
$addSlashes = str_replace('?', "'?'", $builder->toSql());
return vsprintf(str_replace('?', '%s', $addSlashes), $builder->getBindings());
}

You can define below code block as helper function and use wherever required.
It will bind numeric as well as string value with quotations.
public static function getSqlWithBindings($query)
{
return vsprintf(str_replace('?', '%s', $query->toSql()), collect($query->getBindings())->map(function ($binding) {
return is_numeric($binding) ? $binding : "'{$binding}'";
})->toArray());
}
Example:
$query = Document::where('model', 'contact')->where('model_id', '1');
dd(Document::getSqlWithBindings($query));
Output:
"select * from `document` where `model` = 'contact' and `model_id` = 1"

Building upon Douglas.Sesar's answer.
I found I also needed to put the bindings in single quotations to be able to easily paste it into my database IDE.
$sql = $query->toSql();
$bindings = $query->getBindings();
$sql_with_bindings = preg_replace_callback('/\?/', function ($match) use ($sql, &$bindings) {
return json_encode(array_shift($bindings));
}, $sql);

$sqlQuery = Str::replaceArray(
'?',
collect($query->getBindings())
->map(function ($i) {
if (is_object($i)) {
$i = (string)$i;
}
return (is_string($i)) ? "'$i'" : $i;
})->all(),
$query->toSql());

The following function ensures the resulting SQL doesn't confuse bindings with columns by enclosing the ? to be '?'
public static function getFinalSql($query)
{
$sql_str = $query->toSql();
$bindings = $query->getBindings();
$wrapped_str = str_replace('?', "'?'", $sql_str);
return str_replace_array('?', $bindings, $wrapped_str);
}

If you want to get an executed query including bindings from the query log:
\DB::enableQueryLog();
\DB::table('table')->get();
dd(str_replace_array('?', \DB::getQueryLog()[0]['bindings'],
\DB::getQueryLog()[0]['query']));

I created this function. It is partial, might be parameters which are not covered, for me it was enough.
More than welcomed to add your improvements in a comment!
function getFullSql($query) {
$sqlStr = $query->toSql();
foreach ($query->getBindings() as $iter=>$binding) {
$type = gettype($binding);
switch ($type) {
case "integer":
case "double":
$bindingStr = "$binding";
break;
case "string":
$bindingStr = "'$binding'";
break;
case "object":
$class = get_class($binding);
switch ($class) {
case "DateTime":
$bindingStr = "'" . $binding->format('Y-m-d H:i:s') . "'";
break;
default:
throw new \Exception("Unexpected binding argument class ($class)");
}
break;
default:
throw new \Exception("Unexpected binding argument type ($type)");
}
$currentPos = strpos($sqlStr, '?');
if ($currentPos === false) {
throw new \Exception("Cannot find binding location in Sql String for bundung parameter $binding ($iter)");
}
$sqlStr = substr($sqlStr, 0, $currentPos) . $bindingStr . substr($sqlStr, $currentPos + 1);
}
$search = ["select", "distinct", "from", "where", "and", "order by", "asc", "desc", "inner join", "join"];
$replace = ["SELECT", "DISTINCT", "\n FROM", "\n WHERE", "\n AND", "\n ORDER BY", "ASC", "DESC", "\n INNER JOIN", "\n JOIN"];
$sqlStr = str_replace($search, $replace, $sqlStr);
return $sqlStr;
}

You can do something like this:
$escapedBindings = array();
foreach($query->getBindings() as $item) {$escapedBindings[] = '"'.$item.'"';}
$sql_with_bindings = Str::replaceArray('?', $escapedBindings, $query->toSql());

This is a very old question (2015), but since this is the first Google result I got I think it's worth to give my solution as well, in case it's useful for the next person.
Eloquent (5.7 onwards I think, I haven't tested more recent or earlier versions) has a method to change a Builder's from to wrap a subquery:
# Taken from Illuminate/Database/Query/Builder.php - Line 272
public function fromSub($query, $as) {
[$query, $bindings] = $this->createSub($query);
return $this->fromRaw('('.$query.') as '.$this->grammar->wrapTable($as), $bindings);
}
This however requires an already existing instance of \Illuminate\Database\Query\Builder. In order to make an empty one, you can do:
use Illuminate\Database\Capsule\Manager as DB;
$fancy = DB::table("videogames")->where("uses_sdl2", 1);
$empty = DB::table(null);
# Wrap the fancy query and set it as the "from" clause for the empty one
# NOTE: the alias is required
$empty = $empty->fromSub($fancy, "performant_games");
This will warranty that bindings are treated correctly, since they'll be handled by Eloquent itself.

Since the other answers do not properly quote the expressions, here is my approach. It uses the escaping function that belongs to the current database connection.
It replaces the question marks one by one with the corresponding binding, which is retrieved from $bindings via array_shift(), consuming the array in the process. Note, that $bindings has to be passed by reference for this to work.
function getSql($query)
{
$bindings = $query->getBindings();
return preg_replace_callback('/\?/', function ($match) use (&$bindings, $query) {
return $query->getConnection()->getPdo()->quote(array_shift($bindings));
}, $query->toSql());
}

Simple and elegant solution:
foreach (DB::getQueryLog() as $q) {
$queryStr = \Str::replaceArray('?', $q['bindings'], $q['query']);
echo $queryStr . ";\n";
}
(if you use a non-default connection, use DB::connection('yourConn')->getQueryLog() in the foreach command).

Output to the log all queries with inserted bindings sorted from the slowest query to the fastest:
\DB::enableQueryLog();
// Put here your queries
$query = DB::table('table')->whereIn('some_field', [1,2,30]);
$query2 = DB::table('table2')->where('some_field', '=', 10);
$logQueries = \DB::getQueryLog();
usort($logQueries, function($a, $b) {
return $b['time'] <=> $a['time'];
});
foreach ($logQueries as $item) {
\Log::info(str_replace_array('?', $item['bindings'], $item['query']));
\Log::info($item['time']. ' ms');
}

It is all explained here.....
https://ajcastro29.blogspot.com/2017/11/laravel-join-derived-tables-properly.html
I created a scope query for that thing. I think it can also be in macros..
public function scopeJoinDerived($query, $derivedQuery, $table, $one, $operator = null, $two = null, $type = 'inner', $where = false)
{
$query->join(DB::raw("({$derivedQuery->toSql()}) as `{$table}`"), $one, $operator, $two, $type, $where);
$join = last($query->getQuery()->joins);
$join->bindings = array_merge($derivedQuery->getBindings(), $join->bindings);
return $query;
}

Related

Codeigniter count_all_results with having

I have composed a query using Codeigniter's Query Builder class. The query utilizes aliases and the having method. When I call the count_all_results method on this query, an exception occurs. Inspecting the log, I see that the query has stripped out the 'having' clauses. Is there a way to keep these clauses in while calling count_all_results? Thanks for your help.
EDIT: I first believed the problem was knowledge-based and not code-based and so did not share the code, but here it is. Please let me know if more is needed.
Here's the call on the model in the controller.
$where_array = array(
$parent_key.' is not NULL' => null
);
$search_post = $request_data['search'];
if (isset($request_data['filter'])) {
$filter_array = $request_data['filter'];
foreach ($filter_array as $filter_pair) {
if (isset($filter_pair['escape'])) {
$where_array[$filter_pair['filterBy']] = null;
} else {
if ($filter_pair['filterBy'] == 'table3_id') {
$where_array['table3.'.$filter_pair['filterBy']] = isset($filter_pair['filterId']) ?
$filter_pair['filterId'] : null;
} else {
$where_array[$table.'.'.$filter_pair['filterBy']] = isset($filter_pair['filterId']) ?
$filter_pair['filterId'] : null;
}
}
}
}
$like_array = array();
foreach ($request_data['columns'] as $key => $column) {
if (!empty($column['search']['value'])) {
$like_array[$column['data']] = $column['search']['value'];
}
}
$totalFiltered = $this->$model_name->modelSearchCount($search, $where_array, $like_array);
Here's the model methods.
public function modelSearchCount($search, $where_array = null, $like_array = null)
{
$this->joinLookups(null, $search);
if ($where_array) {
$this->db->where($where_array);
}
if ($like_array) {
foreach($like_array as $key => $value) {
$this->db->having($key." LIKE '%". $value. "%'");
}
}
return $this->db->from($this->table)->count_all_results();
}
protected function joinLookups($display_config = null, $search = null)
{
$select_array = null;
$join_array = array();
$search_column_array = $search ? array() : null;
$i = 'a';
$config = $display_config ? $display_config : $this->getIndexConfig();
foreach ($config as $column) {
if (array_key_exists($column['field'], $this->lookups)) {
$guest_model_name = $this->lookups[$column['field']];
$this->load->model($guest_model_name);
$join_string =$this->table.'.'.$column['field'].'='.$i.'.'.
$this->$guest_model_name->getKey();
$guest_display = $this->$guest_model_name->getDisplay();
if ($search) {
$search_column_array[] = $i.'.'.$guest_display;
}
$join_array[$this->$guest_model_name->getTable().' as '.$i] = $join_string;
$select_array[] = $i.'.'.
$guest_display;
} else {
$select_array[] = $this->table.'.'.$column['field'];
if ($search) {
$search_column_array[] = $this->table.'.'.$column['field'];
}
}
$i++;
}
$select_array[] = $this->table.'.'.$this->key;
foreach ($join_array as $key => $value) {
$this->db->join($key, $value, 'LEFT');
}
$this->db->join('table2', $this->table.'.table2_id=table2.table2_id', 'LEFT')
->join('table3', 'table2.table3_id=table3.table3_id', 'LEFT')
->join('table4', $this->table.'.table4_id=table4_id', 'LEFT')
->join('table5', 'table4.table5_id=table5.table5_id', 'LEFT');
$this->db->select(implode($select_array, ', '));
if ($search) {
foreach (explode(' ', $search) as $term) {
$this->db->group_start();
$this->db->or_like($this->table.'.'.$this->key, $term);
foreach ($search_column_array as $search_column) {
$this->db->or_like($search_column, $term);
}
$this->db->group_end();
}
}
$this->db->select('table2_date, '. $this->table.'.table2_id, table4_id, '. 'table5.table5_description');
}
Since count_all_results() will basically run a Select count(*) and not count the rows in your resultset (basically rendering the query useless for your purposes) you may use other Codeigniter methods to get the resultset and the row count.
Try running the query into a variable:
$query = $this->db->get();
From then, you can do pretty much anything. Besides returning the result with $query->result(); you can get the number of rows into another variable with:
$rownum = $query->num_rows();
You can then return that into your controller or even just return the $query object and then run the num_rows() method on the controller
To answer this question, count_all_results() transforms the original query by replacing your selects with SELECT COUNT(*) FROM table. the aliased column would not be selected, and the having clause would not recognize the column. This is why count_all_results() does not work with having.

codeigniter : Commands out of sync; you can't run this command now

I applied all the possible answers but still same problem.
also tried
$this->db->reconnect();
there is no problem in my query
MyCode:
public function GetdistributorsDetails($username){
$sql = "SELECT u.FirstName, u.Email, u.Telephone, u.MobileNumber, u.AlternateMobileNumber, ud.Address1, ud.Pincode,ud.City,s.Statename FROM users u JOIN userdetails ud ON ud.UserId = u.UserId JOIN states s ON s.StateId = ud.StateId WHERE u.Username = ? ";
$result = $this->db->query($sql,array($username));
return $result->result_array();
}
add following code into /system/database/drivers/mysqli/mysqli_result.php
function next_result()
{
if (is_object($this->conn_id))
{
return mysqli_next_result($this->conn_id);
}
}
then in model when you call Stored Procedure
$query = $this->db->query("CALL test()");
$res = $query->result();
//add this two line
$query->next_result();
$query->free_result();
//end of new code
return $res;
you can use this after call
mysqli_next_result( $this->db->conn_id );
Here is example if you don't want change any thing in mysqli drivers files
$sql = "CALL procedureName(IntParam,'VarcharParm1','VaracharParm2','VarcharParm3');";
$query = $this->db->query($sql);
mysqli_next_result( $this->db->conn_id );
$query->free_result();
these are important line place after to remove the error
mysqli_next_result( $this->db->conn_id );
$query->free_result();
Just use this one if you use multiple query make in same function:
$this->db->close();
If your stored procedure returns more than one result, try to add this code between the queries:
$storedProcedure = 'CALL test(inputParam, #outputParam)';
$this->db->query($storedProcedure);
$conn = $this->db->conn_id;
do {
if ($result = mysqli_store_result($conn)) {
mysqli_free_result($result);
}
} while (mysqli_more_results($conn) && mysqli_next_result($conn));
$sql = 'SELECT #outputParam;';
$this->db->query($sql);
Just add next_result function in
system\database\drivers\mysqli\mysqli_result.php file
public function next_result(){
if(is_object($this->conn_id) && mysqli_more_results($this->conn_id)){
return mysqli_next_result($this->conn_id);
}
}
To call query function
$result = $this->db->query("CALL {$Name}({$Q})",$Param);
$result->next_result();
return $result;

is OrderByRaw() safe?

Is the below code safe from SQL injection?
if ($request->has('sort')) {
$s = $request->sort;
if ($request->has('asc')) {
$a = $request->asc;
} else {
$a = 'asc';
}
$query->orderByRaw("ISNULL({$s}), {$s} " . $a);
}
No. As the name suggests, it inserts a raw expression. That is, unsanitized.
If you’re wanting to sort a query, just sort when the require query string parameter(s) are provided:
if ($sort = $request->query('sort')) {
$direction = $request->query('direction');
if (! in_array($direction, ['asc', 'desc'])) {
$direction = 'asc'; // default direction
}
$query->orderBy($sort, $direction);
}
The problem with RAW queries are much more in the bindings (user data) than in the sql query itself, that's why you should avoid them. But it is safer if you add the bindings separately:
public function orderByRaw($sql, $bindings = []) {...}
There is a better approach to achieve this kind of conditional query using when for example:
// You have the $query instance already...
$sort = $request->get('sort');
$query->when($sort, function($query) use($request) {
$order = $request->asc ?: 'asc';
return $query->orderBy($sort, $order);
});
Now, the orderBy will be applied only when the $sort is available and you can add more query constrains further and execute it like:
// $query->where(...);
$result = $query->get();

Laravel: dynamic where clause with Elouquent

I am calling URL with search params which are dynamic. How could I form proper Eloquent query?
In theory:
query
query where(someParam1)
query where(someParam2)
query orderby(someParam3)
query get
I need this kind of structure so I can use where clause if param exists.
If there is some other way in Laravel, please let me know.
It's easy with Laravel. Just do something like this:
$query = User::query();
if ($this == $that) {
$query = $query->where('this', 'that');
}
if ($this == $another_thing) {
$query = $query->where('this', 'another_thing');
}
if ($this == $yet_another_thing) {
$query = $query->orderBy('this');
}
$results = $query->get();
You can just use the where statement.
For ex: on users table or User model, you want dynamic search on name, id. You can do this
$where = [];
$firstName = $request->get('first_name');
if ($firstName) $where[] = ['first_name', 'like'. '%' . $firstName . '%'];
$id = $request->get('id');
if ($id) $where[] = ['id', $id];
$users = User::where($where)->get();
By default, it will return all the users, if anything exists in $where array, it will apply the where condition on that.
You can use like this
$validateUserDetail = User::query();
if (!empty($userDetails['email'])) {
$validateUserDetail->whereemail($userDetails['email']);
}if (!empty($userDetails['cellphone'])) {
$validateUserDetail->wherecellphone($userDetails['cellphone']);
}
$validateUserDetail->select('username');
$validateUserDetail->get()
You can pass dynamic value by below example
$user_auctions = $this->with('userAuctions')
->where('users.id', '=', $id)
->get();
I came here from Google. If you are going to be iterating over more then 5 if statements, its more effective to use a switch statement
if(empty($request->except('_token')))
return 'false';
$models = Vehicle::query();
$request_query = $request->all();
$year_switch = false;
foreach ($request_query as $key => $field_value){
if($field_value != 'any'){
switch($field_value){
case 'X':
case 'Y':
$year_switch = true;
break;
case'Z':
//Dynamic
$models->where($key,'LIKE', $field_value);
break;
}
}
}
You can pass a callback to the where function.
So, you can do something like this:
class TestService {
TestRepository $testeRepository;
public function __construct(TesteRepository $teste) {
$this->testeRepository = $teste;
}
public function getAll(array $filters)
{
$where = function (Builder $query) use ($filters) {
collect($filters)
->each(function ($value, $param) use ($query) {
if ($param === 'test') {
$query->where($param, '=', $value);
} else if ($param === 'test2') {
$query->orWhere($param, '>', $value);
}
});
};
return $this->testRepository->gelAll($where);
}
class TestRepository
{
public function getAll(\Closure $where)
{
$query = TestModel::query();
$query->where($where);
//and put more stuff here, like:
//$query->limit(15)->offset(30)
...
return $query->get();
}
}
And in your controller you pass the filters:
class TestControler ...
{
public function $index()
{
$filters = request()->query();
return $this->testService->getAll($filters);
}
}

Getting data from model to controller

I do have a question : I cannot pass the data from model to controller
You can see some of my codes, please help me.
It does not work!
this is my model "mymodel.php"
....
$query = $this->db->query("SELECT * FROM `rand` WHERE `used` = 0 LIMIT 0, 1");
if($query){
foreach ($query->result() as $row);
}
$t = "EXAMPLE/{$row->code}";
function wandad() {
return $t;
}
.....
and this is my controller mycont.php
...
$this->load->model('mymodel');
$data['new'] = $this->Mymodel->wandad();
$this->load->view('myview',$data);
...
and this is my view myview.php
....
echo $new;
.....
Clearly you The model is not written properly and to corrent this simple do this
1.) I put a default value on $t
2.) I put the query >> loop on the inside function
wandad
so it can be executed once called from controller
function wandad() {
$query = $this->db->query("SELECT * FROM `rand` WHERE `used` = 0 LIMIT 0, 1");
$t = "";
if($query){
foreach ($query->result() as $row){
$t = "EXAMPLE/{$row->code}".'<br>';
}
}
return $t;
}
Here are several issues into your model
Your Foreach function doesn't do anything
$t is not in the same namespace than wandad()
Function wandad is not defined into your model class
I'm not sure of what you wanna get with wandad() function but here's a pattern.
function yourFunction()
{
/* This will return the full query as an array */
$query = $this->db->query("SELECT * FROM `rand` WHERE `used` = 0 LIMIT 0, 1")->result_array();
/* Store variable in the same class */
$this->t = "EXAMPLE/".$query[0]['code'];
/* Close yourFunction() */
}
public function wandad() {
return $this->t;
}
Then into your controller, do that instead :
$this->load->model('mymodel');
$this->mymodel->yourFunction();
$data['new'] = $this->mymodel->wandad();
$this->load->view('myview',$data);
#Touki his foreach actually does something. It will set the variable $row with the last row that is returned from the query. Not the best way to do it ... But it's a way. Better would be to use a limit in the query.
There is a small mistake in your code #Ernesto.that is
foreach ($query->result() as $row){
$t. = "EXAMPLE/{$row->code}".'<br>';
}
but your code was simply nice

Resources