Laravel 8.x - Form checkboxes to pivot table - php

I am building a platform where students can apply for academic courses.
The students use a form to provide some personal information and then choose at least one course to apply for.
When the form is sent, the ApplicationsController validates the inputs and checks that there is at least one course checked. The controller:
public function student_store(Request $request, Course $course)
{
$request->validate([
'fname' => 'required|string|min:2|max:40',
'lname' => 'required|string|min:2|max:40',
'email' => 'required|email|min:6|max:254',
'course-check' => 'required',
'phone' => 'required|digits:10',
'created_at' => \Carbon\Carbon::now(),
'updated_at' => \Carbon\Carbon::now(),
]);
If the validation is ok, a new Applicant is created in the database using the form inputs:
$input = $request->all();
Applicant::create($input);
However the goal is to also create one or more new instances in the pivot table applicant_course, depending on which one or more courses the student has selected. So far I have this:
$applicant = Applicant::where('fname', $input['fname'] )
->where('lname', $input['lname'] )
->where('email', $input['email'] )
->latest()->first();
$checkboxes = $request->all('course-check');
foreach ($checkboxes as $checkbox){
$applicant->courses()->where('course_id', $checkbox)->attach($course->id);
}
}
However, all that the controller function does, is to validate the inputs and create a new Applicant in the database with their data but nothing is added in the pivot table.
Here is the view structure (ignore the $program variables):
#foreach ($courses as $course)
#if ($course->program_id == $program->id)
<div class="course course-{{$program->id}}-{{$course->id}}">
<div class="course-header">
<label class="course-number" for="course-check-{{$program->id}}-{{$course->id}}">{{$program->id}}0{{$course->id}}0</label>
<label class="course-title" for="course-check-{{$program->id}}-{{$course->id}}">{{$course->title}}</label>
<input type="checkbox" name="course-check[{{ $course->id }}]" class="course-check" id="course-check-{{$program->id}}-{{$course->id}}" value="" >
</div>
</div>
#endif
#endforeach
Here is the Applicant model:
class Applicant extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'fname',
'lname',
'email',
'phone',
];
public function courses()
{
return $this->belongsToMany(Course::class)->withTimestamps();
}
}
Can someone help ?
Thanks in advance!

I think you need to change checkbox name like below
<input type="checkbox" name="course-check[]" class="course-check" id="course-check-{{$program->id}}-{{$course->id}}" value="{{ $course->id }}" >
Then in controller
$checkboxes = $request->get('course-check');
$applicant->courses()->sync($checkboxes);
or
$checkboxes = $request->get('course-check');
$applicant->courses()->attach($checkboxes);
To know difference between attach and sync
Ref:https://laravel.com/docs/8.x/eloquent-relationships#updating-many-to-many-relationships

Related

Laravel Livewire accessing json attribute fails with type error

I have a model with some typical columns and one json column. The Json is casted as array:
Model:
protected $casts = [
'client' => 'array'
];
In my Livewire component, I created the following validation rule
Livewire component:
protected $rules = [
'editing.name' => 'required',
...
'editing.client' => 'present|array',
'editing.client.*.tenant' => 'required',
];
I call the 'editModal' method where I type-hint the model and set a public property with it's attributes. Already filtered to the specific item.
Livewire component:
public function editModal(TokenCacheProvider $provider)
{
$this->editing = $provider;
$this->dispatchBrowserEvent('open-modal', ['modal' => 'edit']);
}
My blade is a simple boiler blade component:
Blade:
<div>
<x-input.group inline borderless for="name" label="Name" :error="$errors->first('editing.name')"/>
<x-input.text name="name" class="w-full" wire:model="editing.name" />
</div>
<div>
<x-input.group inline borderless for="name" label="Tenant" :error="$errors->first('editing.client.tenant')"/>
<x-input.text name="tenant" class="w-full" wire:model="editing.client.tenant" />
</div>
Once I load the page I get the following type exception
foreach() argument must be of type array|object, string given
This is because the client attribute is still a string as in the database. It should be an array as I casted it:
So, I don't understand why the client attribute is still a string and not an array as casted.
Thank you
Well it's more a work-around than a solution but Daantje found an Livewire issue on Github which might explain this behavior.
I've changed the architecture from one to two public properties. One for the actual model and a second for the json column.
Livewire component (truncated)
public MyModel $editing; // the model
public array $client; // for the json attribute
protected $rules = [
'editing.name' => 'required',
...
'client.foo' => 'required',
'client.bar' => 'required',
'client.baz' => 'required',
...
];
public function editModal(MyModel $model)
{
$this->editing = $model;
$this->client = json_decode($model->client,true);
$this->dispatchBrowserEvent('open-modal', ['modal' => 'edit']);
}
public function save()
{
$this->validate();
$this->editing->client = json_encode($this->client);
$this->editing->save();
$this->dispatchBrowserEvent('close-modal', ['modal' => 'edit']);
$this->event('Saved', 'success');
}
Two blade input field examples:
<!-- ORM field(s) -->
<div>
<x-input.group inline borderless for="name" label="Name" :error="$errors->first('editing.name')"/>
<x-input.text name="name" wire:model="editing.name" />
</div>
<!-- Json field(s) -->
<div>
<x-input.group inline borderless for="foo" label="Foo" :error="$errors->first('client.foo')"/>
<x-input.text name="foo" wire:model="client.foo" />
</div>
Well, this works but as mentioned it's more a workaround

