Updating form in Laravel goes wrong - laravel

This may be a very simple question, but I can't figure it out! and that's frustrating.
I do my best to explain everything step by step.
This a small Todo list project in Laravel 8
A user can create a Project.
When user clicks on a created project, he goes to the project page (show page)
ShowController.php
public function show(Project $project)
{
return view('projects.show', compact('project'));
}
In the show page the user can add comments via a textarea form field.
show.blade.php
<form action="{{ route('project.update',['project' => $project]) }}" method="post">
#csrf
#method('PUT')
<textarea name="notes" placeholder="Add notes">{{ $project->notes ?? '' }}</textarea>
<button type="submit">Save</button>
</form>
Where it goes wrong is here, by updating the project! as soon as the user enters something in the comments field and clicks save, the form indicates that the following items are required:
The owner_id, title, description field are required. While the model is sent to the show blade page and then in the form action route.
What am I doing wrong here?
UpdateController.php
public function update(ProjectRequest $request, Project $project)
{
$validated = $request->validated();
$project->update($validated);
return redirect($project->path());
}
ProjectRequest.php
public function rules(): array
{
return [
'owner_id' => 'required',
'title' => 'required',
'description' => 'required',
'notes' => 'nullable',
];
web.php
use App\Http\Controllers\Projects\CreateController;
use App\Http\Controllers\Projects\IndexController;
use App\Http\Controllers\Projects\ShowController;
use App\Http\Controllers\Projects\StoreController;
use App\Http\Controllers\Projects\UpdateController;
use Illuminate\Support\Facades\Route;
Route::get('/', [IndexController::class, 'index'])->name('project.index');
Route::get('/projects/create', [CreateController::class, 'create'])->name('project.create');
Route::post('/projects', [StoreController::class, 'store'])->name('project.store');
Route::get('/projects/{project}', [ShowController::class, 'show'])->name('project.show');
Route::put('/projects/{project}', [UpdateController::class, 'update'])->name('project.update');
migration
public function up()
{
Schema::create('projects', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('owner_id');
$table->string('title');
$table->text('description');
$table->text('notes')->nullable();
$table->timestamps();
$table->foreign('owner_id')
->references('id')
->on('users')
->onDelete('cascade');
});
}

You have required rules for fields that does not exist in form. So validation correctly fails.
If you use these rules for storing data, and want different one for updating, then you have at least three solutions:
Make separate form request file. So instead ProjectRequest do for
ex. ProjectUpdateRequest and ProjectStoreRequest.
Use single Request, but detect if it is Update or Store inside rules() function, and return different rules array based on it. Related question: Laravel form request validation on store and update use same validation
Don't use custom FormRequest at all for Update, just make this single validation inside controller update() function.
https://laravel.com/docs/8.x/validation#quick-writing-the-validation-logic
Option 2 seems to be best solution, because you will not have to repeat validation rules for "notes" input in multiple places - everything will be in single file.

If the fields are not required then take them out of your $required array and it should work.

When injecting a model ID to a route or controller action, you will
often query the database to retrieve the model that corresponds to
that ID. Laravel route model binding provides a convenient way to
automatically inject the model instances directly into your routes.
For example, instead of injecting a user's ID, you can inject the
entire User model instance that matches the given ID.
Reference
show.blade.php
<form action="{{ route('project.update',['project' => $project->id]) }}" method="post">
#csrf
#method('PUT')
<textarea name="notes" placeholder="Add notes">{{ $project->notes ?? '' }}</textarea>
<button type="submit">Save</button>
</form>
Also, to update a column, you do not need to validate and update all the columns.
UpdateController.php
public function update(Request $request, Project $project)
{
$request->validate([
'title' => 'nullable|string',
]);
$project->update(['notes' => $request->notes ?? '']);
return redirect($project->path());
}
Note: Add use Illuminate\Http\Request; to the first UpdateController.php file.

Related

Laravel missing parameters to update form in controller

