Laravel: Dynamically fetch orders used in query? - php

I'm trying to create a generic success method that parses my queries and auto sets some response data.
$items = Item::query()
->where('blah', $req->('blah_filter')
->orderBy('name', 'desc')
->paginate();
return success($items, 'yay, some results');
So far it's working fine,
BUT, I would also like to return the "order" used. Is there any way to sniff that out of a paginate or get collection? A list of orders?
EDIT: By order, I mean from the orderBy not related models.
For example with Paginator collection we can return:
return [
'total' => $items->total(),
'per_page' => $items->perPage(),
'current_page' => $items->currentPage(),
'items' => $items->items()
];
BUT can't seem to get the ordering used in the query:
return [
'order' => $items->someCallHere()
];
// trying to return something like this:
[
'order' => [
'by' => 'name',
'direction' => 'desc'
]
]

You need to slightly change the code you have.
Remove the paginate() from the end since you first want to get the orderings.
Add it afterwards in order to retrieve the items.
$items = Item::query()
->where('blah', $req->('blah_filter')
->orderBy('name', 'desc');
// in order to get the ordering used you can use:
$ordering = $items->getQuery()->orders;
// do something with $ordering
// this will be an array like this:
/**
array:1 [▼
0 => array:2 [▼
"column" => "created_at"
"direction" => "desc"
]
]
*/
return success($items->paginate(), 'yay, some results');

Related

Collection query

I have a collection which contains these values
'sales marketing|telemarketing',
what I'm trying to do is query/filter the items in collection but just based on the individual type so the for example value of 'telemarketing'. I have tried
$results = $data->where('Department', 'contains', $type); and also tried LIKE but because of the format with the pipe it's not picking the type/value.
This might be a dumb question but any ideas would be great!
The where-method also can handle only two Parameters. For example:
$data= collect([
['Department' => 'sales', 'price' => 200],
['Department' => 'marketing', 'price' => 100],
['Department' => 'telemarketing', 'price' => 150],
['Department' => 'marketing', 'price' => 100],
]);
$departmentName = "marketing";
$results = $data->where('Department', $departmentName);
dd($results);
Given your example:
[
"Employee" => "Some Company",
"Name" => "John Something",
"Usages" => "sales marketing|telemarketing",
"StartDate" => "1st Mar 2021",
"EndDate" => ""
]
The main issue is that the "Usage" property is a string containing multiple values, with the pipe character acting as a separator.
One solution to filter by one of those values is by mapping your original collection to transform the string in an array with the explode method and then use the filter method to filter based on the Usages you're interested in.
The resulting code might look like this:
$mappedCollection = $collection->map(function($el) {
$el['Usages'] = explode('|', $el['Usages']); // Transform the string into an array
return $el;
});
$result = $mappedCollection->filter(function($el) {
return in_array('sales marketing',$el['Usages']); // Change 'sales marketing' with the desired Usage
});

Get count of unique values with the column name in Laravel 5.2 Collection

I am trying to get the number of unique Brands from my Products Table with their count from a Laravel collection.
I was able to do that using a specific query for the products but the reason i'm using a collection now is because i also want to get the Product Origins (Country), Conditions (Used / New) of the products and i thought it would be much better to use a collection from one query rather than have three separate queries for each data.
The code below works but it doesn't show the count for each unique Brand.
Here is the Table
Here is my Controller
$products = DB::table('products')
->select('products.*')
->whereNull('products.deleted_at')
->get();
$BrandCollection = collect($products);
$Brands = $BrandCollection->unique('Brand')->sortBy('Brand')->keyBy('Brand')->pluck('Brand');
So, the result i'm looking for is
HP 3
Toshiba 2
Lenovo 1
I thought it could be done using concat for the collection but since i'm on Laravel 5.2, i'm looking for other solutions.
If you really want to use collections (not Eloquent) you can do it like this:
$brandsWithCount = $BrandCollection->groupBy('Brand')->map(function($values) {
return $values->count();
})->sort()->reverse();
For example if you set $brandCollection like this:
$BrandCollection = collect([
['Brand' => 'HP'],
['Brand' => 'HP'],
['Brand' => 'HP'],
['Brand' => 'Toshiba'],
['Brand' => 'Toshiba'],
['Brand' => 'Lenovo'],
]);
result will be:
Collection {#372
#items: array:3 [
"HP" => 3
"Toshiba" => 2
"Lenovo" => 1
]
}
as expected.
There is a Collection Helper called CountBy, does exactly what you need.
Collections CountBy
$BrandCollection->countBy('Brand');
It will retourn as expected
#items: array:3 [
"HP" => 3
"Toshiba" => 2
"Lenovo" => 1
]
Simple :D

CakePHP 3 - How to write COALESCE(...) in query builder?

