-
-
Notifications
You must be signed in to change notification settings - Fork 436
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 rule for detecting expensive calls on a Collection #538
Conversation
I just ran this on a private project, only found one entry:
Code: return User
::select('users.*')
->join('…')
->where(…)
->orderBy('users.id', 'asc')
->get()
->first(); Kazinga! Indeed this is a technical bug! My single-hit-verdict: this has potential! |
Hi, Thanks for the idea! This sounds really interesting! I'm on board with this. Right now, I don't know much time to look at the implementation, but I can say some stuff about testing this. Yes, right now we just have the high-level integration tests. But rules should not be tested in the same way. Check out the |
Very little remaining :) https://github.styleci.io/analyses/ADe979 |
Rain of arrows! 🌧️ |
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.
Thank you for this!! Looks great. I just have small nitpicks.
I'm also fine with enabling this by default 👍
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.
Everything looks good!
Just I'm not sure about having [NoUnnecessaryCollectionCallRule]
in the error output. All the other PHPStan rules don't have anything like this. So I think we should follow the same style.
What everybody else thinks?
❤️ this PR. I think having |
The reason why I prepended When a developer only sees I think it might be a good idea to display the name of the rule in the error output for all of the rules that we add. I think that if we were to add more rules, it is quite likely that these rules will be a bit opinionated and for that reason, it is also likely that they will be configurable. Prepending the rule name just makes the life of a developer a bit easier (and we will probably have fewer developers opening issues here). I do agree with you though that having the inconsistency with PHPStan's way of formatting rule errors is not that great. |
Does an error formatter have a access to this information (phpstans options But OTOH, the vast majority of violations I had to deal with, the name of the rule didn't matter so this one, being configurable, truly stands out I guess. |
@mfn I don't think PHPStan error formatters provide that information. I also think displaying the rule name is a good idea. But there are a lot of PHPStan rules out there and as far as I can see none of them have it in the error message like that. Maybe it's PHPStan's job to display it with the errors. What about changing error message to |
@canvural I removed the prefix from the error and changed the error message to your first suggestion. I'm guessing that should be clear enough. |
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 noticed one last thing. If you can fix that it's good to go!
It was just a suggestion. But if no one objected, I guess we can go with that 😄 Thanks! |
Thank you again for this! |
Thanks for taking the time and merging it! It's much appreciated. 😄 |
I just got this on my CI complaining about a case where there's a |
@Daanra Do you have twitter? I would like to make a tweet about this, but can't find your twitter handle. :/ |
@nunomaduro Unfortunately, I do not have Twitter :( |
Description
This pull request serves as a proof of concept for a new rule I'd like to have added to Larastan in light of #326.
This rule would detect method calls such as
count()
on a collection that could have been called immediately on a query builder instance. Using the query builder to determine the result uses less memory and can be much faster.Example 1
To determine the number of users, one could query all users into a Collection and then call
count()
on that Collection:But it is much more efficient to just user the query builder to determine the count:
Example 2
To determine if a certain
$user
has a role with a particular name, we could query all names of the roles that belong to that user and then loop over the collection to find if it contains that particular name:But again, we can also do this in a single query:
More examples
There are plenty more scenario's where only a query could be used to more efficiently determine something. Below are some more examples. On the left side is the incorrect / inefficient form, on the right side is the more efficient form (only using a query).
Implementation
The implementation I provide checks all method calls on any object that is a subtype of
Illuminate\Support\Collection
. It then checks whether that collection was obtained by calling some method on a query builder instance. Depending on what method is called on the collection and what arguments are supplied to that method, the rule might produce an error. For example, if you call sum with a closure:the rule does not produce an error. But if you use the shorthand notation using a string:
It will throw an error. The rule actually checks whether the argument exists as a property on your model. The following, will not report an error:
Considerations
Overriding methods on Custom Collections
Since this implementation supports custom Collection classes, it is possible that a user has overridden some of the native collections methods with a nonintuitive implementation. The
max()
method might do something that is not possible with a regular query and Larastan will report a false positive. We could disable the rule for methods that are called on a custom collection and are also overridden, but I do not think it's worth it as this should be, as far as I know, an extremely rare scenario.Proper testing
As far as I could see, there isn't a proper way to test rules yet in this repository, is there? The current tests are not that useful as they can only check whether no error message is reported, but we should really be checking the inverse.
Here some tests that should produce an error message:
Disclaimer
I have no previous experience with Larastan, PHPStan or PHP-parser. My implementation might be really shitty. This pull request serves more as a proof of concept. You are free to throw this implementation away. As long as something like this gets added to Larastan, I'd be really happy. If you have any suggests for improvements, I'd be happy to implement them.