Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

warpig's avatar
Level 12

Bad Method Call on save()

So, whenever im trying to update something I get this message:

BadMethodCallException Method Illuminate\Database\Eloquent\Collection::update does not exist.

Im trying to update resources and here goes the main one, do I still need to request a validation? Because "request()" requires an array, this is the validation method:

    protected function validatePost() 
    {
        return request()->validate([
            'title' => 'required|max:255',
            'body'  => 'required',
            'slug'  => 'required|max:100',
            'tags' => 'exists:tags,id',
            'category_id' => 'required'
        ]);
    }

Iv'e also tried changing the resulting of the "=>" to arrays, like this:

    protected function validatePost() 
    {
        return request()->validate([
            'title' => ['required', 'max:255'],
            'body'  => ['required'],
            'slug'  => ['required', 'max:100'],
            'tags' => ['exists:tags,id'],
            'category_id' => ['required']
        ]);
    }

...but Im still getting some kind of exception. So, yes I would like to use it even if it's not required to validate, it's something I dont understand, I mean once the data went into the database and got stored, does it really matter at that point? The only way I have for the data to be validated, inside the "update" method is like this:

$post->update($this->validatePost());

Thanks, im doomed I know :-)

https://flareapp.io/share/Lm8OGkb5

0 likes
19 replies
Ap3twe's avatar

Post your update method here

1 like
warpig's avatar
Level 12

It's on the flareapp URL :-) - but here goes anyway:

    public function update(Post $slug, Request $request)
    {   
        $post = Post::find($slug);

        $post->update($this->validatePost());

        $post['user_id'] = auth()->id();

        if ($request->has('image')) {
            $fileExtension = request('image')->getClientOriginalName();
            $fileName = pathInfo($fileExtension, PATHINFO_FILENAME);
            $extension = request('image')->getClientOriginalExtension();
            $newFileName = $fileName . '_' . time() . '.' . $extension;
            $imgPath = request('image')->storeAs('public/img/post_uploads', $newFileName);

            $post->image_url = $newFileName;  
            $post->save();
        }
        
        $post->save();

        $post->tags()->attach(request('tags'));

        return redirect('posts/' . $post->slug);
    }
Ap3twe's avatar

Chnage this $post->update($this->validatePost()); t o $this->validatePost()

warpig's avatar
Level 12

Hmm by not using the update() method anymore, that went away but now the same occurs with the save()... so this is weird

Ap3twe's avatar

What did you do? Did you do what I told you?

warpig's avatar
Level 12

Yes, actually by revising from the "Laravel from Scratch" series, I saw how the validation comes first and then the rest of the logic... so this is it's current state:

    public function update(Post $slug, Request $request)
    {   
        $this->validatePost();

        $post = Post::find($slug);

        $post['user_id'] = auth()->id();

        if ($request->has('image')) {
            $fileExtension = request('image')->getClientOriginalName();
            $fileName = pathInfo($fileExtension, PATHINFO_FILENAME);
            $extension = request('image')->getClientOriginalExtension();
            $newFileName = $fileName . '_' . time() . '.' . $extension;
            $imgPath = request('image')->storeAs('public/img/post_uploads', $newFileName);

            $post->image_url = $newFileName;  
            $post->save();
        }
        
        $post->save();

        $post->tags()->attach(request('tags'));

        return redirect('posts/' . $post->slug);
    }
newbie360's avatar

you problem is about https://laravel.com/docs/7.x/routing#route-model-binding

Route::patch('something/{post:slug}', 'PostController@update')->name('update');

or set the key in your model

route:

Route::patch('something/{post}', 'PostController@update')->name('update');

model:

public function getRouteKeyName()
{
    return 'slug';
}

