Where year filter Laravel collection - php

I have a Laravel collection with a "created_at" attribute, and I want to filter by year, like in eloquent:
$model->whereYear()
But in a collection.
Thx!
#attributes: array:10 [
"id" => 720
"created_at" => "2019-05-15 08:24:00"
"updated_at" => "2019-05-15 08:24:00"
]

You can use the Collections method filter to only find those whose year matches your criteria:
$collection = Model::all();
$collection->filter(function ($value) {
return $value->created_at->year === 2019; // assuming, that your timestamp gets converted to a Carbon object.
});

Related

Get the top value of array

Context: To give context, I am trying to create a graph with the months as the labels and the number of applications as the dataset.
My code:
$apps = Application::whereBetween('created_at', [
Carbon::now()->startOfYear(),
Carbon::now()->endOfYear(),
])
->get()
->groupBy(function ($val) {
return Carbon::parse($val->created_at)->format('M');
})
->toArray();
returns
array:3 [▼
"Jan" => array:2 [▶]
"Feb" => array:1 [▶]
"May" => array:1 [▶]
]
I want to create an array that produces this result:
['Jan', 'Feb', 'May']
I have tried using array keys, values, etc without any joy.
If you are looking for only month list then you can use selectRaw and groupBy in query
$apps = Application::whereBetween('created_at', [
Carbon::now()->startOfYear(),
Carbon::now()->endOfYear(),
])->selectRaw('MONTHNAME(created_at) as month,count(*) as total')
->groupBy('month')
->pluck('total','month')->toArray();
For getting months and count list separately
array_keys($apps) and array_values($apps)

Laravel use attach to include an additional field in pivot table