Laravel 8.x - pass pivot table data to mailable

I have a platform where students can apply for academic courses. When an application is sent, apart from being saved into the database, it must fire an email with the information of the applicant along with the titles of the applied courses.
An Applicant can apply for many Courses, hence the pivot table Applicant_Course. The Applicant model:
class Applicant extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'fname',
'lname',
'email',
'phone',
'is_student',
];
public function courses()
{
return $this->belongsToMany(Course::class)->withTimestamps();
}
The Mail\ApplicationMail:
public function __construct(Applicant $applicant)
{
$this->applicant = $applicant;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
// return $this->view('view.name');
}
The problem is that I cannot figure out how to pass the Applicant object into the mailable. This is what the applicationMail view looks like:
<tr class="email-row">
<td class="left-column"><b>Email: </b></td>
<td class="right-column email-cell">
<a href="{{ $email }}">
{{ $email }}
</a>
</td>
</tr>
#isset($phone)
<tr>
<td class="left-column"><b>Phone: </b></td>
<td class="right-column">
<td class="right-column subject-text">{{ $phone }}</td>
</td>
</tr>
#endisset
<tr>
#foreach ($applicant->courses as $course)
<div class="application-body-item">
<p>{{$course->title}}</p>
<p>Course Code: {{$course->code}}</p>
<hr>
</div>
#endforeach
</tr>
But the line where it says: '#foreach ($applicant->courses as $course)' returns the error: 'Undefined variable: applicant'.
Here is my ApplicationsController:
$input = $request->all();
\Mail::send('applicationMail', array(
'title' => $input['title'],
'org' => $input['org'],
'fname' => $input['fname'],
'lname' => $input['lname'],
'email' => $input['email'],
'course-check' => $input['course-check'],
'phone' => $input['phone'],
'created_at' => $input['created_at'],
'updated_at' => $input['updated_at'],
), function($message) use ($request){
$message->from(env('MAIL_USERNAME'));
$message->to(env('MAIL_USERNAME'))->subject($request->get('subject'));
});
Any idea how to pass the Applicant's data into the mailable so that I can retrieve the pivot table data (from the Applicant_Course table) just like I do inside a normal view ?

How to make different pages of auth() register pages by configuring routes with data defined

When I try to open Studentregister page it opens the default register page of auth().
I'm new in Laravel I m having issues with syntaxes...
I have made only one User class having link with role class.
(User class is generated through $php artisan auth and foreign key for Role class is added to it.)
Now I want to register different users like student, teacher through studentRegister.blade.php or teacherRegister.blade.php. And I have to fix role_id in student page as 1 and in teacher role id as 2. So what will be the syntax.
created different route for student register and teacher register (web.php) ..
Route::get('student/register','Auth\RegisterController#registerStudent');
Route::get('teacher/register','Auth\RegisterController#registerTeacher');
added role variable and send it to view (Auth/RegisterController.php)
public function registerStudent()
{ $role_id = 1;
return view('auth.register',compact('role_id'));
}
public function registerTeacher()
{ $role_id = 2;
return view('auth.register',compact('role_id'));
}
setted value of hidden input with "role" name (Auth/Register.Blade.php);
<input id="role" type="hidden" name="role" value="{{$role}}">
change fillable variable in user.php so you can fill role field.
protected $fillable = [ 'name', 'email', 'password',role ];
added role to your create function on RegisterController
**(Auth/RegisterController.php)**.
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'role' => $data['role'],
'password' => Hash::make($data['password']),
]);
}
In StudentRegister.blade.php I have added this
<input id="role_id" type="hidden" name="role_id" value="1">
In TeacherRegister.blade.php I have added this
<input id="role_id" type="hidden" name="role_id" value="2">

