Automating Scripts with Composer0:00
Now that we have these scripts and tools to automate parts of our development workflow, it'd be nice to automate the automation. One of the ways we might be able to do this is to add these to our scripts tags in composer.json. This could be something like our own custom lint command, which ran both our linter script as well as pint. And if we ran back to our command line, we could do something like composer lint. And we see that it ran both those commands. That's nice, but it's not really automated. Again, we want this to happen automatically for us. Another option might be to have our IDE, in this case PHPStorm, automatically run these on save. That handles the automation part, but it's not very efficient. There's no reason to lint our entire project or format our entire project when a single file is saved. What I really would like is to have a universal tool that could automatically run our scripts for a set of changes. And given those
Introducing Git Pre-commit Hooks0:57
project or format our entire project when a single file is saved. What I really would like is to have a universal tool that could automatically run our scripts for a set of changes. And given those conditions, that makes me think of Git. More specifically, Git hooks. Git actually comes with a built-in set of hooks that you can apply for all sorts of different actions, one of them being the commit action. If we jump out to the command line, we can see in the .git directory, it comes with this hooks folder. And within there, we can see a pre-commit.sample file. Let's actually just jump down into this directory, and we'll move this pre-commit.sample file to pre-commit. Let's take a quick look at this file. We can see it's just a shell script, and there's actually a pretty good example in here of looking over the changed files. Now, it's doing a lot of different things, most of which we don't need. So let's see if there's a way we can pare down what this gives us to do what we
example in here of looking over the changed files. Now, it's doing a lot of different things, most of which we don't need. So let's see if there's a way we can pare down what this gives us to do what we want. So I'm just going to start hacking away at some of these lines because I don't really need most of them. I'm not comparing things against old historic versions. I really just need the currently changed files in the index, or less technically, those files that I'm about to commit. So if we keep going down through all of this noise, we'll see that right in here is actually a command that we kind of care about, and this gives us a good place to start. What this is doing is it's getting all of the files by name only and saying the ones, in this case, that are added. But there's a few other statuses that we're actually going to care about. We'll also want files that are modified, any conflicting files, as well as any renamed files. Pretty much the only thing we don't care about would be deleted files. And with that,
Testing Staged File Detection2:48
actually going to care about. We'll also want files that are modified, any conflicting files, as well as any renamed files. Pretty much the only thing we don't care about would be deleted files. And with that, we don't really need any of this other stuff. So let's just continue to trim this down to where we just have this single command. All right, let's call this files, store this off into a variable, and it looks like I got a little trigger happy and killed some stuff I needed. So let's bring back that closing parenthesis. And for now, I just want to echo files, and let's exit with a non-zero exit code. Again, that's kind of a key thing with all of these scripts. By exiting with a non-zero status code, we actually stop the commit from happening. So while we're testing things out for now, let's just force this to fail. All right, I'm going to save this, and we'll go back up to our top directory. And if I run git status, I don't think I've really made any commits since we started this project. So let's just do git add . to get everything in there. And I'll run git commit -m,
go back up to our top directory. And if I run git status, I don't think I've really made any commits since we started this project. So let's just do git add . to get everything in there. And I'll run git commit -m, and we'll say initial commit. Okay, that's a little hard to see at the bottom of my screen. So let's clear that out and run that one more time. Okay, two things happening here. One, we get all of the files that were changed and ready to commit. That's good. That's what we expected. And also as expected, we see that this command failed. The commit did not take place. So if we run git status, we'll see that all these files are still staged. And if we run something like git log, we'll see that there's nothing but the initial commit. Now, since we're going to use this for our php project, we probably only care about the php files that have been changed. So let's tweak that. We'll go back to our git commit hooks, our pre-commit hook. When we're getting files, we can actually continue to use git to help us do this. So let's end all our options and tell it that we only care about
Running Lint and Pint4:38
let's tweak that. We'll go back to our git commit hooks, our pre-commit hook. When we're getting files, we can actually continue to use git to help us do this. So let's end all our options and tell it that we only care about paths that end in .php. All right, let's save that again, and give us some room. And we'll attempt to run git commit -m. Trying again. Okay, great. We're still getting that failure as expected. The commit didn't happen. If we look at git status, everything is still staged. But now we're limited to just PHP files. Awesome. So the next step would be to pass these to the scripts that we've made. So let's again, just edit that. Instead of echoing these files, let's pass them to our php linter. And we'll pass it files. And we'll also want to run vendor/bin/pint and pass it the files as well. And now if we run git commit -m, automating the automation, we should see that everything passes. Great. And if we run git status, we'll see that we're in a clean state. And if we run git logo, we'll see that we did get a commit for automating the automation. Awesome. So now anytime we went to
should see that everything passes. Great. And if we run git status, we'll see that we're in a clean state. And if we run git log, we'll see that we did get a commit for automating the automation. Awesome. So now anytime we went to make a commit, so long as it didn't contain any syntax errors, and we were successfully able to apply our code style, then the commit takes place. Otherwise, it's going to fail. Now there's a couple things I'd like to do to make this a bit cleaner. First, let's disable this output, we should be able to pass pint the --quiet option. So let's save that and try this again. Let's give ourselves more breathing room. And I'm actually going to reset HEAD to day one to pop off that automation. Let's go ahead and add everything back. And if we go find that commit message, we'll see now that the output from pint is no longer showing up. Okay, great. That solves that and gets everything back to normal. As far as git is concerned. There's one more thing. Let's reintroduce a syntax error. So now I have a change where I've introduced a syntax error. If I run git add -u to pick up any of the changed files,
Fixing Hook and Linter Behavior6:53
everything back to normal. As far as git is concerned. There's one more thing. Let's reintroduce a syntax error. So now I have a change where I've introduced a syntax error. If I run git add -u to pick up any of the changed files, git commit -m "attempt to commit a syntax error." Now we'll see that this failed and it outputs our fancier display of the syntax error we created in one of the previous videos. You might think everything's fine. But this isn't really working the way that we think. If we go back and look, we'll see that the linter is actually being passed the changed files. But it's not doing anything with those changed files. It's only picking up the syntax error because it's actually scanning all PHP files. We need to update our linter to change its behavior if it's passed in a set of files. Something else is that even though the linter is failing, it's actually continuing to run the pint command. And if we remove --quiet, we'll see that that's happening. Let's try to make this commit again to see what's happening. Trying again. Yeah, see that pint is actually running. It makes more sense that if it fails here, it should stop and not try to do anything else. So
we'll see that that's happening. Let's try to make this commit again to see what's happening. Trying again. Yeah, see that pint is actually running. It makes more sense that if it fails here, it should stop and not try to do anything else. So let's fix that before we jump into php land. If we edit this one more time, we can update that by simply calling exec. That's going to make sure that our command runs within the same process. So if it fails, it's going to fail the current process as well. So we'll change these both to exec. I'll leave off the quiet for now. And if we go back and try to run this commit again, we should see that it properly fails now without actually trying to run pint. All right, let's put that quiet back in there and go update our linter. All right, we'll leave our syntax error for now. If we jump into our linter, what we can do is actually do a little check here to see if this has been passed any kind of arguments. And we get all those in argv. Now, if you've worked with this before, you know that the first element in argv is always going to be the script name. So we need to go ahead and pop that off. And we can use array_shift to do that. And now we can update our code to
argv. Now, if you've worked with this before, you know that the first element in argv is always going to be the script name. So we need to go ahead and pop that off. And we can use array_shift to do that. And now we can update our code to basically either use the arguments that are passed in, which will be relative file names, or find them using the Symfony finder. So let's create a little helper function to do that. Let's just call it files is equal to findFiles. Let's take our finder command. And we'll use that as a basis for our new function. So findFiles. Currently, we're not going to pass in any arguments. And by default, what this is going to need to do is return an array of relative file paths. So we can say return array_map. And in this case, we could just map that file that was found to the same thing that we're using up here, the getRelativePathname. So we'll just bring that down to our function. And this should just be over that finder. Now, this is actually an object, but I'm pretty sure that it's iterable. So I want to see if this just works. But first, we need to make a few more adjustments, we no longer need to do this, we can basically say if empty(files), then
finder. Now, this is actually an object, but I'm pretty sure that it's iterable. So I want to see if this just works. But first, we need to make a few more adjustments, we no longer need to do this, we can basically say if empty(files), then exit. And we can change this to now be files, we'll get rid of this assignment and change anywhere we're using path to file. Let's go ahead and clean up some of this old dead code too while we're at it, just tighten things up a bit. Okay, and there's no longer any search results. This is all kind of self explanatory. Alright, let's go back and see if things are still failing. Oh, we have all sorts of issues. Okay. So even though it is an iterable, it's not necessarily an array. So we're going to need to loop over that in a different way. PHP actually comes with a built in method that allows us to do this conversion. So let's just say iterator_to_array(iterator) and we don't really care about the keys in this case. So we can just say false for this second argument to reserve the keys. Let's see if this gets us a bit further. Great, we're back to where we were. So now we need to update this method to instead of getting all these files using the finder, it needs to do
say false for this second argument to reserve the keys. Let's see if this gets us a bit further. Great, we're back to where we were. So now we need to update this method to instead of getting all these files using the finder, it needs to do so with argv. So we can just create an if statement that says if argv, then basically, return argv. And unfortunately, this is a global reference. So we could either pass it in, or we could just say global argv. That's pretty gross. But hey, this is a script. Alright, let's run this again. And I would expect it to do the same thing. But if we actually went back up here, and we output our files, and ran this again, we'll see that it's only doing it against some other file, it's not doing it against the entire project. And just to prove that real quick, we'll go back down here and comment out our if statement to return early. And we should now see that it's doing it against all PHP files. Great. So this is definitely working. So let's finish things up by making an actual clean commit, we'll remove our syntax error from this file. If we go back and run git status, we should see that we've only changed PHP files. And if we go ahead and add all of
definitely working. So let's finish things up by making an actual clean commit, we'll remove our syntax error from this file. If we go back and run git status, we should see that we've only changed php files. And if we go ahead and add all of our changes and run git commit -m "support", passing in files, oops, it looks like we left a var_dump in our code. And that's exactly what I want to work on in the next video.
