Laravel exists custom validation rule unable to validate user id with phpunit - php

I have a validation rule taken from the Laravel Documentation which checks if the given ID belongs to the (Auth) user, however the test is failing as when I dump the session I can see the validation fails for the exists, I get the custom message I set.
I have dumped and died the factory in the test and the given factory does belong to the user so it should validate, but it isn't.
Controller Store Method
$ensureAuthOwnsAuthorId = Rule::exists('authors')->where(function ($query) {
return $query->where('user_id', Auth::id());
});
$request->validate([
'author_id' => ['required', $ensureAuthOwnsAuthorId],
],
[
'author_id.exists' => trans('The author you have selected does not belong to you.'),
]);
PHPUnit Test
/**
* #test
*/
function adding_a_valid_poem()
{
// $this->withoutExceptionHandling();
$user = User::factory()->create();
$response = $this->actingAs($user)->post(route('poems.store'), [
'title' => 'Title',
'author_id' => Author::factory()->create(['name' => 'Author', 'user_id' => $user->id])->id,
'poem' => 'Content',
'published_at' => null,
]);
tap(Poem::first(), function ($poem) use ($response, $user)
{
$response->assertStatus(302);
$response->assertRedirect(route('poems.show', $poem));
$this->assertTrue($poem->user->is($user));
$poem->publish();
$this->assertTrue($poem->isPublished());
$this->assertEquals('Title', $poem->title);
$this->assertEquals('Author', $poem->author->name);
$this->assertEquals('Content', $poem->poem);
});
}
Any assistance would be most appreciated, I'm scratching my head at this. My only guess is that the rule itself is wrong somehow. All values are added to the database so the models are fine.
Thank you so much!

In your Rule::exists(), you need to specify column otherwise laravel takes the field name as column name
Rule::exists('authors', 'id')
Since column was not specified, your code was basically doing
Rule::exists('authors', 'author_id')

Related

Laravel Jetstream - How to join a default team at registration

I am trying to change Laravel Jetstream's logic in order to join a default team at registration rather than create a personal team. I found the method that does this:
public function create(array $input)
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => $this->passwordRules(),
'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['required', 'accepted'] : '',
])->validate();
return DB::transaction(function () use ($input) {
return tap(User::create([
'name' => $input['name'],
'email' => $input['email'],
'password' => Hash::make($input['password']),
]), function (User $user) {
$user->currentTeam = Team::find(1); # SQLSTATE[42S22]: Column not found: 1054 Unknown column 'currentTeam' in 'field list'
$user->save();
});
});
}
The issue here is that currentTeam seems to be an created after the database level and not a column in the Database itself. The documentation says that currentTeam returns the Team model but doesn't say how to update the current users team programmatically.
I can see I can manually use a DB query to insert a row into the team_user table but there must be a method to do this.
return DB::transaction(function () use ($input) {
return tap(User::create([
'name' => $input['name'],
'email' => $input['email'],
'password' => Hash::make($input['password']),
]), function (User $user) {
DB::table('team_user')->insert(['user_id' => $user->id, 'team_id' => 1, 'role' => 'rfn']);
$user->current_team_id = 1;
});
});
Any help would be appreciated, I understand this will be reliant on this team always existing in the Database but further measures will be implemented so it cannot be removed.
After some tinkering with a local Laravel version, this is was I came up with. This assumes the team you wanna add the user to is already created somehow, possibly through a seeder, or created manually. The file you need to edit is app/Actions/Fortify/CreateNewUser.php, namely the createTeam method.
Instead of creating a new team, we want to grab a team from the database, attach it to the newly registered user and then make that now attached team the users current team.
protected function createTeam(User $user)
{
$team = Team::first();
$user->teams()->attach($team);
$user->switchTeam($team);
}
The reason we first need to attach the team first is that switchTeam checks if a user belongs to that team if it doesn't it returns false and doesn't switch to the new team. By attaching the team first we make the user belong to that team before we can actually switch to it as the current team.
you can assign role as well in above answer of #KimHallberg as below
$team = Team::first();
$user->teams()->attach($team, array('role' => 'editor'));
$user->switchTeam($team);

