Skip to content
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

feat(refactor): ModelProperty casting #1333

Merged
merged 13 commits into from Dec 20, 2022

Conversation

erikgaal
Copy link
Contributor

@erikgaal erikgaal commented Aug 15, 2022

  • Added or updated tests
  • Documented user facing changes
  • Updated CHANGELOG.md

Fixes #890

Changes

Adds better support for casting using CastsAttributes, CastsInboundAttributes and Castable. Also refactored the ModelPropertyExtension and extracted a ModelCastHelper that can convert Eloquent casts to PHPStan types.

Breaking changes

@erikgaal erikgaal changed the title Support inbound casting Refactor ModelProperty casting Sep 2, 2022
@szepeviktor
Copy link
Collaborator

@erikgaal Thank you for your contribution!

What does DTO stand for?

@erikgaal
Copy link
Contributor Author

erikgaal commented Sep 3, 2022

@erikgaal Thank you for your contribution!

What does DTO stand for?

Hi there! It stands for DataTransferObject, but we can rename it to ValueObject so it matches the Laravel documentation.

@szepeviktor
Copy link
Collaborator

Thank you!

(🚊 typo 🚊)

@canvural
Copy link
Collaborator

canvural commented Sep 4, 2022

Thank you for the work here. Just FYI I'm currently on holiday and not available to review until 20th September.

@erikgaal
Copy link
Contributor Author

@canvural Hope you had a great holiday! Would you be available to review this PR?

Copy link
Collaborator

@canvural canvural left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I added some comments.

Please also add tests for the cast types that do not have any tests. Like immutable_date

You can do it by adding adding the casts to any model in the Application folder, and then add a new file to https://github.com/nunomaduro/larastan/blob/4bb0df1aee0d0b8485855f068693688c6f7ae7db/tests/Type/GeneralTypeTest.php and test with assertType

$attributeType = match ($cast) {
'int', 'integer' => new IntegerType(),
'real', 'float', 'double' => new FloatType(),
'decimal' => new AccessoryNumericStringType(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessory types cannot be used standalone. You need to do TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()) here

src/Properties/ModelCastHelper.php Show resolved Hide resolved
return true;
private function migrationsLoaded(): bool
{
return ! empty($this->tables);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use count instead of empty

Comment on lines 98 to 102
if (! (new ObjectType(Attribute::class))->isSuperTypeOf($returnType)->yes()) {
return false;
}

return true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can simplify it to return (new ObjectType(Attribute::class))->isSuperTypeOf($returnType)->yes()

src/Properties/ModelCastHelper.php Show resolved Hide resolved
'string' => new StringType(),
'bool', 'boolean' => TypeCombinator::union(new BooleanType(), new ConstantIntegerType(0), new ConstantIntegerType(1)),
'object' => new ObjectType('stdClass'),
'array', 'json' => new ArrayType(new MixedType(), new MixedType()),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do better than mixed for the array key here. Try new BenevolentUnionType([new IntegerType(), new StringType()])

{
$propertyNameStudlyCase = Str::studly($propertyName);

if ($classReflection->hasNativeMethod("get{$propertyNameStudlyCase}Attribute")) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use sprintf here instead.

Comment on lines 140 to 142
declaringClass: $classReflection,
readableType: $this->stringResolver->resolve($modelInstance->getKeyType()),
writableType: $this->stringResolver->resolve($modelInstance->getKeyType()),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use named arguments.

Comment on lines 155 to 160
cast: $cast,
originalType: $this->stringResolver->resolve($column->readableType),
);
$writeableType = $this->modelCastHelper->getWriteableType(
cast: $cast,
originalType: $this->stringResolver->resolve($column->writeableType),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use named arguments.

Comment on lines 173 to 172
declaringClass: $classReflection,
readableType: $readableType,
writableType: $writeableType,
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use named arguments.

@erikgaal
Copy link
Contributor Author

Hi @canvural!

Sorry for taking so long to continue, but I've implemented most of your comments. Please also advise what to do with immutable_datetime.

Kind regards,
Erik

@canvural canvural changed the title Refactor ModelProperty casting feat(refactor): ModelProperty casting Dec 20, 2022
@canvural canvural merged commit e008b92 into larastan:master Dec 20, 2022
@canvural
Copy link
Collaborator

Thank you!

@erikgaal erikgaal deleted the patch-casts-attributes branch December 20, 2022 13:44
@erikgaal
Copy link
Contributor Author

I was just thinking back about this PR and that I noted no breaking changes. However, that's not entirely true. If developers were relying on the old behaviour that typed CastsAttributes as mixed or the underlying primitive, that might be breaking as it will now resolve the correct type.

@canvural
Copy link
Collaborator

@erikgaal That's fine. More precise types leading to errors is acceptable. It's also in PHPStan's BC promise page: https://phpstan.org/user-guide/backward-compatibility-promise#type-inference-capabilities

Just FYI: this PR discovered an issue. https://github.com/nunomaduro/larastan/blob/master/src/Properties/SchemaAggregator.php#L368 this now leads to a object with enum as class name. No idea how it was working before 😄 Could be also a PHPStan change. I'll try to fix it.

@canvural
Copy link
Collaborator

Ah it didn't discover an error. It broke it 😅 In the latest release enum database column is correctly inferred as union of possible options. But after this PR it is object with enum class name.

@canvural
Copy link
Collaborator

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.

Missing support for inbound casting
3 participants