Laravel 5 - Allow to pass only FormRequest rules fields - laravel-5

I would like to use FormRequest validation in which
allow request fields only in rules() return array keys.
In the below code, I would like to allow request fields only 'os', 'number', 'version'. If request include the other field , return error response.
How can I modify the code ?
public function rules()
{
return [
'os' => [
'required',
\Rule::in(['android', 'ios']),
],
'number' => 'required|integer',
'version' => ['required', 'regex:/^\d+.\d+.\d+$/'],
];
}

There is a way you can do this using form request. It may not send the proper response but it works.
In your Form Request's authorize method use the following code.
public function authorize ()
{
$params = $this->request->keys();
$os_status = in_array('os', $params);
$number_status = in_array('number', $params);
$version_status = in_array('version', $params);
$check = $os_status & $number_status & $version_status & (count($params) != 3 ? false : true);
return $check;
}
It will return HTTP response with 403 status code.

Related

Laravel unique validation - change value before checking

I want to check for unique url's by extracting domain name from url.
e.g. https://stackoverflow.com and https://stackoverflow.com/something?foo=bar should show error.
I have form request like below,
public function rules()
{
return [
'name' => 'required',
'url' => 'required|unique:sites,url',
'favicon' => 'nullable|image',
];
}
I tried changing the form request value but it's not working,
// The getDomainName() returns the stackoverflow.com for https://stackoverflow.com
$this->request->set('url',
Site::getDomainName($this->request->get('url'))
);
I also tried following,
$url = $this->request->get('url');
$domain = 'https://' . Site::getDomainName($url);
$uniqueRule = Rule::unique('sites', 'url')
->where(function ($query) use ($domain) {
return $query->where('url', $domain) ? false : true;
});
Any help would be appreciated, thank you!
You need to parse url to extract domain name with protocol and then you can merge into a form request. You have to do it in starting of rules method.
$result = parse_url(request()->url);
request()->merge(['url'=>$result['scheme']."://".$result['host']]);
Solved using prepareForValidation()
protected function prepareForValidation()
{
$this->merge([
'url' => 'https://' . Site::getDomainName($this->request->get('url'))
]);
}

Find data before validate form request laravel

I want to update the data using the request form validation with a unique email role, everything works normally.
Assume I have 3 data from id 1-3 with url:
127.0.0.1:8000/api/user/update/3
Controller:
use App\Http\Requests\Simak\User\Update;
...
public function update(Update $request, $id)
{
try {
// UPDATE DATA
return resp(200, trans('general.message.200'), true);
} catch (\Exception $e) {
// Ambil error
return $e;
}
}
FormRequest "Update":
public function rules()
{
return [
'user_akses_id' => 'required|numeric',
'nama' => 'required|max:50',
'email' => 'required|email|unique:users,email,' . $this->id,
'password' => 'required',
'foto' => 'nullable|image|max:1024|mimes:jpg,png,jpeg',
'ip' => 'nullable|ip',
'status' => 'required|boolean'
];
}
but if the updated id is not found eg:
127.0.0.1:8000/api/user/update/4
The response gets The email has already been taken.
What is the solution so that the return of the data is not found instead of validation first?
The code looks like it should work fine, sharing a few things below that may help.
Solution 1: Check if $this->id contains the id you are updating for.
Solution 2: Try using the following changes, try to get the id from the URL segment.
public function rules()
{
return [
'user_akses_id' => 'required|numeric',
'nama' => 'required|max:50',
'email' => 'required|email|unique:users,email,' . $this->segment(4),
'password' => 'required',
'foto' => 'nullable|image|max:1024|mimes:jpg,png,jpeg',
'ip' => 'nullable|ip',
'status' => 'required|boolean'
];
}
Sharing one more thing that may help you.
Some person uses Request keyword at the end of the request name. The Update sounds generic and the same as the method name you are using the request for. You can use UpdateRequest for more code readability.
What I understand from your question is, you need a way to check if the record really exists or not in the form request. If that's the case create a custom rule that will check if the record exists or not and use that rule inside your request.
CheckRecordRule
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class CheckRecordRule implements Rule
{
protected $recordId;
public function __construct($id)
{
$this->recordId = $id;
}
public function passes($attribute, $value)
{
// this will check and return true/false
return User::where('id', $this->recordId)->exists();
}
public function message()
{
return 'Record not found.';
}
}
Update form request
public function rules()
{
return [
'email' => 'required|email|unique:users,email,' . $this->id.'|'. new CheckRecordRule($this->id),
];
}
So when checking for duplicate it will also check if the record really exists or not and then redirect back with the proper message.

