-
-
Notifications
You must be signed in to change notification settings - Fork 164
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
Laravel 7 native casts #131
Conversation
Worth taking a look at this PR for this feature: |
I'll revisit this shortly :) |
Another relevant PR to to the framework: |
Thanks for this proposal @atymic, this feature would be great to have! Do you think it is possible to:
Introducing this feature may imply that a major version bump is needed for this package as custom casts are only supported in Laravel 7. Previous Laravel applications working with versions Dependencies in |
Yep, I agree it's worth a version bump for this feature. I've also added a way to do native casting (by adding a protected static property on the enum, with the native cast type) Enum ends up looking like so: final class UserType extends Enum
{
protected static $nativeType = 'integer';
const Administrator = 0;
const Moderator = 1;
const Subscriber = 2;
const SuperAdministrator = 3;
} and therefore, model can just be: class NativeCastModel extends Model
{
protected $casts = [
'user_type' => UserType::class,
];
protected $fillable = [
'user_type',
];
} |
Thanks for your work @atymic and for raising this issue in all my favourite Laravel packages (cknow/laravel-money#48) 😄 These are just my two cents and feel free to correct me, but would native casting be avoidable if the This might be part of the breaking changes in the major version bump if @BenSampo thinks it's worth it. It should be a curious edge case to have enums with const FOO = 1;
const BAR = '1'; and that case may be solved by calling |
I hate the PHP type coercion, I think it's much better to be explicit. I don't think you should ever have an enum with |
cc @spawnia who always has good thoughts :) |
How about we do native casting by a simple, overwriteable function on the enum class? /**
* Cast values before constructing an enum from them.
*
* You may need to overwrite this when using string values that are returned
* from a raw database query or a similar data source.
*
* @param mixed $value A raw value that may have any native type
* @return mixed The value cast into the type this enum expects
*/
public static function castNative($value)
{
return $value;
} Less overhead, less string-based magic. |
I agree, Enum's should be monomorphic. We should mention that explicitly as part of the docs. |
Agreed, what I meant is that my previous suggestion to default the flag to Would be great if we didn't have to specify a native cast but that would only be possible with type coercion. |
What do you guys think about introducing the following method in /**
* Retrieve the value that can be casted into Enum
*
* @param mixed $value
* @return mixed
*/
protected function getCastableValue($value)
{
// if the value exists in the enum, return it
if ($this->enumClass::hasValue($value)) {
return $value;
}
// if the value doesn't exist in the enum, return it and let it fail later
if (!$this->enumClass::hasValue($value, false)) {
return $value;
}
// return the value in the enum with the correct type
foreach ($this->enumClass::getValues() as $enumValue) {
if ($value == $enumValue) {
return $enumValue;
}
}
} So that protected function castEnum($value): ?Enum
{
$enum = $this->enumClass;
if ($value === null || $value instanceof Enum) {
return $value;
}
$value = $this->getCastableValue($value);
return $enum::getInstance($value);
} This would let us:
|
@cerbero90 so basically drop explicit casts in favor of implicit PHP style type coercion? |
@spawnia correct, the underlying logic remains the same but we implicitly provide the right type without asking the user |
That is not guaranteed to produce a correct result. Let's suppose the Enum class has two possible values, which are returned as:
In the database the colums is defined as PHP's magic coercion would make it so that every value returned from the database would result in an enum with the value
|
Good point @spawnia. I can be wrong but to me that sounds like an edge case, nonetheless I agree that we should be able to handle it regardless. What about using both solutions: protected function getCastableValue($value)
{
// optionally cast the value into the expected type
$value = $this->enumClass::castNative($value);
// if the value exists in the enum, return it
if ($this->enumClass::hasValue($value)) {
return $value;
}
// if the value doesn't exist in the enum, return it and let it fail later
if (!$this->enumClass::hasValue($value, false)) {
return $value;
}
// return the value in the enum with the correct type
foreach ($this->enumClass::getValues() as $enumValue) {
if ($value == $enumValue) {
return $enumValue;
}
}
} That would solve the use case you mentioned by overriding your public static function castNative($value)
{
return $value === 'true';
} |
@cerbero90 i like it, seems like a good compromise between convenience and offering control to the user. To be honest, while trying to poke holes into PHP's type coercion, it behaves pretty reasonable for the kind of scenarios that would be encountered when coercing strings from a database to enum values. As shown, there can be some edge cases where it will not work cleanly, but overriding |
Awesome, thanks for the input @spawnia and @cerbero90 I've pushed up that strategy and all the tests pass 🎉 |
Good stuff guys 👍 @atymic do you mind changing the required PHP version from |
Docs, Changelog and Upgrade guide are important for this PR. The upgrade guide should give concrete example of how to convert the casts, e.g.: class MyModel extends Model
{
- protected $enumCasts = [
+ protected $casts = [
'foo' => Foo::class,
]; |
I guess if we're doing a major version bump, maybe we should drop the existing casting method completely since it's replaced by custom casts? Or we can leave it (and deprecate it) |
@atymic i think we should try and have a release that contains both in order to allow incremental adoption. Then, we can remove |
Actually it needs a version bump and can not be merged to the current major version as the Castable interface is required. |
Hey guys, any updates on this feature? Let me know if you need help |
Hey, I can help if you need help. |
I'll clean this up and get it ready to merge shortly :) If you'd like to help, could you write the Thanks @shaffe-fr |
Ok, have added docs and made the required changes. Thanks @shaffe-fr but no help needed :) |
@BenSampo should be good to go for review now :) |
Co-authored-by: Benedikt Franke <[email protected]>
Co-authored-by: Benedikt Franke <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nicely done @atymic, looking forward to using this. This approach is more powerful and simpler at the same time.
I've been using for a while this with a custom composer repo, it's very smooth and clean. Only problem I got was caused by a problem in laravel that will be fixed next release. |
@BenSampo sorry to bother you mate, let me know if there's any changes you'd like :) |
All looks good. Awesome job on this @atymic and well supported by @spawnia and @cerbero90 - cheers guys. |
* feat: native eloquent casts * feat: native casts v2 * feat: improved coercion logic * fix: php version constraint * fix: update readme + code review suggestions * chore: update docs * feat: split parse/serialize into separate static methods * Update UPGRADE.md Co-authored-by: Benedikt Franke <[email protected]> * Update README.md Co-authored-by: Benedikt Franke <[email protected]> * fix: code review fixes Co-authored-by: Benedikt Franke <[email protected]>
WIP PR for implementing native eloquent casting.
Current issues:
Currently, we let laravel do native casting, then we apply the enum cast after.Ie.'6'
cast by laravel ->6
cast by us ->EnumInstance
With the new native casts, this isn't possible. I'm thinking we'll need to pass an additional attribute to the constructor to apply a native cast if required.Kind of a bit messy, but not sure if there's a better way?Fixed in the latest commit :)
Also, it seems that natively cast attributes are not used in the
toArray()
which is a breaking change, so we need to see if we can return the object.Edit: also need some way of only running this test on L7 in CI. Should be easy to do via ENV :)