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

slickness's avatar

Hashtags

I would like to save the words in a post which have a # as tags. I get the following error message:

Invalid argument supplied for foreach()

What's wrong with the code?

if($post)
        {
            $tagNames = preg_match_all('/#(\w+)/', $request->get('body'));
            $tagIds = [];
            foreach($tagNames as $tagName)
            {
                //$post->tags()->create(['name'=>$tagName]);
                //Or to take care of avoiding duplication of Tag
                //you could substitute the above line as
                $tag = Tag::firstOrCreate(['name'=>$tagName]);
                if($tag)
                {
                    $tagIds[] = $tag->id;
                }

            }
            $post->tags()->sync($tagIds);
        }
0 likes
38 replies
Snapey's avatar

What you get back from preg_match_all is a count of matches.

Its probably not the function you wanted. You just need preg_match

http://php.net/manual/en/function.preg-match.php

if($post)
        {
            preg_match('/#(\w+)/', $request->get('body'), $tagNames);

    // $tagnames contains an array of results. $tagnames[0] is all matches
            $tagIds = [];
            foreach($tagNames[0] as $tagName)
            {
                //$post->tags()->create(['name'=>$tagName]);
                //Or to take care of avoiding duplication of Tag
                //you could substitute the above line as
                $tag = Tag::firstOrCreate(['name'=>$tagName]);
                if($tag)
                {
                    $tagIds[] = $tag->id;
                }

            }
            $post->tags()->sync($tagIds);
        }

1 like
slickness's avatar

@snapey thank´s for your help. I had to change the code again. with preg_match I received the same error. It works with preg_match_all.

if($post)
        {
            preg_match_all('/#(\w+)/', $request->get('body'),$tagNames);
            // $tagnames contains an array of results. $tagnames[0] is all matches
            $tagIds = [];
            foreach($tagNames[0] as $tagName)
            {
                //$post->tags()->create(['name'=>$tagName]);
                //Or to take care of avoiding duplication of Tag
                //you could substitute the above line as
                $tag = Tag::firstOrCreate(['name'=>$tagName]);
                if($tag)
                {
                    $tagIds[] = $tag->id;
                }

            }
            $post->tags()->sync($tagIds);
        }

How can I make the tags clickable in the view? Every tag has its own view under /tags /id

Snapey's avatar

You would need to find each tag in the body and wrap them in <a href= anchor tags

Snapey's avatar

well you know how to find the tags in the body, you can use preg_replace to replace with the tag surrounded by the link tag

slickness's avatar

Sorry I do not program so long and do not understand how I have to create the function. Where do I write the code? I think in the controller. How do the tags with the url come to the same position in the body?

rawilk's avatar

You would probably write it either as an accessor on the model, or when you're rendering it. You could even do it with JavaScript, but I don't think that's as good of a way to do it.

1 like
slickness's avatar

can someone help me? I tried the code, but it does not work

$postall = $user->posts()->with('comments')->where('status', 1)->latest()->get();

$postall = $postall->map(function ($post, $key) {
    // $post->tags->pluck('name') is an array of #replaceme strings. example: ['#bill', '#joe', '#sally'] 
    // $links is an array of <a href="..">...</a>

    $links = $post->tags->map(function ($tag, $key) {
        return sprintf('<a href="/tags/%d">%s</a>', $tag->id, $tag->name);
    });
    return str_replace($post->tags->pluck('name'), $links, $post);
});

return return view('profiles.profile', compact('postall', 'followers', 'followings'));
Snapey's avatar

Try replacing each tag one by one in your map function rather than creating an array of replacements

slickness's avatar

@snapey It should work with this code. But I get the error message: Property [tags] does not exist on this collection instance.

$postall = $user->posts()->with('comments', 'tags')->where('status', 1)->latest()->get();

        foreach($postall->tags as $tag){

            $link = '<a href="'.route('tags.show',$tag->id).'">'.$tag->name.'</a>';

            str_replace($tag->name,$link,$postall->body);
        }
Snapey's avatar

because $postall is a collection of posts not a single post.

If you want just one post, use first()

if you are wanting to do this on a number of posts then you need an outer loop of posts first

$postall = $user->posts()->with('comments', 'tags')->where('status', 1)->latest()->get();

    foreach($postall as $post)
        foreach($post->tags as $tag){

            $link = '<a href="'.route('tags.show',$tag->id).'">'.$tag->name.'</a>';

            str_replace($tag->name,$link,$post->body);
        }

however I would probably create a model accessor that returns bodyWithLinks

note that you should probably make the body safe with e() before you add your links since in the view you will need to use raw blade tags which means somone could insert unsafe code in the body

slickness's avatar

@snapey I understand this is a bad practice for this feature. Can you please help me create a model accessor? I have never done that and first have to read it.

Snapey's avatar

just get it working in the controller first

slickness's avatar

@Snapey

Unfortunately it does not work. Also not with preg_replace.

foreach($postall as $post)
            foreach($post->tags as $tag){
                $link = '<a href="'.route('tags.show',$tag->id).'">'.$tag->name.'</a>';
                preg_replace('/#(\w+)/',$link ,$post->body);
            }
Snapey's avatar

