WillKoz
WillKoz

Reputation: 255

Laravel Model Accessor and Mutator with wildcard (*)

The issue

I would like to set accessors and mutators for one model attribute but with wildcards I have attribute stored for different languages called tags_en, tags_es, Tags_fr, etc.. for (8) language tags

I would like to set one accessor and one mutator in the Tag model for all of the 8 language sub-types once instead of using couple of accessor/mutator method for each of them.

Normally one would do the following for each of them e.g. tags_en

public function getTagsEnAttribute($tags){
    return join(', ', json_decode($tags));
}

public function setTagsEnAttribute($tags){
    $this->attributes['tags_en'] =
        json_encode(
            array_map('trim', explode(',', $tags))
        );
}

Then I have to repeat them for each language variety of tags which are at the moment 8 languages which is not practical.

My Objective

Any way to do accessors and mutators with a wildcard something like (which of course do not work this way):

public function getTagsWildcardAttribute($tags){
    return join(', ', json_decode($tags));
}

or

something like:

foreach ($tagsAttributes as $TagAttribute){
    //method building code for each tags language
}

Similar Laravel idea exists for Validation with wildcard

I assume that there may be a way to do it through laravel Model class using wildcard. This is similar to Validator where you can validate each element of an array like this:

$validator = Validator::make($request->all(), [ 
    'person.*.email' => 'email|unique:users', 
    'person.*.first_name' => 'required_with:person.*.last_name', 
]);

Questions:

  1. If these model accessors and mutators with wild card are not available in current laravel framework - do you agree with me that this is worthwhile to add in the next laravel versions?
  2. If so, then how to ask laravel makers, officially, to consider this in the upcoming versions?
  3. Can Stackoverflow staff to ask laravel builders for that improvement (if they see it is) in our behalf?

Upvotes: 0

Views: 2030

Answers (3)

topclaudy
topclaudy

Reputation: 819

There is a solution for that, Eloquent Mutators https://github.com/topclaudy/eloquent-mutators

After installing the package, you would be able to register custom accessors/mutators extensions for your Eloquent models.

Upvotes: 0

tptcat
tptcat

Reputation: 3961

It's a bit unclear to me as to how your Database is setup, but from what I understand, it has a table with columns named tags_en, tags_fr, tags_de, etc.

Here's an idea:

  1. Add a property that holds the columns you want to set/get
  2. Create a custom type in $casts
  3. Override the boot() method and use it to set the tagson creating and updating
  4. Override the castAttribute($key, $value) method and use it to cast the value back when you retrieve it (this will function like an accessor)

It would look something like this

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class MyModel extends Model
{
    protected static $tags = [
        'tags_en',
        'tags_fr',
        'tags_de',
    ];

    public static function boot()
    {
        static::creating(function ($model) {
            foreach (static::$tags as $tag) {
                // You can customize exactly what you need to do here
                $model->$tag = json_encode($model->tag);
            }

            unset($model->tag);
        });

        static::updating(function ($model) {
            foreach (static::$tags as $tag) {
                // You can customize exactly what you need to do here
                $model->$tag = json_encode($model->tag);
            }

            unset($model->tag);
        });

        parent::boot();
    }

    /**
     * Cast an attribute to a native PHP type.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return mixed
     */
    protected function castAttribute($key, $value)
    {
        if (in_array($key, static::$tags)) {
            return join(', ', (array) json_decode($value));
        }

        parent::castAttribute($key, $value);
    }
}

When you create an instance of the model:

$t = App\MyModel::create([
    'name' => 'My Tags',
    'tag' => ['key' => 'value', 'key2' => 'value2']
]);

Then when you retrieve an instance:

$t = App\MyModel::find($id);
echo $t->tags_en; // This will be handled by the `castAttribute($key, $value)` override

Regarding your last 3 questions: If you think it should be included, then the best thing is to a) ask via the GitHub repo or even better b) code it up and put in a PR. Stack Overflow staff has nothing to do with Laravel so they can't and won't and wouldn't be able to do anything.

Upvotes: 1

John Mellor
John Mellor

Reputation: 2503

You could extend castAttribute() and setAttribute() methods.

protected function castAttribute($key, $value) {
        if (is_null($value)) {
            return $value;
        }

        if ($this->isTagCastable($key)) {
            return join(', ', json_decode($value));
        }

        return parent::castAttribute($key, $value);
    }

public function setAttribute($key, $value)
{
    if ($this->isTagCastable($key)) {
        $this->attributes[$key] =
            json_encode(
                array_map('trim', explode(',', $value))
            );
    }

    return parent::setAttribute($key, $value);
}

protected function isTagCastable($key) {
    return preg_match('/tags_[a-z]{2}/', $key);
}

Upvotes: 1

Related Questions