Laravel grouping or taking from list in six - php

I'm not sure the title of this topic is correct, I apologize, you suppose I have this list which I get them from the database:
menu-index
menu-create
menu-store
menu-edit
menu-update
menu-destroy
commonCategory-index
commonCategory-create
commonCategory-store
commonCategory-edit
commonCategory-update
commonCategory-destroy
usersRole-index
usersRole-create
usersRole-store
usersRole-edit
usersRole-update
usersRole-destroy
as you can see we can grouping them with the first part of each item that we can have that them in three groups, after grouping them I want to use foreach for each one item that's there. I try to use the Laravel collection, but I can't. Could you help me please how can I do that?

You could do the following:
Model::all()
->groupBy(function($item) {
// Might want to add some checks here, but that depends on your setup
return explode('-', $item->getAttribute('columnName'))[0];
})
->each(function($item) {
// do sth.
});
But it might be more performant to add a group column to your model already.

Related

Laravel 5: How to orderBy, depending on whereHas?

I have 2 Laravel models, Classes and Subjects. Each Class belongs to a Subject.
I'd like to do a query, which sorts and returns the classes depending on whether their subject's ID is within a given array of IDs.
E.g. I can use the code below successfully to get the list of classes, which have the relevant subject IDs.
However, I can't figure out how to get ALL classes (i.e. no filter), and just sort so that the classes which have the relevant subjects are first, followed by other classes.
One approach is to create 2 different Collections, and ->merge them, but that then gets tricky when trying to paginate.
Thanks for your help!
Classes::whereHas('subject',function($q) use ($subject_list) {
$q->whereIn('id', $subject_list);
})->get();
You could perform a leftJoin to get all classes (i.e. no filter), and just sort so that the classes which have the relevant subjects are first
Classes::leftJoin('subjects', function ($join) use ($subject_list) {
$join->on('classes.subject_id', '=', 'subjects.id')
->whereIn('subjects.id', $subject_list);
})
->orderBy('classes.subject_id')
->get();
whereHas() will not help you here. You can use sortByDesc():
$classes = Classes::all();
$classes->sortByDesc(function($i) use($subject_list) {
return in_array($i->subject_id, $subject_list);
});
Or you could execute two queries and merge results:
$classes = Classes::whereIn('subject_id', $subject_list)->get();
$classesNotIn = Classes::whereNotIn('subject_id', $subject_list)->get();
$classes->merge($classesNotIn);
I've tested both approaches and they work, but you'll need to create Paginator manually in both cases.

How to count items join it and paginate?

I'm trying to count some items from the table and join it with the another table so I use the following code
Article::join("article_comments", "article_comments.article_id", "=","articles.id")->
select(["articles.title", "articles.content", "articles.created_at", DB::raw('count(article_comments.id) as commentsCount')])->paginate(10) ;
But I always get only first item
Shouldn't there be a group by if you use aggregates?
Something like:
Article::join("article_comments", "article_comments.article_id", "=","articles.id")->
select(["articles.title", "articles.content", "articles.created_at", DB::raw('count(article_comments.id) as commentsCount')])->
groupBy('articles.id')->
paginate(10);
Also since you're no using your joined table for filtering you might wanna consider eager loading. However in your case you only get articles with comments. When using eager loading you'd get all articles (including the ones without comments).
Something like that should work:
Article::with([
'comments' => function ($query) {
$query
->select('article_id', DB::raw('COUNT(`id`) AS `commentCount`'))
->groupBy('article_id')
}
])
->paginate(10);
Now you should be able to access the count like this:
echo $article->comments->first()->commentCount;
Did not test the solution. You might wanna check if $article->comments->first() exists.
Or if you wanna expand your model a little here are some even better thoughts

laravel eloquent hasmany relation returns when one row required

there are lots of questions in this area, but I can't quite find one that fits. I think it's very basic Laravel understanding.
I have a simple relationship set up between 'properties' and 'images'. For a particular mapping view, I need to get the property data, along with the file name of the 'lead' image. So, I only ever need one image result, according to whether the image is marked '1' as being the lead image in the DB.
Controller code looks like this:
if(Input::get('m') == 'true'){
//$map_data = Property::all();
$map_data = Property::with(array('images' => function($query)
{
//just grab the lead image to put in the marker popups.
$query->where('is_lead', 1)->first();
}))->get();
return View::make('property_map')->with('data', $map_data);
}
But in the view, the only way I can get this to work is with a foreach loop, even though I only ever want one result:
#foreach($property->images as $image)
{{$image->filename}}
#endforeach
Which is obviously daft. I think I'm just missing something simple with that loop in the view, but I may have misunderstood how to address this in the controller too. Think I've read the laravel docs about 20 times now, just can't quite get this little detail. Thanks.
UPDATE - I've just had another go at this, and managed to get rid of the pointless foreach by doing this in the view:
$property->images->first()["filename"]
.. but this still doesn't feel very 'laravel' .. would still appreciate any thoughts on a better way to do this.
Try changing ->get() to ->first().
if(Input::get('m') == 'true'){
//$map_data = Property::all();
$map_data = Property::with(array('images' => function($query)
{
//just grab the lead image to put in the marker popups.
$query->where('is_lead', 1)->first();
}))->first();
return View::make('property_map')->with('data', $map_data);
}

