Jesse Luke Orange
Jesse Luke Orange

Reputation: 1999

Laravel 5.6 - Using Traits

In my project, I have duplicated functions in my Controllers as each Controller needs to have similar functionality but it feels slightly dirty to repeat code.

In both my EventController and my ArticleController I have a duplicated function called handleTags() that literally does the same thing in each model.

The code looks like this:

/**
* Handle tagging of this resource
*
* First, get the tags field and make sure it isn't empty
* Convert the string into an array and loop through the array
* Create new tags if they don't exist
*
* @param Request $request: data from the request
* @param int $id: the ID of the model instance have tags synced to
*/
public function handleTags(Request $request, $id)
{
    $event = Event::find($id);

    if ($request->has('tags')) {
        $tags = $request->get('tags');

        if (!empty($tags)) {
            // Turn a String into an array E.g. one, two
            $tagArray = array_filter(explode(", ", $tags));

            // Loop through the tag array that we just created
            foreach ($tagArray as $tag) {
                Tag::firstOrCreate([
                        'name' => ucfirst(trim($tag)),
                        'slug' => str_slug($tag)
                    ]);
            }

            // Grab the IDs for the tags in the array
            $tags = Tag::whereIn('name', $tagArray)->get()->pluck('id');
            $event->tags()->sync($tags);
        } else {
            // If there were no tags, remove them from this model instance
            $event->tags()->sync(array());
        }
    }
}

Would it be possible to move this functionality into a Trait? Something like Taggable?

Then you would call handleTags() in the relevant Controllers via the trait, in the same way Searchable gives you access to the search() method?

Upvotes: 0

Views: 943

Answers (2)

bondif
bondif

Reputation: 318

You can make a trait in app/Http/Traits/TaggableTrait.php

You just need to pass an object instead of the id, so that the function will be independent from the class type.

then your trait will be something like this :

namespace App\Http\Traits;

use App\Tag;

trait TaggableTrait
{
    /**
    * @param Request $request: data from the request
    * @param App\Article | App\Event $object: the model instance have tags synced to
    */
    public function handleTags(Request $request, $object)
    {
        if ($request->has('tags')) {
            $tags = $request->get('tags');

            if (!empty($tags)) {
                  // Turn a String into an array E.g. one, two
                  $tagArray = array_filter(explode(", ", $tags));

                  // Loop through the tag array that we just created
                  foreach ($tagArray as $tag) {
                      Tag::firstOrCreate([
                         'name' => ucfirst(trim($tag)),
                         'slug' => str_slug($tag)
                      ]);
                  }

                  // Grab the IDs for the tags in the array
                  $tags = Tag::whereIn('name', $tagArray)->get()->pluck('id');
                  $object->tags()->sync($tags);
            } else {
                // If there were no tags, remove them from this model instance
                $object->tags()->sync(array());
            }
        }
    }
}

EventController

use App\Http\Traits\TaggableTrait;

class EventController extends Controller
{
    use TaggableTrait;

    /*** ***** */

    public function update(Request $request, $id)
    {
        /** ***/

        $event = Event::findOrFail($id);
        handleTags($request, $event);

       /*** *** */
    }       
}

Upvotes: 1

Nevo
Nevo

Reputation: 241

I think that a better solution will be to make a Model trait, I will explain my self.

trait HasTags {

    public function handleTags($tags) 
    {
        $tags = array_filter(explode(", ", $tags))

        $tags = array_map(function () {
            return Tag::firstOrCreate([
                'name' => ucfirst(trim($tag)),
                'slug' => str_slug($tag)
            ]);
        }, $tags)

        $this->tags()->sync(collect($tags)->pluck('id'))
    }

    public function tags()
    {
        return $this->morphMany(Tag::class);
    }

}

Model

class Event extends Model
{
    use HasTags;
}

Controller

$event = Event::find($id);

if ($request->has('tags')) {
    $event->handleTags($request->get('tags'));
}

I Write it very quickly and without testing it but this is the general idea. you can event have more refactoring by using all the array manipulations with collections.

Upvotes: 1

Related Questions