Laravel unique request update - laravel

Ive read some things about this on laracasts and Stackoverflow.
I have an update function with validation:
public function update(Customer $customer, StoreCustomer $request)
{
$customer->update($request->validated());
exit();
}
And the validation rules:
public function rules()
{
return [
'code' => 'required|unique:customers,code',
]
}
Now I tried to add a 3rd argument after the unique, so if it would exist it would continue. I tried it like this:
public function rules(Customer $customer)
{
return [
'code' => 'required|unique:customers,code,'.$customer->code,
]
}
but that doesn't seem to do anything. It seems to work if you do the validation in my controller itself, but this looks way cleaner. Any solutions?

If you want to ignore the current customer, you need to change the $customer->code to $customer->id, assuming your primary key is id.
unqiue validation documentation
Ignoring current customer:
'code' => 'required|unique:customers,code,'.$customer->id,

Your Form Request is a Request. It gets filled with the data from the current Request. You can pull the customer from your route as it is bound as a parameter currently. $this->route('customer')
public function rules()
{
return [
'code' => 'required|unique:customers,code,'. $this->route('customer')->code,
// perhaps this should be ignoring by id though?
'code' => 'required|unique:customers,code,'. $this->route('customer')->id,
];
}
Using method injection here could only give you a binding, if one was registered with the container for that exact class, or a new instance of that class, the case here. There is no link between that class name and the concept that there might be a route parameter that currently contains a Model that happens to be of that class.

It seems that the right way to do this was
'code' => 'required|unique:customers,code,'.$this->route('customer')->code.',code',
Since the $customer parameter isnt available in rules() you needed to get the customer another way.

I think the best way do this with Rule:unique
return [
'code' => ['required', Rule::unique('customers', 'code')->whereNot('code', $customer->code)]
]

Related

Making Laravel 9 validation rule that is unique on 2 columns

I am trying to update a row in the pages table.
The slug must be unique in the pages table on the slug and app_id field combined.
i.e. there can be multiple slugs entitled 'this-is-my-slug' but they must have unique app_id.
Therefore I have found that formula for the unique rule is:
unique:table,column,except,idColumn,extraColumn,extraColumnValue
I have an update method and getValidationRules method.
public function update($resource,$id,$request){
$app_id=22;
$request->validate(
$this->getValidationRules($id,$app_id)
);
// ...store
}
When I test for just a unique slug the following works:
public function getValidationRules($id,$app_id){
return [
'title'=> 'required',
'slug'=> 'required|unique:pages,slug,'.$id
];
}
However, when I try and add the app_id into the validation rules it returns server error.
public function getValidationRules($id,$app_id){
return [
'title'=> 'required',
'slug'=> 'required|unique:pages,slug,'.$id.',app_id,'.$app_id
];
}
I have also tried to use the Rule facade, but that also returns server error. Infact I can't even get that working for just the ignore id!
public function getValidationRules($id,$app_id){
return [
'title'=> 'required',
'slug'=> [Rule::unique('pages','slug')->where('app_id',$app_id)->ignore($id)]
];
}
Any help is much appreciated :)
Thanks for the respsonses. It turned out a couple of things were wrong.
Firstly if you want to use the Rule facade for the validation rules, make sure you've included it:
use Illuminate\Validation\Rule;
The other method for defining the validation rule seems to be limited to the following pattern:
unique:table,column,except,idColumn
The blog post that I read that showed you could add additional columns was for laravel 7, so i guess that is no longer the case for laravel 9.
Thanks for your responses and help in the chat!
I recommend you to add your own custom rule.
First run artisan make:rule SlugWithUniqueAppIdRule
This will create new file/class inside App\Rules called SlugWIthUniqueAppRule.php.
Next inside, lets add your custom rule and message when error occured.
public function passes($attribute, $value)
{
// I assume you use model Page for table pages
$app_id = request()->id;
$pageExists = Page::query()
->where('slug', $slug)
->where('app_id', $app_id)
->exists();
return !$pageExists;
}
public function message()
{
return 'The slug must have unique app id.';
}
Than you can use it inside your validation.
return [
'title'=> 'required|string',
'slug' => new SlugWithUniqueAppIdRule(),
];
You can try it again and adjust this custom rule according to your needs.
Bonus:
I recommend to move your form request into separate class.
Run artisan make:request UpdateSlugAppRequest
And check this newly made file in App\Http\Requests.
This request class by default will consists of 2 public methods : authorize() and rules().
Change authorize to return true, or otherwise this route can not be accessed.
Move your rules array from controller into rules().
public function rules()
{
return [
'title'=> 'required|string',
'slug' => new SlugWithUniqueAppIdRule(),
];
}
To use it inside your controller:
public function update(UpdateSlugAppRequest $request, $resource, $id){
// this will return validated inputs in array format
$validated = $request->validated();
// ...store process , move to a ServiceClass
}
This will make your controller a lot slimmer.

Laravel - how to retrieve url parameter in custom Request?

I need to make custom request and use its rules. Here's what I have:
public function rules()
{
return [
'name' => 'required|min:2',
'email' => 'required|email|unique:users,email,' . $id,
'password' => 'nullable|min:4'
];
}
The problem is that I can't get $id from url (example.com/users/20), I've tried this as some forums advised:
$this->id
$this->route('id')
$this->input('id')
But all of this returns null, how can I get id from url?
When you are using resources, the route parameter will be named as the singular version of the resource name. Try this.
$this->route('user');
Bonus points
This sound like you are going down the path of doing something similar to this.
User::findOrFail($this->route('user'));
In the context of controllers this is an anti pattern, as Laravels container automatic can resolve these. This is called model binding, which will both handle the 404 case and fetching it from the database.
public function update(Request $request, User $user) {
}

