Creating CLI Entry Point0:00
Like we discussed at the tail end of the previous video, in this exercise we're going to build a little task app from scratch that we can use directly from the terminal. So we will be starting from scratch here. The only thing I've done is set up Composer autoloading. All right, let's get to it. Of course the first thing we need is our entry point. We'll call this tasks. And then we'll say this should be user/bin/environment. We are using php here. And we're all set to get started. Now to begin, I do want to make sure that we can execute this. So like we did last time, we're going to change it to be executable. And then we'll reference tasks. So if this is the entry point, we need to require the autoloader. Next, we need to set up a new application. So we will use that at the top. And then say new Application(task app, 1.0). And it looks like phpStorm isn't picking up my syntax highlighting. But that's okay, we won't
Building Show Command0:49
So we will use that at the top. And then say new application, task app, and the version number is 1.0. And it looks like phpStorm isn't picking up my syntax highlighting. But that's okay, we won't be in here very long. Why don't we start with the show command. App, add, a new Acme show command. And then finally, we will run the application. Okay, let's get started. So like we did in the last lesson, we'll set up a new symphony command, show command within the Acme namespace. We'll call it show. And we'll say show all tasks. Now let's take a minute and think, what do we really want to do? So when I call tasks show, well, that should read from a database, fetch all of our tasks, and then display them as tabular data. So that represents the specifics. But in general, I really just want to show my tasks, this show tasks. And then because I'm using show here, and I know I'm going to output something to the console, I will send through the output interface.
I really just want to show my tasks, this show tasks. And then because I'm using show here, and I know I'm going to output something to the console, I will send through the output interface here. So let's go ahead and create that. However, I have an inkling that I will probably want to extract this to either a parent class or a trait. And that's because I can imagine other commands wanting the ability to show all tasks. But first things first. So how do we go about displaying all of our tasks? Well, we don't have a database connection just yet. And we're not using anything sophisticated like Eloquent. So why don't we just imagine the interface we might have, maybe we'll set up a very simple adapter to our database. And I will initialize those fields. And one quick note on this, if you use a constructor for a command, then you need to make sure that you call the parent class's constructor. So try not to forget that. Okay,
And one quick note on this, if you use a constructor for a command, then you need to make sure that you call the parent class's constructor. So try not to forget that. Okay, now, within showTasks, I could say this database, and maybe we have a method called fetchAll, and then you give it a table name like this. Then we'll set up our table, so new table, and feed it the output. And then like we did in the last lesson, we'll just set the headers. And maybe we'll stick with the ID of the task and the description of the task. And that'll be enough. Next, I need to set the rows. And hopefully we can just pass that variable directly to it. And then finally, we will render the table. Alright, so if we break it down, this is really what we're trying to do. When we execute this command, I want to show all tasks. And if we dig in further, we will fetch all tasks from our databaseAdapter. Maybe we'll
Implementing Database Adapter3:29
this is really what we're trying to do. When we execute this command, I want to show all tasks. And if we dig in further, we will fetch all tasks from our database adapter. Maybe we'll use something simple like a SQLite database. Then we build up our table, we set our headers, we apply the rows, and we render it to the console. So now, before we can try this out, of course, we need to set up our little database adapter here. And we'll be pretty quick on this one. So database adapter. So let's see, it will need to have the connection. So we will build that up. And that'll come from PDO. And we'll go ahead and import that. Okay, so we had a method called fetchAll where we pass it a table name. So I can say return $this->connection, just using general PDO here. And we'll do a query, select * from, and then the table name. Finally, I'm going to fetch all results from that query. Now I think it's possible you might have a SQL injection problem.
PDO here. And we'll do a query, select * from, and then the table name. Finally, I'm going to fetch all results from that query. Now I think it's possible you might have a SQL injection problem here. But it really doesn't matter in this case, you are literally the only person who will ever use this. So it's not quite as much an issue. But nonetheless, it's always a good practice. So why don't we also have a general query method. Anytime you want to run a general query here, you can pass your SQL here, and then also any parameters that you want to bind to it. And with that, we can use prepared statements to protect ourselves better. So we will say return this connection, we're going to prepare the SQL, and then execute it, but bind the parameters in the process. That way, your SQL could be something like insert into tasks, name, and the values will be name. And then you can bind a key from this parameters array to that specific point, or that
Wiring PDO and SQLite5:10
process. That way, your SQL could be something like insert into tasks, name, and the values will be name. And then you can bind a key from this parameters array to that specific point, or that name. Alright, and you know what, I think that's going to be fine for this little wrapper here. Not very complex at all. So let's go back to our entry point here. And we now know that show command should receive the database adapter here. So let's set that up. $dbAdapter equals new Acme\DatabaseAdapter. And don't forget, we need to pass in that PDO instance here. So we'll set that up at our entry point as well. Try and we'll say $pdo equals new PDO. And like I said, we're going to use SQLite here to connect to it. And the path will be db.sqlite. SQLite3, and we called it db.sqlite. And we'll go ahead and create our table tasks. So very quickly, we have an ID that should be an integer, and that's a primary key. And then we have a description, which will
it db sqlite. And we'll go ahead and create our table tasks. So very quickly, we have an ID that should be an integer, and that's a primary key. And then we have a description, which will be text, and that can't be null. And yeah, I think that's really all we need for this little demo. So tables, and we're all set up. Now let's switch back. We've connected. However, we do want to set a couple quick options. And this is pretty standard PDO stuff. We want to set attributes. And we'll begin with the default fetchMode. So when we fetch our results, do we want it as an associative array, or an object, or to a class, stuff like that. In our case, we want to fetch it as an associative array. And then finally, we'll set one more attribute for the errorMode. And we want it to throw exceptions. So finally, if we had some problem connecting, we will catch an exception. And I think honestly, that could actually be PDOException,
And we want it to throw exceptions. So finally, if we had some problem connecting, we will catch an Exception. And I think honestly, that could actually be PDOException, but this is fine. And we will simply say echo could not connect to the database, and then we will exit. And yeah, that's all we need to do here. So we connect to our database, we pass that connection to our little adapter, and then we pass our adapter to the show command. So now from the show command, we'll accept that. And when we show tasks, we will fetch all tasks from the database table. However, on that note, think about it right now we have no tasks. So what would happen in this case? Well, let's see. We run tasks show, and we just get that. So that might be fine. But if you don't want to do that, then you could do a quick check. For example, if we do not have any tasks, then we will say return output rightLine. And we'll
Adding Tasks Command7:45
So that might be fine. But if you don't want to do that, then you could do a quick check. For example, if we do not have any tasks, then we will say return output, right line. And we'll do just a little bit of info, no tasks at the moment, and then close that out. And that should be enough. So let's see, we run it again. And there you go. Just a quick check there. Okay, so everything's working great here. Now I want to set up a new command so that we can add a task. So let's go back here. And we will add, add command. And let's set that up. And command, the namespace is Acme, add, and add a new task. Alright, so now at this point, one thing I'm thinking is, well, I'm going to need access to that database adapter again. And like we talked about earlier, it's possible that after you add a task, we immediately want to display all current tasks. So it's starting to look like I should extract a parent class here.
And like we talked about earlier, it's possible that after you add a task, we immediately want to display all current tasks. So it's starting to look like I should extract a parent class here. So let's do that. symphony commands, and we're just going to call this command. Okay, we don't need a lot of this stuff here. But we do need these various imports. So let's make sure we alias that to symphony command, so we don't have any duplication. And now if we switch over, here is where we can pull in the database adapter. So I will paste that in. Now if we come back, this is a lot cleaner. We just need to make sure that we no longer extend symphony's command class, we're going to extend our own. And then our command class will of course extend that. And then I will do the exact same thing for the add commands that we just set up. So we're ready to continue. But let's see the error this database. Probably Yeah,
extend that. And then I will do the exact same thing for the add commands that we just set up. So we're ready to continue. But let's see the error this database. Probably Yeah, because that was private instead of protected. Okay, let's continue on. So how can we go about adding a new task? Well, that means we need to accept an argument this time. So addArgument, that will be the description. And this will be required. So I will say inputArgumentRequired. And that should do it. So now when we execute this, we'll go ahead and fetch that getArgument description. And we need to trigger a new database query. So don't forget, we had this helpful query method here. So let's reference that database query, and then we're just going to add SQL here, insert into tasks, the description, and the values for that will be this named parameter here. So we have our SQL, we will then bind the description to it as an array. Remember that
insert into tasks, the description, and the values for that will be this named parameter here. So we have our SQL, we will then bind the description to it as an array. Remember that way, this value will be inserted as that value there. And that way, you don't have to worry about escaping or SQL injection or anything like that. Finally, that should be all we need to do. So we will write a new line, info, task added. So why don't we try this out and see if it works. Remember, at the moment, we have no tasks. So I'm going to add a new task, go to the grocery store. And now if we list our tasks again, there you go. Pretty easy, right? Let's add another one, finish screencast series. And notice how every time I do that, I hit the show command again to see if it works. So that's what I meant when I said, in some situations, we might want to show all tasks elsewhere. So with that in mind,
I hit the show command again to see if it works. So that's what I meant when I said, in some situations, we might want to show all tasks elsewhere. So with that in mind, I'm going to take all of this, and we're going to extract it or push it up. All right, let's do that refactor. And now if we switch over, there you go. So because we've done that, if we go back to show command, this one is incredibly simple. And we can go ahead and get rid of that reference. Next, our add command is very simple as well. But now take a look, we can now say, once you've added a task, let's go ahead and show all tasks again. So let's see what that looks like. Tasks, add, and maybe finish homework. And whoops, looks like we got an error. Yeah, we forgot to pass the output to it. So let's try that one more. Finish homework, assignment, we run it, and now we add the task,
Completing Tasks Command12:06
And whoops, looks like we got an error. Yeah, we forgot to pass the output to it. So let's try that one more. Finish homework, assignment, we run it, and now we add the task, but we also list everything in the process. All right, so let's just do one more for completing a task. I want to say tasks complete, and then I give it the ID. So if I complete three, that should remove this task. And then I also want it to show me the updated task list. All right, let's knock that out. Right here, add a new symphony command, complete command, complete, and we'll say, complete a task by its ID. So now don't forget, we are going to extend our own Command class. And this will need to accept an argument, because we need to pass it the ID of the task that we want to complete. So add argument ID, and that will be required. Okay, so ID is input, getArgument(ID), and then very much like we did before,
the ID of the task that we want to complete. So add argument ID, and that will be required. Okay, so ID is input, get argument ID, and then very much like we did before, we're going to run a quick SQL query. This, database, query, delete from tasks where the ID equals what we bind to it. And then finally, like we did before, we will write a line, writeLine, info tasks completed. Finally, once again, we want to show all tasks. All right, so I will let you take a look at that. And we're ready to try this out. Let's switch back over here. God, this syntax highlighting is killing me. I'll have to figure that out. But anyhow, complete command gets the adapter, and let's see it from the terminal. So I run tasks show. And now I want to complete, how about the ID of three and four. So we run three, and that's gone for, and now that's gone. Let's do all of them and see what happens.
So I run tasks show. And now I want to complete, how about the ID of three and four. So we run three, and that's gone for, and now that's gone. Let's do all of them and see what happens. There you go, we completed that task. And now we have no more tasks at the moment. So we can add a new one, learn more about php. And it works. All right, so that concludes our fun little exercise there. I hope you learned a bit