To start I have a $cart that returns a collection.
Example:
Collection {#391 ▼
#items: array:2 [▼
"027c91341fd5cf4d2579b49c4b6a90da" => CartItem {#393 ▼
+rowId: "027c91341fd5cf4d2579b49c4b6a90da"
+id: "1"
+qty: 3
+name: "item 1"
+price: 9.99
+options: CartItemOptions {#392 ▶}
-associatedModel: null
-taxRate: 21
}
"370d08585360f5c568b18d1f2e4ca1df" => CartItem {#394 ▶}
]
}
I'm using the id to to match the Items
$cart = \Cart::content()->pluck('id', 'qty');
$items = Item::whereIn('id', $cart)->get();
Then attaching the items to the order
$order->items()->attach($items, [
'qty' => ???
]);
How can I also include the qty when attaching? Items attach as expected but also need the qty.
Update: As pointed out by Twitter user AnellyBot, you can achieve this by passing a dictionary to the attach method:
$order->items()->attach(
Cart::contents()->mapWithKeys(function ($item) {
return [
$item->id => ['qty' => $item->qty]
];
})->all()
);
The original answer said that you can't do this with the attach method. That's obviously wrong.
I'm leaving the original answer here, since the code is still technically correct.
Eloquent's attach method does not allow adding extra attributes that are different for each attached record.
You'll have to either attach each record directly, or manually insert the records in the pivot table yourself.
Here's some code to get you started:
$order->items()->newPivotStatement()->insert(
Cart::content()->map(function ($item) use ($order) {
return [
'order_id' => $order->id,
'item_id' => $item->id,
'qty' => $item->qty,
];
})->all()
);
Adapt it to your needs.

Eloquent request with subitems count

I have 2 tables :
Valuechains : id, created_at, updated_at, deleted_at
Segments : id, valuechain_id (Foreign key), created_at, updated_at, deleted_at
And pivot tables (not really important here).
I have a method with sql requests ...
$valuechains list gives me a list of all the value chains which are not (soft) deleted
$valuechainCount counts the number of valuechains which are published
$segmentCount counts the number of segments for each value chains
I try to use the map function in order to add a column which contains the number of segments for each value chains ...
public function vcListAndSegmentCount() {
$valuechainLists = Valuechain::select('valuechains.id', 'lang_valuechain.vcname', 'lang_valuechain.vcshortname')
->join('lang_valuechain', 'valuechains.id', '=', 'lang_valuechain.valuechain_id')
->join('langs', 'lang_valuechain.lang_id', '=', 'langs.id')
->where('langs.isMainlanguage', '=', '1')
->whereNull('valuechains.deleted_at')
->get();
$valuechainCount = Valuechain::whereNull('valuechains.deleted_at')->count();
for ($i=0; $i < $valuechainCount; $i++) {
$segmentCount[$i] = Segment::whereNull('segments.deleted_at')
->where('valuechain_id', '=', $valuechainLists[$i]->id)->count();
}
$valuechainLists = $valuechainLists->map(function ($record) use ($segmentCount) {
$vclists = array_first($segmentCount, function ($value, $key) use ($record) {
return $value['id'] === $record['valuechain_id'];
});
$record['count'] = $vclists;
return $record;
});
dd($valuechainLists);
}
The map methods adds a column my output collection. Unfortunatelly, the new collection is not giving me the right numbers of segments for each value chain... it only adds one value ...
Here is what I obtain :
Collection {#380 ▼
#items: array:4 [▼
0 => Valuechain {#450 ▼
...
#attributes: array:4 [▼
"id" => 1
"vcname" => "Génétique"
"vcshortname" => "Génétique"
"count" => 6
]
#original: array:3 [▶]
...
}
1 => Valuechain {#451 ▼
...
#attributes: array:4 [▼
"id" => 2
"vcname" => "Biotruc"
"vcshortname" => "Biotruc"
"count" => 6
]
...
}
2 => Valuechain {#452 ▼
...
#attributes: array:4 [▼
"id" => 3
"vcname" => "VC3"
"vcshortname" => "VC3"
"count" => 6
]
...
}
3 => Valuechain {#453 ▼
...
#attributes: array:4 [▼
"id" => 4
"vcname" => "VC4"
"vcshortname" => "VC4"
"count" => 6
]
#original: array:3 [▶]
...
}
]
}
I obtain 6, 6, 6 and 6 whereas the count should be 6, 5, 4, 4...
If you are using Laravel >= 5.2 and if you have defined the relationships on the models, you can use the withCount() method.
It would go something like this:
Valuechain::select('valuechains.id', 'lang_valuechain.vcname', 'lang_valuechain.vcshortname')
->withCount(['segments' => function ($query) {
$query->whereNull('deleted_at);
}])
->join('lang_valuechain', 'valuechains.id', '=', 'lang_valuechain.valuechain_id')
->join('langs', 'lang_valuechain.lang_id', '=', 'langs.id')
->where('langs.isMainlanguage', '=', '1')
->whereNull('valuechains.deleted_at')
->get()
or if your Segment model uses the SoftDeletes trait, then it's a bit simpler:
Valuechain::select('valuechains.id', 'lang_valuechain.vcname', 'lang_valuechain.vcshortname')
->withCount('segments')
->join('lang_valuechain', 'valuechains.id', '=', 'lang_valuechain.valuechain_id')
->join('langs', 'lang_valuechain.lang_id', '=', 'langs.id')
->where('langs.isMainlanguage', '=', '1')
->whereNull('valuechains.deleted_at')
->get()
add count value as a property instead of array element.
$valuechainLists = $valuechainLists->map(function ($record) use ($segmentCount) {
$vclists = array_first($segmentCount, function ($value, $key) use ($record) {
return $value['id'] === $record['valuechain_id'];
});
$record->count = $vclists;
return $record;
});
I changed
$record['count'] = $vclists;
to
$record->count = $vclists;
Add a reference: &$segmentCount
$valuechainLists = $valuechainLists->map(function ($record) use (&$segmentCount) {....
Also I'm not sure what your array_first function is doing.

Laravel - replace null with empty array when no relation is found

Is it possible to replace null with an empty array when no relation is found?
E.g. The customer has contacts and contracts but one of the contract has no web.
$customers = Customer::with('contacts', 'contracts.web')
->orderBy('company')->orderBy('prename')->get();
The result would be as following...
2 => array:21 [
"id" => 1
"contacts" => array:2 [
0 => array:12 [
"id" => 1
"customer_id" => 1
]
1 => array:12 [
"id" => 2
"customer_id" => 1
]
]
"contracts" => array:2 [
0 => array:9 [
"id" => 1
"customer_id" => 1
"web" => array:7 [
"id" => 1
"contract_id" => 1
]
]
1 => array:9 [
"id" => 2
"customer_id" => 1
"web" => null // should be replaced with []
]
]
]
As I read in the docs (Constraining Eager Loads), it's only possible to manipulate the query with constraining eager loads.
UPDATE
Contract class
class Contract extends Model
{
public function web()
{
return $this->hasOne(Web::class);
}
}
For further readers here's an explanation how to solve this kind of problem.
Laravel returns an empty array if no records are found on a hasMany relation. If a hasOne relation is implemented, null will be returned.
So if you need an array also if no record is found on a hasOne relation, you need to do the following.
class Contract extends Model
{
public function web()
{
return $this->hasOne(Web::class)
->withDefault(function () {
return new Web();
});
}
}
As implemented like this its not possible to just return an empty array. Why this isn't possible, check out this issue on Laravel GitHub Issue Tracker.
There is existing code that depends on the result of any Eloquent relationship to either be null, a Model instance, or a Collection of Model instances. However, the current functionality of the withDefault() method opens up the potential for returning an object that is not one of those three expected values.
If you return a new \stdClass; or an empty array, an empty instance of web is returned. To get an empty array just instanciate a new Object of the relation class. In my case new Web();.
Your relationship method should be the one handeling this since it's the first place you can fix this
I checked this so it returns an array when the variable is null.
public class Contracts{
public function web(){
$collection = $this->hasMany('App\Web');
return $collection ? $collection : [];
}
}

Laravel 5 select only relationship data on hasMany relationship

I am building an application that has projects and projects have plot_types.
I want to be able to check if a plot_type exists under the current project.
I have the following code:
$testResult = $project->with(['plotTypes' => function($query) use ($row) {
$query->where('name', $row->plot_name);
}])->first()
This produces the following MySQL:
select exists(select * from `projects` where exists (select * from `projects_plot_types` where `projects_plot_types`.`project_id` = `projects`.`id` and `name` = ?)) as `exists`
This SQL returns rows that are NOT related to the $project object. For example when I do dd($project) I get:
#attributes: array:11 [▼
"id" => "4"
"name" => "xxx"
"number" => "1234"
"builder" => "1"
"overall_budget" => "3456.00"
"start_date" => "2016-03-31"
"end_date" => "2016-04-30"
"created_date" => "2016-03-16 15:22:05"
"updated_date" => "2016-03-16 15:22:07"
]
Yet, when I do dd($testResult); it gives;
#relations: array:1 [▼
"plotTypes" => Collection {#767 ▼
#items: array:1 [▼
0 => ProjectsPlotTypes {#770 ▼
#table: "projects_plot_types"
#fillable: array:2 [▶]
+timestamps: false
#connection: null
#primaryKey: "id"
#perPage: 15
+incrementing: true
#attributes: array:4 [▼
"id" => "1"
"project_id" => "1"
"name" => "TYPE 1 - VENTILATION"
"budget" => "324.67"
]
Notice, the project_id above shows 1. This is not related to the current project as the current project id is 4.
Why is this happening?
This is one of those potentially confusing parts of the ActiveRecord model. All your model instances contain the same methods used to retrieve model instances, so it easy think something should work one way when it really doesn't.
Calling $project->with(), this is the exact same as calling Project::with(). Even though you're calling with() on an instance of the project, it isn't going to restrict the loaded objects to only those related to your instance.
When you call $project->with(), the first thing it does is create a new query for all projects, and then adds in the eager loading. You then call first(), which just gets the first project record, and all its eager loaded objects.
To get the plot types for your specific project, you have a couple options.
Just query the relationship. $project->plotTypes() gives you a base query for all the plot types associated with your project. You can add your constraints and get the records from there.
$plotTypes = $project->plotTypes()->where('name', $row->plot_name)->get();
dd($plotTypes);
Load the related plot types with constraints:
// assume your project doesn't have any plottypes loaded yet
$project = Project::find(1);
// load the plottypes relation with constraints
$project->load(['plotTypes' => function($query) use ($row) {
$query->where('name', $row->plot_name);
}]);
dd($project->plotTypes);
Filter the already loaded Collection of related plot types. $project->plotTypes has all the plot types related to your project, but you can use the where() method on the Collection (different than the where() on the query) to filter through the records in the Collection.
// assume your project already has all plotTypes loaded
$project = Project::with('plotTypes')->find(1);
// you just want to get a subset of those pre-loaded plottypes
$plotTypes = $project->plotTypes->where('name', $row->plot_name);
dd($plotTypes);
Use the whereHas method to do the filtering instead of the with
$testResult = $project->whereHas('plotTypes' => function($query) use ($row) {
$query->where('name', $row->plot_name);
})->with('plotTypes')->first();
And moreover do you want to get all the related records or only the first record?
if all then change the first() to get()
Hope this helps

Categories