Setting Up Kata Test0:00
Alright, next up, by request, we'll tackle the 99 bottles of beer kata. So here's the gist, we need to write a function or class method to generate every single verse for this song, from 99 bottles all the way down to no more bottles of beer on the wall. I'm going to add a test here for our Song, so maybe the class is just Song, something like that, and we'll say it gets the lyrics, I guess that's it. So let's do this, I'm going to add a directory here called stubs, and within it we'll add a lyrics.stub, where I will paste in the full lyrics to the song, as you see there. So now, yeah to get started, I could say, well here's what we expect, let's do a quick file_get_contents on the current directory, and then stubs/lyrics.stub, so that'll give us all of those lyrics.
file_get_contents on the current directory, and then stubs/lyrics.stub, so that'll give us all of those lyrics. And then we will instantiate our Song class, and let's see, write a function sing, yeah I'm not writing a general function, it'll be a class method, so we'll do that. Okay, I think we're ready, so now my assertion is that the result equals the expected. Alright, let's give it a shot. So of course it fails, the class Song does not exist. Let's add it now, it has a namespace of App, so I will go ahead and use that, and give it another run. We've changed the error, sing does not exist.
Testing Single Verse2:10
Okay, so with that in mind, why don't we build this up one step at a time. This is the end result, but before I get there, maybe I should assert that it can produce a single verse. For example, the first verse. Alright, so let's go to our lyrics, go to the top, there's verse number one, and once again we'll say expected, let's just do here docs, paste all of that in, like so. Then we'll say the actual result will be if we instantiate the Song and we get a single verse. Okay, so once again, add our assertion that the result equals expected, and we'll give it a run.
Okay, so once again, add our assertion that the result equals expected, and we'll give it a run. Of course it fails. There is no method verse. Okay, so now we have to decide is verse a helper method, or is it part of our public API, and that just depends. I don't have an answer here. I'm going to assume it's part of the API, but if it's not, then we should make this protected. Okay, so let's see.
So why don't we pass in 98? That's the corresponding verse we care about, and then we would do the same thing up here. Okay, so now it's squawking because our verse does not accept a number. So let's do that now. We'll give it another run. It's still failing because we've hardcoded 99, of course. So why don't we just swap it in, like so. So this would not be the number, but it would be number minus 1. So let's just do that outside of it. Like so.
Handling Pluralization Cases6:58
All right, let's give it a shot. Two, give it a run, and of course, it fails, because bottles should now be singular. All right. Okay, so I'm going to show you a couple ways to work on this. The first option would be, as we've done many times in this series, we just check, well, if we're dealing with two, then let's create a seam here and return something different. But I'm going to show you another way, a more legacy-like way that complicates the code that will then require us to refactor the code. Okay? So we'll say right here, we know that down here, if number minus one equals one, then
Okay? So we'll say right here, we know that down here, if number minus one equals one, then we want bottle of beer on the wall instead of bottles of beer on the wall. So let's say, how about this? If the number equals two, then we know once we decrement to one, it should be bottle. So if that's the case, we want bottle, otherwise bottles. All right, let's give it a run. Hmm, still fails. Ah, looks like I'm missing indentation. So let's add it here, run it again, and it passes.
Here's what we expected. Actually, that's not right. Did I? Oh, I'm sorry. This is always what you expect, and then here's the result. You saw this, didn't you? Is there a swap? There is. Thank you, PHPStorm. Swap it.
Thank you, PHPStorm. Swap it. Okay. Very sorry about that. We'll give it another try. So now here's what we expect, but we can see there's a number of issues here. All right. Let's get back to it. So let's see. If the number is 1, then we don't want bottles to be plural.
Implementing Full Sing Method12:52
That works. And then let's run the full suite and that works as well. Okay. So we got it to work. Clearly, you can see once you translate this to a real life project, this is horrible. And as soon as even a single change is requested, this is going to get confusing very quickly. So we're going to refactor this, but not before we get the primary test to work. So let's go back to SongTest. I will go down to the bottom and let's work on this. So we have everything we need.
I will go down to the bottom and let's work on this. So we have everything we need. We just have to implement the sing method. And of course that doesn't work. And real quick, let's swap that. Okay. Of course that fails. All right. Let's go to that sing method. And yeah, well, I know I can call a verse.
Let's go to that sing method. And yeah, well, I know I can call a verse. So for example, if we returned this, of course we're expecting the entire song, but we only returned a single verse. Okay. So you can get the idea. Like if we did this and then a new line and then another verse for 98, we now have a match for the first two verses, but not the remaining. Okay. So it sounds like we simply need a loop or a map.
Okay. So it sounds like we simply need a loop or a map. Now if I want an array with items 99 through zero and php, of course we can do range(99, 0). So what if we did this? Let's do an array_map. This will give us our current number and then we'll pass this here and return it. So we're going to create an array for every single verse in the song. And for each item in that array, I'm going to call the verse method while passing through that number.
And for each item in that array, I'm going to call the verse method while passing through that number. All right. So let's see. It's not going to work, but array does not match expected types. Oh yeah, of course. We're still returning an array here. And I think traditionally we want to return the string instead. So let's implode that, separate each verse by another new line. And is that okay?
So let's implode that, separate each verse by another new line. And is that okay? Let's have a look. Yeah. Okay. It works. All right. So we have a working and tested solution, which is great. In this next section, we'll clean up the code. All right.
Refactoring With Verses Method15:02
In this next section, we'll clean up the code. All right. So first up, let's stick with sing. So it's not overly clear here, but we're getting all of the verses. So why don't we add a method verses, and again, you have to decide is this part of your public API or is it an internal method for your class? I don't know the answer to that. I'm going to keep it all public for now. So now if I could keep it like that, I could say return all of the verses. Next though, maybe we can get individual verses.
So now if I could keep it like that, I could say return all of the verses. Next though, maybe we can get individual verses. So if I want verses 10 through 6, that should be an option. So why don't we provide 99 and 0 up here, and then we'll do start and end. Okay. Give it another run. Oh, it failed. We only did the first one. Oh, sorry. Verse.
Replacing Conditionals With Switch21:03
It's not that big of a deal. Okay. So let's scroll up. Our sing method's looking good. Our versus method is fine. Down here, we have our conditionals. We could even move that to a switch, which might be a little more appropriate. So that would take the shape of this. So we are going to change what we return based upon the number. So these conditionals maybe aren't the correct approach.
So we are going to change what we return based upon the number. So these conditionals maybe aren't the correct approach. Because I really just want to say, well, if the number specifically is 2, then do something. If the number is 1, if the number is 0. Otherwise, my default is going to be what we had here. So let's move all of this up, then take care of 2, and then 1, and then 0. Okay. So get rid of all of that. Let's have a look here. Clean things up a little bit.
