Validating Image Uploads - laravel-5

Yo! I am working on a form where I attach some image.
Form:
{{ Form::file('attachments[]', array('multiple')) }}
Validation:
$this->validate($response, array(
'attachments' => 'required | mimes:jpeg,jpg,png',
));
I have also tried 'image' as validator rule but whenever I post the form with jpg image I get back errors:
The attachments must be a file of type: jpeg, jpg, png.
Working with Laravel 5.3

Since you defined an input name of attachments[], attachments will be an array containing your file. If you only need to upload one file, you might want to rename your input name to be attachments, without the [] (or attachment would make more sense in that case). If you need to be able to upload multiple, you can build an iterator inside your Request-extending class that returns a set of rules covering each entry inside attachments[]
protected function attachments()
{
$rules = [];
$postedValues = $this->request->get('attachments');
if(null == $postedValues) {
return $rules;
}
// Let's create some rules!
foreach($postedValues as $index => $value) {
$rules["attachments.$index"] = 'required|mimes:jpeg,jpg,png';
}
/* Let's imagine we've uploaded 2 images. $rules would look like this:
[
'attachments.0' => 'required|mimes:jpeg,jpg,png',
'attachments.1' => 'required|mimes:jpeg,jpg,png'
];
*/
return $rules;
}
Then, you can just call that function inside rules() to merge the array returned from attachments with any other rules you might want to specify for that request:
public function rules()
{
return array_merge($this->attachments(), [
// Create any additional rules for your request here...
]);
}
If you do not yet have a dedicated Request-extending class for your form, you can create one with the artisan cli by entering: php artisan make:request MyRequestName. A new request class will be created inside app\Http\Requests. That is the file where you would put the code above in. Next, you may just typehint this class inside the function signature of your controller endpoint:
public function myControllerEndpoint(MyRequestName $request)
{
// Do your logic... (if your code gets here, all rules inside MyRequestName are met, yay!)
}

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 test unable to find a file at path using Storage::fake()

I have created my own 'disk' in config/filesystems.php which looks like so;
'uploads' => [
'driver' => 'local',
'root' => storage_path('app/public') . '/uploads'
],
This seems to work fine when in my controller, it uploads the file and returns a response. my code is as follows;
public function store(Request $request)
{
if ($request->hasFile('filename')) {
foreach ($request->file('filename') as $image) {
$fileName = md5($image . microtime()) . '.' . $image->getClientOriginalExtension();
$image->storeAs('', $fileName, 'uploads');
}
}
// return goes here
}
but when I go to test my store method using the following code;
public function testUserCanSuccessfullySubmitSingleImage(): void
{
Storage::fake('uploads');
$this->postJson('/upload', [
'filename' => UploadedFile::fake()->image('image1.jpg')
]);
Storage::disk('uploads')->assertExists('image1.jpg');
Storage::disk('uploads')->assertMissing('missing.jpg');
}
I get the following error;
Unable to find a file at path [image1.jpg].
Failed asserting that false is true.
I have followed a few tutorials, but they all say the same thing and im really lost.
Any help would be greatly appreciated.
Cheers
The problem is that you are renaming your file in the controller with md5($image . microtime()) so you cannot assert that image1.jpg exists since you changed the name.
What you could do is let laravel name the file and then check that in your test:
In your Controller:
Replace storeAs with store, store will generate a unique ID to serve as the file name.
public function store(Request $request)
{
if ($request->hasFile('filename')) {
foreach ($request->file('filename') as $image) {
$image->store('', 'uploads');
}
}
}
In your test:
To assert if the image exists we will use the same method to generate the unique ID as laravel does when saving the image. Replace 'image1.jpg' with $image->hashName() in your assertion.
public function testUserCanSuccessfullySubmitSingleImage(): void
{
Storage::fake('uploads');
$this->postJson('/upload', [
'filename' => $image = UploadedFile::fake()->image('image1.jpg')
]);
Storage::disk('uploads')->assertExists($image->hashName());
Storage::disk('uploads')->assertMissing('missing.jpg');
}
From the docs:
In web applications, one of the most common use-cases for storing
files is storing user uploaded files such as profile pictures, photos,
and documents. Laravel makes it very easy to store uploaded files
using the store method on an uploaded file instance. Call the store
method with the path at which you wish to store the uploaded file:
public function update(Request $request)
{
$path = $request->file('avatar')->store('avatars');
return $path;
}
There are a few important things to note about this example. Note that
we only specified a directory name, not a file name. By default, the
store method will generate a unique ID to serve as the file name. The
file's extension will be determined by examining the file's MIME type.
The path to the file will be returned by the store method so you can
store the path, including the generated file name, in your database.

What is the correct order of form validation and retrieving input values?

