I’m working on a Laravel 9 project and have created a custom validation rule called ValidModelOwnership
which should check that the field a user is trying to add is owned by a model based on some values passed to it.
I’ve written the rule, but when debugging and outputting $model->toSql()
the id is empty?
What am I missing?
My rule:
<?php namespace AppRules; use IlluminateContractsValidationRule; use IlluminateSupportFacadesLog; class ValidModelOwnership implements Rule { /** * The model we're checking */ protected $model; /** * Array of ownership keys */ protected $ownershipKeys; /** * Create a new rule instance. * * @return void */ public function __construct($model, $ownershipKeys) { $this->model = $model; $this->ownershipKeys = $ownershipKeys; } /** * Determine if the validation rule passes. * * @param string $attribute * @param mixed $value * @return bool */ public function passes($attribute, $value) { $model = $this->model::query(); $model = $model->where($this->ownershipKeys); Log::debug($model->toSql()); if (!$model->exists()) { return false; } return true; } /** * Get the validation error message. * * @return string */ public function message() { return "The :attribute field doesn't belong to you and/or your company."; } }
And my usage in my controller:
/** * 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) ], 'country_id' => [ 'required', 'numeric', new ValidModelOwnership(Country::class, [ ['company_id', 80] ]) ], 'product_id' => [ 'required', 'numeric', new ValidModelOwnership(Product::class, [ ['company_id', 80] ]) ], 'processing_class' => 'required|string', '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); } }
I’ve hard-coded my id’s to illustrate that even when set statically, it’s not passed through:
[2023-01-19 09:40:59] local.DEBUG: select * from
products
where (company_id
= ?) andproducts
.deleted_at
is null
Advertisement
Answer
Laravel (and most other frameworks) extract out variables when building SQL queries to prevent SQL injection.
So the following eloquent query:
User::where('name', 'Larry');
will become:
SELECT * FROM `users` WHERE `name` = ?
and it will also pass an array of bindings: ['Larry']
. When SQL processes the query it replaces replaces the ?
with the values in the bindings.
So if you want to see the full query you need to log the SQL and the bindings:
Log::debug($model->toSql()); Log::debug($model->getBindings());