در حال بارگذاری ...

Motivation for Automation0:00

When building a Laravel application, I find I'm often repeating the same steps and writing the same code. Whether it's making core components like controllers, or models, or views, or migrations. Of course, Laravel comes with artisan make commands. There's pretty much a make command for every one of these components. While these are helpful at creating common components, some make commands even create multiple components at a time. These files are mainly class stubs, so I have to fill in a lot of the blanks. Call me a lazy developer or just excited to build the app, but I don't want to run all these commands or copy and paste files. I like to get into the code as fast as possible, so I'm a big fan of anything that helps me do that.

but I don't want to run all these commands or copy and paste files. I like to get into the code as fast as possible, so I'm a big fan of anything that helps me do that. So let's say, for example, I was building the beginnings of an application like LayerCast. I'd need to create the foundational models and their migrations. I'd probably want a few controllers like this main listing page and viewing a video page. Probably a controller for leaving comments, and maybe a quick API to mark the video as watched. The application would probably also use form requests for validation, events, mailables, and I'd definitely want tests. So let's build it. And we're going to do so in less than 10 minutes without writing a single line of php. You're probably thinking, no way, JMac, but we can.

Introducing Blueprint Tool1:20

And we're going to do so in less than 10 minutes without writing a single line of php. You're probably thinking, no way, JMac, but we can. And we can do so with Blueprint. Blueprint is an open source project I started. It uses what I call a draft file where you define models and controllers. From this simple definition, Blueprint generates multiple different LayerValue components. This way you can jump directly in and focus on writing the custom code for your application. You may quickly add it as a development dependency to any one of your projects. To save time, I already created a new LayerValue application and ran this command. I'll bring up this project in my IDE.

Defining Models in Draft1:54

To save time, I already created a new LayerValue application and ran this command. I'll bring up this project in my IDE. Again, this is a new LayerValue project. No additional models, migrations, or views. We'll generate all that with Blueprint. So let's create that draft.yaml file. First, I'll define a model section. Since this is a new application, I'll create some of the foundational models. Let's start with the Video model. The syntax is column name followed by the definition where I can specify the data type, attributes, and any modifiers.

Let's start with the Video model. The syntax is column name followed by the definition where I can specify the data type, attributes, and any modifiers. So I'll define a title, an optional synopsis, and a duration with seconds precision. I'll also generate a Comment model. This one will have a userId and some content. And it should probably have some kind of approved flag too. Finally, I'll make a Watch table. This will be a simple table which tracks the videos a User has watched. So it'll have a userId and videoId for that linkage. I'll also have some kind of completedAt timestamp.

So it'll have a user_id and video_id for that linkage. I'll also have some kind of completed_at timestamp. So those are the models that will help me get started. Notice Blueprint uses the same data types and modifiers as Laravel migrations. It goes back to that original motivation of prioritizing the developer experience. You don't have to learn anything new with Blueprint. It's just a condensed version of the same syntax you would use in Laravel. Also notice that I didn't have to specify the ID or timestamp columns. Blueprint will default Laravel conventions wherever possible. Of course, you can turn these off if you want.

Blueprint will default Laravel conventions wherever possible. Of course, you can turn these off if you want. For example, the watches table probably doesn't need timestamps. So just like in Laravel, I can disable this with timestamps(false). Let's go ahead and build these just to see what Blueprint does. I'll jump back to the command line and run php artisan blueprint build. This command accepts a single argument of the draft file. And if you've named your draft file simply draft.yaml, you don't even need to specify it. When I run this, it outputs a list of all the files it created. So from that 15 line YAML file, we see Blueprint generated migrations, models, and factories.

When I run this, it outputs a list of all the files it created. So from that 15 line yaml file, we see Blueprint generated migrations, models, and factories. Let's take a quick look at a few of these to see the code. Looking at the video table migration, we see Blueprint properly created all the columns using the data types we defined. It got the precision on the duration correct and automatically added those ID and timestamp columns. Jumping over to the comment factory, we see it properly set up the relationship between the User model, as well as the appropriate fake data for the content and approved columns. Finally, looking at the watch model, we see Blueprint not only created the model class, but automatically set its fillable, casts, and dates properties. It even created the eloquent methods for the belongsTo relationships for a User, as well as for the video.

Defining Controller Actions4:39

but automatically set its fillable, casts, and dates properties. It even created the Eloquent methods for the belongsTo relationships for a User, as well as for the Video. All of this code is ready to run, and it follows the latest LayerValue conventions. Okay, let's get back to our draft file for controller actions. First, I'm going to run the php artisan erase command to remove the previously generated code. Great. Now going back to the draft file, I'll define a controller section. I want to define a VideoController with an index action that queries all videos and renders the index template. I also want to have a show action, which renders the show template and passes the video. Next, I'll define a CommentsController that allows me to create a comment.

I also want to have a show action, which renders the show template and passes the video. Next, I'll define a CommentsController that allows me to create a comment. I'll need a store action that should receive the content, and I'll want to validate that. Then I'll want to save the comment. Let's say I also want to do something like fire off an event that there's been a new comment. And maybe I want to send an email to the administrator so they know to review that comment. Finally, I'll flash a message and redirect them back to the comments create page. Last, let's define a WatchController. And I want this to be an API controller. It's just going to have a single store action.

And I want this to be an API controller. It's just going to have a single store action. It should validate the data, save the watch, and respond with a 204. And that's it. Just as with the model definitions, all of these statements within the controller follow a very similar syntax as Laravel. We see terms like save, validate, and flash. And also common terms like query, render, and fire. This may feel a little dense at first, but it's streamlined to provide that greasy fast speed. Alright, let's run the blueprint build command again. We see it output a lot more components this time.

