AbehoM's avatar

Different types of tags/categories for a model

I have an Ad model and I wanted to add services and payment methods that this ad accepts, for example, let's say that this Ad made by a man that works with electricity, painting, general services, and also he accepts credit card, bank transfer payments.

What would be the best way to handle multiple tagging/categories system? Is there a library to help with that or I just have to create a many to many model for each relationship like this? Is there a way to make it more reusable so I don't have to be writing many to many relationships to every single different tagging option for the user? For example: services, extra services, payment methods accepted.

The reason I want it as tags/categories is because I will make a search with filters so the users can find Ads based on what they want, like I want someone that works with electricity and also accepts bank transfer.

0 likes
8 replies
martinbean's avatar

@zefex It sounds like you have three models, then: an Ad, a tag type, and tags.

willvincent's avatar
Level 54

Dunno that it's really necessary, but yeah there is laravel-categories .. and a few others, but this appears to be the most popular, and was last updated today.

I think you could probably get by with your tags/categories having a 'type' flag, assuming you'll have a (even if large) limited number of types, in your example you have services and payment types, so lets go with that...

Your tags could either belongTo a parent Type as @martinbean is suggesting, or you could just as easily use an enum or a string value, especially if the number of types will be limited.

Then for ease you'd probably want your ads to have multiple relationships for the various types, ie:

class Ad extends Model {
  public function services() {
    return $this->belongsToMany(Tag::class)
                ->where('type', 'service')
  }

  public function paymentMethods() {
    return $this->belongsToMany(Tag::class)
                ->where('type', 'payment_method')
  }
}

Or, it may make sense to just go ahead and keep payment_methods and services totally separated as their own models. That's probably the route I'd go just based on the limited details you've provided as it's likely that payment_methods will have other stuff associated with them, and likewise, perhaps, will services. Those strike me more as different types of entity than simple tags.

1 like
willvincent's avatar

Good choice, didn't realize there was a spatie package, that's definitely the way to go if you're using a package, and they have one available.

AbehoM's avatar

@willvincent The problem tho I find is that I can't actually validate the tags being added by the user, like for instance, I created a table seed for tags where I add a services type with a few tags, if I list the services tags and the user selects a few and send I have to use syncTagsWithType, the problem is that internally it's using findOrCreate meaning that if the user adds another tag manually it will kinda "hack" the system so I need to validate somehow if the selected tag is actually a tag of type service and exists.

willvincent's avatar

You could override the default syncTagsWithType method and make it do a findOrFail instead.

It lives in the HasTags trait, I'd suggest creating your own HasTags trait that extends the package's trait, and override just that one method, or others if necessary.

Something like this ought to do the job..

use Spatie\Tags\HasTags as BaseHasTags;

trait HasTags
{
    use BaseHasTags;

    /**
     * @param array|\ArrayAccess $tags
     * @param string|null $type
     *
     * @return $this
     */
    public function syncTagsWithType($tags, string $type = null)
    {
        $className = static::getTagClassName();
        $tags = collect($className::findOrFail($tags, $type));
        $this->syncTagIds($tags->pluck('id')->toArray(), $type);
        return $this;
    }
}

There are several other methods in there that use a findOrCreate, you may or may not need/want to override those too.

AbehoM's avatar

I was thinking more about a validation level thing, like using exists but I can't actually use exists. For example, if the user selects: electricity, painting, general services, I need to know if they actually exists on the database and has the type of them is services, but I got no idea how to do that.

Please or to participate in this conversation.