تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Project overview and tests0:00

Today, if you'd like to come along, I'm working on this old repository of mine that will convert PHP data types and values into their JavaScript form. So it's kind of an easy way to quickly pass PHP data to your JavaScript. The only thing is, I originally wrote this about four years ago, and I've only done light updates since then. I've merged a few PRs and things of that nature. But anyways, I'd like to do some refactoring of this codebase together. So I have no huge game plan, we're just going to see what we can refactor. Okay, so let's take a look. Let's do a quick 30-second overview of the project. First, it looks like we have a test for the main transformer itself. So for example, if we call a put method and we give it foo bar, then it should spit out the JavaScript syntax for defining foo on the window object. And if we give this a run, I think I have vendor/bin,

if we call a put method and we give it foo bar, then it should spit out the JavaScript syntax for defining foo on the window object. And if we give this a run, I think I have vendor, bin, php spec, run, yeah, everything's at green. So it does give us some flexibility to start refactoring. Now, the way it will work in your codebase is you would say something like, JavaScript, and I want to put foo as bar. And that's going to then, once again, make this foo variable available to you. So JavaScript is a facade. And here's the facade accessor. And if we look into my service provider, we're binding that accessor to an underlying class called transformer. So here is the transformer class. And this is ultimately what we are going to be refactoring. So let's just go through it together. So we have transformer put. Here, we can probably normalize this in a bit. It's just conforming and normalizing your

Cleaning Transformer methods1:24

what we are going to be refactoring. So let's just go through it together. So we have transformer put. Here, we can probably normalize this in a bit. It's just conforming and normalizing your variable. But we can see here, first, we have to translate the variables to something JavaScript friendly. But notice the comment there and the method name, buildJavaScript. It's basically redundant, right? So let's get rid of that. Next, and then we'll actually bind those variables to the view, viewBinder. It's redundant. Let's get rid of that junk. Okay, that looks good. So next, build the JavaScript syntax. So what happens here is, remember, I haven't worked on this codebase in years. So I'm having to relearn it myself. So it makes for a good lesson because we can learn it together. So build the JavaScript syntax. So translate the array of PHP variables to the expected JavaScript syntax. How about to a JavaScript syntax? Now, what do we have here?

object so it's available to you. Okay, so that looks okay. Build namespace declaration, maybe something like construct namespace, and do the refactor. Okay, are we still at green here? Yeah. Alright, next, we have our namespace. So like window.app exists. Next, we filter through our variables. So yeah, remember, the variables could be something like this foo bar, and we expect a foo variable to be equal to bar. And if we take a look at the tests, let's look at this. Yeah, so if you give it foo bar, this is what should be constructed in your JavaScript. But if we scroll down, yeah, so here, if our variable is letters, and that's equal to an array, then ultimately, this is the JavaScript that we wanted to construct, or it handles Booleans and objects, all of that stuff. Okay, so if we come back, I keep losing my space. So we're going to filter through each of the variables as key. Well, that's not really the key, it's the variable name.

all of that stuff. Okay, so if we come back, I keep losing my space. So we're going to filter through each of the variables as key. Well, that's not really the key, it's the variable name. So let's update that. Next, we're going to build the variable initialization. Okay, so we're basically saying here, like window.app.foo equals, and then here, we have to take the php value and convert it for JavaScript. So this is ultimately what we would want. So let's see, optimize value for JavaScript. For every transformable type, let's see if it needs to be transformed. Okay, so here's what it looks like it's doing. We have these different types, and these are the types that can be transformed. So how do you transform a PHP array to something suitable for JavaScript? So we can see, we filter through all of those types, and we call a method, and each method has the opportunity to respond to it. So this is a common, pretty cool design.

Refactoring into transformer classes5:18