Custom validation using laravel 5.8

Good day,
I am trying to update brand data using Laravel 5.8 and I made a custom validation function to validate the name of the brand but my problem is when I attempt an update, the validation fails and I get a message saying Opps name Is Exist Before
I need to update this validation function to link the brand id with the brand name to perform the update without showing the validation error.
Thanks in advance.
Here is my code:
public function update(Request $request, $id)
{
//prepare data for validation
request()->validate([
'name' => [
'required',
'min:2', // validate english name is exist before
function ($attribute, $value, $fail) {
$englishname=Brand::where(['name'=>$value,'deleted'=>1 ])->first();
if(false !=$englishname) {
$fail('Opps '.$attribute.' Is Exist Before.');
}
},
],
'keywords' => 'required|min:2',
'ar_name' => [
'required',
'min:2',// validate english name is exist before
function ($attribute, $value, $fail) {
$arname=Brand::where(['ar_name'=>$value,'deleted'=>1])->first();
if(false !=$arname) {
$fail('Opps '.$attribute.' Is Exist Before.');
}
},
],
'ar_keywords' => 'nullable',
'status' => 'required|integer',
],[],[
"name"=>"Brand Name",
'keywords' => 'Brand KeyWords',
'ar_name' => 'اسم الماركة',
'ar_keywords' => 'الكلمات الدليلية',
]);
// start pass data to model
$branddata=array(
'name' =>$request->name,
'keywords' =>$request->keywords,
'ar_name' =>$request->ar_name,
'ar_keywords' =>$request->ar_keywords,
'last_updated_by'=>auth()->user()->id,
'status' =>$request->status,
);
//start update data
$updateddata=Brand::where(['id'=>$id,'deleted'=>1])->update($branddata);
if (false !==Brand::create($updateddata))
{
return redirect(route("brand.edit"))->with("messageSuccess","Brand Updated Successfully");
}else{
return redirect(route("brand.edit"))->with("messageSuccess","Brand Updated Successfully");
}
}
You can use the unique validation rule from Laravel with extended attributes passed for the validation checks.
If you want to validate that the name attribute must be unique (but allows for the same model instance), you can do something like follow:
'name' => "required|string|email|unique:<table name for the Brand::class>,name,{$id}"
This validation rule will check for uniqueness of the provided name for every rows except the one with $id as its primary key.
If your $id variable is not a primary key, you can specify the column name for the variable as follows:
'name' => "required|string|email|unique:<table name for the Brand::class>,name,{$id},<column name of the id>"

laravel database seeder adding add foreign key id randomly to seeds

I am trying to create seeders for testing purposes. I have users that belongs to a room via a room id, these rooms are created via a room seeder, in my users seeder, I create a user and update the room_id attribute like this,
factory(App\User::class, 150)->create([
'host' => false,
'room_id' => App\Room::inRandomOrder()->first()->id
]);
My problem is that all users generated here, all get the same room id, how can a truly get a random room id from the database and use it in my seeder?
I had the same problem with seeding. The problem is that, you are overriding the factory's default model attributes by passing an array of "values". You are passing the value of App\Room::inRandomOrder()->first()->id to the create method. So you would have all users with the same room_id.
To solve this issue, in laravel 8, you can move the 'room_id' => Room::inRandomOrder()->first()->id to your UsersFactory definition:
class UsersFactory {
...
public function definition()
{
return [
'room_id' => Room::inRandomOrder()->first()->id
];
}
...
}
And create users like this,
App\User::factory()->count(150)->create([
'host' => false
]);
In older version of laravel, define your factory as below:
$factory->define(App\User::class, function ($faker) use ($factory) {
return [
'room_id' => Room::inRandomOrder()->first()->id
];
});
And create users like this,
factory(App\User::class, 150)->create([
'host' => false,
]);
Try:
App\Room::all()->random()->id
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
$users = factory(\App\User::class, 150)->create([
'host' => false,
'room_id' => $this->getRandomRoomId()
]);
}
private function getRandomRoomId() {
$room = \App\Room::inRandomOrder()->first();
return $room->id;
}
Try this one. It works for me. Hopefully it works for you.
Try this one. Also, make sure that you have multiple auto incremented room entries in the room table.
$factory->define(App\User::class, function ($faker) use ($factory) {
return [
'host' => false,
'room_id' => $factory->create(App\Room::class)->id
];
});