So I have a little project, in it, there's the possibility to upload banners images to be shown on the main page. All the stuff related to the DB are already created and the option to create a banner is working, it creates the banner and then stores the image on the DB for use later. Now I'm trying to work on an edit function so I can change the description under the bannners. I have an Edit route in my controller which returns a view where I edit said banner then it calls the update function on the controller. But no matter what I put here, I'm always getting the Missing Required Parameters error once I try to Save the edit and open my controller through the Update function. Here's the code as it is now:
The route definition:
Route::resource('banner', 'BannerController');
The edit function on my controller:
public function edit($id)
{
return view('admin/edit-banners',['id'=>$id]);
}
The update function has not been implemented because I always start with a dd() function to check if everything is working fine:
public function update(Request $request, $id)
{
dd($request);
}
And here's the form line in my edit view that is trying to call the update route:
<form class="card-box" action="{{ route('banner.update',[$banner]) }}">
I also added this at the beginning of the view to store the data from the DB into a variable:
#php
use\App\Banner;
$banner = Banner::where('id','=',$id)->get();
#endphp
The $banner variable contains all the information on the banner being edited, and I can get the new description at the controller with the $request variable, so I honestly don't know what should I put here as parameters, any ideas?
The $banner variable is not a Model instance, it is a Collection.
Adjust your controller to pass this to the view instead of dong the query in the view:
public function edit($id)
{
$banner = Banner::findOrFail($id);
return view('admin.edit-banners', ['banner' => $banner]);
}
You could also use Route Model Binding here instead of doing the query yourself.
Remove that #php block from your view.
The form should be adjusted to use method POST and spoof the method PUT or PATCH (as the update route is a PUT or PATCH route) and you should adjust the call to route:
<form class="card-box" action="{{ route('banner.update', ['banner' => $banner]) }}" method="POST">
#method('PUT')
If you include $id in your function declaration then when you call the route helper it expects you to give it an id parameter. Try with
<form class="card-box" action="{{ route('banner.update',['id' => $id]) }}">
You should be able to retrieve the form data just fine form the $request variable. More info here.
The code below should be the error source. $banner variable then is an array but the update function accept object or id.
#php
use\App\Banner;
$banner = Banner::where('id','=',$id)->get();
#endphp
You should try to replay this code by this one...
#php
use\App\Banner;
$banner = Banner::find($id);
//you should put a dd here to view the contain of $banner if you like
#endphp
Hop it help...

mass assignment on [App\comment] in laravel

trying to add a comment in my blog , so i got this new error :
Add [body] to fillable property to allow mass assignment on [App\comment].
this is thr controller :
public function store (blog $getid)
{
comment::create([
'body' =>request('body'),
'blog_id'=> $getid->id
]);
return view('blog');
}
and this is the form :
<form method="POST" action="/blog/{{$showme->id}}/store" >
#csrf
<label> Commentaire </label> </br>
<textarea name="body" id="" cols="30" rows="2"></textarea> </br>
<button type="submit"> Ajouter commentaire</button>
</form>
web.php :
Route::post('blog/{getid}/store', 'commentcontroller#store');
To avoid filling in any given property, Laravel has mass asignment protection. The properties you want to be filled should be in the $fillable property on the model.
class Comment {
$fillable = ['body', 'blog_id'];
}
Bonus
For keeping up with standards. You should not name your classes in lower case, it should be Blog and Comment, both in PHP code and in file name.
Id should not be filled but associated, so they will be loaded properly on the model. So imagine your Comment model having the blog relationship.
class Comment {
public function blog() {
return $this->belongsTo(Blog::class);
}
}
You should assign it instead. Where you would get the Blog by using Model binding, therefor you should name the parameter $blog so the binding will work. Additionally using Request dependency injected, is also a good approach.
use Illuminate\Http\Request;
public function store(Request $request, Blog $blog) {
$comment = new Comment(['body' => $request->input('body')]);
$comment->blog()->associate($blog);
$comment->save();
}

Assign specific roles from where the user clicks

I would like to assign roles depending on which button the user clicks:
For example:
- If you click on I want to be an Advisor, redirect to the Laravel registration form and assign the role of advisor.
- If the user clicks on I want to be a Buyer, they redirect to the Laravel registration form and assign the buyer role.
But I do not know how to do it.
I have this code in my 'RegisterController':
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
//'password' => Hash::make($data['password']), //mutador en User model
'password' => $data['password'],
'surname1' => $data['surname1'],
'surname2' => $data['surname2'],
'comunidad_id' => $data['cbx_comunidad'],
'provincia_id' => $data['cbx_provincia'],
'municipio_id' => $data['cbx_municipio'],
]);
//dd(Request::url());
// $user->assignRole('Asesor');
//quiero asignar varios roles depende de el botón que clicken
return $user;
}
For now, what I have done is to add such a parameter, in the view that calls the view 'register':
href="{{ route('register','Asesor') }}"
and in the view 'register' send it by post in a hidden:
<div class="form-group">
<?php
$pos = strpos(Request::fullUrl(), '?');
$cadena = substr (Request::fullUrl() , $pos+1, strlen(Request::fullUrl()) );
?>
<input type="hidden" name="role" id="role" value="{{ $cadena }}">
</div>
Then in the controller I do this:
if ($data['role'] == 'Asesor=')
{
$user->assignRole('Asesor');
}
return $user;
But I don't know if it's the right way to act.
I think, you could work with events like this:
In your EventServiceProvider class, create an item inside your property $listen:
'App\Events\User\Created' => ['App\Listeners\User\AssignRoles'],
After that, you going to run the command:
php artisan event:generate
Now, you need to turn on this event in your User class declaring protected property $dispatchesEvents, like this:
protected $dispatchesEvents = ['created' => 'App\Events\User\Created'];
After all call create method in your User class, the created event is going to be called and run AssignRoles logic.
In your App\Events\User\Created class you need to inject User into __construct method, like this:
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
Remember to put the complete path of User class!
This is the object that is going to be filled with data coming from User::create method.
Inside your Listener AssignRoles you have the event linked with the user filled and you can get it this way:
public function handle(Created $event)
{
$event->user;
// ...
}
Inside your Listener AssignRoles you can get all Requested params in your __construct method:
private $request;
public function __construct(Illuminate\Http\Request $request)
{
$this->request = $request;
}
With requested params in your hand you can apply the logic depending on the clicked button inside handle method:
public function handle(Created $event)
{
$event->user;
// here is the best place to do all the logic about roles that is going to be attached in this user. E.g:
switch($role = $this->request->role) {
case $role == 'Asesor':
$event->user->roles()->assignRole('Asesor');
break;
case $role == 'Buyer':
$event->user->roles()->assignRole('Buyer');
break;
}
}
To send role param into Request you need to create a form with hidden element,
<input type='hidden' name='role' />
create more than one submit button to fill role hidden element
<input type='submit' value='I want to be an Advisor' onClick='setRole("Advisor")' />
<input type='submit' value='I want to be a Buyer' onClick='setRole("Buyer")' />
And, finally you need a logic to setRole js method. Good Look. ;-)
For assign Role to user.
Controller function will be like.
/* assign role */
if(is_array($request['role']))
{
foreach($request['role'] as $d)
{
$user->roles()->attach(Role::where('id',$d)->first());
}
}
return redirect()->route();

