Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

yehiakhalil's avatar

Where to add logic validation

I am having an endpoint that submits a user's rating for a salon. I want to validate that one user can only have one rating for a salon.

In which layer am i supposed to put this validation logic? Are policies a valid option or are they only used for authorization validation?

Here are the models

class Salon extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'phone', 'email', 'address', 'type', 'lat', 'lng', 'active_code', 'is_active'];

    public function ratings()
    {
        return $this->morphMany(Rating::class, 'model');
    }
}
class Rating extends Model
{
    use HasFactory;

    protected $fillable = ['client_id', 'model_type', 'model_id', 'rating', 'comment'];

    public function save(array $options = [])
    {
        if (!$this->client_id) {
            $this->client_id = auth()->user()->id;
        }
        return parent::save($options);
    }

    public function model()
    {
        return $this->morphTo();
    }

    public function client()
    {
        return $this->belongsTo(Client::class);
    }
}
class Client extends Authenticatable
{
    use HasFactory, HasApiTokens;

    protected $fillable = ['name', 'email', 'phone', 'gender', 'birthdate', 'password', 'type', 'address', 'lat', 'lng', 'verification_code', 'verified', 'latest_verification_code'];

    protected $casts = [
        'birthdate' => 'date'
    ];

    public function setPasswordAttribute($password)
    {
        $this->attributes['password'] = bcrypt($password);
    }

    public function ratings()
    {
        return $this-morphMany(Rating::class,'model');
    }
}

This is the controller

class RateSalonController extends Controller
{
    public function store(RateSalonRequest $request)
    {
        $salon = Salon::find($request->salon_id);
        $salon->ratings()->create($request->validated());
        return apiResponse(['message' => 'Rating submitted successfully!'], Response::HTTP_OK);
    }
}
0 likes
6 replies
yehiakhalil's avatar

Thank you for your input! i have attached some code to the question for more clarification.

webrobert's avatar

@yehiakhalil, hmmm,

if you control the frontend too you could certain make a policy, the you could use can(vote), etc to hide the vote button, but the logic itself could go on the model, $salon->hasVoted(auth()->user) the method is just sugar for

$this->ratings()->contains($user)` // that's not quite right..., 

(I'm seeing a $trait for ratings)

but if you decide to make a policy the logic will be there to use.

And/or, add a rule for unique rule to your RateSalonRequest based on the user

1 like
yehiakhalil's avatar

@webrobert I dont actually have any control over the frontend as i am developing an api, however i managed to create a custom Rule that does this validation and i am now calling it inside the FormRequest class

class UniqueSalonRating implements Rule
{
    public function passes($attribute, $value)
    {
        return Rating::whereHasMorph('model', [Salon::class], function ($query) use ($value) {
                $query->where('client_id', auth()->user()->id)->where('model_id', $value);
            })->count() == 0;
    }

    public function message()
    {
        return 'You have voted for this employee before.';
    }
}

And calling this rule inside the rules method as follows

public function rules()
    {
        return [
            'salon_id' => ['required', 'exists:salons,id', new UniqueSalonRating],
            'rating' => ['required', Rule::in([1, 2, 3, 4, 5])],
            'comment' => ['sometimes', 'string']
        ];
    }
webrobert's avatar
Level 51

@yehiakhalil,

if you add this to your Salon model (and again this is prime for a trait for ratings...)

public function voters()
{
    return $this->ratings->map->client;
}

then your logic can be

public function passes($attribute, $salonId)
{
    return Salon::find($salonId)
                ->voters()->doesntContain(auth()->user());
}

and now you have a method for $salon->voters() too

1 like

Please or to participate in this conversation.