How do I write this kind of COALESCE() statement in the query builder?
SQL
SELECT COALESCE(n.value, p.value) AS value
FROM nodes n
LEFT JOIN parents p ON p.id = n.parent_id
PHP
I can retrieve both the child and parent values and then go through the result set and just use the parent one if the child one is empty, but if there is a more elegant way to build it into the query itself, I would prefer that.
$child = $this->Nodes->find()
->select(['id', 'value'])
->where(['Nodes.id' => $id])
->contain([
'Parents' => function ($q) {
return $q->select('value');
}
])
->first();
if (empty($child->value)) {
$child->value = $child->parent->value;
}
Update 1
So this is what I have at the moment, but it doesn't work.
$child = $this->Nodes->find()
->select(['id', 'value'])
->where(['Nodes.id' => $id])
->contain([
'Parents' => function ($q) {
return $q->select([
'value' => $q->func()->coalesce([
'Nodes.value',
'Parents.value'
])
]);
}
])
->first();
Returns:
object(Cake\ORM\Entity) {
'id' => (int) 234,
'value' => (float) 0,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Nodes'
}
The child value is NULL and the parent value is 1.00 so I would expect the entity value to be 'value' => (float) 1.00 but I assume it's coming out of the query as FALSE converted to (float) 0.
Update 2
It seems aliasing the coalesce to a name which already exists as a normal field does not work. It requires a unique field name for the coalesce result.
Update 3
I did another test and selected the name field from the two tables instead, and it just returns the actual strings I entered into the function (they do not get evaluated as column names):
return $q->select([
'value' => $q->func()->coalesce([
'Nodes.name',
'Parents.name'
])
]);
The returned entity has:
'value' => 'Nodes.name'
So my new question would be how to get the query builder to evaluate the strings as table/field names?
I could not get Cake's coalesce() function to evaluate the parameters as fields, it was just returning the actual strings of the field names.
I got it working by manually creating the COALESCE statement instead.
// Create the query object first, so it can be used to create a SQL expression
$query = $this->Nodes->find();
// Modify the query
$query
->select([
'id',
'value' => $query->newExpr('COALESCE(Nodes.value, Parents.value)')
])
->where(['Nodes.id' => $id])
->contain('Parents')
->first();
CakePHP's coalesce() uses the following format to differentiate field names from literal values:
'value' => $q->func()->coalesce([
'User.last_name' => 'identifier',
', ',
'User.first_name' => 'identifier'
])
The code above should yield results like Smith, John.
The default behavior is to treat the element as a literal.
See https://api.cakephp.org/3.3/class-Cake.Database.FunctionsBuilder.html#_coalesce
The docs don't explain this well at all. H/T to https://www.dereuromark.de/2020/02/06/virtual-query-fields-in-cakephp/ for a clear example.
See http://book.cakephp.org/3.0/en/orm/query-builder.html#using-sql-functions
Haven't tried it but I guess it's:
$child = $this->Nodes->find()
->select(['id', 'value'])
->where(['Nodes.id' => $id])
->contain([
'Parents' => function ($q) {
return $q->select(['value' => $query->func()->coalesce([
/* Fields go here... I think. :) */
])]);
}
])
->first();
If this isn't working check the unit tests of the core how to call this function.

Laravel 5.1 loading relationship data into selected row

