skip to Main Content

i’m working on a Laravel 9 API project whereby I have the following models:

  • Buyer
  • BuyerTier
  • BuyerTierOption

And each relate to one another, e.g: Buyers have BuyerTiers, and BuyerTiers have BuyerTierOtpions.

I’ve encountered a slight problem with the default Laravel validation whereby my URL already contains the buyer ID that I want to create a new tier for, but I need to make sure this ID not only exists, but belongs to my company.

How I’d get around this is to create a field in my request called buyer_id and create a custom rule like ValidModelOwnership which validates the ID and company, but I feel like Laravel should be able to do this as it’s in the URL, what am I missing:

/**
 * Store a newly created resource in storage.
 *
 * @param  IlluminateHttpRequest  $request
 * @return IlluminateHttpResponse
 */
public function store($company_id, $buyer_id, Request $request)
{
    $this->authorize('create', BuyerTier::class);

    $validator = Validator::make($request->all(), [
        'name' => [
            'required',
            'string',
            Rule::unique(BuyerTier::class)
                ->where('buyer_id', $buyer_id)
                ->where('company_id', $company_id)
        ],
        'buyer_id' => [
            'required',
            'numeric|min:50',
            new ValidModelOwnership(Buyer::class, [
                ['company_id', $company_id],
                ['id', $request->input('buyer_id')]
            ])
        ],
        'country_id' => [
            'required',
            'numeric',
            new ValidModelOwnership(Country::class, [
                ['company_id', $company_id],
                ['id', $request->input('country_id')]
            ])
        ],
        'product_id' => [
            'required',
            'numeric',
            new ValidModelOwnership(Product::class, [
                ['company_id', $company_id],
                ['id', $request->input('product_id')]
            ])
        ],
        'processing_class' => 'required|string|alpha_num',
        'is_default' => [
            'required',
            'boolean',
            new ValidDefaultModel(BuyerTier::class, $buyer_id)
        ],
        'is_enabled' => 'required|boolean'
    ]);

    if ($validator->fails()) {
        return response()->json([
            'message' => 'One or more fields has been missed or is invalid.',
            'errors' => $validator->messages(),
        ], 400);
    }

    try {
        $tier = new BuyerTier;
        $tier->user_id = Auth::id();
        $tier->company_id = $company_id;
        $tier->buyer_id = $buyer_id;
        $tier->country_id = $request->input('country_id');
        $tier->product_id = $request->input('product_id');
        $tier->name = trim($request->input('name'));
        $tier->description = $request->input('description') ?? null;
        $tier->processing_class = $request->input('processing_class');
        $tier->is_default = $request->boolean('is_default');
        $tier->is_enabled = $request->boolean('is_enabled');
        $tier->save();

        return response()->json([
            'message' => 'Buyer tier has been created successfully',
            'tier' => $tier
        ], 201);
    } catch (Exception $e) {
        return response()->json([
            'message' => $e->getMessage()
        ], 400);
    }
}

And I’m making a post request to:

  • {{endpoint}}/api/company/1/buyers/1/tiers/

2

Answers


  1. You can do this using Route-Model Binding

    For checking the if the buyer_id exists you can use Route-Model Bindings :

    Route

    /**
    * For naming parameters anything you prefer but I think its good if you
    * name it the same as their model.
    *
    * The `scopeBindings` method will tell if the buyer is connected to the company.
    */
    Route::post('company/{company}/buyers/{buyer}/tiers')->scopeBindings();
    

    Controller

    /**
    * Take note that the parameter name must be the same of parameters from the
    * route so that the route-model binding will be applied. You don't need to add
    * a condition to check if the id exist from the database since it will
    * automatically return a 404 status code if the id do not exist
    */
    public function store(Company $company, Buyer $buyer, Request $request) {...}
    
    Login or Signup to reply.
  2. Your post is a bit confusing and you do not provide data about all models relation mentioned in your post, but I just assume your buyer model has a relationship of belongsTo company model.

    And as I understand, you want to validate if the $buyer_id exists and owned by $company_id that you pass as parameters on your route.

    First option, you could simply query the model and check for thier relation.

    public function store($company_id, $buyer_id, Request $request) {
    
        $buyer = Buyer::find($buyer_id);
    
        if ( !$buyer )
            return response()->json(['message' => 'Buyer does not exist'], 404 );
        
        if ( $buyer->company_id !== $company_id )
            return response()->json(['message' => 'Buyer '.$buyer_id.' isn't owned by Company '. $company_id], 403 );
        
    
        // Request validation
        $request->validate([
            .
            .
            .
        ]);
    
        //valid request continue
    
    }
    

    2nd options is to just merge the buyer id to your request then perform a rule to validate buyer id existance and related model owner

    public function store($company_id, $buyer_id, Request $request) {
    
        $request->merge([ 'buyer_id' => $buyer_id ]);
        
        // Request validation
        $request->validate([
            'buyer_id' => [
                Rule::exists('buyers', 'id')->where('company_id', $company_id)
            ]
            .
            .
            .
            //Additional Rules
        ]);
    
        //Valid request, continue
    
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search