Integrating PHPStan with Laravel0:00
Now that we have played with php stand and have learned how to use it, it is signed for us to integrate it with Laravel. Due to Val's magic php stand has a hard time working with Laravel by default, and that's why we're gonna use a package called L Stand that is a php extension and it bridges that gap between Laravel and php stand. So with no further ado, let's jump into the code. Okay, so we are exactly
Installing Larastan Package0:17
So with no further ado, let's jump into the code. Okay, so we are exactly where we laughed off in the last lesson. First, let's run php artisan serve to make sure everything's passing. And then let's run our test to make sure we're green. Good. Now let's jump over to the documentation and we're going to follow the install instructions. Let's copy this installer then. And now we're gonna make some adjustments to our php.ini config file.
And now we're gonna make some adjustments to our php send config file. The first one is let's have php send scan the entire abstract, not just php. Stand on php. Now let's jump into Laravel Tenancy's documentation, and we're gonna copy those two lines here, those three lines. And this is essentially telling php send to load the Tenancy extension. Let's space this here.
Choosing PHPStan Level1:07
to load the alert 10 extension. Let's space this here. And now we're good to run php artisan on our project. Let's run vendor/bin/phpstan and see what we get. Okay, we got 28 errors. That's a lot. Let's do a test. What if we passed level 1 instead? Okay, just one error. So that's a little bit easier. And here's my suggestion. If you're installing php, run php artisan on an existing project if you're getting a lot of errors.
stand in layer stand on an existing project if you're getting a lot of errors. And if you're getting a quantity of errors that makes it unrealistic for you to fix all of them. Start with a lower level, start with one or two. And as you fix those errors, you start upping your php center level. The sweet spot for most level of applications is five or six, some applications to run on nine or even max. But for most cases, the best bang
or six, some applications to run on nine or even max. But for most cases, the best bang for your buck is around level five. In our case, let's revert back to level five. 28 errors is pretty okay and we're gonna get rid of them real fast. So let's rerun a php stan. And you can see some errors are pretty weird. For example, it's saying function team should return Model seen, but returns Model, no.
For example, it's saying function team should return up model seen, but returns model, no. So if we look at helpers or PHP, there's nothing uncommon here. We have a User and then we're accessing a relation. Now you don't know what you don't know. So if you were dealing with this for the first time, maybe you wouldn't know what to do with it, but I have already dealt with it several times. So I'm gonna give you a shortcut when it comes to Laravel.
but I have already dealt with it several times. So I'm gonna give you a shortcut. When it comes to Laravel and PHP, stand and layer, stand by extension, you have to tell PHP stand a lot of things. You have to give it context, especially when it comes to relations. The problem here is that we're calling user, which returns a User instance, and then we're calling team, which is going to load the team relation.
Typing Eloquent Relationships2:48
and then we're calling team, which is going to load the team relation. But the problem is phpstand does not know what that means. So we'll have to tell phpstand, we'll have to give it some extra content. Here's what we're gonna do. Let's go to User. And in fact, let's run phpsent just on that helper file. Oops. Let's add analyze here. So let's go to User. And here, this team relation gonna add a doc block. And on this hasOne class.
And here, this Team relation gonna add a doc block. And on this has one class. If I go to it, you're gonna notice that it uses generics. So we have two types, two generic types. We have relatedModel and declaringModel. So we can do something like this. We can say this is going to return a Team. And that the declaringModel is this. If we run php stan again, we are back to green because now php stan knows what we are returning.
If we run php stand again, we are back to green because now php stand knows what we are returning. So let's jump between all of our models and just keep filling those relations. Let's go to Team and let's type this relation, this returns that belongsTo and it belongsTo a User. And this is the declaring model Invoices. This hasMany Invoices and this is the wrong type. This is the invoice type we declared on our php sent playground.
This is the invoice type we declared on our php sent playground. So nice, gotcha. Here, let's use the right one. And the declare model is this and all of our relations support generics. So for clients, same thing. This has many clients. And that's it. What else do we have? We have invoice. So let's see what types we have there rather what relations we have here. So let's do belongsTo client.
what relations we have here. So let's do belongsTo Client. This belongs to Team this. And it seems we had already the fine line items. So let's just add the declaring model here. And let's also do Payments. So now let's rerun php stand against all of our files. And we went from 28 to 17. That's a huge improvement. Let's see, what else do we have? Okay, our model Team, undefined property.
Fixing Model Type Errors4:42
Let's see, what else do we have? Okay, our model Team, undefined property. Let's go to team line 42. Oh, this is incorrect. We should be doing ownerEmail here. Call. Let's see what else we have. So an Invoice, we have a bunch of problems. So let's take a look at line 67 for example. Oh, we are returning the wrong type here. This actually returns an InvoicePayment. Let's rerun php artisan stand. Okay, 14 errors. That's better.
This actually returns an invoice payment. Let's rerun php artisan stand. Okay, 14 errors. That's better. Let's see what we have on invoice cannot call method major and float. So let's go 1, 1, 3. And this is we're calling method major. And it is thinking the total amount is a float. It does not understand the same money instance. So another gotcha. Here's what you're gonna do. Let's go to our cast method. Let's add a proper doc block here.
Let's go to our cast method. Let's add a proper doc block here. And now we can tell php exactly what this method returns, which is an associative array. Let's do this. We're gonna say status is going to be app\Models\Invoice::STATUS_DUE_DATE is going to be immutable. Daytime, we can use a string literal here. Total amount is going to be app\Casts\MoneyCast and recipients is going to be already notice.
Total amount is going to be app casts, money cast and recipients is going to be already notice that this is not an array, but it is a string. So it's the string content. It's not this, it's an actual string because this is what we return here. So let's rerun php artisan serve. And now we're down to seven errors. Let's see what else we have invoice interest simulation issued.
Let's see what else we have invoice interest simulation issued. Let's go to invoice interest simulation issued lines 32, 33 and 35. So this is 32, and this is an actual bug. So php sent, just caught an actual bug. We don't have tasks for this class, so this would go to production. Total amount, as you know, is a money object. So we have to call major here. We also had 33.
Total amount, as you know, is a money object. So we have to call major here. We also had 33. So 33 is an interest instance, so we gotta go with percentage. And total interest is also, well, it doesn't exist. So yeah, this is a method now. So totalInterest. And then we can do major. This returns a money instance and they're calling major on it. Sotp sent, actually caught a bug.
and they're calling major on it. Sotp sent, actually caught a bug. We didn't have task for this file, we're not printing it. And php sent, caught it for us ahead of time. Let's rerun php stand. And we now have four errors. InvoicePayment line 27. Let's see. Oops, that's not what I'm looking for. InvoicePayment line 27. Oh, it's saying need to return static when it actually returns null.
Oh, it's saying need to return static when it actually returns void. So let's just fix this type hint, let's rerun php stan. We are down to three errors. Let's go to Client delete. So return type void should be compatible with return type bool of the parent method. If we go to the parent method, let's just hover over it. It returns bool. So yes, we should adjust that. Let's do bool. And now we can just return parent::delete.
Let's do ball or no. And now we can just return parent::delete. Okay, now we just have a few problems on invoice like php. Let me close all of this. Let's go to invoice and let's go to line 180 5. Okay, so line 180 5 is a queryScope and queryScopes are extremely hard for php to understand, especially in this case where we're calling a different scope from within a scope context.
where we're calling a different scope from within a scope context. So overdue is a query scope and then unpaid is also a query scope. You can see it right here. You're probably gonna stumble upon some situations where it's really hard to make php stand, understand what's going on and the effort's just not worth it. And in those cases when it's either not worth it
Ignoring Unfixable Scope Errors8:16
and the effort's just not worth it. And in those cases when it's either not worth it or just impossible to make php stand understand, we can tell php sent to ignore a specific line just like you can do with for example, eslint on JavaScript. So here's what we're gonna do. We can do something like this. We can add a comment and say php stand, ignore next line. Or we can add a comment to the line
We can add a comment and say php stand, ignore next line. Or we can add a comment to the line and say, php stand ignore line. I prefer this one for this case. Let's rerun php stand. And now we only have one error line 1 91. So here's what php stand is saying. Due date, this property, which is a Carbon immutable, does not accept Carbon interface. And this is in fact, correct. We're saying right here when we cast this,
And this is in fact, correct. We're saying right here when we cast this, that it should return an instance of ImmutableDateTime, which is a CarbonImmutable. And if we look at CarbonImmutable, the interface, how to load, if we scroll down enough, you can study implements CarbonInterface. So we're trying to pass the parent to a child. We expect this to be CarbonImmutable, but we're trying to pass a Carbon
We expect this to be CarbonImmutable, but we're trying to pass a CarbonInterface, which is the parent. And this is not compatible, it's not a subtype. So let's go to create copy and let's update this with CarbonImmutable, which is actually what we want. We want this entire application to always use immutable date times. And now let's rerun php artisan and we're back to green.
to always use immutable DateTime instances. And now let's rerun php artisan serve and we're back to green. So we have given phpstan enough information to understand what's going on within your application. You can see that most of what we did was actually adding annotations to relationships and to the cast method. Just these two things alone solved most of the errors. Now just out of curiosity, let's see what it would look like if we were
Trying Level Six Strictness10:01
Now just out of curiosity, let's see what it would look like if we were running this on level six. So let's jump into php send at neon. Let's update this to level six. And before we run this on our layer of application, let's run this on our php stand playground. So let's do a php stand analyze app.php, send hb. Okay, let's jump into php center php. This will be line 85.
Okay, let's jump into php center php. This will be line 85. And notice that we don't tell all what this is returning. So at level six, we're required to type our return array or eatables in general. So we can say this is going to return in our array OFTs, let's rerun php, stand and rabine. Then let's run php stand across our entire project and we got 33 errors.
Stand across our entire project and we got 33 errors. Okay, that's a lot. Let's try to pick one and just see how we could fix them. If we look at invoice, we have a lot of errors. So let's jump into invoice and let's run php artisan against that model. Okay, so most of them are on our credito scopes, so let's look at them. Let's go at invoice. Let's see, this is our periscope.
on our credito scopes, so let's look at them. Let's go at invoice. Let's see, this is our periscope. And let's add a doc block here. We can say this is a builder and it takes a model and this model is going to be invoice itself like this. And this is also going to return that same builder. So let's run php artisan and see if we get five errors now. And we have four errors now, so we're gonna do the same thing to paid. I'm actually gonna copy this.
so we're gonna do the same thing to paid. I'm actually gonna copy this. It's the exact same thing here. And this one doesn't return anything. Actually we don't need, we don't need Craig scopes to return anything. So let's get rid of those and let's add it to this one as well. And to this one, let's rerun this line 1 64.
And to this one, let's rerun this line 1 64. Well, this is incorrect. It doesn't return anything. There should be void. Let's rerun php stand. And now that we have typed those scopes, we can go back to the one we were ignoring and could read of this. Now php send knows what unpaid is. So now let's fix this one. Line 21 we're using hasFactory, but we also have to tell our avail.
Line 21 we're using hasFactory, but we also have to tell our avail what factory is being used. And if you look here, we also have a template. So let's do the following. We're gonna do add use we're using hasFactory and the factory class is InvoiceFactory. Let's rerun this and we're back to green. So this is just an example of which things would be flagged at level six.
So this is just an example of which things would be flagged at level six. And now we should safely revert to level five. Let's run phpstan across all of our files and we should still be green. Green. And it was good for us to go to level six because now we've seen how to type as factory and we have also seen how to type our query scopes like the ones we have here. We've typed query scopes.
our query scopes like the ones we have here. We've typed query scopes. We didn't have to worry about emitting errors from php stand like we were doing with one of our kscope usages. Okay, I think that's enough for now. As you've seen, even though it was a little bit of work, we had to change a lot of files. It's actually pretty simple to tell php stand and laravel stand about what's going on from the 28 errors. We started with just fixing the relationships.
and large stand about what's going on from the 28 errors. We started with just fixing the relationships that is passing those generic types, solar and understands what's going on, fixing the cast method and also fixing the microscopes solved most of them, in fact, we only got the microscope errors on level six and just typing them also fixed other errors, including one of the errors we had surpassed at level five. So even though some things, for example, query scopes might only be flagged at a higher level.
So even though some things, for example, query scopes might only be flagged at a higher level. If you're using query scopes and you don't want to keep omitting those errors, you can still type them at a lower level and php send will understand what's going on. Besides typing relationships, query scopes has factory and also our cast attribute. Most of it was just fixing actual bugs. We had some actual bugs on the application including an
Most of it was just fixing actual bugs. We had some actual bugs on the application including an untested file, and we would not have been able to catch them ahead of time without phpstan. So phpstan already proved to be very useful. We caught some bugs before shipping any code. Okay, that was a lot in phpstan with this lesson. It makes it four lessons on phpstan exclusively. As usual, I hope you enjoyed this lesson and I hope you can use the powerhouse that is php.
I hope you enjoyed this lesson and I hope you can use the powerhouse that is php, stand on your level projects. I'll see you in the next lesson. Bye.
