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

olimorris's avatar

Eloquent attach() method for multiple inserts into a pivot table

So following on the excellent Laravel 5 Fundamentals series here at Laracasts. I have been inspired to add tags to my many jobs which I post up on my website. However, I let the user define what tags they would like rather than me prescribe a set list, so I quite neatly use Eloquent's firstorcreate method.

My issue is that when I want to link my jobs up to my tags in the 'job_tag' pivot table, I cannot seem to multiply insert rows into the pivot table. For example a user might add a description a job and then add multiple tags. Eloquent nicely creates the jobs and the tags which need creating but only inserts one row into the pivot table.

My code is below and would super value people's views on how this may work. I have not found any answer to this in any documentation or on stack overflow so would be nice to have a documented solved answer for the community to see.

My controller:

public function store(CreateJobRequest $request, JobContract $repository)
    {
        try
        {
            $repository->createJob($request->except('tags'));
            $repository->createTags($request->input('tags'));
            $repository->save();
        }
        catch (FailedJobCreateException $e)
        {
            return Redirect::back()
                ->withErrors($e->getErrors())
                ->withInput($request->all());
        }

        Flash::overlay(Lang::get('rocketcandy.success-job-create'));

        return Redirect::route('home');
    }

My EloquentJobRepository:

function createJob(array $data)
    {
        $data['user_id'] = Auth::id();

        if (! $job = $this->job->fill($data))
            throw new FailedJobCreateException;

        // Probably need to queue this
        return true;
    }

function createTags($data)
    {
        $tags = $this->splitTags($this->cleanTags($data));

        for ($i = 0; $i < count($tags); $i ++)
        {
            if (! $tag = $this->tag->firstOrCreate(array('name' => $tags[$i])))
                throw new FailedJobCreateException;

            $this->tagIds[] = $tag->id;
        }

        return true;
    }

public function save()
    {
        $this->job->save();

        foreach ($this->tagIds as $tagId)
        {
            if (! $this->job->tags()->attach($tagId))
                throw new FailedJobCreateException;
        }

        return true;
    }

Tags are added in the following manner 'tag1, tag2, tag3' and are then trimmed and exploded in PHP.

0 likes
8 replies
willvincent's avatar
Level 54

Pass an array.

Instead of this

foreach ($this->tagIds as $tagId) {
  if (! $this->job->tags()->attach($tagId))
    throw new FailedJobCreateException;
}

Do this

if (! $this->job->tags()->attach($this->tagIds)) {
  throw new FailedJobCreateException;
}
4 likes
olimorris's avatar

So that's really interesting because it inserts everything into the database just fine once I pass an array. However it throws the FailedJobCreateException regardless.

1 like
JarekTkaczyk's avatar

@blackbird sync on the other hand always evaluates to true, since it returns non-empty array ;)

@olimorris attach doesn't return any value, so it evaluates to false always. Also, your repository doesn't look good.

You should rather wrap the code in a transaction, otherwise in case the exception is thrown in the middle of the method, something is already saved, but something else is not..

Use something like this:

// Eloquent Repository
public function createJobWithTags($data)
{
    $tags = $this->splitTags($this->cleanTags( array_get($data, 'tags') ));
    $tags = array_map(function ($tag) {
        return ['name' => $tag];
    }, $tags);

    $connection = $this->job->getConnection();

    $connection->transaction(function () use ($data, $tags) {
        $job = $this->createJob($data);

        foreach ($tags as $tag)
        {
            $job->tags()->firstOrCreate($tag);
        }
    });
}
1 like
willvincent's avatar

@JarekTkaczyk Interesting. Not so according to the docs:

Both attach and detach also take arrays of IDs as input:

$user = User::find(1);

$user->roles()->detach([1, 2, 3]);

$user->roles()->attach([1 => ['attribute1' => 'value1'], 2, 3]);    
2 likes
olimorris's avatar

@JarekTkaczyk thanks for the advice regarding transactions. A much more logical and error proof way of inserting into the database.

ehsaneha's avatar

you should convert your string to array when trying to attach multiple objects

$tags= array_map('intval', explode(',', $request['tags'])); 
$post->attach($tags); 

Please or to participate in this conversation.