Laravel FormRequest with variables

How do I put my validation logic within a FormRequest, knowing that my validation rules need variables set by the controller?
public function store()
{
$commentable = Comment::getCommentable(request('input1'), request('input1'));
// I need this $commentable above in my validator below!
$this->validate(request(),[
'commentable_type' => 'required|string|alpha', // Post
'commentable_id' => 'required|uuid|exists:' . plural_from_model($commentable) . ',' . $commentable->getKeyName(),
'body' => 'required|string|min:1',
]);
// ...
}
Here is my actual code: https://i.imgur.com/3bb8rgI.png
I want to tidy up my controller's store() method, moving the validate() in a FormRequest. However, as you can see, it needs the $commentable variable, which is retrieved by the controller.
I guess I could make it so that the FormRequest could retrieve that variable itself as well, but that would be an ugly duplicate (since it would also probe the database twice...). So that is not a good solution at all.
Any idea? Cheers.
Your FormRequest class can do pre-validation steps (including adding/modifying the input data, as shown below) via the prepareForValidation hook:
protected function prepareForValidation()
{
$this->commentable = Comment::getCommentable($this->input('input1'), $this->input('input1'));
$this->merge([
'commentable_id' => $this->commentable->id,
'commentable_type' => $this->commentable->type
]);
}
You'll be able to use $this->commentable in your rules() function, as well.

Laravel Multi level relationship in API Resource

My problem is, the api resource loading which i really not needed.
Look into my Api Resource files
//BoxItemResource.php
public function toArray($request)
{
return [
'box_id'=> $this->box_id,
'item_id'=> $this->item_id,
'item'=> new ItemResource($this->item)
];
}
//ItemResource.php
public function toArray($request)
{
return [
'id' => $this->id,
'shipping_price' => $this->shipping_price,
'condition_id' => $this->condition_id,
'condition' => new ConditionResource($this->condition)
];
}
//ConditionResource.php
public function toArray($request)
{
return [
'id'=> $this->id,
'name'=> $this->name
];
}
//controller
return BoxItemResource::collection(
BoxItem::with([
'item'
])->paginate(1)
);
My problem is, I just need only BoxItem and Item here. I don't really want to load condition.
If i remove the condition relation from ItemResource.php, it will work. but the problem is I am using the ItemResource.php in some other place which need this condition.
Is it possible to deny loading condition relation ship here.
more clearly, I want to load the relationship which I mention in controller(in ->with()) .
Thanks in advance.
API resources allow for conditional attribtues and conditional relationships.
For attributes, which in my opinion is sufficient to use in your case, this means you can simply wrap the attribute value in $this->when($condition, $value) and the whole attribute will be removed from the result if $condition == false. So a concrete example of your code:
return [
'box_id'=> $this->box_id,
'item_id'=> $this->item_id,
'item'=> $this->when($this->relationLoaded('item'), new ItemResource($this->item))
];
Or if you prefer using the relationship style:
return [
'box_id'=> $this->box_id,
'item_id'=> $this->item_id,
'item'=> new ItemResource($this->whenLoaded('item'))
];
Maybe you are looking for Conditional Relationships?
Ideally, it should look like the following:
public function toArray($request)
{
return [
'box_id'=> $this->box_id,
'item' => ItemResource::collection($this->whenLoaded('item'))
];
}
The item key will only be present if the relationship has been loaded.

Check if field exists in Input during validation using Laravel

I want to make sure that certain fields are posted as part of the form but I don;t mind if some are empty values.
The 'required' validation rule won't work as I am happy to accept empty strings. I have tried the below, but as the 'address2' field is never sent, the validator doesn't process it.
Any ideas?
$rules = array(
'address2' => 'attribute_exists'
);
class CustomValidator extends Illuminate\Validation\Validator {
public function validateAttributeExists($attribute, $value, $parameters)
{
return isset($this->data[$attribute]);
}
}
You can use Input::has('address2') to check if something is posted by address2 input name. See the example:
if(Input::has('address2')) {
// Do something!
}
In Laravel 5,
if($request->has('address2')){
// do stuff
}
You should make custom validator like this.
use Symfony\Component\Translation\TranslatorInterface;
class CustomValidator extends Illuminate\Validation\Validator {
public function __construct(TranslatorInterface $translator, $data, $rules, $messages = array())
{
parent::__construct($translator, $data, $rules, $messages);
$this->implicitRules[] = 'AttributeExists';
}
public function validateAttributeExists($attribute, $value, $parameters)
{
return isset($this->data[$attribute]);
}
}
This will make AttributeExists work without to use require. For more explain about this. When you want to create new validator rule. If you don't set it in $implicitRules, that method will not work out if you don't use require rule before it. You can find more info in laravel source code.
When you submit a form each and every field is posted, matter of fact is if you leave some filed empty then that field value is null or empty. Just check the POST parameters once, to do so open the firebug console in firefox and submit the form, then check the post parameters. As you want to accept empty string what is the use of any rule?
else You can do this
$addr2=Input::get('address2');
if(isset($addr2)){
//do here whatever you want
}else{
//do something else
$addr2='';//empty string
}
Actually, Laravel has a method to validate if an attribute exists even if not filled.
$rules = [
'something' => 'present'
];
All the validation rules are stored in Validator class (/vendor/laravel/framework/src/Illuminate/Validation/Validator.php), you can check for the implementation of each rule, even no documented rules.

Resources