Well you need to look at the post->body after calling this. Did anything get inserted? Is it just a question of a missing or misplaced quote.

Just saying 'it does not work' does not get us nearer the solution

slickness's avatar

the a href elements are not added to the body. The body is issued without a href element.

Snapey's avatar
Snapey
Best Answer
Level 122

I wrote the little test example. It works fine, wrapping each tagged word #chocolate with href to tags/chocolate

        $body = e('Marshmallow tiramisu #wafer chupa chups toffee . Tart croissant #wafer . Cake gingerbread halvah pie sesame snaps donut #danish oat cake sweet roll. Powder candy canes cake cheesecake. Lollipop pudding tiramisu . Muffin tart dragée fruitcake #chocolate cake gummi bears. Gummies fruitcake cake topping #tootsie roll gingerbread jelly-o lollipop. Marzipan pastry brownie muffin #candy.');

        preg_match_all('/#(\w+)/', $body, $tags);

        $tagList = Collect($tags[1]);

        foreach($tagList->unique() as $tag) {

            $body = str_replace('#'.$tag, '<a href="/tags/' . $tag . '">#' . $tag . '</a>',$body);

        }

        return $body;

gives

Marshmallow tiramisu <a href="/tags/wafer">#wafer</a> chupa chups &lt;script&gt; toffee . Tart croissant <a href="/tags/wafer">#wafer</a> . Cake gingerbread halvah pie sesame snaps donut <a href="/tags/danish">#danish</a> oat cake sweet roll. Powder candy canes cake cheesecake. Lollipop pudding tiramisu . Muffin tart dragée fruitcake <a href="/tags/chocolate">#chocolate</a> cake gummi bears. Gummies fruitcake cake topping <a href="/tags/tootsie">#tootsie</a> roll gingerbread jelly-o lollipop. Marzipan pastry brownie muffin <a href="/tags/candy">#candy</a>.
1 like
Snapey's avatar

I tried to add this to the bottom of the above post, but cloudflare blocked it

The tags in the body are first fetched from the body so its not looking for a separately held list of tags.

Note that the original body is wrapped in e() before the tags are added so that it can be safely sent to the browser with {!! !!}

This could be easily made into an accessor on the model so that you can just use ->taggedBody instead of getting the body and then tagging in it.

slickness's avatar

Many Thanks. I changed the code a bit and inserted it in the post model.

public function getBodyAttribute ($value){
        preg_match_all('/#(\w+)/', $value, $tags);

        $tagList = Collect($tags[1]);

        foreach($tagList->unique() as $tag) {

            $value = str_replace('#'.$tag, '<a href="/tags/' . $tag . '">#' . $tag . '</a>',$value);

        }

        return $value;
    }

But now I can not show the tagview anymore. i get the error message: Trying to get property of non-object

    public function show($name)
    {
        $tag = Tag::find($name);

        return view('tag', compact('tag'));
    }
Snapey's avatar

You have not touched the tags code. Why would this show an error

slickness's avatar

before I used the code, everything worked. The problem is, the tags were called via tags/id.

   public function show($id)
    {
        $tag = Tag::find($id);

        return view('tag', compact('tag'));
    }

But the url element uses the name tags / name. Then I changed it:

public function show($name)
    {
        $tag = Tag::find($name);

        return view('tag', compact('tag'));
    }

Now I get the error message: Trying to get property of non-object

Snapey's avatar

Ok I see.

FInd only works with the Id column

Use where ('name', $name) instead of find

slickness's avatar

i change the code to

    public function show($name)
    {
        $tag = Tag::where('name', $name);

        return view('tag', compact('tag'));
    }

and get the error message: Undefined property: Illuminate\Database\Eloquent\Builder::$name

Snapey's avatar

this is basics

$tag = Tag::where('name', $name)->get();

are we at best reply yet?

slickness's avatar

No, unfortunately not. If i use this:

    public function show($id)
    {
        $tag = Tag::find($id);

        dd($tag);

        return view('tag', compact('tag'));
    }

i become at /tags/2 this:

Tag {#303 ▼
  #fillable: array:1 [▶]
  #connection: "mysql"
  #table: null
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #withCount: []
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: false
  #attributes: array:4 [▶]
  #original: array:4 [▶]
  #changes: []
  #casts: []
  #dates: []
  #dateFormat: null
  #appends: []
  #dispatchesEvents: []
  #observables: []
  #relations: []
  #touches: []
  +timestamps: true
  #hidden: []
  #visible: []
  #guarded: array:1 [▶]
}

if i use this:

 public function show($name)
    {
        $tag = Tag::where('name', $name)->get();

        dd($tag);

        return view('tag', compact('tag'));
    }

i become at /tags/testtag this:

Collection {#299 ▼
  #items: []
}
Snapey's avatar

Its just basic Eloquent stuff

If you are not getting $name into the method then check the route

If you are not getting anything back from the query, check that you have a tag matching $name and a column called 'name'

Snapey's avatar

are we done with this one now? resolved?

slickness's avatar

No, I can not find a solution. It does work with the id but not with the name.

Snapey's avatar

Did you check the things suggested?

Next

Please or to participate in this conversation.