In my Laravel application I have a sign up process in which users must select a category they fall under, each category is in it's own form.
In each of these forms there are a set of checkboxes and users must tick at least one, otherwise validation should fail, I've been doing some reading and found two great, similar questions:
Handling multiple forms on a single page
Laravel request validation with multiple forms on the same page
At the moment I'm feeding all 3 forms to the same method:
/**
* Store a user's selected investor type and progress onto next stage
*
* #param Request $request
* #return void
*/
public function storeInvestorType(Request $request)
{
$user = auth()->user();
$user->investor_type = $request->get('investor_type');
$user->declaration_date = Carbon::now();
$user->save();
Log::info("{$user->log_reference} has declared that they are a '{$user->investor_type}' investor.");
return redirect()->route('user.member-type');
}
Which literally just updates a column in a database.
Would it be cleaner to have 3 separate methods or just to name each form?
An update
I have added a name="something" to each submit button so that I can do something like this in the controller:
/**
* Store a user's selected investor type and progress onto next stage
*
* #param Request $request
* #return void
*/
public function storeInvestorType(Request $request)
{
$user = auth()->user();
if ($request->has('high_net_worth')){
if(!$request->has('high_net_worth_criteria')){
return redirect()->back()->withErrors('Please tick at least one criteria that specifies you are a High Net Worth investor');
} else{
$investor_type = "High Net Worth";
}
} elseif ($request->has('self_certified_sophisticated')) {
if (!$request->has('self_certified_sophisticated_criteria')) {
return redirect()->back()->withErrors('Please tick at least one criteria that specifies you are a Self-Certified Sophisticated investor');
} else {
$investor_type = "Self-Certified Sophisticated";
}
} elseif ($request->has('other')) {
$investor_type = "Other";
}
$user->investor_type = $investor_type;
$user->declaration_date = Carbon::now();
$user->save();
Log::info("{$user->log_reference} has declared that they are a '{$user->investor_type}' investor.");
return redirect()->route('user.member-type');
}
Related
I have a notes model. Which has a polymorphic 'noteable' method that ideally anything can use. Probably up to 5 different models such as Customers, Staff, Users etc can use.
I'm looking for the best possible solution for creating the note against these, as dynamically as possible.
At the moment, i'm adding on a query string in the routes. I.e. when viewing a customer there's an "Add Note" button like so:
route('note.create', ['customer_id' => $customer->id])
In my form then i'm checking for any query string's and adding them to the post request (in VueJS) which works.
Then in my controller i'm checking for each possible query string i.e.:
if($request->has('individual_id'))
{
$individual = Individual::findOrFail($request->individual_id_id);
// store against individual
// return note
}elseif($request->has('customer_id'))
{
$customer = Customer::findOrFail($request->customer_id);
// store against the customer
// return note
}
I'm pretty sure this is not the best way to do this. But, i cannot think of another way at the moment.
I'm sure someone else has come across this in the past too!
Thank you
In order to optimize your code, dont add too many if else in your code, say for example if you have tons of polymorphic relationship then will you add tons of if else ? will you ?,it will rapidly increase your code base.
Try instead the follwing tip.
when making a call to backend do a maping e.g
$identifier_map = [1,2,3,4];
// 1 for Customer
// 2 for Staff
// 3 for Users
// 4 for Individual
and so on
then make call to note controller with noteable_id and noteable_identifier
route('note.create', ['noteable_id' => $id, 'noteable_identifier' => $identifier_map[0]])
then on backend in your controller you can do something like
if($request->has('noteable_id') && $request->has('noteable_identifier'))
{
$noteables = [ 'Customers', 'Staff', 'Users','Individual']; // mapper for models,add more models.
$noteable_model = app('App\\'.$noteables[$request->noteable_identifier]);
$noteable_model::findOrFail($request->noteable_id);
}
so with these lines of code your can handle tons of polymorphic relationship.
Not sure about the best way but I have a similar scenario to yours and this is the code that I use.
my form actions looks like this
action="{{ route('notes.store', ['model' => 'Customer', 'id' => $customer->id]) }}"
action="{{ route('notes.store', ['model' => 'User', 'id' => $user->id]) }}"
etc..
And my controller looks this
public function store(Request $request)
{
// Build up the model string
$model = '\App\Models\\'.$request->model;
// Get the requester id
$id = $request->id;
if ($id) {
// get the parent
$parent = $model::find($id);
// validate the data and create the note
$parent->notes()->create($this->validatedData());
// redirect back to the requester
return Redirect::back()->withErrors(['msg', 'message']);
} else {
// validate the data and create the note without parent association
Note::create($this->validatedData());
// Redirect to index view
return redirect()->route('notes.index');
}
}
protected function validatedData()
{
// validate form fields
return request()->validate([
'name' => 'required|string',
'body' => 'required|min:3',
]);
}
The scenario as I understand is:
-You submit noteable_id from the create-form
-You want to remove if statements on the store function.
You could do that by sending another key in the request FROM the create_form "noteable_type". So, your store route will be
route('note.store',['noteableClass'=>'App\User','id'=>$user->id])
And on the Notes Controller:
public function store(Request $request)
{
return Note::storeData($request->noteable_type,$request->id);
}
Your Note model will look like this:
class Note extends Model
{
public function noteable()
{
return $this->morphTo();
}
public static function storeData($noteableClass,$id){
$noteableObject = $noteableClass::find($id);
$noteableObject->notes()->create([
'note' => 'test note'
]);
return $noteableObject->notes;
}
}
This works for get method on store. For post, form submission will work.
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Requests\NoteStoreRequest $request
* #return \Illuminate\Http\Response
*/
public function store(NoteStoreRequest $request) {
// REF: NoteStoreRequest does the validation
// TODO: Customize this suffix on your own
$suffix = '_id';
/**
* Resolve model class name.
*
* #param string $name
* #return string
*/
function modelNameResolver(string $name) {
// TODO: Customize this function on your own
return 'App\\Models\\'.Str::ucfirst($name);
}
foreach ($request->all() as $key => $value) {
if (Str::endsWith($key, $suffix)) {
$class = modelNameResolver(Str::beforeLast($key, $suffix));
$noteable = $class::findOrFail($value);
return $noteable->notes()->create($request->validated());
}
}
// TODO: Customize this exception response
throw new InternalServerException;
}
So I'm beginning to struggle with Doctrine2 when it comes to a many-to-many relation for a project where the relation has 1 extra column.
I have the following tables:
Profiles
id
extra data
Skills
id
name
profile_has_skills
profile_id
skill_id
level
Now I added the level column later on, and noticed some problems happening, of course I am missing level now whenever I try to create the relation.
My question is, with the code below, how would I go over to add this in my doctrine?
My controller:
public function store(Request $request)
{
$time = new DateTime();
$this->validate($request, [
'name' => 'required',
'lastname' => 'required',
'gender' => 'required',
'profile_skills' => 'required'
]);
$this->em->getConnection()->beginTransaction();
try {
$profile = new Profile(
$request->input('company_id'),
$request->input('name'),
$request->input('lastname'),
$request->input('gender'),
new DateTime(),
$time,
$time
);
$company = $this->em->getRepository(Company::class)->find($request->input('company_id'));
$profile->addCompany($company);
foreach($request->input('profile_skills') as $skill => $level) {
$skill = $this->em->getRepository(Skill::class)->find($skill);
$skill->level = $level;
$profile->addSkill($skill);
}
$this->em->persist($profile);
$this->em->flush();
$this->em->getConnection()->commit();
} catch (OptimisticLockException $e) {
$this->em->getConnection()->rollBack();
throw $e;
}
return redirect(route('profiles.index'));
}
My ProfileHasSkill entity looks as follow:
/**
* #ORM\Entity
* #ORM\Table(name="profile_has_skill")
*
*/
class ProfileHasSkill
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #Column(type="integer", name="profile_id")
*/
protected $profile_id;
/**
* #Column(type="integer", name="skill_id")
*/
protected $skill_id;
/**
* #Column(type="integer", name="level")
*/
protected $level;
/**
* #param $profile_id
* #param $skill_id
* #param $level
*/
public function __construct($profile_id, $skill_id, $level = 0)
{
$this->profile_id = $profile_id;
$this->skill_id = $skill_id;
$this->level = $level;
}
And my addSkill method inside the profile entity is as follow:
public function addSkill(Skill $skill)
{
if ($this->skills->contains($skill)) {
return;
}
return $this->skills->add($skill);
}
But anytime I try to run this it gives me the following error
An exception occurred while executing
'INSERT INTO profile_has_skill (profile_id, skill_id) VALUES (?, ?)'
with params [3, 2]: SQLSTATE[HY000]: General error: 1364 Field 'level'
doesn't have a default value
Now I know one way to get rid of this error is setting a default value in the database, but I much rather just find out why it's not picking up my skill level that I'm also passing?
As per my solution which has worked, by reading another question passed by #Nicola Havric - Read as follow That doctrine does not support extra columns in a many-to-many relation. Thus you should use the relation as it's own entity. My own solution was to change the way I wanted it to run with flushing.
In my controller I changed my code as follow:
try {
$profile = new Profile(
$request->input('company_id'),
$request->input('name'),
$request->input('lastname'),
$request->input('gender'),
new DateTime(),
$time,
$time
);
$company = $this->em->getRepository(Company::class)->find($request->input('company_id'));
$profile->addCompany($company);
//Flush the user, so I can grab it's profile ID
$this->em->persist($profile);
$this->em->flush();
foreach($request->input('profile_skills') as $skill => $level) {
$skill = $this->em->getRepository(Skill::class)->find($skill);
$skill->level = $level;
$profile->addSkill($skill);
}
$this->em->getConnection()->commit();
Inside my Profile Entity function:
public function addSkill(Skill $skill)
{
//I left this check since it only checks if the relation is set already. If so, it will skip it.
if ($this->skills->contains($skill)) {
return;
}
//Since this function gets called inside a loop, I can call the entity to add a new "relation" to the table.
(new ProfileHasSkill($this->getId(), $skill, $skill->level))->addSkill($this->getId(), $skill, $skill->level);
return true;
}
Inside my ProfileHasSkill entity:
public function addSkill($profileId, $skill)
{
//Creating a new ProfileHasSkill inside the table.
$profileSkill = new ProfileHasSkill(
$profileId,
$skill->getId(),
$skill->level
);
/*Since I do a roll-back inside my controller in case something goes wrong.
I decided to add the flush here.
As far no additional checks where needed in my case
since I require a Profile instance and a Skill instance inside the Profile entity.*/
EntityManager::persist($profileSkill);
EntityManager::flush();
}
The thing with many-to-many relationships is that any additional columns other than two primary keys from both tables are considered pivot columns, when attaching entities to such relationships you want to use the method attach which accepts array of ids as first parameter and an array with pivot columns, take the following into consideration.
public function addSkill(Skill $skill)
{
if ($this->skills->contains($skill)) {
return;
}
//Dunno what this method does
return $this->skills->add($skill);
//But this is the correct way of adding a skill
$this->skills->attach($skill->id, ['level' => $skill->level]);
}
Hope this can clarify few things even though Eloquent was used as an example; here is the manual link for the above code.
I am building an application that uses the repository pattern. Now what I have done is reuse functionality but I have hit a bit of a hiccup. I have one view that shows several different models on it. These models are related through a one-to-one relationship (MovieBasic to MovieDetail) and one-to-many relationship (MovieBasic to MoviePersonnel). The issue I am having is that I have two different request to validate my forms. They are MovieBasicRequest, which validates my movie's basic information (Title, synopsis) and MovieDetailRequest, which validates my movie's detail information (price, screen type, runtime, etc). So to distinguish between which request to use I have added a parameter to my url as follows:
movie_basic.blade.php
<?php $params = ['id' => $movie->id, 'type' => 'movie_basic']; ?>
<h4>Movie Baiscs <span class="pull-right">Edit</span></h4>
<hr>
<table class="table table-bordered">
<tbody>
<tr>
<td>{{ $movie->movie_title}}</td>
</tr>
<tr>
<td>{{ $movie->movie_synopsis }}</td>
</tr>
</tbody>
</table>
I know that using the <?php ?> tags is not best practice but I will clean that up later. So because of my $params the URL will look like so
www.moviesite.dev/1/edit?movie_basic
Which will call the edit function in the controller like so
MovieController.php
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return Response
*/
public function edit($id)
{
$movie = $this->movieBasic->find($id);
return view('cms.edit', compact('movie', 'type'));
}
In this case the type does not really play a role because of the relationship between MovieBasic and MovieDetail models. However it does play a role in my update function below:
MovieController.php
/**
* Update the specified resource in storage.
*
* #param int $id, MovieBasicRequest $request
* #return Response
*/
public function update($id)
{
if(strcmp($_GET['type'], 'movie_basic') == 0)
{
$movie = $this->movieBasic->find($id);
$this->request = new MovieBasicRequest;
$this->movieBasic->update($id, $this->request);
}
elseif(strcmp($_GET['type'], 'movie_detail') == 0)
{
$movie = $this->movieBasic->find($id);
$this->request = new MovieDetailRequest;
$this->movieDetail->update($id, $this->request);
}
return redirect()->action('MovieController#show', compact('movie'));
}
Essentially what this function does is determine what is being passed in and from there call the correct request. However the way I have it now it just creates an empty array and thus validates nothing. Is there any way to uses these requests to validate information passed in? Or to validate input before I pass it to the update function of the repository?
PS. I have also tried this:
$this->movieBasic->update($id, MovieBasicRequest $request);
but I get an "Undefined variable $request" error.
You should better combine them. And you can use sometimes on your form validation for handling both where you will only validate present fields. So that your MovieRequest can be like below
class MovieRequest extends Request
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'movie_title' => 'sometimes|required|min:3',
'price' => 'sometimes|required|integer'
// and so on
];
}
}
So you can update your controller as below and use for the both pages. For instance, if the price is not set within the request, then it will skip validating it, but if it's present and empty, then it will throw an error message as it's required.
public function update($id, MovieRequest $request)
{
$movie = $this->Movie->find($id);
$movie->fill($request->all());
$movie->save();
return redirect()->action('MovieController#show', compact('movie'));
}
I have the 2 simple tables below:
CUSTOMERS
id, email
CLAIMS
id, customer_id(fk), description
I created the related models (Customers.php and Claims.php) and set-up relationships: hasOne() and belongsTo().
I also have my related RESTful controllers ready: CustomersController.php and ClaimsController.php.
What would be the best solution if I need to create/update records in both tables by submitting one form? Create one general controller? Mix models?
I have been searching in Laravel docs and on Google and still have no idea how to achieve this.
Customer model
public function claims(){
return $this->hasMany('App\Claims');
}
Claims model
public function customer(){
return $this->belongsTo('App\Customer');
}
Now in controller u need to send request in store action
Something like this
class CreateCustomerClaim extends Request {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'email' => 'required',
'description'=> 'required'
];
}
}
Now in store action send your request, grab data from request and insert it in db
public function store(CreateCustomerClainsRextends $request)
{
//example
$customer= new Customer($request->all());
Auth::user()->claims()->save($customer);
}
if u need to update use same request in update function, when u grab data from request just use update. Here is example where i update 3 different tables from one request
public function update($id,ArtikalUpdateRequest $request)
{
$article = Artikal::findOrFail($id);
if($article !== null){
$article->update($request->all());
\DB::table('artikal_podkategorija')
->where('artikal_id', $article->id)
->update(array('podkategorija_id' => $request['podkategorija']));
\DB::table('arikalslike')
->where('artikal_id', $article->id)
->update(array('NazivSlike' => $request['NazivSlike']));
$slika = \DB::table('arikalslike')
->where('artikal_id', $article->id)->first();
$image = Request::file('image');
//dd($image);
if($image != null){
$destinationPath = 'uploads/artiklislike/';
$thumb = $slika->SifraSlike;
$fileName = $thumb;
$nazivthumb = $slika->NazivThumb;
$slika->NazivSlike = $request['NazivSlike'];
$slika->NazivThumb = $nazivthumb;
$slika->SifraSlike = $fileName;
$slika->artikal_id = $article->id;
//Snima sliku
$img = Image::make(Input::file('image'));
$destinationPath = $destinationPath.$fileName;
Image::make($img)->save($destinationPath);
// Snima sliku u manjem formatu thumb
$destinationPath = 'uploads/artiklislike/';
$img = Image::make(Input::file('image'));
$destinationPath = $destinationPath.$nazivthumb;
Image::make($img)->resize(300, 200)->save($destinationPath);
}
}
return redirect('artikli')->with(['flash_message' => 'Uspiješno ste obrisali artikal!']);
}
This has been driving me crazy all day!!
My form creates users. The admin who creates these users can add several rows of inputs and then saves everything at the end. I'm using the clone() method in jQuery and I have already done the client side validation for required inputs etc... I have a very strong Domain layer but now I need server side validation so that Laravel can check if the email already exists since it must be unique. I am very proficient in Laravel, however with the new FormRequest objects I'm stuck on how to loop through each input etc since the FormRequestObject only seems to accept one entry. This all in ajax...
My formData looks like this:
counter:2
_token:KwGAUheSXbzkInh1RZ4RPenx4Fd4fF5DsPm5bjyO
firstname_1:name1
lastname_1:last1
email_1:email#email.com
password_1:keith
firstname_2:name2
lastname_2:last2
email_2:email#email.com
password_2:keith
As you can the input names have an incrementing id so the rules() in form request must loop through these. I'm really stuck, cannot find one example online.
My form reuqest class looks like this:
<?php namespace hidden\Http\Controllers\UserAccess\Requests;
use hidden\Http\Requests\Request;
use Illuminate\Auth\Guard;
use hidden\Domain\Services\UserAccess\GetUserFromEmailService;
class CreateAdministratorRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'firstname' => 'required',
'lastname' => 'required',
'email' => 'required|email|unique:users,email',
'password' => 'required'
];
}
}
My controller method so far (It will eventually loop through the inputs and dispatched a command but pointless at the moment):
/**
* #param CreateAdministratorRequest $request
*/
public function createAdministrators(CreateAdministratorRequest $request)
{
// dispatch command per entry
}
If you want to keep your structure as possible you could do the following.
public function rules()
{
$counter = $this->get('counter');
$emails= [];
// validate manually if every submitted email is unique
// so, we need every email value
for ($i = 1; $i <= $counter; $i++){
$emails[] = $this->get('email_' . $i);
}
// Now, validate if every email between them are unique
// array_unique() delete repeated values, so we compare
// the size before and after filter the values
$unique = (count($emails) === count(array_unique($emails)));
if (! $unique){
return redirect()->back()
->withMessage('Sorry, all emails should be different');
}
// Now, you need to add every field into the rules
$rules = [];
for ($i = 1; $i <= $counter; $i++){
$rules['email_' . $i] = 'required|email|unique:users,email';
$rules['lastname_' . $i] = 'required';
$rules['firstname_' . $i] = 'required';
$rules['password_' . $i] = 'required';
}
return $rules;
}