Alright, let's run the blueprint build command again. We see it output a lot more components this time. As before, it created all the model components, but now it created the controllers, events, form requests, and mailable components too. It also created view templates. It even created HTTP tests for the generated controllers. We also see a new updated list as Blueprint updated the routes file with our generated controller actions. Again, pretty awesome how much was generated from this simple YAML definition. Let's take another quick look at the generated code. We see the VideoController is pretty straightforward, doing all the simple resourceful methods, and display the index page as well as the show page. It even implicitly bound the model and updated the doc block.

We see the VideoController is pretty straightforward, doing all the simple resourceful methods, and display the index page as well as the show page. It even implicitly bound the model and updated the doc block. Looking at the WatchController, Blueprint properly namespaced it under API. The store action uses a form request for validation. Blueprint generated this form request, complete with the validation rules, all based off the model definition. The controller then saves the new record and responds with a 204 No Content using Laravel's fluent response methods. Going back to the CommentController, it has a simple create action. We see the store action also validates the data with a form request, saves it, as well as fires the events and sends a mailable, both of which Blueprint created classes for as well as the reference properties within them. It flashes that message, and finally redirects using the resourceful route naming convention.

Reviewing Generated Tests7:49

both of which Blueprint created classes for as well as the reference properties within them. It flashes that message, and finally redirects using the resourceful route naming convention. Let's go look at those tests. I'll focus on the test for the CommentController to demonstrate just how much of this test is set up. It creates the test class using all the appropriate traits. There's a happy path test for the create action to return the correct view. We see it verifies the store action uses a form request with one of the additional assertion methods. The test case for store sets up fake data. It properly fakes the event and mail facades to later perform assertions. It then sends the request with the expected data, asserts the comment was in the database,

It properly fakes the event and mail facades to later perform assertions. It then sends the request with the expected data, asserts the comment was in the database, verifies it, redirects, and also flashes the message to the session. It also adds the assertions for the event and mail facades to ensure they receive the correct comment model. I mean, come on. This is pretty awesome. But wait, it gets better. Remember how I talked about building Blueprint to leverage LayerValue conventions? Let's erase this previously generated code and go back to the definition. It's a common LayerValue convention to use resourceful controllers.

Let's erase this previously generated code and go back to the definition. It's a common Laravel convention to use resourceful controllers. Although I followed the practice here, I wrote it all out. For example, the VideoController is pretty much doing what you would expect for the index and show actions. It also looks like I called this post for some reason. So if I'm leveraging this resourceful convention, I can write even less using Blueprint's shorthands. So instead of typing all this out, I can just say resource index, show. And that's it. If I run the build command again, we'll see it generated the same components. And when I look at the VideoController, we see that we get a similar output, but this time with the correct variable names.

If I run the build command again, we'll see it generated the same components. And when I look at the VideoController, we see that we get a similar output, but this time with the correct variable names. So if you were maybe thinking that was a lot of YAML, the shorthands make it even easier to generate components quickly. And that's the last point I want to make about Blueprint. It doesn't generate all of your code. Remember that flask message? I'd still need to go in and define the actual message. I'd also need to fill in the view templates with the front-end code. You know, all the custom stuff. That's the point.

Blueprint Architecture Overview10:04

You know, all the custom stuff. That's the point. Blueprint's goal is to help you get started quickly so you can focus on writing the custom parts of your code. There'll be times where it might generate everything, but there'll also be times where it makes sense to let it do some of the work and you do the rest. Okay, let's finish up by looking under the hood of Blueprint itself. Let's jump over to the Blueprint project. On the surface, Blueprint is just like any other Laravel package. It has a service provider which registers the commands we've been using. But underneath, Blueprint is a transpiler. It all starts with a language parser.

But underneath, Blueprint is a transpiler. It all starts with a language parser. This uses Symfony's YAML parser to break up the draft file into an array of tokens. From there, these tokens are analyzed and passed through different lexers and turned into an object representation so they're easier to work with. For example, the model lexer turns an array of model tokens into a model object. This object has methods that allow Blueprint to quickly access the name, namespace, columns, relationships, and other information about the model. After analysis, Blueprint has an array of these objects. This is effectively what they call an abstract syntax tree. In fact, I use that terminology within Blueprint. This tree is then passed through a series of generators.

In fact, I use that terminology within Blueprint. This tree is then passed through a series of generators. Each generator is responsible for reviewing the tree and outputting code for a specific component. Sticking with the model, let's look at the model generator. We'll see it loops over the tree and only focuses on the model objects. Much like the Laravel make commands, it populates a stub with all the different pieces of the model class, including its properties, relationships, and traits it uses. Ultimately, each of the generators output code and return a list of the files they created or updated. And that's what we saw in the build command output. Going back to the service provider, Blueprint registers all of these core generators. We see ones for the model, controller, statements, as well as routes and tests.

Going back to the service provider, Blueprint registers all of these core generators. We see ones for the model, controller, statements, as well as routes and tests. What's important here is these generators are registered. I did this so you could register your own generators with Blueprint. For example, I know many developers have asked about generating components for Laravel Nova based on the model definitions. And this would be possible by calling this registerGenerator method. You may do so from within a Laravel application or another package, since Blueprint is registered as a singleton. Just like the core generators, it would receive the abstract syntax tree to generate code. Maybe someone in the community could also make one to generate views based on a definition. There's so much potential. This is really just the beginning.

Maybe someone in the community could also make one to generate views based on a definition. There's so much potential. This is really just the beginning. So that's a quick look under the hood at Blueprint. For a deeper dive, feel free to browse the repository on GitHub. If this video gave you some ideas, don't hesitate to open an issue, or better yet, a PR with a new feature. You can also check out my Bluecast repository to review the draft file and the generated code from this video. It's all on the demo branch.

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