Laravel - CreateMany if not exists - laravel

I have a table that stores tags name as "tag"
tag
id->Integer(unique)
title->String;
url->string;(unique)
Also I have a table to store places named as place
place
id->Integer(unique)
title->string
latitude->string
longtitue->string
A place may have many tags, here is my place_tag table
place_tag
id->Integer(unique)
tag_id->Integer
place_id->Integer
when I try to update place I need to do this
1- check all tags posted.
2- add them to "tag" db if not created before
3- write relationship with tag and place
But I think Laravels ORM can handle it, I'm walking around but can't find a good solution.
Please see my update procedure what am I doing wrong.
public function update($id)
{
$place=Place::findOrFail($id);
$place->fill(Input::all());
$place->save();
$tags=explode(',',Input::get('tags'));
$tags_data=array();
foreach($tags as $tag) {
$tags_data[]=new Tag(array('title'=>$tag,'url'=>$tag));
}
$place->tags()->detach();
$place->tags()->saveMany($tags_data);
return Redirect::to('admin/places');
}

You can do it this way:
$tagIds = [];
foreach ($tags as $tag) {
$tag = trim($tag);
if ($tag == '') {
continue;
}
$fTag = Tag::firstOrCreate( [ 'title' => $tag, 'url' => $tag ] );
$tagIds[] = $fTag->id;
}
$place->tags()->sync($tagIds);
I assumed one Tag can be set to many Places (n:n relationship), so first you basically find tags and if it doesn't exist you create it and then using sync you synchronize relationship table (insert or remove data from pivot when necessary)

Related

Laravel query on Many to Many relationship