Laravel - Test Edit Form with Value tag

I'm using Laravel 5.6 to develop a website.
Currently, I want to write a test codes for the website. I'm also new to building a website in general and this is learning curve for me to learn what I'm doing wrong.
I created a Profile based on a User model and the Profile should only be editable by the authenticated User only.
The form is actually working without errors on the browser side but once i run phpunit, it will fail.
Test Script:
/** #test */
public function an_authenticated_user_can_view_the_profile_page()
{
// Generate fake instance of authenticated user
$this->be($user = factory('App\User')->create());
// Will get the URL
$response = $this->get('/profile/'.$user->name);
// Check whether the string exists
$response->assertSee('Personal details for '.$user->name);
}
Controller:
class ProfileController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function show(User $user)
{
return view('user.profiles.show', compact('user'));
}
public function update(Request $request)
{
$this->validate(request(), [
'company' => 'required',
'street' => 'required',
'city' => 'required',
'zip_code' => 'required',
'state' => 'required',
'country' => 'required',
'phone' => 'required',
]);
$profile = \Auth::user()->profile;
$profile->update($request->all());
return back()->with('success', 'Profile updated!');
}
}
View:
<div class="heading">
<h3 class="text-uppercase">Personal details for {{ $user->name }}</h3>
</div>
<form method="POST" action="/profile">
{{method_field('PATCH')}}
{{csrf_field()}}
<input type="hidden" value="{{ $user->profile->id }}" name="id">
<div class="col-md-6">
<div class="form-group">
<label for="company">Company</label>
<input id="company" type="text" class="form-control" name="company" value="{{ $user->profile->company }}" required>
</div>
</div>
</form>
Image of the commented out Form test:
Commented Form
Image of the not commented Form test:
Not commented Form
I am rather confused why my test is failing once I insert the form with a value tag. If i commented out the form or just remove the value tag, the test will pass.
Been searching for the few days and still can't find the right answer to this. Am i using the right Assertion? What am I missing here? Any inputs will help me to further understand this. Thanks!
I found the answer. It was actually the factory that I've created.
In the User model, every registration leads to creating an empty Profile.
This is the new way of how I write the test script:
/** #test */
public function an_authenticated_user_can_view_the_profile_page()
{
//Generate a fake profile
$profile = factory('App\Profile')->create();
// Assign it to the user
$user = $profile->user;
// Authenticate the user
$this->be($user);
// Will get the URL
$response = $this->get('/profile/'.$user->name);
// Check whether the string exists
$response->assertSee('Personal details for '.$user['name']);
}

Laravel Model binding many to many realtionship does not populate data

I have two tables with a many to many relation (Project and Center, the pivot table is ProjectCenter).
These are my models:
Project:
class Project extends Model {
public function centers()
{
return $this->belongsToMany('App\Models\Center', 'ProjectCenter', 'IDProject', 'IDCenter');
}
public function getCenterListAttribute()
{
return $this->centers->lists('IDCenter')->all();
}
}
Center:
class Center extends Model {
public function projects()
{
return $this->belongsToMany('App\Models\Project', 'ProjectCenter', 'IDCenter', 'IDProject');
}
}
Controller -> edit:
public function edit($id)
{
$project = Project::find($id);
$centerList = Center::lists('Name', 'IDCenter')->toArray();
return view('project/add', array('centerList' => $centerList))->with('project', $project);
}
And the view:
{!! Form::label('centers_list', 'Center*') !!}
{!! Form::select('centers_list[]',
$centerList,
null,
array(
'class' => 'form-control ',
'required' => 'required',
'multiple' => true,
'data-placeholder' =>
'Select a center'
)
) !!}
But I can not select the data already stored previously.
For example: the project 8 (IDProject) has two centers (1 and 2) but the data is not populated in the multiple select:
What am I doing wrong?
You all time get same result $centerList = Center::lists('Name', 'IDCenter')->toArray(); but you must be get product centers using query with model.
$project = Project::with("centers:Name,IDCenter")->find($id);
$centerList = $project->centers->pluck('Name', 'IDCenter')->toArray();
I already solve the problem using a foreach to select the related centers:
<select multiple="multiple" name="centers[]" id="centers" class="form-control select2" required="required" data-placeholder="Select a center">
#if($centerList)
#foreach($centerList as $key => $center)
<option value="{{$key}}" {{ (collect($selectedCenters)->contains($key)) ? "selected='selected'" : '' }} >{{$center}}</option>
#endforeach
#endif
</select>

Categories