I have a timesheet table and a user table in my database. The following relationship is setup on the timesheet model.
/**
* The user that owns the timesheet.
*
* #return Object
*/
public function user()
{
return $this->belongsTo('App\Models\User\User');
}
The above means I can get user data when I select timesheets from the database by using something like:
$this->timesheet->whereStatus('Approved')->with('user');
This will load the user object in the result and if converted to an array will look like so;
0 => array:13 [▼
"id" => 1
"user_id" => 2
"week" => 1
"year" => 2016
"week_ending" => "Sunday 10th January 2016"
"total_hours" => "45.00"
"token" => "0e6796a2dc68066c8d36ff828c519af00657db02b733309b8a4ac0f7b5d6a385"
"status" => "Approved"
"supervisor_id" => 1
"approved_by" => 1
"created_at" => "2016-01-13 15:42:49"
"updated_at" => "2016-01-14 14:52:07"
"user" => array:7 [▼
"id" => 2
"first_name" => "Bill"
"last_name" => "Andrews"
"email" => "Bill.andrews#domain.com"
"status" => 1
"created_at" => "2016-01-13 15:38:18"
"updated_at" => "2016-01-14 14:50:03"
]
]
However, I only need first_name and last_name from the user table. Is there a way to merge/flatten the user array with the timesheet so that it looks like this instead;
0 => array:14 [▼
"id" => 1
"user_id" => 2
"week" => 1
"year" => 2016
"week_ending" => "Sunday 10th January 2016"
"total_hours" => "45.00"
"token" => "0e6796a2dc68066c8d36ff828c519af00657db02b733309b8a4ac0f7b5d6a385"
"status" => "Approved"
"supervisor_id" => 1
"approved_by" => 1
"created_at" => "2016-01-13 15:42:49"
"updated_at" => "2016-01-14 14:52:07"
"first_name" => "Bill"
"last_name" => "Andrews"
]
I have tried to use eager loading like so;
$this->timesheet->with(['user' => function ($query) {
$query->select('first_name', 'last_name');
}])->get()->toArray();
However, it results in the following output;
array:126 [▼
0 => array:13 [▼
"id" => 1
"user_id" => 2
"week" => 1
"year" => 2016
"week_ending" => "Sunday 10th January 2016"
"total_hours" => "45.00"
"token" => "0e6796a2dc68066c8d36ff828c519af00657db02b733309b8a4ac0f7b5d6a385"
"status" => "Approved"
"supervisor_id" => 1
"approved_by" => 1
"created_at" => "2016-01-13 15:42:49"
"updated_at" => "2016-01-14 14:52:07"
"user" => null
]
The reason why the user relationship is null in your second example is because in order for Eloquent relationships to work, it needs the keys that tie the relationships. In other words...
Get timesheet.
Get user with only first_name and last_name.
Build relationship.
Since you did not fetch the user's id, the user's id and the timesheet's user_id do not match so the relationship cannot be built.
In order for your query to work, you need to adjust it like this:
$this->timesheet->with(['user' => function ($query) {
$query->select('id', 'first_name', 'last_name');
}])->get()->toArray();
With that out of the way, if you want a flattened result, I think it's best to use joins rather than eager loading because of the nature of eager loading.
$this->timesheet
->join('users', 'timesheets.user_id', '=', 'users.id')
->select('timesheets.*', 'users.first_name', 'users.last_name')
->get()->toArray();
This assumes that your table names are users and timesheets.
Have you tried to get a list
->lists('first_name', 'last_name');
or if you wanted to perform a select
->select('first_name', 'last_name')->get()
Update
You can also perform eager loading to eager load related objects. Here is an example
$users = App\User::with(['posts' => function ($query) {
$query->select('first_name', 'last_name');
}])->get();
Please let me know if that helps.
Laravel models have a way to modify data before getting/setting an attribute. You can actually add the attributes to the model by defining a getter function. This will let you reference the user names in the same way you would the user_id or status. These functions are also great for changing date formats for a view or sanitizing form input.
/**
* Get the first name of the user.
*
* #return string
*/
public function getFirstNameAttribute()
{
return $this->user->first_name;
}
/**
* Get the last name of the user.
*
* #return string
*/
public function getLastNameAttribute()
{
return $this->user->last_name;
}
That's exactly what a join do.
From the documentation
The query builder may also be used to write join statements. To
perform a basic SQL "inner join", you may use the join method on a
query builder instance. The first argument passed to the join method
is the name of the table you need to join to, while the remaining
arguments specify the column constraints for the join.
$this->timesheet
->leftjoin('user', 'user.id', '=','timesheet.user_id')
->get()
->toArray();
If you want to be more selective on your fields, you can choose what you select;
$this->timesheet
->leftjoin('user', 'user.id', '=','timesheet.user_id')
->select('timesheet.*', 'user.first_name', 'user.last_name')
->get()
->toArray();
As other have suggested, it might be better to use the DB query builder such as
this->timesheet = DB::table('timesheet')
->where('timesheet.status', 'Approved')
->leftjoin('user', 'user.id', '=','timesheet.user_id')
->select('timesheet.*', 'user.first_name', 'user.last_name')
->get()
->toArray();
In this case it's better to use Raw SQL or Query builder.
Because relations made for using database data as an relational objects.

Laravel sync Relation with optional parameters

I use the sync function for syncing a belongsToMany Relation:
$model->products()->sync($productIds);
In the $productIds array there is flat array with some Id's -
something like this:
$productIds = [1,3,5,6];
What I want:
The pivot table has also additional columns like "created_by" and "updated_by".
But how can I add these fields to my array WITHOUT doing a foreach loop?
Is there a shorter way to do this?
I need an array like this:
$productIds = [1 => [
'created_by' => 1,
'updated_by' => 1
],3 => [
'created_by' => 1,
'updated_by' => 1
],5 => [
'created_by' => 1,
'updated_by' => 1
],6 => [
'created_by' => 1,
'updated_by' => 1
]];
Yes I know I can do it with foreach and add the columns while I loop through the array. But I want do it shorter.. is there a way to do it shorter (perhaps with laravel)?
It should be enough to pass what you have set in $productIds in your code example to sync().
This method works not only with array of integers. You can also pass an array where key is the synced ID and value is the array of pivot attributes that should be set for given ID.
This should do the trick:
$productIds = [
1 => [
'created_by' => 1,
'updated_by' => 1
]
//rest of array
];
$model->products()->sync($productIds);
Just make sure you have defined those fields as pivot fields in your relation definition.
In order to generate such table based on a list of IDs in $productIds you can do the following:
$productIds = array_fill_keys($productIds, array(
'created_by' => 1,
'updated_by' => 1,
));

Categories