-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Add model property types to magic where methods. #989
base: master
Are you sure you want to change the base?
Conversation
That actually looks pretty cool! Can you please run |
Alright. I actually made the change right here on GitHub. 😉 |
I wonder why the expectations are still wrong. Could it be because I force-pushed? I can undo that force-push by force-pushing the previous version of the commit, and then committing the snapshot updates as a new commit. |
Well, force-pushing appears to break the test system. 😁 Do we need a test for the test system. 😆 |
Seems everything worked out in the end? |
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.
Now seeing some of the changes, I'm not sure the approach is entirely correct.
Basically it assumes that the "cast output" is the sole allowed input for those magic methods.
But this isn't correct, is it? No one requires passing a Carbon
class for example.
Carbon works because it has some special support or is simply converted to string (didn't check in detail) 🤔
But when I imagine other custom cast objects, it will depend on the objects capability whether they work correctly or not.
What do you think about this scenario?
tests/Console/ModelsCommand/CustomDate/__snapshots__/Test__test__1.php
Outdated
Show resolved
Hide resolved
I personally am fine with the current results. However, I understand what you're getting at. In that case, I think the property should also have any other acceptable types specified, or the where param type needs to have its own type method to determine the type based on the column/property type and any additional rules. I'll dig through Laravel's code to understand the where method and how type hinting can play a part. |
Focusing only on what's possible with dynamic wheres, the where method is called like this,
With all this being said, I think what I can do, for now (time will tell what else may need to be done), is:
Any additional thoughts on this would be appreciated. Aside: In the Rails community, dynamic finders, as they're called in Rails, have been frowned upon for quite some time, and in fact were deprecated in favor of using findBy(hash); e.g., findBy(foo: "blah", bar: "bleh"). I don't know why they deprecated dynamic finders, but I do think it makes things easier to use, and more robust to changes. There may have also been a performance reason, but that's conjecture. |
From my experience I can come up with two reasons against them:
|
@mfn, so are you against merging this in because magic-where-methods should not be used, in your opinion? I'm just wondering if I should do any more work on this PR, such as adding |
@jpickwell no, this was additional data for your last paragraph starting with "Aside: In the Rails community, dynamic finders, as they're called in Rails, have been frowned upon for quite some time" ;-) If one doesn't like them, they can already disable them via config I yet have to assess the feedback. |
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.
I tested this on a real project and noticed another problem.
Imagine you've a property like this:
* @property int|null $parent_id
Before:
* @method static Builder|Post whereParentId($value)
Your PHP generates this:
* @method static Builder|Comment whereParentId(integer $value)
So my code using whereParentId(null)
now is shown as error:
So this definitely needs a bit more refinement / testing.
@jpickwell are you interested in continuing this? |
@mfn, I can take a look into that. |
🙏 |
I refactored the setProperty method to use a new helper method getTruePropertyType which is then also used for the where methods. This fixed the missing null issue, and also does the type overrides, which was missing before. |
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.
This looks really good 🤞
There's one small feedback left and can you please also add a changelog entry under Added
?
@@ -14,8 +14,8 @@ | |||
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate newModelQuery() | |||
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate newQuery() | |||
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate query() | |||
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate whereCreatedAt($value) | |||
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate whereUpdatedAt($value) | |||
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate whereCreatedAt(\Carbon\CarbonImmutable|null $value) |
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.
I mention this in my previous feedback but it might just have gotten lost: any method accepting Carbon (or a variation thereof) also accept strings, it doesn't just accept an instance (or null).
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.
Understood, but if you look at the created_at property, it's also \Carbon\CarbonImmutable|null
, so this is not an issue with this new feature.
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.
The whereProperty
method parameter type is now exactly the same as the corresponding @property
.
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.
Understood, but if you look at the created_at property, it's also
\Carbon\CarbonImmutable|null
, so this is not an issue with this new feature.
…
ThewhereProperty
method parameter type is now exactly the same as the corresponding@property
.
I pointed this out in #989 (review)
Basically it assumes that the "cast output" is the sole allowed input for those magic methods.
However this assumption isn't correct.
Let's look at this tinker session:
$ ./artisan tinker
Psy Shell v0.10.4 (PHP 7.4.10 — cli) by Justin Hileman
>>> $user = App\Models\User;
=> App\Modes\User {#4302
}
>>> $user->deleted_at;
=> null
>>> $user->deleted_at = '2020-10-10 12:12:00';
=> "2020-10-10 12:12:00"
>>> $user->deleted_at
=> Carbon\CarbonImmutable @1602331920 {#4301
date: 2020-10-10 12:12:00.0 UTC (+00:00),
}
>>> $user->deleted_at = \Carbon\CarbonImmutable::now();
=> Carbon\CarbonImmutable @1599856846 {#4319
date: 2020-09-11 20:40:46.689963 UTC (+00:00),
}
>>> $user->deleted_at
=> Carbon\CarbonImmutable @1599856846 {#4309
date: 2020-09-11 20:40:46.0 UTC (+00:00),
}
Setting either string or Carbon is fine.
I noticed something else in a private project I tested this:
Migration:
$table->jsonb('keywords')->nullable();
Model:
* @property array|null $keywords
…
protected $casts = [
'keywords' => 'array',
];
Before:
* @method static Builder|Automation whereKeywords($value)
After:
* @method static Builder|Automation whereKeywords(string|null $value)
I think string
is practical in this context, there's not much point about this anyway as it's a JSON column.
But then I've another model with a similar column which however generated this:
Migration:
$table->json('some_other_column')->nullable();
Model:
* @property array $some_other_column
…
* @method static Builder|Attachment whereSomeOtherColumn(mixed|null $value)
…
protected $casts = [
'some_other_column' => 'array',
];
…
public function setSomeOtherColumnAttribute(?array $value): self
{
… }
public function getSomeOtherColumnAttribute(): array
{
… }
The mixed
is in fact also not that bad here, actually might make even more sense.
Though I could not figure out why one is string
here and one is mixed
. I tried removing the set/get but it didn't change 🤷♀️
Whilst I'm not yet in the clear regarding the latter new examples, I think that date casts not accepting string is bad. The laravel-ide-helper
is supposed to help but would get in the way here.
These were only easy examples with $date
and array
casts, but maybe properties in $casts
have to be handled differently?
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.
I'm not disputing the string issue with dates. However, that's an existing issue in laravel-ide-helper
. You have the same issue with setting the magic properties. I'm not going to fix this issue in this PR. That's outside the scope of the PR. If you want that fixed, then create an issue and maybe someone will create a PR to rectify the issue.
If you look at the CustomDate
snapshot, you'll see that the @property
tags and the corresponding where
@method
tags have the same value type:
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\CustomDate\Models;
use Illuminate\Database\Eloquent\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\CustomDate\Models\CustomDate
*
* @property \Carbon\CarbonImmutable|null $created_at
* @property \Carbon\CarbonImmutable|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate query()
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate whereCreatedAt(\Carbon\CarbonImmutable|null $value)
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate whereUpdatedAt(\Carbon\CarbonImmutable|null $value)
* @mixin \Eloquent
*/
class CustomDate extends Model
{
}
The type for the $value
parameter is exactly the same as the corresponding @property
type. And, when I say "exactly the same", I mean that the string is calculated using the same helper method in the command class, see the following lines in the ModelsCommand.php
file in this PR:
482
-- getting the initial type for a property487
-- passing the type to thesetProperty
method495
-- getting the "true" type for the property for thewhere
method by callinggetTruePropertyType
728
-- same method call, but for the property itself822
-- shows that no other type manipulation is performed when generating the PhpDoc@property
tags
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.
I'm not going to fix this issue in this PR. That's outside the scope of the PR. If you want that fixed, then create an issue and maybe someone will create a PR to rectify the issue.
That's a pretty strong feedback here, not sure where this is coming from.
It made me think though whether this whole feature should be optional (opt-in/out => debatable)
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.
Using the example of dates as \Carbon\CarbonImmutable
and not allowing string
s is an issue that already exists in the code base. I did not introduce this issue. If you look at the diffs for the test snapshots, you'll see that the @property
tags did not change.
My objection to your feedback is that you're asking me to fix an issue, an issue that already existed, when I'm trying to introduce a new feature. If this existing issue stood in the way of introducing this new feature, then I would fix it. However, the issue does not hinder this feature's implementation. Clearly, what this feature does do is highlight the existing issue. I agree the issue should be fixed, but I believe that it should be fixed in a new PR, one that targets the issue specifically.
I don't have any objection to adding a flag for this feature.
Just to be extra clear, to reiterate what I've been saying, the @property
tags have not been affected by my changes.
Here's what the CustomDate test snapshot looks like before my changes:
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\CustomDate\Models;
use Illuminate\Database\Eloquent\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\CustomDate\Models\CustomDate
*
* @property \Carbon\CarbonImmutable|null $created_at
* @property \Carbon\CarbonImmutable|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate query()
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|CustomDate whereUpdatedAt($value)
* @mixin \Eloquent
*/
class CustomDate extends Model
{
}
Notice that the $created_at
and $updated_at
properties have the same issue you're asking my to fix. I feel like you're ignoring this fact, and just assuming that somehow I have introduced this issue when in reality I have not.
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.
I created a new PR (#1056) to fix the type annotations for date attributes/properties.
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.
One more thing: looking at the JSON example you gave, I will try to fix that one. That has to do with the command not checking casts until after getting properties from the table:
// ModelsCommand::generateDocs
if ($hasDoctrine) {
$this->getPropertiesFromTable($model);
}
if (method_exists($model, 'getCasts')) {
$this->castPropertiesType($model);
}
castPropertiesType
should probably be called from getPropertiesFromTable
.
- Added changelog entry. - Removed unnecessary braces around interpolated variable. - Made property type assignment one line.
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.
@jpickwell awesome, thanks for your feedback!
Quite a lively discussion going in here ;)
My suggestion to finalize this would be:
- add config (please also add an entry to README for this)
- make it opt-out (aka enable it by default)
What do you think (as you said: independent of "the one issue" and the other PR)?
I'm working on fixing the cast issue at the moment. I can add a new config option with test and readme entry. Unlike the date type issue, not handling casts is a hindrance of this feature. |
No description provided.