CakePHP Pagination Helper Sort Nulls Last - sorting

I am using CakePHP 2.3.6 and I am trying to use the Pagination helper to sort data, but I want to add Nulls Last to my query.
Has anyone achieved this functionality?
echo $this->Paginator->sort('status_id', null, array('direction' => 'desc'));
This doesn't work for because the Pagination helper is not setup to handle it.
echo $this->Paginator->sort('status_id', null, array('direction' => 'desc NULLS LAST'));

The code isn't the most efficient and I am sure I could be assigning by reference, but this works for my model.
function beforeFind( $query)
{
$queryOrderArray = $query['order'];
if(isset($queryOrderArray)){
$i = 0;
foreach($queryOrderArray as $myOrderArray){
if(isset($myOrderArray) && is_array($myOrderArray)){
foreach($myOrderArray as $myField => $myOrder){
if($myOrder == 'desc'){
$myOrder .= ' NULLS LAST';
$myOrderArray[$myField] = $myOrder;
}
}
}
$queryOrderArray[$i] = $myOrderArray;
$i++;
}
}
$query['order'] = $queryOrderArray;
return $query;
}

Related

Change the Value of an existing Laravel Collection key

I have a Laravel Collection $page in which I am trying to update some values based on a value in another collection. But instead up updating a new element is being added to $page.
if($page->get('Lang') !== null ){
$l = $page->get('Lang');
foreach ($l as $thisLang => $value) {
if($lang == $thisLang){
// this is where I am attempt is overwrite the variable
$page->Name = $value->Name;
break;
}
}
}
Being fairly new to Laravel I am not sure what I am doing wrong. Any code corrections are appreciated.
I have also tried :
$page->update( ["Name" , $value->Name] );
but get the error
"Method Illuminate\Support\Collection::update does not exist."
Instead of use $page->Name need use $page['Name'].
Corrected code is
if($page->get('Lang') !== null ){
$l = $page->get('Lang');
foreach ($l as $thisLang => $value) {
if($lang == $thisLang){
// this is where I am attempt is overwrite the variable
$page['Name'] = $value->Name;
break;
}
}
}

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();

Call to a member function row() on a non-object