Passing return data to another function in same controller laravel

Try to connect to external API.
In first function, I already received token with authentication.
To send POST request, I need to put xtoken that I received from first function as second function.
I don't know how to send value to second function (registerUser)
Route::get('/connect', 'Guzzlecontroller#registerUser')->name('registeruser');
this is my route file
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Client;
use App\Http\Controllers\Auth\RegisterController;
class GuzzleController extends Controller
{
public function Gettoken()
{
$client = new \GuzzleHttp\Client();
$request = $client->get(
'http://api01.oriental-game.com:8085/token',
[
'headers' => [
'X-Operator' => 'mog189b',
'X-key' => 'sQxAVNaEMe0TCHhU',
]
]
);
$response = $request->getBody();
$tokenReturn = json_decode($response, true);
$xtoken = array("x-token:" . $tokenReturn['data']['token'],);
$this->registerUser($xtoken);
}
public function registerUser($xtoken)
{
$client = new \GuzzleHttp\Client();
$url = "http://api01.oriental-game.com:8085/register";
$request = $client->post($url, [
'headers' => $xtoken,
'body' => [
'username' => 'test1',
'country' => 'Korea',
'fullname' => 'test user1',
'language' => 'kr',
'email' => 'testuser1#test.com',
]
]);
$response = $request->send();
dd($response);
}
}
Too few arguments to function App\Http\Controllers\GuzzleController::registerUser(), 0 passed and exactly 1 expected
this is error I am getting.
please help me to how to send $xtoken value to registerUser function
The problem is Laravel is calling registerUser directly instead of going through getToken. So the token is never retrieved and passed to the register action.
Instead of calling registerUser() from Gettoken(). Have Gettoken() return the token and call it from registerUser()
public function Gettoken()
{
...
return $xtoken;
}
public function registerUser()
{
$xtoken = $this->Gettoken();
...
}

Laravel action when form validation generates error

