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 App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Log;
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 \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
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
CodePudding user response:
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());