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

Eloquent model timestamps are treated as Carbon\Carbon|null #1580

Open
magnetas opened this issue Mar 10, 2023 · 14 comments
Open

Eloquent model timestamps are treated as Carbon\Carbon|null #1580

magnetas opened this issue Mar 10, 2023 · 14 comments

Comments

@magnetas
Copy link

magnetas commented Mar 10, 2023

  • Larastan Version: 2.5.1
  • --level used: 3
  • Pull request with failing test:

Description

Larastan treats/resolves eloquent model timestamps being of Carbon\Carbon|null type instead of Illuminate\Support\Carbon.

Prepared a simple case in a fresh Laravel project. Basically my arrow function says it'll return an Illuminate\Support\Carbon instance and that's the eloquent casted created_at value. Get the following error:

Anonymous function should return Illuminate\Support\Carbon but returns Carbon\Carbon|null

If I check the actual type (i.e. dd($user->created_at)) it in fact is Illuminate\Support\Carbon.

Any clue how this can be sorted out?
Thank you!

Laravel code where the issue was found

namespace App;

use App\Models\User;
use Illuminate\Support\Carbon;

class SomeClass
{
    public function someMethod(): void
    {
        $users = User::get();

        $timestamps = $users->map(fn (User $user): Carbon => $user->created_at);
    }
}
@szepeviktor
Copy link
Collaborator

Hellooo @magnetas! 👋🏻
Did you know Carbon has built-in PHPStan extensions?

includes:
    - vendor/nesbot/carbon/extension.neon

@szepeviktor
Copy link
Collaborator

szepeviktor commented Mar 10, 2023

...as for this specific case: try returning a non-nullable value.

@magnetas
Copy link
Author

Hellooo @magnetas! 👋🏻 Did you know Carbon has built-in PHPStan extensions?

Hi @szepeviktor! First of all thank you for such a prompt reaction!

I wasn't aware of this, but unfortunately it doesn't help with my case - appended it to the includes list and keep getting the same errors.

@szepeviktor
Copy link
Collaborator

@magnetas
Copy link
Author

...as for this specific case: try returning a non-nullable value.

well...the thing is eloquent model timestamp fields are nullable (i.e. before persisting them they are null. Also DB columns behind them are nullable). Tried hinting my return as ?Carbon (nullable support one) - no luck.

@magnetas
Copy link
Author

Maybe we have a 🐞 bug?

https://github.com/nunomaduro/larastan/blob/6327eb91b4256615b0193eb62403d91fc6f2eb98/tests/Type/data/model-properties.php#L60

Yes, this seems to be related. If this assertion passes it'd mean that created_at is nullable Carbon\Carbon instance. However it isn't since Laravel has the wrapper (I guess mostly for mocking).

@magnetas
Copy link
Author

magnetas commented Mar 10, 2023

@szepeviktor could this be the problem?

https://github.com/nunomaduro/larastan/blob/6327eb91b4256615b0193eb62403d91fc6f2eb98/src/Properties/ModelCastHelper.php#L154-L156

If $dateClass is illuminate carbon then types are unified (illuminate and nestbot carbon). I get a ObjectType instance that has both className and cachedDescription set to "Carbon\Carbon" string.

@szepeviktor
Copy link
Collaborator

You can debug it in your function: \PHPStan\dumpType($user->created_at);

@magnetas
Copy link
Author

Technically Date::now() is used to set the timestamp values.
And we also have this:

https://github.com/nunomaduro/larastan/blob/6327eb91b4256615b0193eb62403d91fc6f2eb98/tests/Type/data/date-extension.php#L24

which passes as well...

@magnetas
Copy link
Author

You can debug it in your function: \PHPStan\dumpType($user->created_at);

Neat!

This is what I get:

 ------ ---------------------------------
  Line   SomeClass.php
 ------ ---------------------------------
  14     Dumped type: Carbon\Carbon|null
 ------ ---------------------------------

@szepeviktor
Copy link
Collaborator

@canvural Could you help? I'm lost.

@erikgaal
Copy link
Contributor

erikgaal commented Mar 12, 2023

@magnetas is right here. The union of \Illuminate\Support\Carbon and \Carbon\Carbon is simplified to \Carbon\Carbon, because one extends the other. When I refactored this code, the union was already there. However, thinking about this now again I'm doubting whether that is the correct behaviour?

https://github.com/nunomaduro/larastan/blob/3e96560fa98c927460e1d0a308a598bc25aaca09/src/Properties/ModelPropertyExtension.php#L151-L153

@magnetas
Copy link
Author

Just commented these three lines out in my example project, cleared phpstan cache and ran analysis again - all green.

https://github.com/nunomaduro/larastan/blob/11bdd132565d37e808e4ac985a580b492c2d8887/src/Properties/ModelCastHelper.php#L154-L156

Also dumpType now returns proper value - Illuminate\Support\Carbon|null

Not sure why the union was needed there in the first place. I think what this part of the cast helper does is it determines what type is returned when casting datetimes/timestamps and the union should not be needed.

However it may be needed when defining writable type (since \Carbon\Carbon or even string can be used to set the value.

https://github.com/nunomaduro/larastan/blob/11bdd132565d37e808e4ac985a580b492c2d8887/src/Properties/ModelPropertyExtension.php#L146

Might be missing some context here too. If that's the case then maybe the property extension should do the checks and decide whether additional union is needed...

@magnetas
Copy link
Author

Just tried applying the same (commenting out those three lines with union) to actual Laravel 10 project.

I get 18 such errors across the codebase using phpstan level 3.

Skipping the union meant no more errors.

Now I could prepare a PR if this is REALLY how it should behave, but again - I might be missing something

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

No branches or pull requests

3 participants