I am working with a form request file like this:
ProjectCreateRequest.php
public function rules()
{
$project_name = $this->project_name;
$meta_activity = $this->meta_activity;
return [
'project_name' => 'required|max:255|unique:projects',
'customer_name' => 'required|max:255',
'otl_project_code' => 'sometimes|max:255|unique:projects,otl_project_code,NULL,id,meta_activity,'.$meta_activity,
'estimated_start_date' => 'date',
'estimated_end_date' => 'date',
'LoE_onshore' => 'numeric',
'LoE_nearshore' => 'numeric',
'LoE_offshore' => 'numeric',
'LoE_contractor' => 'numeric',
'revenue' => 'numeric',
'win_ratio' => 'integer'
];
}
There is the otl_project_code that must be unique with the meta_activity.
In case someone enters a pair of otl_project_code and meta_activity that already exists, it goes back to the create page with the error written below.
I would like to get instead that in the controller, I can catch this information, do something on the database then redirect to an update url.
Because I am working with a form validation request file, everything is entered in my controller like this:
public function postFormCreate(ProjectCreateRequest $request)
and I don't know how to catch this specific error in my controller to execute some actions with all the fields I submitted and not go back to the create page. Of course, this needs to happen only when there is the specific error I mentionned above.
Override the FormRequest response function in your ProjectCreateRequest:
/**
* Get the proper failed validation response for the request.
*
* #param array $errors
* #return \Symfony\Component\HttpFoundation\Response
*/
public function response(array $errors)
{
if ($this->expectsJson()) {
return new JsonResponse($errors, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
}
That's the public response on the FormRequest class so you can write your own logic to perform DB queries and redirect where needed.

Better way for testing validation errors

I'm testing a form where user must introduce some text between let's say 100 and 500 characters.
I use to emulate the user input:
$this->actingAs($user)
->visit('myweb/create')
->type($this->faker->text(1000),'description')
->press('Save')
->see('greater than');
Here I'm looking for the greater than piece of text in the response... It depends on the translation specified for that validation error.
How could do the same test without having to depend on the text of the validation error and do it depending only on the error itself?
Controller:
public function store(Request $request)
{
$success = doStuff($request);
if ($success){
Flash::success('Created');
} else {
Flash::error('Fail');
}
return Redirect::back():
}
dd(Session::all()):
`array:3 [
"_token" => "ONoTlU2w7Ii2Npbr27dH5WSXolw6qpQncavQn72e"
"_sf2_meta" => array:3 [
"u" => 1453141086
"c" => 1453141086
"l" => "0"
]
"flash" => array:2 [
"old" => []
"new" => []
]
]
you can do it like so -
$this->assertSessionHas('flash_notification.level', 'danger'); if you are looking for a particular error or success key.
or use
$this->assertSessionHasErrors();
I think there is more clear way to get an exact error message from session.
/** #var ViewErrorBag $errors */
$errors = request()->session()->get('errors');
/** #var array $messages */
$messages = $errors->getBag('default')->getMessages();
$emailErrorMessage = array_shift($messages['email']);
$this->assertEquals('Already in use', $emailErrorMessage);
Pre-requirements: code was tested on Laravel Framework 5.5.14
get the MessageBag object from from session erros and get all the validation error names using $errors->get('name')
$errors = session('errors');
$this->assertSessionHasErrors();
$this->assertEquals($errors->get('name')[0],"The title field is required.");
This works for Laravel 5 +
Your test doesn't have a post call. Here is an example using Jeffery Way's flash package
Controller:
public function store(Request $request, Post $post)
{
$post->fill($request->all());
$post->user_id = $request->user()->id;
$created = false;
try {
$created = $post->save();
} catch (ValidationException $e) {
flash()->error($e->getErrors()->all());
}
if ($created) {
flash()->success('New post has been created.');
}
return back();
}
Test:
public function testStoreSuccess()
{
$data = [
'title' => 'A dog is fit',
'status' => 'active',
'excerpt' => 'Farm dog',
'content' => 'blah blah blah',
];
$this->call('POST', 'post', $data);
$this->assertTrue(Post::where($data)->exists());
$this->assertResponseStatus(302);
$this->assertSessionHas('flash_notification.level', 'success');
$this->assertSessionHas('flash_notification.message', 'New post has been created.');
}
try to split your tests into units, say if you testing a controller function
you may catch valication exception, like so:
} catch (ValidationException $ex) {
if it was generated manually, this is how it should be generated:
throw ValidationException::withMessages([
'abc' => ['my message'],
])->status(400);
you can assert it liks so
$this->assertSame('my message', $ex->errors()['abc'][0]);
if you cannot catch it, but prefer testing routs like so:
$response = $this->json('POST', route('user-post'), [
'name' => $faker->name,
'email' => $faker->email,
]);
then you use $response to assert that the validation has happened, like so
$this->assertSame($response->errors->{'name'}[0], 'The name field is required.');
PS
in the example I used
$faker = \Faker\Factory::create();
ValidationException is used liks this
use Illuminate\Validation\ValidationException;
just remind you that you don't have to generate exceptions manually, use validate method for common cases:
$request->validate(['name' => [
'required',
],
]);
my current laravel version is 5.7

Resources