Preventing Laravel adding multiple records to a pivot table

I have a many to many relationship set up and working, to add an item to the cart I use:
$cart->items()->attach($item);
Which adds an item to the pivot table (as it should), but if the user clicks on the link again to add an item they have already added it creates a duplicate entry in the pivot table.
Is there a built in way to add a record to a pivot table only if one does not already exist?
If not, how can I check the pivot table to find if a matching record already exists?
You can also use the $model->sync(array $ids, $detaching = true) method and disable detaching (the second param).
$cart->items()->sync([$item->id], false);
Update:
Since Laravel 5.3 or 5.2.44, you can also call syncWithoutDetaching:
$cart->items()->syncWithoutDetaching([$item->id]);
Which does exactly the same, but more readable :)
You can check the presence of an existing record by writing a very simple condition like this one :
if (! $cart->items->contains($newItem->id)) {
$cart->items()->save($newItem);
}
Or/and you can add unicity condition in your database, it would throw an exception during an attempt of saving a doublet.
You should also take a look at the more straightforward answer from Barryvdh.
#alexandre Butynsky method works very well but use two sql queries.
One to check if cart contains the item and one to save.
To use only one query use this:
try {
$cart->items()->save($newItem);
}
catch(\Exception $e) {}
As good as all this answers are because I had tried them all, one thing is still left unanswer or not taken care of: the issue of updating a previously checked value (unchecked the checked box[es]). I do have something similar to the above question expect i want to check and uncheck features of products in my product-feature table (the pivot table). I am a newbie and I have realised none of the above did that. The are both good when adding new features but not when i want to remove existing features (i.e. uncheck it)
I will appreciate any enlightenment in to this.
$features = $request->get('features');
if (isset($features) && Count($features)>0){
foreach ($features as $feature_id){
$feature = Feature::whereId($feature_id)->first();
$product->updateFeatures($feature);
}
}
//product.php (extract)
public function updateFeatures($feature) {
return $this->features()->sync($feature, false);
}
or
public function updateFeatures($feature) {
if (! $this->features->contains($features))
return $this->features()->attach($feature);
}
//where my attach() is:
public function addFeatures($feature) {
return $this->features()->attach($feature);
}
Sorry guys, not sure is I should delete the question because having figure out the answer myself, it sounds a bit stupid, well the answer to the above is as simple as working #Barryvdh sync() as follows; having read more and more about:
$features = $request->get('features');
if (isset($features) && Count($features)>0){
$product->features()->sync($features);
}
There are some great answers posted already. I wanted to throw this one out here as well though.
The answers from #AlexandreButynski and #Barryvdh are more readable than my suggestion, what this answer adds is some efficiency.
It retrieves only the entries for the current combination (actually only the id) and it than attaches it if it does not exist. The sync method (even without detaching) retrieves all currently attached ids. For smaller sets with little iterations this will hardly be a difference, ... you get my point.
Anyway, it is definitely not as readable, but it does the trick.
if (is_null($book->authors()->find($author->getKey(), [$author->getQualifiedKeyName()])))
$book->authors()->attach($author);
I just had this problem and was able to solve it.
For future readers: instead of using atach() you can use
syncWithoutDetaching()
This will make sure you do not get any duplicates!
There are more alternatives to attach()
That might be usefull in the laravel documentation
Note that this is not for Laravel 4
$branch->permissions()->syncWithoutDetaching([1,2,3]);

Yii fetching records by type

I'm wondering if Yii has an efficient method for grouping items by type.
Let's say I have the following model:
Tag
------
id
name
type_id
And let's say there are 5 different types of Tags. I want to be able to display in my index all tags in sections by type_id. Is there a Yii-way of accomplishing this?
Outside a framework I would write a function such that results fetched from the DB were stored like this:
$tags[$typeID][] = $tag;
Then in each section I could do something like:
foreach( $tags[$typeID] as $tag )
{
// Here are all tags for one $typeID
}
But I'm having difficulty figuring out how to do this in Yii without:
A) looping through the entire result set first and rewriting it or,
B) running 5 different queries.
When using ActiveRecord simply specify the "index" in the DBCriteria. So in a query do:
ActiveRecordClass::model()->findAll(array('index'=>'type_id'));
That will return an assoc array that your after. TBF it probably executes exactly the same code, but this is obviously easier to use that performing it everywhere.
Assuming that your active record class is called MyActiveRecordClass, the simplest approach should be sufficient:
$models = MyActiveRecordClass::model()->findAll();
$groupedModels = array();
foreach ($models as $model) {
$groupedModels[$model->typeID][] = $model;
}
If you give more specific information about how you intend to display the grouped results it might be that a better approach can be worked out.

Categories