we have nothing to do here. Otherwise, if it's something simple that we can encode, like a STDClass or JSON serializable, then we will do that. Otherwise, if it's a class with a toJSON method on it, like your Eloquent classes, then we can take care of that because it will automatically be done. So let's clean this up. If a toJSON method exists, the object can cast itself automatically. Otherwise, if the object doesn't even have a toString method, we can't proceed. Okay, so yeah, in situations where we don't know how to convert this object for JavaScript, then we're going to throw an exception. Okay, so here's what I'm seeing right now. All of these methods have the prefix transform. And actually, if you attended my Laracon 2017 presentation, this is one of the tips I offered. I said, seek out or learn to pinpoint repeated prefixes like a hawk. And the idea is when you find that over and over in a class, many times

presentation, this is one of the tips I offered. I said, seek out or learn to pinpoint repeated prefixes like a hawk. And the idea is when you find that over and over in a class, many times there's a refactor begging to be performed. And the technique would be the prefix then becomes a method on a class. And the class name would be whatever comes after that. So in this case, you'd have a NumericTransformer class and then a method called transform. And then for this one, you might have a BooleanTransformer class and then a transform method on it. All right, so why don't we perform that refactor? Because right now we're at green, and I think this is a good use case. We have all these transformers. Yeah, it might be simpler to give them their own files. Okay, so here's where we set that up. We filter through the different string types, and then we construct a method name like transformString. But now we're going to

Okay, so here's where we set that up. We filter through the different string types, and then we construct a method name like transformString. But now we're going to construct a class. So let's do this really quickly. Like $class equals new Transformer(), and then $class->transform. We'll refactor this in a second. But yeah, that's kind of what we want to do. And then that would accept the JavaScript. So let's see. For each of these types as $transformer. So yeah, now it looks like we're not dealing with types, we're dealing with Transformers. So like this would then become a StringTransformer, and this would be an ArrayTransformer class, right? So I'm going to use screencast magic to populate these as well. There we go. So now we have these new Transformer classes. None of them exist yet, but we're writing the code that we would ultimately like. And now this shouldn't be

There we go. So now we have these new transformer classes. None of them exist yet, but we're writing the code that we would ultimately like. And now this shouldn't be called types. This is our array of transformers. So I'm going to do a refactor toward that. Okay, so now if we go back, we construct our transformer. So like new BooleanTransformer for each of those. And then we call a transform method on it. So what we could do now is just say, well, JavaScript is going to be construct that class and then call transform on it. And it looks like we do need to give it the value, right? So if the value is bar, yeah, we're going to pass that through. It can transform it if necessary, and then return the value as the JavaScript variable. So now let's run our tests. Of course, everything's going to blow up, right? Because these don't even exist yet. All right, that's our next step. So right here in our source directory,

Implementing transformer classes8:31

So now let's run our tests. Of course, everything's going to blow up, right? Because these don't even exist yet. All right, that's our next step. So right here in our source directory, I'm going to create a new directory called transformers. And then within there is where we would have each of these. So like StringTransformer. And we'll have to give this the namespace. What are we using here? All right. Lericast, Utilities, JavaScript, Transformers. Okay, so it looks like we can have an interface if we want, but I don't think it's necessary here. Because I'm not going to be exposing that interface to anybody else is going to be adding their own transformers. So I don't see any huge value to creating one. Okay, so transform the value. Yeah, okay. So we have String. Once again, we'd have another one called ArrayTransformer. Paste that in. And then once again, let's use screencast magic to populate.

Okay, so transform the value. Yeah, okay. So we have string. Once again, we'd have another one called ArrayTransformer. Paste that in. And then once again, let's use screencast magic to populate the rest of those. Okay, and that's done. All right, so now we have transformers for every single different type. And if we switch back to the Transformer class, yeah, these all refer to those. So let's do this. Let's import all of these now. But remember, if we run this, it's still going to blow up. Yeah, so we can see the classes do exist, but the return values just simply aren't working. Okay, so we can do these one at a time. I can say transformString and then just move that over. So go to StringTransformer. Paste that in. And then it looks like we need an escape method there. So let's see what this does. Okay, so is this used anywhere else in the project? Nope. It looks like it's exclusive to string. Okay, that's good. So that means I can take

