No Compromises

PHPStan is a great tool, but if you're not careful it might push you to write code in a way you wouldn't normally write it. How can you balance a tool's strengths with your team's style of writing code?

  • (00:00) - We really like PHPStan
  • (01:30) - Can you go too far with a tool, though?
  • (03:40) - Things to consider if you're newer to Laravel or PHP
  • (05:30) - PHPStan has helped us find bugs
  • (06:25) - Accessing route parameters in a type-safe way
  • (09:22) - Know how the tool works, to make it work for you
  • (11:13) - Return types on controller actions
  • (14:22) - Silly bit

Need help getting unstuck with a Laravel issue? We can help!

Creators & Guests

Host
Aaron Saray
Host
Joel Clermont

What is No Compromises?

Two seasoned salty programming veterans talk best practices based on years of working with Laravel SaaS teams.

Joel Clermont (00:00):
Welcome to No Compromises, a peek into the mind of two old web devs who have seen some things. This is Joel.

Aaron Saray (00:08):
And this is Aaron.
I started programming with EDIT.COM and then after... that was a old file in DOS.

Joel Clermont (00:23):
It's not a website. That is like a DOS executable, okay.

Aaron Saray (00:26):
Yeah, there is EXE and dot Com files. I started with EDIT.COM and then I went to Notepad. Not Notepad++, just Notepad. But now we have some better tools.

Joel Clermont (00:38):
Yeah, I suppose, we definitely do. I'm getting a little nostalgic for GW-BASIC right now though. But will save that for another day. Yeah, like one tool in particular, and not an editor per se but kind of in that same realm we've been talking about and really enjoying using is PHPStan. I mean, I like it. My background is not purely PHP, I've done a lot of C#, I've dabbled even in strongly typed functional languages, Elm. Aaron, remember Elm?

Aaron Saray (01:14):
Mm-hmm (affirmative).

Joel Clermont (01:15):
There is a part of me that really likes the strong typing and some of the guarantees it can give you. Now, that's a little newer to PHP world and it feels a little bolted on in some cases. But PHPStan is a really nice tool to give me some of those things I kind of miss from a language like C#. But as with all things there is a balance to be had and I know there was a project recently where we were leveling up through PHPStan. I got to a certain point and you're like, "Joel, what are you doing to this code?" Like, it was making the code very unlike we would write it.

Aaron Saray (01:56):
Very hard to follow based off all the paradigms that we've developed as a team and that we've even talked about on this podcast.

Joel Clermont (02:04):
Right. One of the nice things about Laravel is there's really convenient ways of doing things that you do all the time. Like accessing data in a named route for example. There's Larastan, which helps with a lot of the magic, if you want to call it magic, things that PHPStan alone wouldn't detect. But even still there are things that PHPStan at higher levels, I would say starting at level 5 and going up from there, that just don't gel with the way we like to write Laravel code. Kind of the debate we were having, I thought might be a fun topic for today is don't go too far, right? Like, is there a point where we just decide this level is enough? Or we don't care about that rule because the tool would essentially be dictating to us how to write the code and maybe the benefits it gives if we follow that don't outweigh the negatives of just changing how we write code and keeping code the way that we think it's most maintainable.

Aaron Saray (03:08):
Yeah. I think there's a point to be had there too where it depends on how long that you've worked in Laravel, in PHP and things like that. Whether you've learned the best practices through mistakes. I think that's something that we can say we have done is learned through mistakes, done a lot of things wrong. So that's how we've kind of developed our own way of doing stuff, "our right way". Which is not the only right way but you have that. Then I like to look at those tools like PHPStan, Larastan as an add-on. So it adds on to what I already know, it helps me catch when I don't do the perfect thing that I'm meant to do or if I've made a actual bug.
I think it's different though if you consider different audiences. If you just started maybe programming in PHP or in Laravel and you're not really familiar with all the ways that you might do something. I see a lot of people doing that, like they mix up all the different ways to maybe get the current user out of the session. You know, sometimes it's the auth facade, sometimes it's the auth global function, sometimes it's from the request. And they're all just kind of mixed up and it's hard to keep track of what's going on. Those cases, when you do something like that, PHPStan, Larastan will start saying, "I don't know who this is," or, "This could be a nullable object." When you start to see that that's kind of hinting to you that the tools lost track of where you're at so probably another developer could too. Or at least it's going to require a lot more thinking on their part than maybe getting it once and using it throughout the process.

