How to manipulate the laravel query builder output data using php - laravel

Assume I have a simple laravel query builder.
$data = DB::table('users')->get();
(I want to use only query builder and not eloquent since the query will be very complex in reality)
and the table fields are userid, firstname, lastname, gender
before I pass the $data to my view I would like to modify the output data using PHP
Say I wanted to add the prefix Mr or Miss to firstname depending on the gender column or something like that.. (please don't suggest a pure sql solution since the complexity of the condition will be much more complex that just adding a prefix.. I had given this as a simple use case only)
Is it possible ?

just iterate the result
foreach ($data as $key => $value) {
if ($value->gender === 1) {
$value->lastname = 'Mr ' . $value->lastname;
} else if ($value->gender === 0) {
$value->lastname = 'Miss ' . $value->lastname;
}
}

Related

Using WhereRaw on collection ignores relation

I made a route to search a particular collection - Customers.
Customer Model
public function location() {
return $this->belongsTo('App\Location');
}
Location Model
public function customers() {
return $this->hasMany('App\Customer');
}
On the index page, I'm simply showing the customers with the data from $location->customers()
$location comes from the model route binding.
This is my search controller:
if ($request->input('search') != null) {
$customers = $location->customers();
$search = strtoupper($request->input('search'));
$searchQuery = 'UPPER(email) LIKE (?) OR CONCAT(UPPER(first_name), " ", UPPER(last_name)) LIKE (?)';
$customers = $location->customers()->whereRaw($searchQuery, ["%{$search}%", "%{$search}%"]);
$customers = $customers->paginate();
}
return response()->json(['results' => $customers], 200);
When a search is executed, I get 10 times as many results in some cases because it's grabbing all customers rather than the specific location relationship.
How can I make whereRaw use the relation?
It is because the OR condition messes up with the query. To avoid that wrap those two query parts with brackets ().
$searchQuery = '( UPPER(email) LIKE (?) OR CONCAT(UPPER(first_name), " ", UPPER(last_name)) LIKE (?) )';
Eloquent builder approach:
$customers = $location->customers()->where(function($query) use ($search) {
$query->whereRaw('UPPER(email) LIKE (?)', ["%{$search}%"]);
$query->orWhereRaw('CONCAT(UPPER(first_name), " ", UPPER(last_name)) LIKE (?)', ["%{$search}%"]);
});
Explanation
Example logical expression:
firstCondition && secondCondition || thirdCondition
In above example expression thirdCondition does not even care about firstCondition or secondCondition because of the ||.
So if you want to check this thirdCondition with secondCondition, then you have to explicitly wrap this two conditions with brackets ().
firstCondition && ( secondCondition || thirdCondition )
This same logic applies for mysql queries too.

flattening joins in eloquent

If I wanted to JOIN two tables I could do this:
$books = App\Book::with('author')->get();
The problem with that is that the results are kinda nested. eg. If I wanted to get the DOB of the author I'd have to do this:
foreach ($books as $book) {
echo $book->author->dob;
}
But what if I, instead, wanted to be able to do this?:
foreach ($books as $book) {
echo $book->dob;
}
How might I go about doing that? I suppose there could be a conflict dob was a column in the book table and the author table and that doing author_dob would be better but let's assume there isn't a conflict.
I guess a view could do the trick but is there a way to do that in Laravel without having to create a view?
$books = DB::table('books')
->join('authors', 'authors.id', '=', 'books.author_id')->get()
Actually you can do the same using eloquent. This way, you'll have access to all functions and extra parameters in your Book model file
App\Book::join('authors', 'authors.id', '=', 'books.author_id')->get()
You might want to use leftJoin() instead of join()

Prevent SQL injection for queries that combine the query builder with DB::raw()

In Laravel 4, I want to protect some complex database queries from SQL injection. These queries use a combination of the query builder and DB::raw(). Here is a simplified example:
$field = 'email';
$user = DB::table('users')->select(DB::raw("$field as foo"))->whereId(1)->get();
I've read Chris Fidao's tutorial that it is possible to pass an array of bindings to the select() method, and therefore prevent SQL injection correctly, by using prepared statements. For example:
$results = DB::select(DB::raw("SELECT :field FROM users WHERE id=1"),
['field' => $field]
));
This works, but the example puts the entire query into a raw statement. It doesn't combine the query builder with DB::raw(). When I try something similar using the first example:
$field = 'email';
$user = DB::table('users')->select(DB::raw("$field as foo"), ['field' => $field])
->whereId(1)->get();
... then I get an error: strtolower() expects parameter 1 to be string, array given
What is the correct way to prevent SQL injection for queries that combine the query builder with DB::raw()?
I discovered the query builder has a method called setBindings() that can be useful in this instance:
$field = 'email';
$id = 1;
$user = DB::table('users')->select(DB::raw(":field as foo"))
->addSelect('email')
->whereId(DB::raw(":id"))
->setBindings(['field' => $field, 'id' => $id])
->get();
Eloquent uses PDO under the hood to sanitize items. It won't sanitize items added to SELECT statements.
The mysqli_real_escape_string method is still useful for sanitizing SQL strings, however.
Consider also (or instead) keeping an array of valid field names from the users table and checking against that to ensure there isn't an invalid value being used.
$allowedFields = ['username', 'created_at'];
if( ! in_array($field, $allowedFields) )
{
throw new \Exception('Given field not allowed or invalid');
}
$user = DB::table('users')
->select(DB::raw("$field as foo"))
->whereId(1)->get();