there. So let's see what this does. Okay, so is this used anywhere else in the project? Nope. It looks like it's exclusive to string. Okay, that's good. So that means I can take that entire method and bring it down here, which means this can now leave entirely. And let's go back to transformString. And now, short of stealing the doc block, yeah, if I come back, this can now leave. So let's run that again. So 7 passed and 6 failed. But yeah, I bet before we made this change, whoops, yeah, 4 passed and 9 failed. So we're making good progress. Okay, so let's go back. What's the next one? transformArray. So we're going to take that, move it down here, paste it in. Once again, grab the doc block to save myself some time. And let's come back, run it again. Now 8 passed and 5 failed. Okay, so you can see the basic format here. So once again, it's the exact same process over and over. So I will fast forward

some time. And let's come back, run it again. Now 8 passed and 5 failed. Okay, so you can see the basic format here. So once again, it's the exact same process over and over. So I will fast forward and extract each of these. All right, so now we can see that the Transformer class is much simpler. In fact, it's 140 lines total, because now all of those transformer methods have been deleted, and instead they've been upgraded to their own classes. And each of those classes is ridiculously simple. We just have an interface of transform, and each of those implements it. So now, because they all implement that interface, our Transformer class can simply call it, with the understanding that each of those can respond to that message and return whatever optimized JavaScript it needs to. Okay, so next, this comment, let's see if it needs to be...yeah, that's redundant. We don't need that anymore. Okay, so we transform the value, and if one of these returns something that's not null,

Moving Transformer into directory11:51

Okay, so next, this Comment, let's see if it needs to be...yeah, that's redundant. We don't need that anymore. Okay, so we transform the value, and if one of these returns something that's not null, if it returned a value, then it was the proper transformer type. So we just return from that early. Otherwise, it continues over the foreach. So let's run the test. We're still at green. So this is looking good to me. Let's go over this again. Looks like I can do a refactor here. Now the next thing I'm seeing is we're referencing a lot of classes within the transformers directory. And sometimes when you find yourself doing this, it might mean that the class you're currently in should be in that same directory. So let's see what that might look like. Let's move that into transformers, like that. And now this will need to have an updated name. And you'll see once we do that, all of these are no longer necessary, because you're in the same directory. So I can refactor

like that. And now this will need to have an updated name. And you'll see once we do that, all of these are no longer necessary, because you're in the same directory. So I can refactor that entirely. Okay, but I bet things blew up. Yeah, we're gonna have to update the path. So let's see utilities, JavaScript, transformers. And then Oh, yeah, I think I have to put this in a matching directory. Is that the way php spec works? It's been a while since I've used it. I do like it, though. Alright, so let's update that. And let's try fatal error argument one passed to the transformer must be an instance of viewBinder, but it wasn't. Okay. So we are looking for viewBinder. Did we just not import that? Yeah, maybe run it almost there, but it failed on this one. Okay, let's exit out. So where is it exception? Okay, here it is. Yeah. So what are we doing here? JavaScript transformers, and then import that. Let's run that one again. And yeah,

Simplifying put() and naming14:19

projects if you're if you're hooking into Symfony or a different framework entirely. So we do expose that interface and we have one implementation. And you can see here all it does is it listens for when Laravel fires that event that the view is being composed. And when it does, we spit out the constructed JavaScript within script tags. Very, very simple. So anyways, that looks okay to me. We have our namespace, we have our list of transformers. All right, this next step, I want to keep working on this put method. So when you say JavaScript, put foo bar. So the issue here is, let's imagine that this is what you gave us. So you said JavaScript, put foo bar. But as it turns out, if we take a look at the test, you can also pass it through as arguments here. So that means I could also say foo is bar, and those should work identically. Okay, so we check to see is the first item, we could even do this, but I'm not going to force

as arguments here. So that means I could also say foo is bar, and those should work identically. Okay, so we check to see is the first item, we could even do this, but I'm not going to force php 7 in this case. So anyways, if this is equal to an array, then the variables equals that. Okay. Otherwise, if count arguments is 2, so I guess we're checking to see, did you pass more than one argument? In that case, the first item is foo. Yeah, so we're just normalizing this down to an array. Otherwise, if neither of these matches, then you gave us something in the wrong format. So we throw an exception. So in these situations, we're basically normalizing the arguments down. So maybe we could extract this. Let's see, we remove it entirely, everything blows up. So why don't we, and if we bring it back, we're green. Okay, just sanity check. So why don't we extract a method, something like