Passing variables to replace wildcard on laravel Route?

I'm middle of practice laravel , basic lesson 11th on laracast, wondering that if I create an entity from form page like below
<html> blahblah..
..
<form method="post" action="{{ Route('customModel.store') }}">
forms.. many forms..
</form>
..
</html>
When I submit this form, data will flow through the router.
Route::post('/customModel', [
'as'=>'customModel.store',
'uses'=>'CustomModelController#store
]);
The CustomModelController has its method named store and problem is here..
public function store( Request $request )
{
$CustomModel = CustomModel::create([
'name' => Request('name'),
'desc' => Request('desc')
]);
// Here is the PROBLEMMMM..
return redirect('/field/'. $CustomModel->id );
}
It feels really... mm... weird using redirect function directly and attach some variables directly to fill wildcard value.
Is there other ways to replace redirect()?
Something like do something with Route or another?
You can also use route() method of Illuminate\Routing\Redirector class as:
return redirect()->route('route_name', ['id' => $id]);

Laravel Editing post doesn't work

There are routes
Route::get('posts', 'PostsController#index');
Route::get('posts/create', 'PostsController#create');
Route::get('posts/{id}', 'PostsController#show')->name('posts.show');
Route::get('get-random-post', 'PostsController#getRandomPost');
Route::post('posts', 'PostsController#store');
Route::post('publish', 'PostsController#publish');
Route::post('unpublish', 'PostsController#unpublish');
Route::post('delete', 'PostsController#delete');
Route::post('restore', 'PostsController#restore');
Route::post('change-rating', 'PostsController#changeRating');
Route::get('dashboard/posts/{id}/edit', 'PostsController#edit');
Route::put('dashboard/posts/{id}', 'PostsController#update');
Route::get('dashboard', 'DashboardController#index');
Route::get('dashboard/posts/{id}', 'DashboardController#show')->name('dashboard.show');
Route::get('dashboard/published', 'DashboardController#published');
Route::get('dashboard/deleted', 'DashboardController#deleted');
methods in PostsController
public function edit($id)
{
$post = Post::findOrFail($id);
return view('dashboard.edit', compact('post'));
}
public function update($id, PostRequest $request)
{
$post = Post::findOrFail($id);
$post->update($request->all());
return redirect()->route('dashboard.show', ["id" => $post->id]);
}
but when I change post and click submit button, I get an error
MethodNotAllowedHttpException in RouteCollection.php line 233:
What's wrong? How to fix it?
upd
opening of the form from the view
{!! Form::model($post, ['method'=> 'PATCH', 'action' => ['PostsController#update', $post->id], 'id' => 'edit-post']) !!}
and as result I get
<form method="POST" action="http://mytestsite/dashboard/posts?6" accept-charset="UTF-8" id="edit-post"><input name="_method" type="hidden" value="PATCH"><input name="_token" type="hidden" value="aiDh4YNQfLwB20KknKb0R9LpDFNmArhka0X3kIrb">
but why this action http://mytestsite/dashboard/posts?6 ???
Try to use patch instead of put in your route for updating.
Just a small tip you can save energy and a bit of time by declaring the Model in your parameters like this:
public function update(Post $id, PostRequest $request)
and get rid of this
$post = Post::findOrFail($id);
EDIT
You can use url in your form instead of action :
'url'=> '/mytestsite/dashboard/posts/{{$post->id}}'
Based on the error message, the most probable reason is the mismatch between action and route. Maybe route requires POST method, but the action is GET. Check it.
Try to send post id in hidden input, don't use smt like that 'action' => ['PostsController#update', $post->id]
It contribute to result action url.

Resources