In this article
The Issue
I was recently working on some updates to Find A PR (an open-source Livewire application I created and maintain). As part of the updates, I added a computed property to one of the Livewire components, which caused PHPStan to throw an error I hadn't seen before.
If you're not familiar with computed properties in Livewire, they are methods that are decorated with the #[Livewire\Attributes\Computed] attribute. This allows you to define a method that can be accessed as if it were a property on the component. The benefit is that they can be used to compute values based on other properties or values, and the result is memoised for the duration of the request, so you can access it multiple times without recalculating it. A bonus is that you can also cache the value for future requests if you choose to.
To understand the issue I encountered, let's first look at the code that I added to the Livewire component:
app/Livewire/ListIssues 1declare(strict_types=1); 2 3namespace App\Livewire; 4 5use Illuminate\Support\Facades\Cache; 6use Livewire\Attributes\Computed; 7use Livewire\Component; 8 9final class ListIssues extends Component10{11 // ...12 13 #[Computed]14 public function gitHubRateLimitExceeded(): bool15 {16 return Cache::has('GitHub-Rate-Limit-Exceeded');17 }18 19 public function mount(): void20 {21 // ...22 23 if (! $this->gitHubRateLimitExceeded) {24 // ...25 }26 27 // ...28 }29 30 // ...31}
In the code example above, we can see that I've got a gitHubRateLimitExceeded method that is decorated with the #[Computed] attribute. This allows me to access it as if it were a property on the component, like so: $this->gitHubRateLimitExceeded.
Although this is completely valid code that runs without issue, the problem is that there isn't actually a gitHubRateLimitExceeded property on the component. Instead, it's accessed using magic methods by Livewire under the hood. So when I ran PHPStan after making these changes, it returned an error:
1------ --------------------------------------------------------------------------------------- 2 Line Livewire/ListIssues.php 3 ------ --------------------------------------------------------------------------------------- 4 106 Access to an undefined property App\Livewire\ListIssues::$gitHubRateLimitExceeded. 5 🪪 property.notFound 6 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property 7 at app/Livewire/ListIssues.php:106 8 ------ --------------------------------------------------------------------------------------- 9101112 [ERROR] Found 1 error
The Fix
After a quick search online, I found a package created by Caleb White called calebdw/larastan-livewire that adds support for Livewire components to Larastan. You can check out the repo at: https://github.com/calebdw/larastan-livewire.
The main issue that this package solves is that it allows PHPStan to recognise computed properties on Livewire components.
I've seen Caleb around online quite a bit, and he's always come across as a knowledgeable, helpful developer, so I trusted this package would be a good solution. It helped that the package is still maintained and has 880k downloads (at the time of writing this article). So I decided to give it a try.
I installed the calebdw/larastan-livewire package via Composer by running the following command:
1composer require calebdw/larastan-livewire --dev
I then registered the package in my phpstan.neon by adding the following:
phpstan.neon1includes:2 - ./vendor/calebdw/larastan-livewire/extension.neon
This resulted in my phpstan.neon looking like so:
phpstan.neon 1includes: 2 - ./vendor/larastan/larastan/extension.neon 3 - ./vendor/calebdw/larastan-livewire/extension.neon 4 5parameters: 6 7 paths: 8 - app 910 level: 51112 ignoreErrors:1314 excludePaths:
I then ran PHPStan again (using ./vendor/bin/phpstan) and the error was gone. Whoop whoop! Thanks to the package, PHPStan now recognises computed properties on Livewire components, so calling $this->gitHubRateLimitExceeded no longer throws an error.
I was really happy with this solution, and it was a relief not to have to start tinkering to get it working. I was able to just fix the issue and move on to my next task.
Thanks again, Caleb, for building an awesome package and sharing it with the community!
Alternative Fix
Before discovering the calebdw/larastan-livewire package, the first fix I considered was adding a @property-read annotation to the class docblock, like so:
app/Livewire/ListIssues 1declare(strict_types=1); 2 3namespace App\Livewire; 4 5use Illuminate\Support\Facades\Cache; 6use Livewire\Attributes\Computed; 7use Livewire\Component; 8 9/**10 * @property-read bool $gitHubRateLimitExceeded11 */12final class ListIssues extends Component13{14 // ...15 16 #[Computed]17 public function gitHubRateLimitExceeded(): bool18 {19 return Cache::has('GitHub-Rate-Limit-Exceeded');20 }21 22 public function mount(): void23 {24 // ...25 26 if (! $this->gitHubRateLimitExceeded) {27 // ...28 }29 30 // ...31 }32 33 // ...34}
Doing this would fix the PHPStan issue. However, I avoid docblock annotations where possible, as they can be incorrect or become outdated.
For example, if we were to remove the gitHubRateLimitExceeded method, change its return type, or rename it, we would also need to remember to update the docblock annotation. If we forget, it could confuse other developers working on the project. It's one of the reasons I also like to use data transfers objects (DTOs) and similar structures in my code because they allow me to define the structure of data in the code itself, rather than relying on docblocks.
Conclusion
Hopefully, this article has given you a quick insight into the calebdw/larastan-livewire package and how it might help you fix PHPStan issues in your Livewire components.
If you enjoyed reading this post, I'd love to hear about it. Likewise, if you have feedback to help me improve future posts, I'd love to hear it.
You might also be interested in checking out my 220+ page ebook “Battle Ready Laravel” which covers similar topics in more depth.
You might also want to check out my other 440+ page ebook, “Consuming APIs in Laravel,” which teaches you how to use Laravel to consume APIs from other services.
If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter below.
Keep on building awesome stuff! 🚀