i am getting error Call to a member function row() on a non-object in codeigniter my controller is
public function edit_survey_pro($id)
{
$id = intval($id);
$survey = $this->model->get("surveys",array("ID" => $id),100000);
if (sizeof($survey) == 0) $this->template->error(lang("error_32"));
$this->template->loadContent("user/edit_survey_pro", array(
"survey" => $survey->row()
)
);
}
my model is
function get($table,$where='',$perpage=0,$start=0,$order_by='',$arr='')
{
$this->db->from($table);
if($perpage != 0 && $perpage != NULL)
$this->db->limit($perpage,$start);
if($where){
$this->db->where($where);
}
if($order_by){
$this->db->order_by($order_by);
}
if($arr=='')
$query = $this->db->get()->result();
else
$query = $this->db->get()->result('array');
if(!empty($query))
if($perpage != 0 && $perpage != NULL)
$result = $query;
else
$result = $query[0];
else
$result = array();
return $result;
}
here loadContent() is just load the content with view path
public function loadContent($view,$data=array(),$die=0){
//something to load the content
}
in my model I am getting the result as an array of object in $query and then it is returned as $result like this -
$query = $this->db->get()->result(); but at the controller $survey stores array of object and i want to show the content of that array of object ,previously I use
$this->template->loadContent("user/edit_survey_pro", array(
"survey" => $survey->row()
)
);
to get that data but the problem is $survey->row() cannot return that data bcoz it is not an object it is array of object so it can't be returned through row() method
so instead of this I just call the first element of that data like this-
$this->template->loadContent("user/edit_survey_pro", array(
"survey" => $survey[0]
)
);
Somehow its works for me bcoz I want to show the first row of the data
if sembody wants to show all data then I think he shuld try logic to increment the key value of that array of object for me it is $survey[] you can use foreach loop for increment the of value of the key element
The problems i see are your model, I will dissect it and add comments to your original code to point out the issues:
function get($table,$where='',$perpage=0,$start=0,$order_by='',$arr='')
//above there are problems, you are setting some of your parameters to
//equal blank, but below, in your conditionals, you are checking if they
// exist. They will always exist if they are set to blank. Fix them by
// setting them = NULL like this:
// get($table,$where=null,$perpage=0,$start=0,$order_by=null,$arr=null)
{
$this->db->select();// <-- you forgot this
$this->db->from($table);
if($perpage != 0 && $perpage != NULL)
//when will $perpage = null? , if never, then you dont need it.
$this->db->limit($perpage,$start);
if($where){
//change this to if(isset($where)). Also why do you use
//curly braces here, but not in the above if statement if only
//one line is affected in your if. I would remove the
//curly braces here.
$this->db->where($where);
}
if($order_by){
// change this to if(isset($order_by)). Same thing as
//above about the curly braces here
$this->db->order_by($order_by);
}
if($arr=='')
// change this to if(isset($arr)).
$query = $this->db->get()->result();
else
$query = $this->db->get()->result('array');
//change this to: $query = $this->db->get()->result_array();
if(!empty($query))
//change the above to if($query->num_rows > 0). Also, here since
//your code body is longer then one line, you will need curly braces
//around your if statement
if($perpage != 0 && $perpage != NULL)
//again, will $perpage ever be NULL? However, why do you need
//this conditional at all, if the limit above is already
//doing this job?
$result = $query;
else
$result = $query[0];
else
$result = array();
return $result;
}
after applying all the changes:
MODEL:
function get($table, $where=null, $perpage=0, $start=0, $order_by=null, $arr=null)
{
$this->db->select();
$this->db->from($table);
if($perpage != 0)
$this->db->limit($perpage, $start);
if(isset($where))
$this->db->where($where);
if(isset($order_by))
$this->db->order_by($order_by);
if(isset($arr)) {
$result = $this->db->get()->result_array();
} else {
$result = $this->db->get()->result();
}
return $result;
}
CONTROLLER
public function edit_survey_pro($id) {
$id = intval($id);
$survey = $this->model->get("surveys",array("ID" => $id),100000);
if (!$survey) {
$this->template->error(lang("error_32"));
} else {
$data["survey"] = $survey;
$this->template->loadContent("user/edit_survey_pro", $data);
}
}
I think when you use $this->db->get(), you need to pass the table name as param like this:
$this->db->get('table_name')->result();

Laravel how to get query with bindings?

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;
}

Ignited+datatables -> order by

I'm using codeigniter with datatables and i want to order a select by a column.
How can i do that ?
$this->datatables->select('col_1, col_2, col_3');
$this->datatables->from('table');
....$this->datatables->order ?!?
Thanks
use:
$this->db->order_by("column name", "desc");
You can use the aaSorting parameter to order the table on initialization.
$(document).ready( function() {
$('#example').dataTable( {
"aaSorting": [[2,'asc'], [3,'desc']]
} );
} );
where 2 and 3 are column's index
You can use this way i am controlling here all required value with codeigniter DATA-TABLE and MYSQL select include a small joining
function get_ComponentList($rowperpage, $row, $search='',$order, $dir)
{
$this->db->select('a.component_id, a.component_name as component_name, a.eco_code as eco_code, b.component_name as parent_name');
$this->db->from('component_info as a');
$this->db->join('component_info as b', 'b.component_id = a.parent_id', 'left');
$this->db->order_by($order,$dir);
if($search != ''){
$this->db->like('a.component_name', $search);
$this->db->or_like('a.eco_code', $search);
$this->db->or_like('b.component_name', $search);
}
$this->db->limit($rowperpage,$row);
$query = $query->result();
return $query;}
As you want to use with $this->datatables, you need to make a custom function for that, add the below custom function in Datatables.php library file:
public function corder_by($column, $type = ASC)
{
$this->order_by[] = array($column, $type);
$this->ci->db->order_by($column, $type);
return $this;
}
If the sorting type is not defined "ASC/DESC", then it will by default sort as "Ascending".
And use it as:
$this->datatables->corder_by('column_name','desc');

Resources