Skip to content

[5.5] Added custom attribute types #21419

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.5] Added custom attribute types #21419

wants to merge 1 commit into from

Conversation

RoccoHoward
Copy link
Contributor

For the ability to create custom cast mutators.

In 5.5, a lot of model functionality was transferred to concerns (traits) removing the ability to override various methods in other traits.

This feature provides the ability to override existing casts built-in to Laravel, or define your own using similar naming convention used elsewhere in Laravel.

For example, we have a UUID attribute that is stored as binary in the database. When we use this value though, we need it cast as a string. Currently we would have do this with a defined mutator on each model.

This update would allow us to define a mutator in a trait that we apply to models that have this field. We would create a castUuidValue method which takes a value, mutates, and returns the value. In the UUID case we would convert the binary value into a string.

In my personal cases, I use packages for both UUID and JSON cast types which no longer work in 5.5 due to the method collision from the new traits.

@sisve
Copy link
Contributor

sisve commented Sep 27, 2017

How is the reverse casting handled, when the values (the string) needs to be written back to the database (the binary array)?

@RoccoHoward
Copy link
Contributor Author

Thanks @sisve - good point. I'll add that in to.

@taylorotwell
Copy link
Member

See @sisve's comment. Not fleshed out and is actually very complicated to do in a proper way IMO.

@linaspasv
Copy link
Contributor

@therocis @taylorotwell maybe it should be done in a very similar style like we have custom validation rules in Laravel 5.5 already? In this case we would need to introduce Cast interface with fromDatabase and toDatabase methods.

This is on how the model could look like:

use App\Casts\MoneyCast;

class Order extends Model
{
    protected $casts = [
         'money' => new MoneyCast
    ];
}

This is on how the custom cast definition could look like:

use Illuminate\Database\Eloquent\Cast;

class MoneyCast implements Cast
{
    public function fromDatabase($value)
    {
         // should return casted value (string, object, etc..)
    }

    public function toDatabase($object)
    {
         // should reverse-transform value / object back to the string that needs to be stored in the database
    }
}

This would defintely introduce a lot of flexibility and re-use across different models too.

@sisve
Copy link
Contributor

sisve commented Sep 28, 2017

There has been some development/research into this area earlier; look into #18229 and if you want to continue on that approach.

@rasmuscnielsen
Copy link
Contributor

@sisve @taylorotwell Another possible implementation that could work is having a CastingManager in the same way the Notification system has a ChannelManager.

Something like

Casting::driver('array') - each driver then having the above fromDatabase() and toDatabase() method.

This allows package developers to hook into the casting and extend with their own by Casting::extend('money', ...). All the existing eloquent casts could be extracted to drivers as well.


A lot of the previous discussions on this feature in #13706 has been around mutability and caching which divides people a lot. People then argue to just use getters and setters leaving the models to have 2*n functions with duplicated logic.
Keeping this feature to basically just be a wrapper around get-attribute and set-attribute might ease the divide.
Instead of having mutability as discussed in previous PR you'd just have to be explicit (which also works for immutable value objects in turn)

$product->price = $product->price->add(100, 'USD');

Finally the object itself are not aware of Eloquent which has been another pain point discussed. There are no more coupling to your model and 'risk' of referencing relations than what would otherwise have been going on in the getter and setter.

Wouldn't this be a treat for 5.6 ? :-)

@RoccoHoward
Copy link
Contributor Author

For those that may be interested, I've developed https://github.com/hnhdigital-os/laravel-model-schema which provides the mechanism for custom attribute types.

The package does a bit more, such as grouping everything into a protected schema property and adding the ability to set per attribute, an auth checking method (per attribute, or a callable method to check). The way it's implemented, it continued to provide the properties that are expected.

I have 73% tested at time of writing and hope to get to 100% shortly (only L5.5, but will play with 5.6 shortly).

@rasmuscnielsen Your example was actually my first test of the custom type. I've implemented a Money\Money casting. I'll be implementing a JSON upgrade to do a similar thing.

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.

5 participants