How to validate keys of an array with Laravel Form Request? - laravel

What is the correct way to validate that a param in the request should be in the following format: array with a key of a certain value, and a boolean value:
"countries" => array:2 [
"usa" => true
"canada" => true
]
In my Form Request I have the following rule:
public function rules()
{
return [
'countries' => ['bail', 'array'],
'countries.*' => ['string', new ValidateCountries],
'countries.*.' => ['boolean'],
];
}
My ValidateCountries passes() method:
public function passes($attribute, $value)
{
$countries = ["usa", "canada", "uk", "france"];
return in_array(strtolower($value), $countries);
}

Got it by accessing the $attribute param instead of the $value. Easy one, but my brain is fried.
public function passes($attribute, $value)
{
$attribute = explode(".", $attribute)[1];
$countries = ["usa", "canada", "france", "uk"];
return in_array(strtolower($attribute), $countries);
}

Related

Howto reusing Form Request inside loop

I am making a "/bulk" endpoint for my API/REST made in Laravel 8.
My problem is that I don't know how to reuse the same FormRequest that I have for the create or update
json post to: /cars/bulk
{
"cars": [{"model": "A", "year": 2021, "plate": "AA11BB"},{"model": "B", "year": 2021, "plate": "AA12BB"},{"model": "C", "plate": "AA13BB"}]
}
// CarController.php
public function store(CarRequest $request)
{
$car = $this->carService->store($request, Car::class);
}
public function update(CarRequest $request, Car $car)
{
$this->carService->update($request, $car);
}
public function bulk(Request $request)
{
$this->carService->bulk($request);
}
// CarService.php
public function store($request, $modelClass)
{
# My code....
}
public function update($request, $model)
{
# My code....
}
public function bulk($request)
{
foreach ($request->cars AS $carData )
{
$car = Car::where('plate','=',$carData->plate)->first()
# here is the problem,
# howto validate each $car by reusing CarRequest
if ($car){
$this->update($carData, $car);
} else {
$this->store($carData, Car::class);
}
}
}
This is de form request for each item, i have use to for bulk or one request
class CarRequest extends BaseRequest
{
public function authorize()
{
$this->setModel(Car::class);
return $this->isAuthorized();
}
public function rules()
{
$this->setModel(Car::class);
$rules = parent::rules();
$rules = [
'model' => 'required',
'year' => 'required|numeric',
'plate' => 'required'
];
return $rules;
}
public function messages()
{
# multiples messages
}
}
I need reuse my request
Edit: add form request
So I wouldn't suggest using it within a loop how you have in your example, what would be better would be to create a second, bulk request that valiates an array like this.
public function rules()
{
$this->setModel(Car::class);
$rules = parent::rules();
$rules = [
'cars' => ['required', 'array'],
'cars.*.model' => 'required',
'cars.*.year' => 'required|numeric',
'cars.*.plate' => 'required'
];
return $rules;
}
You can do it with same CarRequest form request class as follows.
public function rules()
{
return[
'cars.*.model' => 'required',
'cars.*.year' => 'required',
'cars.*.plate' => 'required',
];
}
Laravel will expect that you are sending nested array. If you dont want to mix it up with CarRequest you can do it with creating another FormRequest class.

Validate if input is url or email in Laravel 9

I am trying to validate a field but I am not able to do it.
I have this Request Validator
public function rules()
{
return [
...
'input' => 'required|url|email',
];
}
What I want to do is to validate if the input is url or email, can be one of them, what can I do? Here I am validating both cases, but the input can be one of them. Thanks.
public function rules(): array
{
return [
'input' => ['required', function ($field, $value) {
$data = [$field => $value];
$emailValidator = Validator::make($data, [$field => 'email']);
$urlValidator = Validator::make($data, [$field => 'url']);
if ($urlValidator->passes() || $emailValidator->passes()) {
return true;
}
$this->validator->getMessageBag()->add($field, $field . ' must be a valid email address or a valid url');
return false;
}],
];
}

How to return all inserted bulk data Laravel?

I use bulk insertion in Laravel:
return User::create($user);
Where $user is array of rows.
How to return successfully inserted rows as array?
Now it returns only last inserted model.
Full code is:
public function register(Request $request)
{
foreach ($request->email as $key => $value) {
$hashed = Hash::make($key);
$user = [
"name" => $request->get('name')[$key],
"email" => $value,
"password" => $hashed,
];
}
return User::create($user);
}
Could I do the following?
public function register(Request $request)
{
foreach ($request->email as $key => $value) {
$hashed = Hash::make($key);
$user = [
"name" => $request->get('name')[$key],
"email" => $value,
"password" => $hashed,
];
}
try {
User::create($user);
return $user;
} catch ($e) {
}
}
I think this could work:
public function register(Request $request)
{
$created = [];
foreach ($request->email as $key => $value) {
$hashed = Hash::make($key);
$user = [
"name" => $request->get('name')[$key],
"email" => $value,
"password" => $hashed,
];
$created[] = User::create($user);
}
return $created;
}
Or if you want to use bulk insert, you have to select the inserted models after, like this:
public function register(Request $request)
{
$users = [];
foreach ($request->email as $key => $value) {
$hashed = Hash::make($key);
$users[] = [
"name" => $request->get('name')[$key],
"email" => $value,
"password" => $hashed,
];
}
if(User::insert($users))
{
return User::whereIn('email', array_column($users, 'email'))->get();
}
return 'error';
}