these situations, I will often just inline it and get rid of that variable entirely. I sometimes like to think of temporary variables as a very subtle smell. That doesn't mean you're not going to have them, because you almost always will. But I still think of them as like, I hate that I have to create this. Is there some way that I can keep from doing it? If not, no problem. But if you can inline it here, like we just did, then that's a good win. And we're still at green. So we have our list of variables. Now here's another case where it's just a matter of how far you want to take it. So this is something you'll see Taylor or Otwell do in Laravel quite a bit, where he will just inline that. And if you want it to be subtly more readable, you could do something like this. Build the JavaScript syntax, but first we need to normalize the input based on the argument list. And that should still give us green. Yeah, but at some point you could see like we could

Build. Yeah, okay. Construct JavaScript. And during construct JavaScript. Okay, are we green? Yeah, that fixes that. So let's go back to put. And we've simplified this quite a bit. Construct our JavaScript. We then bind that JavaScript to the view. And in our Laravel implementation, once again, we just listen for the event and we spit it in there. And then finally, we return that JavaScript. So that means if you say JavaScript put foo bar, that method is actually going to return the JavaScript modified syntax, which is good. Alright, so I think that's quite a bit cleaner than we had before. Next though, let's just go over this and then we'll call it a day. So we construct the JavaScript that constructs the namespace. It filters through the variables. I like this to be consistent, because up here, we called it, well, I thought we did. Oh, we got rid of that variables variable. That's okay, though. Let's rename that to variables. Kind of

I like this to be consistent, because up here, we called it, well, I thought we did. Oh, we got rid of that variables variable. That's okay, though. Let's rename that to variables. Kind of feels weird to have an argument called variables. But in this case, it actually makes sense, because it refers to the JavaScript variables to create. So for each one of those, as the name of the variable and its value, we build the variable initialization. That still seems gross. Especially up here, we have construct. But now in here, we have build. So maybe I could say construct. Or really, what we're doing is we are initializing it. So can I just say initialize variable and do that refactor? Yeah. Okay, so now construct the namespace for every variable, initialize it, and that's what sets the value on your namespace. And then that will be optimized value for JavaScript. So that's when we then give the opportunity to transform the value. So rather

and that's what sets the value on your namespace. And then that will be optimized value for JavaScript. So that's when we then give the opportunity to transform the value. So rather than optimize, because we're not optimizing it, we're converting it. So maybe here, could I rename this to convertToJavaScript and do that refactor? All right, run that again. Still a green, but yeah, we're just working on naming here. This is the important stuff. Now, rather than optimize variable initialization, blah, blah, blah, that junk. No, we're converting the value to JavaScript. That's all we're doing here. So we have that, and then we have just a helper here that normalizes the input. Okay, so you know what? I think we're going to call it a day. Everything is at green. So let's take a look at what we did here. First, we see that we took all of those transform methods that were on the Transformer class, and those have now been upgraded to first class citizens. So we

So let's take a look at what we did here. First, we see that we took all of those transform methods that were on the Transformer class, and those have now been upgraded to first class citizens. So we have one for arrays and booleans and null and numeric and object and string. We then, and in this case, it unfortunately doesn't show up because we moved the file to a new directory. But you can see that we now have 144 lines, and let's just go see what we had originally. So originally, we had this weird class name that was 2Inch. So we reduced this by 100 lines. Doesn't seem like a lot, but you know what? If I ever need to extend this, it becomes much more simple to take in because now this class isn't concerned with the actual transformation for an array or a boolean. It just knows that it can handle these, and then it delegates the classes to do that very thing. All right, so I think this has been a good refactor. Our tests are returning

array or a boolean. It just knows that it can handle these, and then it delegates the classes to do that very thing. All right, so I think this has been a good refactor. Our tests are returning green, which means everything is still working. So I will push this up to GitHub, and we'll call it a day. Thanks for coming along.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