and then controller

    public function update(Post $post, Request $request)
    {   
        // $post = Post::find($slug);

        dd($this->validatePost());

the better way is move the rules to From Request, so you don't need $this->validatePost()

    public function update(Post $post, UpdatePostRequest $request)
    { 

and then your rules should be, otherwise, the client able submit a array

'title' => ['required', 'string', 'max:255'],
'body'  => ['required'],
'slug'  => ['required', 'string', 'max:100'],
'tags'  => ['exists:tags,id'],
warpig's avatar
Level 12

This is literally so hard to understand. I'll look into that - actually I do get my array when I try to dd($this->validatePost()); and yes, Iv'e settled the getRouteKeyName method to be slug.

newbie360's avatar

no , not that hard trust me, just

  1. create a Route
  2. create a Form Request
  3. in controller use them
public function update(Post $post, UpdatePostRequest $request)
{   
    $post->update($request->validated());
    
    // do the rest....
1 like
Ap3twe's avatar

That is how you learn, don't give up. We all have been there before and we still do till this day.

Show your route - web.php and model, and whole controller code so we can help you

1 like
warpig's avatar
Level 12

Route:

Route::resource('posts', PostsController::class)
    ->except('index', 'show')
    ->parameters(['posts' => 'slug'])
    ->middleware('auth');

Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = [
        'title',
        'body',
        'slug',
        'image_url',
        'category_id'
    ];

    protected $guarded = [];

    public function getRouteKeyName()
    {
        return 'slug';
    }

    public function author()
    {
        return $this->belongsTo('App\Models\User', 'user_id');
    }
    
    public function category()
    {
        return $this->belongsTo('App\Models\Category');
    }

    public function tags()
    {
        return $this->belongsToMany('App\Models\Tag')->withTimeStamps();
    }
    
    use HasFactory;
}

Controller:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post;
use App\Models\Tag;
use App\Models\Category;
use Illuminate\Pagination\Paginator;

class PostsController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {   
        if (request('tag')) {
        $post = Tag::where('name', request('tag'))
            ->firstOrFail()->posts;
    } else {
        $post = Post::latest()->simplePaginate(13);
        $category = Category::select('image_url')->get();
    }   
        return view('posts.index', [
            'posts' => $post
        ]);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {   
        $category = Category::all();
        
        return view('posts.create', [
            'tags' => Tag::all(),
            'category_id' => $category
        ]);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->validatePost();

        $post = new Post(request(['title', 'body', 'slug', 'image_url', 'category_id']));
    
        $fileExtension = request('image')->getClientOriginalName();
        $fileName = pathInfo($fileExtension, PATHINFO_FILENAME);
        $extension = request('image')->getClientOriginalExtension();
        $newFileName = $fileName . '_' . time() . '.' . $extension;
        $imgPath = request('image')->storeAs('public/img/post_uploads', $newFileName);
    
        $user = auth()->user();
        $post->image_url = $newFileName;
        $post->user_id = $user->id;
    
        $post->save();
        $post->tags()->attach(request('tags'));
    
        return redirect('/posts');
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show(Post $slug)
    {
        $category = Category::get();
        $post = Post::get();
        return view('posts.show', ['post' => $slug, 'category' => $category]);
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit(Post $slug)
    {   
        $post = Post::find($slug);
        $category = Category::all();

        return view('posts.edit', [
            'post' => $slug,
            'tags' => Tag::all(),
            'category_id' => $category
        ]);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Post $slug, Request $request)
    {   
        dd($this->validatePost());

        $post['user_id'] = auth()->id();

        if ($request->has('image')) {
            $fileExtension = request('image')->getClientOriginalName();
            $fileName = pathInfo($fileExtension, PATHINFO_FILENAME);
            $extension = request('image')->getClientOriginalExtension();
            $newFileName = $fileName . '_' . time() . '.' . $extension;
            $imgPath = request('image')->storeAs('public/img/post_uploads', $newFileName);

            $post->image_url = $newFileName;  
            $post->save();
        }
        
        $post->save();

        $post->tags()->attach(request('tags'));

        return redirect('posts/' . $post->slug);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $post = Post::findOrFail($id);

        $oldImage = public_path() . '/storage/img/post_uploads/' . $post->image_url;
        if (file_exists($oldImage)){
            unlink($oldImage);
        }

        $post->delete();
        return redirect('/dashboard/posts/');
    }

    protected function validatePost() 
    {
        return request()->validate([
            'title' => ['required', 'max:255'],
            'body'  => ['required'],
            'slug'  => ['required', 'max:100'],
            'tags' => ['exists:tags,id'],
            'category_id' => ['required']
        ]);
    }
}
newbie360's avatar

in Post model you have getRouteKeyName(), now route model binding will find the column slug instead id

in the route delete this line ->parameters(['posts' => 'slug'])

Route::resource('posts', PostsController::class)
    ->except('index', 'show')
    ->middleware('auth');

focus your update problem only here

controller

    public function update(Post $post, Request $request)
    {
        $post->update($this->validatePost($request));
        
        dd('check the database record is updated.');

i'm not sure you can call helper do this...so

    protected function validatePost($request) 
    {
        return $request->validate([
            'title' => ['required', 'string', 'max:255'],
            'body'  => ['required'],
            'slug'  => ['required', 'string', 'max:100'],
            'tags' => ['exists:tags,id'],
            'category_id' => ['required']
        ]);
    }

btw, controller name should be singular PostController

https://github.com/alexeymezenin/laravel-best-practices#follow-laravel-naming-conventions

1 like
warpig's avatar
Level 12

By removing ->parameters(['posts' => 'slug']) I can't see the old values on the form.

warpig's avatar
Level 12

@newbie360 the problem has been fixed but I now receive another exception that has to do with the attachment of tags.. not sure if by only changing names somewhere?

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1-1' for key 'post_tag_post_id_tag_id_unique' (SQL: insert into post_tag (created_at, post_id, tag_id, updated_at) values (2021-01-19 00:39:54, 1, 1, 2021-01-19 00:39:54))

warpig's avatar
Level 12

And the form doesnt show any of the old values, hehe not in the two for one errors :-(

newbie360's avatar

in the pivot table 1-1 is exists, and in your code use

$post->tags()->attach(request('tags'));

change to

$post->tags()->sync(request('tags'));

you may read again the doc what is difference of ->attach() and ->sync()

1 like
warpig's avatar
Level 12

I will try this, and yes I will search for more info about this. Also, thanks for that git repo.

Please or to participate in this conversation.