Joel Clermont (04:47):
Yeah. I mean, that's a good point. Because skill level and experience level, I think apply. And let's be honest, if you've ever used PHPStan some of the errors are a little like computer-sciencey. Like, this type is contravariant with that type. I'm like, "What just happened?" I mean, the docs are nice and there's articles. In fact, I like how a lot of the errors now you can actually link to an article on PHPStan that tells you what's actually happening. But that could really throw somebody off if they're like, "You know what, you're new to PHP, you're new to Laravel, we're going to start you out at PHPStan level 9 and let's just see how you do." That would not be a fun time for sure. Again, I just want to make sure it's clear. I love PHPStan, it has actually caught bugs.

Aaron Saray (05:33):
Me too.

Joel Clermont (05:34):
When I was leveling up in this particular project we're thinking about, it found a couple legitimate bugs in the code. I'm like, "Wow, that was..."

Aaron Saray (05:41):
That Joel wrote and not me.

Joel Clermont (05:44):
I want to go look now but I don't remember it so I can't defend myself.

Aaron Saray (05:50):
You caught one of mine that's why I threw that out there. I was like, "Cool." You were fixing it, you're like, "Well, PHPStan found, and Larastan found this now." I was like, "But I was just complaining yesterday about Joel messing stuff up and now I have to eat a little humble pie here."

Joel Clermont (06:05):
Yeah. I guess maybe the point I'm trying to make through all of this is tool good, benefits good but don't go too far with it. Maybe one or two tangible examples would help to demonstrate this point. We like form requests, we like validation, and a lot of times a form request you might need to get something out of the route, like the user. You know, this was an API where the user ID was part of the URL.

Aaron Saray (06:39):
The client or the author, you're editing something of theirs.

Joel Clermont (06:43):
Order ID, whatever. So $this->route(�user�) or order or client. Super handy, right? Some of these rules, we only use it in one place so it'd just be kind of in-lined into the rule definition. PHPStan doesn't like that because this route I think returns string or null or object or null. I can't remember. But it's definitely not returning a user model or a client model.

Aaron Saray (07:10):
Well, it should be clarified it returns whatever. The doc @return, the PHPDoc, so string or null or whatever.

Joel Clermont (07:19):
Right. And that's stuff that's in Laravel framework so it is what it is. But I think the first thing I did, and maybe this was what even triggered this conversation, is, all right, I extracted it out of the rule definition and I captured it in a variable. And then you can add to make PHPStan happy, I could add a @var docblock right above it saying, "No, this is definitely a user." Like, we knew it was because we had middleware in place, you couldn't get to this point if it didn't exist. And you're like, "Joel, why are you doing that? Why are you making the code worse?" You know, more verbose, less readable, just kind of more robotic. I don't know if that's a good way of describing it. I was, "Well, because PHPStan didn't like it." So we had a whole debate about that. What we ended up doing, which I think was a pretty nice compromise, is we wrote our own route helper. Which was just a thin wrapper around, what maybe the five or six different named routes that we needed access to that just returned the right type. We could still inline it, it was still extremely readable. In fact, maybe it was even a little more readable. And I thought it was a good compromise.

Aaron Saray (08:32):
Well, and just to make it clear, it was because a single @var docblock... I love doing that too, I do that all the time. But this one was nested in a folder that was in a certain context and we knew that we'd always get the user. So why should you have to do that doc each time if you know that every single form request you're going to use the user, as well as maybe other stuff, then you can put that helper on there. And when that one just has a return type of user, then you don't have to do those @vars. But we don't want to go too complicated to make a helper for every single time we use something once.

Joel Clermont (09:08):
No, that's right. Yeah, please. That's a good clarification. In fact, Aaron likes the @var docblocks so much, I think you have a tattoo of that, don't you? Or no, maybe? That would be a very nerdy tattoo. All right. One other example I'll throw out there because I think this also demonstrates something to where it's... There's an alternative to changing your code for the tool and that is just knowing how the tool works and what levers it gives you to adapt it to your code base. For example, in Laravel, I think this one we were using sort of the built-in auth stuff you get, is it Breeze? I can't remember what the current name for it. But where it just scaffolds some routes for you. So that scaffolded auth uses things like the Authenticatable interface or the VerifiesEmail trait. Those things are users, they are the user model but they're not type hinted that way. PHPStan, we would be using in the password reset flow $this->user or however it comes back in and it's like, "No, that's not a user, you can't call a method on that."
In this case, we could have used a Vardoc block. That's fine, we do that. Again, we like that. But I tried a different approach here knowing how PHPStan works and you can actually create your own stubs. A stub is basically just a dummy file that has a PHP class definition in it or a trait definition. And then you can add your own type hints to it and it sort of layers it on top of the type hints in, for example, the Laravel framework Authenticatable trait. That's actually how Larastan works, it's a big series of stubs. In this case, we just wrote like two pretty simple stubs. I think each one was like 10 lines of code and that solved the problem that way too. That's another thing, is like maybe you don't have to change your code, maybe you just have to know how the tool works and you can extend it or tweak it slightly to give you the result you want.