How to use unique validation in Yii 2

I want to make usernames and email addresses unique.
I am using yii base to develop my App. It doesnt not work for me.
My Model:
public function rules()
{
return [
[['username', 'email', 'password'], 'required'],
[['username', 'email'], 'unique']
];
}
My Controller:
public function actionCreate()
{
$model = new Userapp();
$post = Yii::$app->request->post('UserApp');
if (Yii::$app->request->isPost && $model->validate()) {
$model->email = $post['email'];
$model->username = $post['username'];
$model->password = $model->setPassword($post['password']);
if($model->save()){
return $this->redirect(['view', 'id' => $model->id]);
}
}
return $this->render('create', [
'model' => $model,
]);
}
Yii2 has a bunch of built in validators see.
One of which is unique
From Yii2 docs.
// a1 needs to be unique in the column represented by the "a1" attribute
['a1', 'unique'],
// a1 needs to be unique, but column a2 will be used to check the uniqueness
of the a1 value
['a1', 'unique', 'targetAttribute' => 'a2'],
Update:
In your rules array, add the unique validator to email and username like so:
public function rules()
{
return [
[['username', 'email', 'password'], 'required'],
[['username', 'email'], 'unique'],
];
}
Then before saving the model:
if(!$model->validate()){
return false;
}
Update 2:
You are trying to validate the model before any attributes have been assigned. Update your controller code to the following:
public function actionCreate()
{
$model = new Userapp();
$post = Yii::$app->request->post('UserApp');
if (Yii::$app->request->isPost) {
$model->email = $post['email'];
$model->username = $post['username'];
$model->password = $model->setPassword($post['password']);
if($model->validate() && $model->save()){
return $this->redirect(['view', 'id' => $model->id]);
}
else {
return false;
}
}
return $this->render('create', [
'model' => $model,
]);
}
the idea was to do a re-direction only when $model->save() is true otherwise render back the original model to be created/updated

Test the exist validator in Yii2 without database with mockeryBuilder()

I want to test my AR model without connect to database in Yii 2 so I use mockBuilder() but I dont know how can I pass the mock object to the model exist validator, for example:
class Comment extends ActiveRecord
{
public function rules()
{
return [
[['id', 'user_id', 'post_id'], 'comment'],
['comment', 'string',
'max' => 200
],
['user_id', 'exist',
'targetClass' => User::className(),
'targetAttribute' => 'id'
],
['post_id', 'exist',
'targetClass' => Post::className(),
'targetAttribute' => 'id'
]
];
}
}
class CommentTest extends TestCase
{
public function testValidateCorrectData()
{
$user = $this->getMockBuilder(User::className())
->setMethods(['find'])
->getMock();
$user->method('find')->willReturn(new User([
'id' => 1
]));
$post = $this->getMockBuilder(Post::className())
->setMethods(['find'])
->getMock();
$post->method('find')->willReturn(new Post([
'id' => 1
]));
// How can I pass to $user and $post to exist validator in Comment model?
$comment = new Comment([
'user_id' => 1,
'post_id' => 1,
'comment' => 'test...'
]);
expect_that($comment->validate());
}
}
ok, It's not a best code just I'd like to introduce what I want to do.
Yii2 ExistValidator uses ActiveQuery::exists() for check existence and you should replace generated validator to mockobject where the method createQuery returns mockobject of ActiveQuery where ::exists() return something you want (true/false) e.g.
$activeQueryMock = $this->getMockBuilder(ActiveQuery::className())
->disableOriginalConstructor()
->setMethods(['exists'])
->getMock();
$activeQueryMock->expects($this->any())
->method('exists')
->willReturn($value); //Your value here true/false
$model = $this->getMockBuilder(Comment::className())
->setMethods(['getActiveValidators'])
->getMock();
$model->expects($this->any())
->method('getActiveValidators')
->willReturnCallback(function () use ($activeQueryMock) {
$validators = (new Comment())->activeValidators;
foreach ($validators as $key => $validator) {
if (($validator instanceof ExistValidator) && ($validator->attributes = ['user_id'])) {
$mock = $this->getMockBuilder(ExistValidator::className())
->setConstructorArgs(['config' => get_object_vars($validator)])
->setMethods(['createQuery'])
->getMock();
$mock->expects($this->any())
->method('createQuery')
->willReturn($activeQueryMock);
$validators[$key] = $mock;
break;
}
}
return $validators;
});
$model->validate();

Resources