Laravel Backpack - Set extra attributes before page save

In my PageTemplates.php I have a field like this:
$this->crud->addField([
'name' => 'adres',
'label' => 'Adres',
'type' => 'address',
'fake' => true,
]);
Now I would like to save also the latitude and longitude of the address they give in (if it can be found). I've copied the PageCrudController and changed the config in config/backpack/pagemanager.php to:
return [
'admin_controller_class' => 'App\Http\Controllers\Admin\PageCrudController',
'page_model_class' => 'App\Models\Page',
];
In my store function I have:
public function store(StoreRequest $request)
{
$address = $request->request->get('adres');
$addressObj = app('geocoder')->geocode($address)->get()->first();
if($addressObj)
{
}
$this->addDefaultPageFields(\Request::input('template'));
$this->useTemplate(\Request::input('template'));
return parent::storeCrud();
}
But what do I place in the if statement? How can I add (= set) an extra field to the extras field in my database?
In backpack 4.1, I solved my issue by the following way :
Override the store method in my controller, set my extra field in request and then call the backpack store method
Don't forget to add include backpack trait
Hope the solution will help someone
use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation { store as traitStore; }
public function store()
{
$this->crud->setOperationSetting('saveAllInputsExcept', ['save_action']);
$this->crud->getRequest()->request->add(['updated_by' => backpack_user()->id]);
return $this->traitStore();
}
Fixed it by doing the following:
Add latitude and longitude as hidden fields:
$this->crud->addField([
'name' => 'latitude',
'type' => 'hidden',
'fake' => true,
]);
$this->crud->addField([
'name' => 'longitude',
'type' => 'hidden',
'fake' => true,
]);
Set attributes by doing the following:
if($addressObj)
{
$request['latitude'] = $addressObj->getCoordinates()->getLatitude();
$request['longitude'] = $addressObj->getCoordinates()->getLongitude();
}
}
Change parent::updateCrud to parent::updateCrud($request);.
For people still looking at this issue, I'd recommend you follow the advice in the note under the Callbacks section of Laravel Backpack's docs if you don't just want to observe changes made from the Backpack admin panel, you just need to create an Observable.
To do this you can do the following:
Create an Observer class: php artisan make:observer YourObserver --model=YourModel
Add your code to the generated event methods you wish to observe.
Register the Observer by calling the observe method on the model you wish to observe in your EventServiceProvider's boot method like so:
public function boot()
{
YourModel::observe(YourObserver::class);
}
Or equally you can register the Observer to the $observers property of your applications' EventServiceProvider class:
protected $observers = [
YourModel::class => [YourObserver::class],
];

How to perform validation when updating a unique field using Http\Request in Laravel

I have a customers table that I use a CustomerRequest to validate the fields, through the use of the rules function. The table has an email field which is required and is unique. This works fine when I create the customer, however when I update the customer info (let's say I got their last name spelled wrong) my request fails because the email address is already in the database.
Here is my CustomerRequest:
public function rules()
{
return [
'givenname' => 'required',
'surname' => 'required',
'email' => 'required|unique:customers,email',
];
}
I would like to reuse the CustomerRequest for all of the customer vaildation, how can I go about doing this?
You need to check here for request type as well as customer id for update and then return rules according to request. Something like this
public function rules(Request $customer_request)
{
return [
'givenname' => 'required',
'surname' => 'required',
'email' => 'required|unique:customers,email,'.$customer_request->get('customer_id'),
];
}

Categories