Aaron Saray (11:14):
Right.

Joel Clermont (11:15):
We like three so I'm going to give you one more example, Aaron.

Aaron Saray (11:18):
I think a good third one to round this out would be the whole sort of back and forth we saw in the framework of the controller types. Well, not in the framework but in the project. Where the newest version of the project's skeleton had the return types added specifically to the controllers that were in there and there was some discussion about removing them and then now they're gone again. I remember that I was a pretty big fan of having them everywhere. I said, "Well, let's have return types everywhere."

Joel Clermont (11:50):
Yeah, consistency

Aaron Saray (11:50):
Yeah. I was like, "I don't understand why everyone's complaining, but okay." Then I started doing it to some of our projects and I definitely remember this too. There was like something I wrote had a return type that something happened, that it was the wrong return type. I think it was like a testing request and whatnot, one of those things. I just didn't have all the return types on that and you weren't doing any of the return types either, and you just said, "Why would we do that?"

Joel Clermont (12:18):
Yeah, and I'll explain my thinking there. Because here, again, Laravel is so flexible. There's, I don't know, seven, eight different types of objects you can return from a control action.

Aaron Saray (12:30):
There's strings or null.

Joel Clermont (12:31):
String and a PHP array, whatever. And that's really nice. Once you type hint it to something you're limited. Now you can always change it or you can do a giant union type. It could be one of these six things, which I think is also not really nice to look at. But in my mind it was almost like writing a unit test for a function within the Laravel framework. It was like we're adding types to a method that the only thing that's going to call this method is the framework. Like, when the framework is resolving the route and it's instantiating the controller and it's executing the action, it's the only thing that's calling that. We're not calling those methods ourself. To jump on that, if it was a protected method in the controller that was maybe shared by two different actions, we would put a return type on that. Like, a check that returns a bool or an array of data or something. But the actual controller actions Laravel doesn't need that type it knows how to handle it. It's certainly not a bug it's catching, it really was just noise at that point so we made the decision, anything the controller or the framework itself is using don't add a return type, you know? I think like controller actions for sure. Middleware, was that another one? I don't remember exactly where-

Aaron Saray (13:51):
Yeah, I think so. I mean, there isn't such a... Joel likes rules.

Joel Clermont (13:54):
I love rules.

Aaron Saray (13:55):
There isn't such a standard. Because for example, the rules method of the form request we return arrays on there.

Joel Clermont (14:03):
True.

Aaron Saray (14:03):
But what it was just like, what does this actually give us and what does it confirm and is it the only way of doing something? To be honest, I still didn't agree for a long time and then all of a sudden, it made sense to me. Like, "Okay, I get it." Then it was just hard to be like, "Wow, I was wrong. I don't know want to admit this."

Joel Clermont (14:20):
This might be the first time I'm hearing this, Aaron. So go on about that, I like that.

Aaron Saray (14:23):
I didn't... What?

Joel Clermont (14:31):
I talked about my kids before and some of the interesting things they say. Some of them kind make me think like, "Where did that come from?" Or, "What even made them think of that?" The other day, my son has this thing he does where he kind of comes up with weird opposites of things, I'm going to give you one example. He's like, "Dad, does the existence of Silly Putty imply that somewhere there's Serious Putty?" Just for context, this was in a car ride, Just randomly he drops this question on me. And I'm like, "What are you talking about?" But then his brother says, "Yeah, it's called C4."

Aaron Saray (15:23):
Some of the projects I'm working on I get a little stuck. I don't know what to do, so I reach out to Joel and I say, "Hey, can you help me?"

Joel Clermont (15:31):
And if you don't have a Joel, we can help you. Go to masteringlaravel.io. Need help? Get unstuck. It's a service we offer and we'd love to help you get unstuck.