I wonder if I should do form validation before retrieving input values or vice versa.
I usually do validation first as I see no benefit in trying to access input values that might not be valid. However, a coworker looked at my code recently and found it strange. Is there any correct order for these steps?
public function createGroups(Request $request)
{
$this->validate($request, [
'courses' => 'required_without:sections',
'sections' => 'required_without:courses',
'group_set_name' => 'required',
'group_number' => 'required|integer|min:1'
]);
$courses = $request->input('courses');
$sections = $request->input('sections');
$group_set_name = $request->input('group_set_name');
$group_number = $request->input('group_number');
Positioning the validation for your controller logic at the beginning of a method is probably the way to go here, as you have required parameters defined. If you receive data that does not fully satisfy the requirements, you produce a validation error back to the user. This follows the productive "Fail Fast" line of thinking: https://en.wikipedia.org/wiki/Fail-fast
It's also important that you're not using any data that hasn't passed your stringent requirements from validation. Data that fails validation should no longer be trusted. Unless there's some other reason you need to be, say, logging any incoming data from the frontend, the order here looks good to me.
I totally agree with #1000Nettles response, to elaborate a little bit more on his/her answer (who should be the accepted one): There isn't any need to continue with your business logic when the data doens't comply with your specifications. Let's say you expected a string of a N characters long, because you defined your database with that limitation (in order to optimize the db desing), will you try to persist it even when it'll throw an exception? Not really.
Besides, Laravel has a particular way to extract validation classes: Form Request. This are injected in controllers. When a call reach the controller it means that already passed the validation, if not, an 422error be returned.
Create a custom request and keep the mess out of your controller, it doesn't even hit your controller function if validation failed and can just grab the data in your controller if validation passed.
php artisan make:request GroupRequest
In app/Http/Requests/GroupRequest.php:
public function authorize()
{
// return true;
return request()->user()-isAdmin; // <-- example, but true if anyone can use this form
}
public function rules()
{
return [
'courses' => ['required_without:sections'],
'sections' => ['required_without:courses'],
'group_set_name' => ['required'],
'group_number' => ['required', 'integer', 'min:1'],
];
}
The best part is you can even manipulate the data in here (GroupRequest.php) after it has been validated:
public function validated()
{
$validated = $this->getValidatorInstance()->validate();
// EXAMPLE: hash password here then just use new hashed password in controller
$validated['password'] = Hash::make($validated['password']);
return $validated;
}
In your controller:
public function createUser(UserRequest $request) // <- in your case 'GroupRequest'
{
$validated = $request->validated(); // <-- already passed validation
$new_user = User::create($validated); // <-- password already hashed in $validated
return view('dashboard.users.show')->with(compact('user'));
}
In your case, if you use my GroupRequest block above, you can return to view in 1 line of code:
public function createGroups(GroupRequest $request)
{
return view('example.groups.show')->with($request->validated()); // <-- already an array
}
In you blade view file, you can then use your variables like {{ $group_set_name }} and {{ $group_number }}

How do I make all validate() calls to execute in a controller?

In a controller I am using two validation like this:
public function update(Request $request){
if( $request->hasFile('img1') ){
$request->validate(
[
'img1'=>'image'
]
);
}
if( $request->hasFile('img2') ){
$request->validate(
[
'img2'=>'image'
]
);
}
}
Now if I upload incorrect file types for both img1 & img2 Only the first validation is checked and laravel redirects user to the original form page. This way message for only first validation is displayed. Even though second file type was also incorrect. I want to make sure all validate methods are checked are executed before I get redirected to the page I came from i.e., the page containing form.
Also I can't put validate method for file in one if statement as img1 and img2 might not be present at the same time. Because user might just want to upload one file.
You need not invoke validate method more than once for validating a single request. In the validate method you can validate all the inputs from the request, and if the validation fails, then it will automatically return the page which the request came from. And also you need not check whether the request has the input or not, before the validation. It will automatically be managed by laravel.
In the following validation required is used to specify the field is mandatory, and it will only accept file types specified in mimes, and max is used to specify the maximum file size in kilobytes.
public function update(Request $request){
$request->validate([
'img1' => 'required | mimes:jpeg,jpg,png | max:1000',
'img2' => 'required | mimes:jpeg,jpg,png | max:1000',
]);
}
you can try
public function update(Request $request){
$request->validate([
'img1' => 'required_without:img2',
'img2' => 'required_without:img1'
]);
}
If have more than two fields and only one is required, use required_without_all:foo,bar,...

laravel 5 double validation and request

I did this validation and works:
public function salvar(CreateEquipamento $Vequip, CreateLocalizacao $VLocal)
{
$this->equipamento->create($Vequip->all());
$equipamento = $this->equipamento->create($input);
return redirect()->route('equipamento.index');
}
what I want is to also do something like get the last created equipment ID and include in the array to validate and create for Local validation (CreateLocalizacao $VLocal) because i've two tables, one for the equipment and another one who stores all the places where my equipment was in.
$input['equipamento_id'] = $equipamento->id;
$this->localizacao->create($VLocal->all());
How could I do something like this?? thx in advance !
I do a "workarround" solution ;)
$localizacao = [
'equipamento_id' => $id,
'centrocusto_id' => $input['centrocusto_id'],
'projeto' => $input['projeto'],
'data_movimentacao' => $input['data_movimentacao']
];
$this->localizacao->create($VLocal->all($localizacao));
I dont know if this is the best way to do it but works, but if somebody has the right way to do post please!
Are you using Laravel 5?
If yes, use form Requests, they make everything easier. If you need to validate two things from one form, you just put two requests in the controller method. I use this when I register an user for an ecommerce page. I need to validate the user data and the address data, like this:
public function store(UserRegisterRequest $user_request, AddressCreateRequest $add_request)
{
//if this is being executed, the input passed the validation tests...
$user = User::create(
//... some user input...
));
Address::create(array_merge(
$add_request->all(),
['user_id' => $user->id]
));
}}
Create the request using artisan: php artisan make:request SomethingRequest, it generates an empty request (note the authorize function always returns false, change this to true or code that verifies that the user is authorized to make that request).
Here's an example of a Request:
class AddressCreateRequest extends Request {
public function authorize()
{
return true;
}
public function rules()
{
return [
"fullname" => "required",
//other rules
];
}
}
More on that on the docs:
http://laravel.com/docs/5.0/validation#form-request-validation

Resources