I have an API to keep tracked photos and tags. Each photo can have N tags and one tag can be linked to N photos. That's done using 3 tables:
Photo's table.
Tag's table.
photo_tag relation table.
Now I'm working to get all photos tagged with a set of tags the idea is to make requests to my API with a list of tags and get a list of photos that has at least all the tags.
I've been trying with the whereIn operator.
This is my code (now it's all hardcoded):
$photos = Photo::whereHas('tags', function (Builder $query) {
$query->whereIn('tag', ['a5', 'mate', 'Brillante']);
})->get();
return response()->json($photos, 200);
When I execute it it return all that photos that match one tag and I need only photos that hast all the requested tags (in this example a5, mate).
I'm working on Laravel 9.
Edit:
As Tim Lewis suggested I've tried looping:
$tags = array("a5", "mate", "Brilante");
$photoQuery = Photo::query();
foreach($tags as $tag) {
\Log::debug($tag);
$photoQuery->whereHas('tags', function($query) use ($tag) {
return $query->where('tag', $tag);
});
}
$photos = $photoQuery->get();
Now it's returning an empty list I think because is looking for Photos that only have the 3 tags I hardcoded on the array.
Edit 2:
It seems that those changes were right, but for some reason Postman was not showing me any results of those changes are the solutions to my issue.
Since the whereIn() method matches against any of the values provided, and not all, you'll need to modify this. Specificying a number of whereHas() clauses, 1 for each Tag, should work:
$photoQuery = Photo::query();
foreach ($request->input('tags') as $tag) {
$photoQuery = $photoQuery->whereHas('tags', function ($query) use ($tag) {
return $query->where('tag', $tag);
});
}
$photos = $photoQuery->get();
Now, depending on the tags being sent to your API (assuming through the $request variable as a 'tags' => [] array), this query will include a whereHas() clause for each Tag, and only return Photo records that have all specified Tags.

Deleting nested comments and related data from laravel controller

ive implemented nested comments in laravel with parent_id and there's another table votes where related data's are stored.
I've hasMany relation defined in comments model. Now when i delete a comment, it should delete all its replies and votes as well.
To delete votes i used
$review->votes()->delete();
which works perfectly. but i'm stuck with deleting votes for nested replies.
If i use foreach loop how to loop inside all levels which is dynamic.
public function deletereview($id=null){
$review = Review::find($id);
foreach($review->replies as $reply){
$reply->votes()->delete();
//how to do this for all levels?
$reply = $reply->votes(); // this doesn't work
}
return back();
}
Kindly advise on the proper way of doing it.
Note : i've read through the cascade options from migrations but that doesn't explain anything for nested comments(reply of replies and its related data's).
Thanks
-Vijay
// Review Model
public function deleteRelatedData() {
// Delete all votes of this review
$this->votes()->delete();
// Calling the same method to all of the child of this review
$this->replies->each->deleteRelatedData();
}
// Controller
public function deletereview($id=null){
$review = Review::find($id);
$review->deleteRelatedData();
return back();
}
I would recommend use observer for this.
https://laravel.com/docs/5.8/eloquent#observers
public function deleted(Review $review)
{
foreach($review->replies as $reply){
$votes = $reply->votes;
Votes::destroy($votes)
}
Destroy method allow you to delete multiple models.
For any next level you have to use another foreach loop in this case.
$reply = $reply->votes(); doesn't work since you should use
$votes = $reply->votes;
//or
$votes = $reply->votes()->get();

Order by count in many to many polymorphic relation in Laravel

Let's take the example from the doc : https://laravel.com/docs/5.7/eloquent-relationships#many-to-many-polymorphic-relations it's easy to get all posts with their tags count doing Post::withCount('tags')->get().
But how to get all tags with their usage count ? To have them ordered by most used / less used.
If I do Tag::withCount(['video', 'post'])->get() I will have 2 attributes videos_count and posts_count. In my case I would like a unique taggables_count that will be the sum of the two. In a perfect world by adding a subselect querying the pivot table.
I would suggest simply doing the call you already did, which is Tag::withCount(['video', 'post'])->get(), and add this to your Tag model:
// Tag.php
class Tag
{
...
// Create an attribute that can be called using 'taggables_count'
public function getTaggablesCountAttribute()
{
return $this->videos_count + $this->posts_count;
}
...
}
and then in your loop (or however you use the items in the collection):
#foreach($tags as $tag)
{{ $tag->taggables_count }}
#endforeach
This setup requires you to get the Tags with the withCount['video', 'post'] though. If you do not, you will likely get 0in return for $tag->taggables_count.
If you're really concerned about speed, you would have to create the query manually and do the addition in there.
So after more searching I find out there is no way to do it with only in one query due to the fact that in mysql we can't do a select on subselet results. So doing Tag::withCount(['videos', 'posts']) and trying to sum in the query the videos_count and posts_count will not work. My best approach was to create a scope that read results in the pivot table :
public function scopeWithTaggablesCount($query) {
if (is_null($query->getQuery()->columns)) {
$query->select($query->getQuery()->from . '.*');
}
$query->selectSub(function ($query) {
$query->selectRaw('count(*)')
->from('taggables')
->whereColumn('taggables.tag_id', 'tags.id');
}, 'taggables_count');
return $query;
}
To use it :
$tags = Tag::withTaggablesCount()->orderBy('name', 'ASC')->get();
So now we have a taggables_count for each tag and it can be used to order by. Hope it can help others.

laravel 5 update in database

I created a row in my 'movies' table called 'slug' where i want to store the slug of the titles of my movies.the problem is i already have 250 movies and i don't want to manually enter the slug to each one of them so i am trying to make a script to automatically update all the slugs.But i failed to do that:
This is my code in FilmController.php:
class FilmController extends Controller {
public function film()
{
$title = DB::table('movies')->select('title');
$slug = str_replace(" ","-",$title);
DB::table('movies')->update(array('slug' => $slug));
}
}
And this is in my routes:
Route::get('update','FilmController#film');
This is the error that pops up when i go to localhost/update:
Object of class Illuminate\Database\Query\Builder could not be converted to string
Can somebody tell me how could i change my code so i can put in my slug field in all the movies the title of the movie with '-' insted of space between the words?
$title is an object containing all titles, and not a string,so str_replace will not work on it
you can try using a loop to update each record according to its title
something like
$titles = DB::table('movies')->select('title','id')->get();
foreach ($titles as $title){
$slug = str_replace(" ","-",$title->title);
DB::table('movies')->where('id',$title->id)->update(array('slug' => $slug));
}
I recomend use a seeder if you want to make a script that update or create default info on your database.
Laravel Database Seeding doc
And title have a collection of titles not a single title.

Display collection of Shopping cart rules and products categories associated to each rule

I want to check if there is a sales promotion on the product then stick the promotion label on that product on category list page. But I don't know how to loop through all the shopping cart rules and retrieve the products/categories associated to each rule.
EDITED
Thanks seanbreeden, but I can't pull the skus from $conditions. var_dump($conditions); shows this:
{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";}a:7:{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:1:{i:0;a:7:{s:4:"type";s:42:"salesrule/rule_condition_product_subselect";s:9:"attribute";s:3:"qty";s:8:"operator";s:2:">=";s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:1:{i:0;a:5:{s:4:"type";s:32:"salesrule/rule_condition_product";s:9:"attribute";s:12:"category_ids";s:8:"operator";s:2:"==";s:5:"value";s:2:"23";s:18:"is_value_processed";b:0;}}}}}a:7:{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:2:{i:0;a:5:{s:4:"type";s:32:"salesrule/rule_condition_address";s:9:"attribute";s:13:"base_subtotal";s:8:"operator";s:2:">=";s:5:"value";s:2:"45";s:18:"is_value_processed";b:0;}i:1;a:7:{s:4:"type";s:42:"salesrule/rule_condition_product_subselect";s:9:"attribute";s:3:"qty";s:8:"operator";s:2:">=";s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:1:{i:0;a:5:{s:4:"type";s:32:"salesrule/rule_condition_product";s:9:"attribute";s:3:"sku";s:8:"operator";s:2:"==";s:5:"value";s:46:"test-config, BLFA0968C-BK001, BLFA0968C-CR033X";s:18:"is_value_processed";b:0;}}}}}a:6:{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";}a:6:{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";}a:7:{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:1:{i:0;a:7:{s:4:"type";s:42:"salesrule/rule_condition_product_subselect";s:9:"attribute";s:3:"qty";s:8:"operator";s:2:">=";s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:1:{i:0;a:5:{s:4:"type";s:32:"salesrule/rule_condition_product";s:9:"attribute";s:3:"sku";s:8:"operator";s:2:"==";s:5:"value";s:16:"BLFA0968C-CR033X";s:18:"is_value_processed";b:0;}}}}}
but when I loop through $conditions i.e.
$rules = Mage::getResourceModel('salesrule/rule_collection')->load();
foreach ($rules as $rule) {
$conditions = $rule->getConditionsSerialized();
foreach ($conditions as $condition) {
var_dump($condition);
}
}
it doesn't show anything so don't really know how to pull skus here.
EDIT2
As Alaxandre suggested, I'm not using unserialized approach. I'm doing it like this now:
$rules = Mage::getResourceModel('salesrule/rule_collection')->load();
foreach ($rules as $rule) {
if ($rule->getIsActive()) {
//print_r($rule->getData());
$rule = Mage::getModel('salesrule/rule')->load($rule->getId());
$conditions = $rule->getConditions();
$conditions = $rule->getConditions()->asArray();
foreach( $conditions['conditions'] as $_conditions ):
foreach( $_conditions['conditions'] as $_condition ):
$string = explode(',', $_condition['value']);
for ($i=0; $i<count($string); $i++) {
$skus[] = trim($string[$i]);
}
endforeach;
endforeach;
}
}
return $skus;
And then checking in list page if sku matches within $skus array then show the label. But again there are limitation with this approach as well. I'm think of another approach (I'm not sure if thats is possible).
Thinking of creating a new table (to save the sales rules products).Everytime save the sales rule, catch the save rule event and update the table with Rule name and all the associated products. Then on the list page check that table, if products exist in the table, show the appropriate label. Now I think the event is adminhtml_controller_salesrule_prepare_save (not 100% sure) but I don't know how to get the sku from the rule condition in the observer to save in the new table.
I would suggest you to do it like this. When you had a product to cart, each rules are checked to calculate the final price and reduction. You can know which rules are applied to each item of your cart. In the table sales_flat_quote_item you have the column applied_rule_ids. I think you can access to this in php, by a function getAllItemsInCart or something like this (you have to find out). After you do $item->getAppliedRuleIds() and finally you can get the name of the rule apply to an item (product).
Good luck :)
Edit:
I read again your request and I think my answer doesn't fit with your request.
Your case is even more complicated. For each product on your catalog page you have to apply all the rules of your website. But Mage_SalesRule_Model_Validator process expect item and not product...
If you have lot of rules this task will slow down your catalog and this is really not good! The best would be to cache this result of the rules label in the database, may be in the table catalog_category_product or... (and even better to generate this cache automatically).
Edit2:
Other possibility would be to have a new field in rule creation where you set manually the related products (sku). You save this data in the table salesrule or in a new table salesrule_related_sku.
Then when you display the catalog you check for the sku and if the rule still active.
This solution would be the easiest one :-)
You could pull the getMatchingProductsIds from /app/code/core/Mage/CatalogRule/Model/Rule.php and compare them with the skus displayed on the category list page.
$catalog_rule = Mage::getModel('catalogrule/rule')->load(1); // ID of your catalog rule here, or you could leave off ->load(1) and iterate through ->getCollection() instead
$catalog_rule_skus = $catalog_rule->getMatchingProductIds();
hth
EDIT
Here's a way to get the serialized conditions:
$rules = Mage::getResourceModel('salesrule/rule_collection')->load();
foreach ($rules as $rule) {
$conditions = $rule->getConditionsSerialized();
var_dump($conditions);
}
EDIT 2
There would have to be a better way to do this. The only way I could pull that data was to unserialize then iterate with foreach through each layer. Anyone have any better ideas for this? This works but is very sloppy.
$rules = Mage::getResourceModel('salesrule/rule_collection')->load();
foreach ($rules as $rule) {
if ($rule->getIsActive()) {
$conditions = $rule->getConditionsSerialized();
$unserialized_conditions = unserialize($conditions);
$unserialized_conditions_compact = array();
foreach($unserialized_conditions as $key => $value) {
$unserialized_conditions_compact[] = compact('key', 'value');
}
for ($i=0;$i<count($unserialized_conditions_compact);$i++) {
if (in_array("conditions",$unserialized_conditions_compact[$i])) {
foreach($unserialized_conditions_compact[$i] as $key => $value) {
foreach($value as $key1 => $value1) {
foreach($value1 as $key2 => $value2) {
foreach($value2 as $key3 => $value3) {
$skus[] = explode(",",$value3['value']);
}
}
}
}
}
}
}
}
var_dump($skus);
The rules are associated to all product for a website. There is no rules set for a specific products/categories from the database point of view. For each product in the cart, Magento will validate all the rules you have for a website. This operation is done in the class Mage_SalesRule_Model_Validator. The only way to solve your request is to extend the function process from this class (at least I think so :p).
I wanted the same thing as you want. I wanted to get associated SKUS, Category Ids and any other conditions value to generate Google feeds to be used in Google merchant promotions.
I have used the recursive function to reach to last children of the condition and fetch its value.
I am checking based on the attribute value of the condition. If an attribute value is blank then go one step down and check if attribute value present and if so then fetch the value of it otherwise continue to go down.
Here is the code that I used to fetch values. Which will also work for the case, when two conditions are on the same level.
public function get_value_recursively($value){
foreach($value as $key => $new_value) {
if(strlen($new_value[attribute]) == 0){
$value = $new_value[conditions];
return $this->get_value_recursively($value);
}else{
$resultSet = array();
if (count($value) > 1){
for ($i=0;$i<count($value);$i++) {
$resultSet[] = array('attribute' => $value[$i][attribute], 'value' => $value[$i][value]);
}
$result = $resultSet;
}else{
$result = array('attribute' => $new_value[attribute], 'value' => $new_value[value]);
}
return json_encode($result, JSON_FORCE_OBJECT);
}
}
}
according to #seanbreeden answer you can call this function from first foreach
It will return the result like this :
{"0":{"attribute":"category_ids","value":"5, 15"},"1":{"attribute":"sku","value":"msj000, msj001, msj002"}}
P.S. I am not PHP dev. So, Ignore layman style code. :)

Resources