 It looks like it's cutting off a little top, but that's okay. I do. This should be fine. I'll just remember that. Thank you, sir. What's that? Okay, I think we're about ready to get started here. So this is Entities 201, creating custom entities. My name is Ron. I'm kind of curious, did anyone go to Entities 101 last year? Those hands, a few people? Anybody watch it or look at the slides if you didn't go? It's okay. It's not a prerequisite or anything. Anybody in here have experience writing codes, custom code in D8? A little bit some. Good. Okay, great. And what about D7? Oh, even better. Fantastic. Okay, so... I've been a Dribble developer for quite some time. Pretty much full stack all the way across. Currently, I'm a Senior Solutions Architect in Aquia, which means I'm a professional problem solver. So I get called in to talk about how Dribble can solve problems and how we can do things that people haven't thought of. And Dribble's really good at that. You can find me online pretty much everywhere. RL Northcut is usually me. And if it's not, then someone's pretending to be me. So today's agenda is going to be pretty straightforward. We kind of have it broken into two sections. The first section is we're going to do a basic entity using Dribble console. A show of hands, people that have used Dribble console. Awesome, fantastic. Really, really just fast. Just spend a brief few moments on that just for folks following along. And then we're going to spend the majority of the time building a module with the custom entity. And the intention here is to kind of go through the process and it's going to be a very simple example. But I want to be very thorough in how we do it. For some folks it's probably going to be just affirming that you already know what you know. Hopefully for other folks it will show you that you don't need to be intimidated because it's more complex overall but the tools make it much easier. And we'll discuss that. So first off, tools and setup. Everything that I'm using. I've got a local dev environment. I'm using dev desktop just because I use that at work. For the base site that we're doing for testing I'm using the lightning distribution. It's a nice distro. And there's some inheritance stuff you can do if you want to extend it. So check that out, it's pretty cool. I'm using Dribble console for the scaffolding. Part of that is for just to be faster. Part of that's also because it's easy and I'm a developer so I'm lazy. And then finally some type of IDE. I'm going to be using PHP storm even though it's a memory hog but you could use Adam or text pad or Vim or whatever you like. It won't really change much. So for the first part we want to create a basic entity and for those that remember from last year an entity is a loadable thingy that's optionally fieldable. So we want to be able to create a thingy. To do so we've got a handful of steps to generate the module to house it to generate the placeholder code for the entity itself. Take a quick look and then turn it on and see how it works. So this is going to be down and dirty pretty quick and these are the commands that we're going to be using here. So let's take a look at this. For the folks that don't know Drupal console is a really great command line tool kind of in the vein of Drush it does some similarities but it does things in a different way it's built on top of symphony console it also does some very cool things otherwise so if you install it it's nice because it gives you some scaffolding that allows you to generate different types of code in this case we'll be doing a module and an entity but you can also do plugins, permissions all kinds of fun stuff. Interesting side note I just learned not long ago that this is actually powered by twig under the hood in console which is actually kind of cool so let's do let's go ahead and generate the module and it's just going to take us through a series of questions and as we answer them it's going to do all the hard work for us so we've got a thingy we'll use the same machine name custom thingies we'll go ahead and get a module file even though we don't need it we don't need a feature we don't need a JSON no dependencies we'll keep it simple so no templates and no unit tests and we'll go ahead and confirm generation boom now we have a module so the next step is to go through and create our custom entity and for folks that's all the session last year everything is an entity in Drupal now the thing that's different is that there are two types content entities are things that we think about like content types and users taxonomy config entities are things like views image styles etc those are thingy good I'm going to just stick with the defaults for most of this we'll go ahead and add bundles because that's always fun sure make it revisionable okay there we go now we have a thingy module let's table it and see what happens so technically this was kind of the teaser in the description that we're going to generate a custom entity in under five minutes and that's exactly what we've just done now it's a pretty dumb entity at this point you can see that we can create different types just like you'd expect from content types so thingy type 1 and then we can also modify this go through and add fields do all the normal stuff that you'd expect and we can go ahead and add thingies now so it works lovely now this right out of the box without doing anything else isn't overly useful I mean at this point it's really just kind of a weaker node but the important thing is that right now without ever aside from the command line I haven't touched any code written any code I've got a functional module and I've got a functional custom entity that I can now go and iterate on and create and work with to do the stuff I want to do and out of the box it works you turn it on it doesn't blow up your site I don't know about you but for me that's like a big deal so pretty quick pretty handy definitely something to play with and again it'll take you five minutes so if you haven't already done so so that was the first part very quick very simple and now is where we get down to the fun part so what we want to do is we want to create a more complicated custom entity and by the time we get done we're going to understand why in order to accomplish what we wanted to do we had to have a custom entity to do that in this case I'm going to be basically mimicking CSS injector at a low level in D7 also asset injector is what it's called in D8 pretty straightforward nice little tool good for doing quick demos or if you've got people that you don't want them to be able to touch code but they want to do things if you want to iterate quickly but one of the nice things about this example is it lets us touch a whole lot of different parts of Drupal all very simply so we're going to be creating content types we have to define a schema some additional methods that aren't provided out of the box we're going to be messing with the file system we're going to be creating files and deleting them and trying to do some garbage upkeep we've got to do some stuff at the install of the module to make sure that it's prepped and ready and then we've also got to go through and the really fun part is we have to figure out how to take these CSS and possibly JavaScript files that we don't know where they are or what they're going to be called in February and attach them to the page dynamically and luckily Drupal gives us the ability to do all of this so as I go through this what I could do is I could actually just talk through it enable module and we could go and that would probably be the smart thing to do but I thought it might be a little bit more fun if we just kind of go through the code and kind of package it and put it together and hopefully I don't miss a semicolon and it works at the end so that'll be me going down in flames if it doesn't work but let's go back over and let's go through the process we just did this time we'll make a few changes we'll pay a little bit closer attention to the process as we go along so the first thing we want to do is we want to define our module in this case we'll just call it asset that'd be a horrible name for a module by the way we have stuff in Drupal called asset you don't want to do that but we're going to do that just for fun DC 217 2017 set example nice okay so we definitely want to create a module file and that's we're going to need to put some of our code as we'll see later we don't currently want to define this as a feature because I'm not currently interested in trying to take some configuration from the UI and shove it in there but that is something that you could potentially do that would be actually really helpful if you were working on a custom module for your own project or for a customer's project don't need a composer.json don't have any dependencies unit test it actually would be actually really good if I did a unit test but I didn't a themeable template no again I want to try and keep this as simple as possible it's going to spit out a ton of code so let's limit it and there we go so now we actually have our module great so now let's generate the actual entity and then we'll be we'll be all set so we've got we'll call it asset this is good I would probably put it someplace else in practice but we'll leave it under structure for now no bundles although that is an interesting idea translatable is fine revisionable is cool okay there we go so that's it now we're done right no we're not okay so now we've created our entity and it's got the usual stuff however we've got to add some additional fields the database schema in order for us to be able to take some of the information we're saving and push it in there and this process is these are probably the longest individual snippets that we're going to see except for one and what you'll notice that's going on here there's a couple of different things one is we're not just defining the schema we also are taking this opportunity to go through and talk about the way it's going to be presented to the user with the form we could do this in other ways but this is actually really handy because I can determine if something is required if this particular field is revisionable versus another which is very powerful some of the other options that are there and as you can see we actually are able to set whether or not the display or the form are going to be configurable so in theory you can take your custom entity and go through the UI later we won't be doing that in this example but it's pretty nice that you have that ability so let's go ahead and grab this and come over and we'll see that this is actually created unfortunately I think this might be a little small that's not horrible so what we're seeing inside the module is kind of the basic stuff that you'd expect and again I haven't created any of these files this was all done for me the actual entity file itself and it's almost entirely what we're going to be using is going to be under source entity asset.php and I know it's kind of silly it's 256 lines of code that I haven't written but if you make sure that you're paid bases on lines of code you should have custom entities all over the place it's a pro tip okay so what I typically like to do is go through and refine this and figure out what do I need or not need do I really need to know the last time that it was updated does it matter who the author is maybe it does in this case we're just going to leave the boilerplate there and just keep it nice and simple so let's go ahead and add our new field here the second one so the first field I'm sorry is a large text area where we're going to put whatever our CSS is the second thing we want is the actual type of content that this is in this case we're just talking about whether it's CSS or JavaScript but it could be extended later to be other things you could have like for example if you want to be able to add sprites or CSS images or things like that you could add that capability as well of course then you probably want to take this in a different direction let's go ahead and add that one in and then the last one that we have here is for the actual URI now one thing to note here is that we are using the URI system in Drupal to determine how these things work so we're not trying to write out the full path to where these files are going to be located and the reason for that is because we don't know that could change now as a friend of mine showed me earlier there are ways that we could work around that and there could be advantages or disadvantages but for all intents and purposes we're just going to assume that we're putting this into the public file system and that's all she wrote okay so we're good here we'll go ahead and save great, step two of 46 pay attention so the next thing we want to do is we actually want to modify the interface this is going to be really useful if we start extending this later or making it useful for other things mostly I kind of just wanted to put this in here just so you guys have a sense of how this works because this interface, asset interface is actually what's being used in this here we go in this actual class so we're going to have to match whatever we're putting in the interface in here as well but this just makes it just keeps it nice and easy for us we could probably skip this and be okay but again just want to be a little thorough let's go into the next piece so we want to take those individual functions that we added and we actually want to put the guts in for what they're going to do on this entity, these methods and this is where we're actually starting to outside of the schema we're actually starting to write code now and what you're seeing here is an example of what we're going to see over and over through this where you actually have some very small, relatively simple functions any one of which is not that crazy and not that scary so if you look at the first one get type all we're doing is we're just returning whatever the type is for this particular entity CSS or JavaScript very very simple for get file name we're actually going through grabbing the name making it lowercase replacing the spaces and special characters on underscore we see this with machine names all the time in fact I stole that the regex from somewhere in core because I don't like regex get file URI which takes those other two functions get file name and get type and actually uses those to generate what that URI should be based on the current state now what you'll notice is that all the way through this we're referencing this and this is kind of one of those magic words and it's talking about whatever object, whatever entity we're working with right now you get used to it but then once you get used to it it's really really cool because then you don't have to worry about the abstraction of which particular entity am I working with you just know that within the realm of this object when I instantiate this object the methods that I'm calling are going to work on it so it encapsulates and keeps it all together that can it make sense maybe in some places where we're actually starting to break away from the from the default of the boilerplate that was put in place so now we're starting to talk about the next step we want to do is we actually want to be able to save the files and so what what I did was I wrote the methods we'll see in a moment to save the file and I tied them in and wired it up and went to go save it because it's trying to save to a folder that doesn't exist a real bonehead moment so what we want to do is we want to make sure that that folder does exist before we try and do anything and a lot of people might be very happy to know that the process for doing that is almost identical to what it was in seven it's an install file and there's some hooks you can throw in there which we will actually go do right now in seven this should be very comfortable for you so asset.install alrighty so first things first I highly recommend shamelessly stealing code that works whenever you can and you can refine it later you can work on it make it your own but it's the secret to my success and so what I did was knowing that the image styles module creates folders I just went and grabbed that same code and I made a few tweaks as you can see it's actually doing the exact same thing three times it's just creating a different variation it's probably a more elegant way to do this but this works out quite well and it's highly readable very maintainable this might be something that you're writing that you're not going to touch for another six or nine months and you're trying to figure out what something was so I highly recommend trying to keep it clean or you know the old adage you should write your code like the next person that's going to work on it is an axe wielding maniac that knows where you live so make it easy make it understandable so we've got here two things the first is the install it's going to create those folders for us so every time we install this module it's going to create the folders and we're good to go however we also want to make sure we clean up after ourselves so whenever we uninstall the module it's actually going to delete and you can see I actually still have generated images there so I totally stole it it's actually just going to delete the entire folder and everything under it to go through and start manipulating the file system and the public folders very simple very easy okay and now that's one more one more file down so now that we've got a place where we can actually store and save the files we need to actually write the code that does it and again it's three lines to do this jump back over to entity and drop this in so as you can see we're using the get file uri which is basically I probably should have called that generate file uri honestly but as we've already established I'm lazy and easier to type get so we're generating what that uri should look like based on the name of this particular CSS file what type it is etc and then just writing that out one thing to make note of is that we are using the the file exists replace flag in that unmanaged save data function we I think the next one is file exists I don't remember basically it's the one where it adds underscore zero underscore one underscore two we don't want to do that because the database is actually going to be storing all of our revisions which is very cool so we can actually go back and see things back in time we don't have to worry about that here alrighty now once we've got the ability to save the file we also need to have the ability to delete the file and again pretty straightforward let's try this again and what we're doing here is actually going through and just saying let's pass through this this parameter for the ORI if we don't send it then it's just going to delete whatever the current asset we're working with is but we can specifically say which one to delete and that's going to be important later when we start talking about garbage collection one thing I also want to mention here is that you'll notice we're using this file unmanaged save data function and there's a difference between files that are managed by Drupal and files that are not so if we were to use a managed file that would actually create a file entity it would have it listed in the list of files in the Drupal UI you would be able to manage and manipulate it that way and that might be an interesting use case here we want the interface that we're creating kind of abstract that away so that people aren't going to get confused and say why did we add a CSS file I don't this doesn't make sense to me and then the the last thing we have to do is we're going to be able to we're going to want to clear the cache after we update a file so that it will actually show up so about an hour ago I got a text from a friend of mine who couldn't make it to the con and I said yeah I'm just going over my slides and he said oh man you know trying to give me advice just talk about entities and objects and things and then at the very telling he said oh and be sure to clear cache so I took a screen shot of this slide and sent it and I was like that's sage advice but like I mentioned earlier this isn't anything new I went into the core and common dot ink the core function for Drupal flush all caches and I just pulled out the piece for CSS and JavaScript and I plopped it in here I should note that these calling it this way through the service is actually deprecated so you don't want to rely on this but if you go look at Drupal flush all caches you'll see that and you'll know what to do so good stuff mostly it's just easy let's see okay step seven so how many folks here have worked with object oriented programming before fantastic so for those folks who may not have one of the things that makes object oriented programming so powerful is the ability to extend objects and then either replace those methods or functions that are tied to them or augment them if you will and that's precisely what we're doing here so in Drupal seven days if we wanted to do something in this case after we save the new asset we want to go through and we want to save it down to a file and we want to clear the cache in Drupal seven we would just use a a node or an entity what would it be it's not entity save goodness it's been a while does anybody know no bonus points for helping me out it's like after you save an entity and then it runs the hook what's that hook in seven okay yeah there's a pre-save update yeah so okay so there's a couple obviously I've been away too long in this case we actually have the a similar thing in this case it's post-save that would be like update now what you can do is you can either go in and just kind of guess at some of these things or the smarter thing to do which eventually is where I ended up is taking a look at what you're extending in this case we're extending the revisionable content entity base which I believe just extends the content entity base which I believe extends just the entity base and each one of those is either adding or slightly changing some methods you might have to look in a couple of places to see what's there but some of the stuff that we see all over the place like the post-save is what you'll expect thing that's really cool here though you'll notice the second line is taking we're actually calling the post-save on that parent entity the parent class before we do anything else and if we didn't have that line we would basically be replacing it so what this enables us to do is to augment and extend that without losing anything upstream and without having to worry about what's going on up there so this is why the going object oriented while it can be more complex adds a lot of really cool things it makes it very easy so now we're writing this function and we're down to just three lines moreover the lines that we have specifically save file and clear file cache are as we've already seen easy to understand they make a lot of sense there's nothing crazy there even though those are built on functions which are built on functions which are built on functions it's a very very complicated system but because it's so well structured so well organized it makes our job much much easier I've actually heard of some very large implementations thousands of sites on a single code base and massive project that's even at full velocity is going to take several years and actually as they progressed their velocity has actually gone up because number one the developer is more comfortable they're reusing the same components over and over again and number three even when they have to go back and do something it's much easier for people to understand and usually you don't hear about that about a project getting more complicated and yet going faster but because we have such a great architecture it actually does enable us to do that so the other thing we want to do again with the garbage collection being good stewards is we want to make sure that after we delete the parent entity one of those assets on a database we want to go through and actually delete the file as well and we have that lovely delete file method that we created to take care of that for us so we're good to go okay so at this point we pretty much have everything that we need we could turn this on right now and we can actually generate assets and have that save and delete the files back and forth and all of that stuff will work of course now we have to attach it to the page and that's where things get a little tricky luckily it's not horribly difficult and all of this should be very familiar to folks from D7 because we're going to do this in the actual module file itself which is nuts so the first thing that we want to do is we want to actually go in and add the use statement and see that we create it and if we go back to the asset.php we can see that the namespace is drupal slash asset slash entity and it is the asset class with a capital A that's how we know how to grab this so what we could actually do is in the one place we need to call it we could call it in this way we could say here I'll jump over here we could actually say asset equals all of this stuff load multiple and I actually did this when testing and load multiple is a default we could actually do something like this and since we're only using it right now in one place in my code that actually wouldn't be so bad however we don't know what's going to happen later or how else we might need to extend this so we're going to go ahead and put it in as a use statement so we don't have to worry about it and this is a great example of how object-oriented program allows us to lazy load stuff because if you're calling in d7 with bootstrap you're loading everything the upside there is that you go into a dot module file and you call a function you know that functions there because that module function that exists the downside is every single time you hit the page and you're trying to load something up it loads up all of Drupal all over again caching helps with that but at the end of the day we want to try and make use of this so that we can get really really fast and once you start talking about things like APIs and kind of doing things at more of the machine level that speed really starts to pay off so this is where you get a little bit interesting now is anybody here added in d8 added a library css javascript in a module a few people okay it can be a little bit weird you have to create a yaml file which defines what a library is for this module and then you have to go in and attach it to something to a page to a form to an element and that's what tells Drupal in this case we can't create that yaml file because we have no idea what these files are going to be called where they might be located or how many of them there will be so what we have to do is we are using this wonderful hook library info build and what this allows us to do is to go through and define the structure the actual array that the yaml file represents in this case it's going to be blank right and then we're loading all of the assets so every single one of those asset entities that we have we're going to load up the batch we're going to loop through that batch and each one individually we're going to add into that array at the end of the line we're going to return that entire array that we've now constructed and we're going to be doing that periodically just to make sure that we've got it so basically what we're doing here is instead of using the yaml file that we typically would do we're creating that array and we're creating the content of that file dynamically and passing it directly into Drupal so it knows which files are associated with this library does that make sense? yeah this is probably the trickiest part of this whole thing everything else has been very very simple okay so we're adding this in and then the now that we've actually got a library defined we have to actually add it to a page in this case we're using hook page attachments alter because we're going to add it to every single page again in the theme of keeping things simple great done so at this point if you were developing this you've probably been going through and turning the module installing it uninstalling it testing it getting bugs seeing what happens maybe not I mean that's my experience but at this point I noticed that I went in and I had a I had an asset that I created and I changed the title I'm going through checking revisions all this stuff works and then all of a sudden I look and I've got a backlog of test one test five test two test seven every single time I'm changing the title it's creating a new file and so I have to have a way to go through and be able to delete that because if it's named exactly the same it will delete it automatically but if we change the name once that asset's been created it's going to create another one and so we're going to end up with stuff floating around out there so basically what I did was created another method here called dedupe it grabs the the actual old URI that will need to be stored in the database it generates the new one for the current asset in case we've changed the title compares them and if they're different then we call delete file on the old one and you'll notice that every other time we've called delete file we haven't been passing in a parameter because it's just been assuming to use whatever the current asset is but because we added that parameter and we now have the ability to with the same method pass in this old one so we'll delete that before it creates a new one so we've got this in place and then this is that the final step here so we need to call the dedupe and we also need to add the URI to the database because we haven't done that yet we haven't actually created a form space for it we don't want someone to just write it in because it's going to be wrong and the the trick is that this has to be done before you save the new entity because as soon as you save the new entity it will write the URI then the one in the database and the one that you've generated will both match so can anyone guess how we might do this anyone have an idea it's okay everyone's asleep so basically what we're going to do is we're going to go in and do this in the presave and the presave was actually generated for us as part of the boiler plate so I don't have to delete that I can just drop this stuff in and we're good to go saying dedupe is not found not copy this in let's see yeah I put it in the wrong place didn't I I did that's why it's a total risk okay it's also why it's good to have a nice IDE in place because it can help you catch those things before you start ripping your hair out lovely okay we have our module and it should work let's find out I'm only slightly nervous about that okay so let's enable so far it hasn't blown up this is good okay so step one we're able to enable the modules turned on and nothing has been destroyed so far step two we check the site and it's still going so this is good so step three let's go and actually test this thing out so we have the asset list let's go ahead and add an asset we'll call this test number one it's a CSS and we'll do something that will be easy to see that it's changed that should let us know and we've got revision so that's cool let's see I might need to clear cash again get it started always have to be mindful when you're running a demo that something is going to go wrong so you're just hoping it's not something big also don't be afraid to tell people that when you do mess up that's how they know that it was live and you did it on purpose just to prove this isn't can so apparently this is working so let's go take a quick look and thank you thank you the bar is low that's good so it did create our folders it's actually created our test one I want to see something here let's go back to our new asset and let's change it so let's change it to change title we'll add something else I'm not a designer folks and then let's watch the file find her over there and just see what happens oh okay boom so it deleted the old one and it created the new one everything looks like it works and I probably have something wrong with my caching that's lovely but I think that at that point I think I've earned my woohoo on that so that was that's really what I was trying to get to with this entire session so why does this make sense to do this as a custom entity other than because I wanted to have a session that would just demonstrate that a couple things one is we created we needed a custom schema for this and more importantly we have the ability to put this all in a single table so if I go create a new content type for this and I try and do something weird every single file field that I add is going to add another one or two tables in the database so if you have a custom entity let's say you're pulling something in from an external system and you've got 20 50 pieces of data that's going to be a huge mess in your database but even if you do nothing else but just give it a place to live that's all in a single table that's going to be much more efficient it's going to keep things much cleaner so that's a really good use case for having a custom entity I had some additional methods that we needed particularly around files and saving and all of those fun things and we had some additional workflow steps clear caching which apparently I need to dig into a bit more and then we were also taking the default methods and we had to extend them and part of that was due to the new structure part of that was due to the dedupe kind of garbage collection thing so extending those default methods anytime you're starting to do something crazy that might not be a good time to start looking at a custom entity so a couple of final notes looks like we're just about on time here first off the best way to learn is just to get in and play with this I mean I'm pretty confident that these were very simple functions everyone seems to get each piece individually and all the pieces make sense and I think some people were looking at me like I was a doofus for even explaining it doing it versus seeing it is going to actually help it sink so I highly recommend that all you need is a little bit of time if you wanted to use something like this as an example if you want to just take a Drupal console generate your scaffolding and go you should be fine and above all just keep things simple and don't try and architect the end state out of the gate a much better way to go is to find that MVP do as little to see if it works give yourself a high five and then just add another little bit of complexity on top of that oh sorry one last thing homework I always give homework sorry so everyone that's here now should have the basics they need to create some type of custom entity custom module so I think it'd be a really great thing to do be even better if you were able to take that and either port a module from seven to eight possibly create your own or at least help someone get started on that process by just going through and doing something rough and dirty and then they come in and help you clean it up there are a lot of resources out there that you can go take a look at that are way better than this so I would highly recommend you do that and then finally in your next project don't look for an excuse to use a custom entity but keep custom entities in mind as a way to solve a problem that otherwise might be difficult or hard to maintain confusing etc and with that I'm done unless there's any questions yes if you send it to me I'll grade it and I'll put a sticker so yeah in fact do you get a sticker just for asking that's good again it depends on what I'm trying to accomplish if it's going to be if I'm going to create a content type so the question is when do you choose content type versus if I'm going to create a content type that is going to be incredibly complex and I don't necessarily need the flexibility of the UI to be constantly changing and shifting that around I'll probably do that as a custom entity because I want to keep that a lot more efficient in the database if that makes sense and we want to kind of lock that down now what can be really handy though is to actually go through and use create a custom content type on just a sandbox site and go through and actually work out how you want it to work what do you want to form to