Laravel Eloquent query with optional parameters

I am trying to learn whether or not there is a simple way to pass a variable number of parameters to a query in Eloquent, hopefully using an array.
From what I can find, there doesn't seem to be a way to do this without looping through the Input to see what was set in the request.
Examples here: Laravel Eloquent search two optional fields
This would work, but feels non-Laravel to me in its complexity/inelegance.
Here is where I am, and this may not be possible, just hoping someone else has solved a similar issue:
$where = array("user_id" => 123, "status" => 0, "something else" => "some value");
$orders = Order::where($where)->get()->toArray();
return Response::json(array(
'orders' => $orders
),
200
);
That returns an error of course strtolower() expects parameter 1 to be string, array given.
Is this possible?
Order::where actually returns an instance of query builder, so this is probably easier than you thought. If you just want to grab that instance of query builder and "build" your query one where() at a time you can get it like this:
$qb = (new Order)->newQuery();
foreach ($searchParams as $k => $v) {
$qb->where($k, $v);
}
return $qb->get(); // <-- fetch your results
If you ever want to see what query builder is doing you can also execute that get() and shortly after:
dd(\DB::getQueryLog());
That will show you what the resulting query looks like; this can be very useful when playing with Eloquent.
You can try this:
Method 1:
If you have one optional search parameter received in input
$orders = Order::select('order_id','order_value',...other columns);
if($request->has(user_id)) {
$orders->where('orders.user_id','=',$request->user_id);
}
//considering something_else as a substring that needs to be searched in orders table
if($request->has('something_else')) {
$orders->where('orders.column_name', 'LIKE', '%'.$request->something_else.'%');
}
$orders->paginate(10);
Method 2:
If you have multiple optional parameters in input
$orders = Order::select('columns');
foreach($input_parameters as $key => $value) {
//this will return results for column_name=value
$orders->where($key, $value);//key should be same as the column_name
//if you need to make some comparison
$orders->where($key, '>=', $value);//key should be same as the column_name
}
return $orders->paginate(15);

Active record db inserting in codeigniter

I'm working on a project (using codeigniter) where I use a lot of Active record .
The problem i'm facing is the following :
I receive an array of data and i have to fill few tables by using :
$this->db->insert('db_name', $data);
Now sometime $data contains elements not available inside the table so instead i have to do something like :
unset($data['action']);
unset($data['date']);
Before inserting or I just build another array that contains the exact same element of a specific table .
$arr = array( 'x' => $data['x'])
I already used Kohana before and i know when you insert using the ORM it just ignores elements not available in a specific table .
Is there something like that in Codeigniter ?
PS) Without using any external library
CI ActiveRecord is not an ORM. If you want to use an ORM with codeignter, your only option is to use a third-party library: http://datamapper.wanwizard.eu/
You can always loop through the array before sending it to the db and unset data that doesn't contain anything (if in fact the delineator is an empty element):
foreach($data as $k => $v){
if($v == ''){
unset($data[$k]);
}
}
Otherwise, you could create switch spaghetti to unset the elements based on the db and the page sending the data:
switch ($page){
case "page1":
unset($data['blah']);
break;
....
}
As far as I'm aware the built-in feature like that doesn't exist in CI's Active Record (by the way, it is not an ORM).
If unsetting the array elements manually is too much of a hassle, the auto proccess would look like:
function my_insert ($table, $data) {
$query = $this->db->query('SHOW columns FROM '.$table);
$columns = array();
foreach ($query->result_array() as $row) {
$columns[] = $row['Field'];
}
foreach ($data AS $key => $value) {
if (!in_array($key, $columns)) {
unset($data[$key]);
}
}
$this->db->insert($table, $data);
}
It's not tested and some other checks may be needed, but that should help you to take off.

Resources