Skip to content

[5.8] Add less greedy mutator #29375

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed

[5.8] Add less greedy mutator #29375

wants to merge 1 commit into from

Conversation

browner12
Copy link
Contributor

Scenario

I have a Product that comes in multiple colors. Available colors are defined as a JSON field on the products table. I cast the colors to a Collection in the model.

class Product extends Model
{
    $casts = [
        'colors' => 'collection',
    ];
}

My CRUD forms use a <textarea> to accept a comma separated list of colors. When that data is submitted to my controller, I need to explode and then trim the colors.

class ProductController
{
    public function store(Request $request)
    {
        $product = new Product;
        $product->colors = array_map('trim', explode(',', $request->input('colors')));
        $product->save();
    }

    public function update(Request $request, Product $product)
    {
        $product->colors = array_map('trim', explode(',', $request->input('colors')));
        $product->save();
    }
}

This is okay, but we can see we're duplicating some code here. If we could move the trimming (and maybe even the exploding if you like) into the Model, we would have 1 point of transformation for this data.

We could try to add a mutator to our model:

class Product
{
    protected function setColorsAttribute($value)
    {
        $this->attributes['color'] = array_map('trim', $value);
    }
}

but due to the greediness of the mutator (https://github.com/laravel/framework/blob/5.8/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L571), we would need to reimplement the casting logic ourselves.

Proposed Solution

This PR allows us to bypass this greediness with a slightly modified mutator method.

class Product
{
    protected function setAndCastColorsAttribute($value)
    {
        return array_map('trim', $value);
    }
}

Since we are returning a value here, the Model can now continue to perform its normal casting logic after we are done with any changes we want to make to the input.

If you wanted to be so bold, you could even go a step further and accept both array and comma separated string inputs:

class Product
{
    protected function setAndCastColorsAttribute($value)
    {
        if(is_array($value)) {
            return array_map('trim', $value);
        }
        return array_map('trim', explode(',', $value));
    }
}

Definitely open to naming suggestion here. Will add tests if there's interest in the feature.

Would also be curious to know if the mutators were originally made this greedy intentionally. Didn't have any luck finding info from old commits. If it wasn't intentional, maybe we discuss making the default be not greedy?

check for the less greedy mutator, defined by `setAndCastCustomAttribute()`
@mpyw
Copy link
Contributor

mpyw commented Aug 2, 2019

It's not so bad, but it seems to be a little complicated.
Is the following implementation not enough?

class Product
{
    protected function setColorsAttribute($value)
    {
        $this->attributes['colors'] = $this->castAttribute('colors', array_map('trim', (array)$value));
    }
}

@mfn
Copy link
Contributor

mfn commented Aug 2, 2019

I agree with @mpyw , that added complexity seems unwarranted for something such simple.

@taylorotwell
Copy link
Member

I think @mpyw's solution is pretty good.

@browner12
Copy link
Contributor Author

I think it's silly to have to reimplement the casting logic in the mutator. Any thoughts as to why we return with a mutation?

@browner12 browner12 deleted the mutate-and-cast branch November 21, 2019 23:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants