 Hi, thanks for coming so I've reserved the first five rows, but besides that feel free to come closer. I Was kind of surprised when I got the auditorium, but you know it's fun So this is your first Drupal 8 and 9 module Yeah, so I am Ted Bowman. I'm a principal software engineer at Acquia as part of the Drupal acceleration team I'm Ted bow on Twitter and Drupal.org We do a lot of stuff at the Drupal acceleration team lately. I've been working on Drupal 9 preparation just Update module stuff and just trying to make sure it all goes smoothly. I worked on layout builder, but there's a bunch of other stuff that we do So how many people here have already started to make Drupal 8 modules Okay, how many people made Drupal 7 modules? Oh Great sort of my audience. How many people have an object-oriented programming background some sort already Sort of pretend that I can see your hands is very bright lights, but that's fine. No, I think I have a general idea So let's make a module. This is a lot to cover. So I'm going to go kind of quickly I'll have a little time for questions at the end, but feel free to Catch me afterwards. Also all the source code is available with tons of comments in the source code So the module we're going to make is called roll notices And basically I'm going to show you the module in eight different stages from like a hello world to a finished module And on the project page You can get the module in folders which each stage or you can use git tags Like I use up here to like go from like one to eight So what the module does is it allows? user admins to set notices for each users and then it's displayed in a block and it's displayed other ways It's always trouble when you're making a Teaching module to find something that hasn't been done already So example would be you have a message to your editors that says check that spelling and admins stop deleting people's accounts So we're going this module is going to provide a permission. It's going to provide a settings form It's going to provide a block. We're going to use a service. It's going to take into account caching The last two I don't think we'll get to here But it'll add an extra field to your profile to show you your messages And it will allow the ability for other modules to alter your notices But if you look at the module code later on those last two items will be there also So let's look at the finished module just to see an idea of like what we're trying to make so basically When we go to list people, there'll be a new tab. Let me bump this up. There'll be a There'll be a new tab called role notices and when we click that there will be a There'll be a text area for each role that we have and as we add new roles that will show up there we'll be able to You know have notices for each different role and then when you go and see the block so this person is an administrator and Also an authenticated user so they see two messages. You're great and you're the greatest admin So that's the basics of what we're going to cover today in the module at The module source you would actually be able to also have an managed display say like, you know show your notices or whatever here And also there would be the ability there's another module Submodule that alters those notices before they go out to show you sort of how hooks work because we still have hooks in Drupal 8 Okay, so I think that is pretty much the end of my slides go into slides mode for a moment Let's try it. All right So what we're going to do is I have the module right now checked out at the simplest Not the simplest. Okay. I have this little script. I'm gonna go to the hello world example. It's gonna check out the module It's gonna check out the module at the earliest stage and it's gonna clear cash So I'm not gonna clear cash each time I do something new I'll try to mention when you need to clear cash, but anytime you like have a new route or new block You're gonna need to clear cash so If we look at this The first thing that we need to do is we need to have a dot info dot yaml file and This is going to determine the machine name of your module. So this is roll underscore notices dot info dot yaml so this is like the Info files in Drupal 7 So very similar except we're using yaml format We have the name type module because it could be a theme or a profile description package We're gonna show later how we can make this for a Drupal 9 to but Right now where it's simply a Drupal 8 module And then just has one dependency on block because one of the main things it does is it displays a block So there's no need in having the module if you don't have block enabled though most people do So that gets it on the module page. I think I've enabled it, but I'm going to Disable it just to be sure So I'm just disabling the module so we can start from scratch Okay, so it's successfully disabled. So we will go to extend and We will look in the search for roll We see our module here. I thought you disabled it. Yes. I'm in the wrong tab Okay, so it should be disabled on this one So if I look for roll here We have roll notices. We have a description that we saw there and then we have our dependency on block So let's enable it So the first thing that this first stage is going to do is it's sort of like it doesn't really affect the other Work with the other part of the module, but I just wanted to get something out on the page a hello world So how we do that is we need to create a route So if you're familiar with Drupal 7 there was hook menu which did a whole bunch of things That's been split up into a bunch of different areas And so the the ability of hook hook menu to say at this particular path I want you to call this function has been moved to your dot routing dot yaml file So again, it's my machine name of my module roll notices dot routing dot yaml and then In here we have individual routes. So this is a hello world route has a machine name there has a path And then the controller basically says what's going to take over this so I could technically just Have this go to a global function, but that's usually not done in Drupal 8. You usually have a class And then a method on that class So basically needs to be something it can call So if we look at and then we have a permission So the permission here is saying if you don't have this permission you can't get to this route Or you can go there, but you go access denied So if we go here This is very similar to Drupal 7 in that I'm returning a render array. It's of the type markup and And then my markup is hello world that is translated with the t function So I could use the global t function here But usually if you're in a class you should not you can use the stink string translation And it's going to trade which will provide you with the t function and then also like I think singular plural translation stuff So pretty similar to Drupal 8. So let's see what that looks like if we go How we would find it if we didn't have a menu item is we would just copy the path go to the site and Hello world live demo gods so far have been Shining on me. Okay, so that's what we do. We can see that if we change it It should change With it. So let's put a little emoji say that So we have our emoji here. So So yeah, hello world, you know, this is the basics of getting something on a page So the other thing that this first stage of the module does is it creates a menu item? so if we look at the roll notices dot Links dot menu This is again in Drupal 7 was part of hook menu. I think if I remember correctly So we're providing again a machine name We use for our machine name usually the machine name of our module dot something else so that we don't clash with other modules Title description and then a route name. So basically it doesn't have to be our route It just has to be a route that exists That's going to be Where Drupal should make a link to that route and then the parent Menu item so if I search this I would find this in another links dot menu provided by the system module So how we see that menu is if we go to System reports There should be a new item roll notices. Hello world same page. We just saw just providing the menu item for there Okay, so let's go to Let me look and see if that's all the files and here. Oh, yeah, here's where we define our permission Again, this was I think hook permissions in Drupal 7 We have a machine name here, and then we have a human readable name if this was a I forget what the call if it was like a security permission then there would be another Section here. I forgot what the key is but basically to say this don't only give this to trusted users We don't have to create our own permission. We could simply in our route use access Content or content access whatever it is there for that's provided by the node module actually is provided by the system module now and But this is an example of you know, we want to say okay only people who have our permission can see our form to set the notices Okay, so Let's go to Step simple form. Let's go to a slightly more advanced form. Oh, I forgot to mention one thing about the No, we haven't got to the form yet. Sorry. I'm gonna skip back to Yeah, okay, so now we're at the simple form state, so we have a new route up here that is Roll notices settings form. We haven't we have a different path and in this time instead of pointing to an actual method on a class When we have control the underscore controller, we have to point to something a method that's callable It has to be a public function Here if we do underscore form this has to point to a class that implements the form interface class and as long as it implements the form interface class correctly then Drupal will know What to do? Let's see how that works and again, we're gonna use the same permission so This is our class that I pointed to and then we have form base here so form base Implements the form interface. I was telling you about So because it does that I have to have a good get form ID And I just have to return a string there It's unique to my form and then what's actually going to make the page to make the form is the Build form so in Drupal 7. I think this was like in hook menu You would point to a form callback or something basically it was like a global method a global function here What we're actually returning is very similar to Drupal 7 in the sense that it is a form array So the form API documentation so on the link inside the finished module is a link to more documentation on this But basically the simplified version is we're making one text area and we're having one submit button and How we're gonna let's see that looks like roll notices So here of my test message here says it saved I can see that when I reload the form it's still there So how it saves it in this case is because this module is meant to save the notices on Production so the idea is you don't want to have to move your notices from Development via config onto your production server. You want to be able to just set notices live So we're using the Drupal state service. So this is basically we'll save this on your local state So it's not deployable And then I call so this is how I get a service from Drupal which will make our own in a little bit So that'll have more information about how services work but the state service has This method called a public method called get where I can Get of us state setting and I'm saying the default should be empty string So basically if it hasn't been set just return me an empty string Let's see if this is so one thing's nice I'm using PHP storm is I can just say okay, I can do control B here And it'll take me to the class that implements this service. That's because I have a couple Plugins added the symphony plug-in the Drupal symphony bridge plug-in And I think the annotations plug-in and I always forget like where each one stops But if you have the combination of those three it's sort of aware of services and what class they attach to So I get the I get the value in this case if we never install the module before or we haven't set set something before Then this would be a default value of just empty string, but as soon as we submit it once So because we implemented the form interface it knows to build it here And it knows Drupal knows when I submit it to call my public submit form method So here when I submit instead of like in Drupal 7 your submit callbacks, I think we'd both get two arrays We get an array for the form here of the current form But then the form state comes back in a form state interface object So instead of like going through a nested array. I can just say okay, the method is get what's it get value and My value here is the same value as my key for my text area. So notice So these two are the same and so I have all for all of the elements that I would have in my form I would have a corresponding Value item in the form state. So this case it would be a string In this case, I use the Drupal state service again. Oh I do it do different ways actually I forgot so up here. I'm calling it in a way that says I know there's a service called state But on the Drupal class, there's a couple helper functions for like really commonly used services and so The one of them is the state one which basically if I looked at it just says get me the state service So in this case instead of calling get this is the form submit So I'm going to call set again I use the same key as I did up here So when I get it roll notices dot test when I set it roll notices dot test And then I just set this notice here and I send a message and you'll notice here I'm using the deprecated Drupal set message. So we'll see later on how I will remove that and it'll make it all Drupal 9 compatible All right, so let's go to a little more advanced form So here I wouldn't actually have to clear cash because I haven't changed anything that Drupal needs to Cash as far as like definitions or anything Since my class hasn't changed it would still call this method each time it made the form So this is a little this is more complicated. I'm not going into all of us right now But basically what I'm doing is I'm getting the roll names I'm just calling this global function that Drupal provides So this will have the role IDs and then the role the role IDs as the keys and the role values will be the role names and Then I'm making a form element as it can sort of like as a container here Basically, I'm setting this pound tree, which means that everything below this that's nested in this array will come back as We'll come back as one array instead of me having to get like notice for authenticated users notice for administrators, so I Basically what to do as I go over each role and I make a text area. So instead of Having one text area that I had before all of these are nested with the role IDs This is pretty much the same The only difference here is that when I get the state here I'm saying if it nothing has been set give me an empty array So the idea being here that instead of storing a string now in that state, and I've changed I've changed the key slightly I'm gonna store an array Because I want one notice for each string so I don't have to serialize it anything myself And the submit function is I think exactly the same except for the key for my value for my form state thing Has changed because it's notices and the key for my state has changed from notice to notices So I'm just going to save that in the same way that I would and then the messages the message is the same I've actually updated here because I was doing it stages. I forgot but we'll go back to it later to update to Drupal 9 So let me show you how that works so I reload this I Have I think I made a few role I made one extra role editor so I have editor administrator and authenticated user I've already filled out these values I'm going to change this one say hi admins and for editors. I'm going to say check your Spelling and I'm going to spell it Incorrectly and I'll save this and we can see that you know if I reload it It saves this because it's saving it in the Drupal state and bringing it back each time Okay, so for the next one. Let's make a simple block. So the idea is there's no point in saving these Notices unless I can display them somewhere So we're going to display them in a block so that the site builder can place the block wherever they want to So blocks are provided in plugins in Drupal 8 So a lot of things that in Drupal 7 were like hook something info I think there was like hook block info where you'd say, okay These are all the blocks that I provide a lot of that in Drupal 8 has moved to plugins So instead of having a hook where I say these are all the blocks that I provide I Make the plug-in in the correct folder here. So if we look at this inside my SRC directory and all of my All of my source code should be in this SRC directory I have a plug-in folder and then under that I have a block folder and usually to learn this I mean you can look at the the plug-in manager for blocks But usually the easiest way is just to look where other modules put their plug-ins for like if you want to block plug-in or field format a plug-in So I have a roll notices block dot php file which corresponds to the same class and in this one One thing is important here is that I am In this class. I have an annotation. So this is metadata in Comments some people call it code in comments. It's really more metadata in comments So I use this at block to tell it. This is a this is a block annotation I give it an ID so roll notices and then an admin label So this is the label that will show up on the block layout page And I have a link here if you go to the source code some more about how this works After you get used to it It's pretty easy often times if you use Drupal console, it'll generate this for you But also if you just copy another block and then just fill in your own values there. It's usually pretty easy to This block is extending block base almost all blocks That you would make yourself you would do this You don't have to but has a lot of helper functions in there for you and has implemented some stuff for you This is common for like field formatters or field types. They'll have a base class that you usually can just extend and the base class will let me see if I Like it won't Extend the things that I need it won't give me the methods that I absolutely need to implement so obviously The the block base can't do the build for me because it doesn't know what should be rendered in my block Right, that's up to me. So if I if I forgot to do the build one here So I'm going to change this to build X PHP storm is going to say hey, you haven't implemented the build function So the build function technically all I would need to do here is return our render array So it's very similar to Drupal 7 as far as what it expects Here I this is similar to what we did in the form in the sense that the first thing I'm going to do is I am going to try to make this text a little smaller. Oh, it's my Yeah, okay So the first thing I'm going to do is I'm going to use the same Drupal state service And I'm going to get the key that I saved in the form submit function The same basically this is straight exactly the way of my form submit works Then the next thing I'm going to do is I'm going to call A helper function that's going to get me the current user and after that I'm going to get the roles So this sort of this line here basically what I'm doing is saying I know which roles I have I know they correspond to certain role IDs and I know my notices I know their keys also correspond to role ID So I'm going to intersect the two and I only want the notices for the current user's role So after I do that Again, I think this is very similar to Drupal 7 is that I have this pound theme function and Or sorry pound theme property in my render array. So this is basically saying I don't want to determine How the HTML is ultimately rendered? I want to tell Drupal. This is a list Render this however you render lists and if I looked at the template file It would say okay I need something called items which is going to be in my case going to be strings So I just pass in the notices that I got from the state and then it just does a whole bunch of stuff Basically passes it ultimately on to the twig fun the twig file and renders the list so In this one in this session, I'm not going to really say how to make new ones But you can also provide new I think it's hook theme. You can provide new templates So let's look and see what that looks like. I may have already placed the block. Let's go back to the Home page and see I have not placed the block. So we're going to go to structure block layout and This block will just be like any other block if I go to sidebar first Roll my role notices block again So this is directly from the annotation and the mod and the category is from my module name Though I think I can override it in the annotation, but if I don't it comes from my module name So this comes from the annotation I place block I am going to just not do anything different. It's there. So if I go back to the site then over here I have my Hello These are my two messages because I'm an authenticated user and an admin. So basically if I looked at the source the twig The twig template would have just made it an unordered list with the two items here. All right. So it renders are basically So I'm basically only in charge of a couple things if I'm gonna make a block the very minimum is I got to do block access because I don't Because I'm not using I'm not allowing messages for the unauthenticated users I could have done that but I'm not only thing I'm doing a block access is checking to see if they are authenticated so what I do is I get the block access will send me an object of the type account interface and On that there's a method called is authenticated. So that's going to return a true or false and then here What block access is going to return again like the definition is actually in the interface itself or the documentation It's going to return a It's going to return an access result object So this is basically saying Allow this if this is true and this will be true if the current user is currently authenticated So if I was to look at this as an unauthenticated user, I would not see the block at all If I logged out, I wouldn't see the block at all So that's the basics of the build Let me see if there's anything new in this step. I have this little script that tells me what files changed Okay, so only this file changed in my test. So there are tests if you want to look at Just for my sanity. So I don't break the module as I change it But I don't go into that here Okay, so Let's go back and we made the block So we're gonna add now we're gonna next thing we're gonna do is we're going to Move a bunch of stuff into a service. So right now we have a few things going on Multiple places the same here. We get the notices the same in the form We get the notices the same and in the finished version of the module We also get the notices when we make the user profile Field so the idea here is I'm doing the same thing multiple times the same way and my Form my block and ultimately my field all have to know how to get these notices And if I wanted to change that it would be difficult. It's also would be super easy for me to accidentally Change the some letter in this key and get it wrong and I wouldn't get an error But it would basically say well, I don't have any notices. So What I'm gonna do here is I'm gonna make a service to basically Encapsulate the getting and setting of notices so that all of these classes have to do is load the service and say Get me notices or set notices. So that's the next step Which is Five simple notices manager Okay, so let's look so To make a service basically all I need to do is in this I Have a class or sorry I have a yam another YAML file called role notices dot services dot YAML and in that file I have this services top key and it's basically To define a service the very minimum is just to say I have a service. Here's the machine name and I have a class that is that service and Drupal will take care of constructing that class for Each time somebody asked for the well not each time because it's one instance of this one instance of the class Anytime asked anybody asked for services the same one. So if I go to the notices manager This is a class. I just put it at the top level often the often modules put it just right directly under src And This class if you notice it doesn't like extend anything though. So there's nothing special about this class It doesn't have to extend, you know services space or whatever So I have a few public functions on this or public methods on this class one is get user notices One is get all notices and one is set all notices And you'll see here basically the get all notices and Set all notices are doing the exact same thing. I was doing before when I was getting a notice is just using the state service getting and setting up at the top for the Get user notices. I'm doing what I did in the block where he said give me the notices and then determine what you what Roles the user has then intersect the two So I only get notices for the user So basically I made this class and to load it or to use it in the form. I Just say Instead of calling the state service I call my own I tell Drupal load my service here and Then I say get all notices because in the form I want to display all of I want to be able to edit all of them Not just the use of the current users So and here the form that's the only line that's changed in the form or two lines and then down here I do Drupal service and I get my the same service and I say set all notices sending at the form value So basically the form has changed very little just instead of knowing directly that at stored in state. It just says hey Notices manager. I want notices and I have notices. I want to save now So it doesn't care how it saves like so I could easily if I wanted to change this module to say hey I would really like to stage save these notices as configuration all I would have to do As far as the form is can form and the block is concerned nothing would change Because it doesn't really it doesn't know how the notices are stored at this point. So the block class The only thing that's not some manager again The block class the only thing that has changed is instead of it having to know Instead of it having to know how to Basically, you know, it has to know before it had to know to intersect the roles that the user had and the notice The notices that existed to make the notices it just says get user notices. So the service takes care of that The other thing that's nice about that is later on in the module when I provide the altar so that modules can alter which services Which notices appear and there's a sub module called role notices weight That says only get the notice with the roll with the highest weighted lowest weight I forget but the idea here is that if When I get the notices and display them in new ways like on the user profile each place doesn't have to know to call that altar hook So I'm not going to show you anything on the site because there's not there's no Functionality from the user point of view that's actually changed at this point It's just sort of encapsulating the logic that I had before into a service Alrighty so We're on five. I'm going to load six So right now actually I'm going to Not do that. I'm gonna go back to five because I want to show you the problem that six is solving. So right now We haven't told anything to Drupal about when to change these notices like so the block We haven't said hey when somebody updates updates notices make sure you re-render the block. So right now let's look at the site and I am going to yeah, okay, so I have hello and high admins and I'm gonna open up another tab and I'm going to Go to roll notices and I'm going to say Thanks for being a member So I've changed the notice Actually, this is going to update It's going to update now because I cleared cash when I change the get files, but let's do it again and Add a bunch of exclamation points and save it And hopefully yeah, so now it didn't update So the first time the only reason that up type updated the first time is because I didn't show it doesn't you don't see this But every time I go to a new stage I clear cash So that's why that you don't want to have to obviously every time you save your notices to okay remember to clear cash So here I didn't actually bring in the new It didn't bring in the new Exmolation points because Drupal didn't know like didn't have any reason to re-render it. So let's look at how that is done Well, we have to go to the new stage, which is six and So when we do that Basically, the only thing that's changed here is that basically In my block. I have this pound cash. So I'm basically telling Drupal Hey, this is some information about when to invalidate This rendered output. So the first thing I do is put context user dot rolls and This is a context a cache context provided by Drupal core that is the combination of the user role So basically if this was the only thing I had then it would cache it for anybody who has the same roles for me as Me so the idea is that if I get the new editor role I want to see the editor messages So it has to Drupal has to know that oh when the combination of roles changes make sure you re-render this So you get the new message as most person might have conversely if I take the editor role away I should no longer see that message the other thing that I'm doing here is I I'm Return I want to also set a an array of strings which are render tags or sorry cash tags and here I just have an element in the array called tags and Here again, I'm using my manager so up here I call the service and set it to notices manager and What I'm doing is I'm calling I made a new public function called get render tags Because I don't want the block itself and ultimately the user field when I have it to have to figure out how to do that So here I just basically if I get a bunch of role IDs I make strings that's role notice colon role ID role notice colon role ID So for any for all the notices that are displayed. That's what I would have here So I am going to kind of I'll I'll show you that it works and then we'll go on to the next one because we have very little time Okay, so I'm gonna save this so it didn't before it didn't update Now it should update Yeah, so it updated there so So basically I think that's it for this stage was just to add the cash tags. Where's my block? and One thing if you do test out this module user one has a different Doesn't actually use its combination of roles It always has this is super one So this if you test this out test it with not user one for this the tag thing will work But the way core does roles for user one is different But it will still user one would still update it with new notices Okay, so I'm gonna skip dependency injection for right now But if you want to take a look at it that step is in the one so we can see the Drupal 9 thing And the dependency injection step also has a lot of inline comments to explain what's going on So what I'm going to do now is I'm going to go to step 8 where I go back and I make sure that I'm using a deprecated function So I have something to upgrade so one thing about this module is the last time I did this session was in 2015 And so I got this module out dusted it off And I ran the upgrade and the only thing I had to change besides the info file was this right here Drupal set message so the thing two things I'm gonna have to change the Drupal set message and Something in info file, which I'll show you in a second I have the update or module update status upgrade status module and I'm not basically I'm not gonna run it right now. I've already run it and What it's telling me is hey Drupal set message needs to change So it's basically telling me what I actually need to change it to and it says if you want this to actually be compatible with Drupal 9 Here's what you do. I am going to do that switch to 9 compatible module And I won't even show you this on this site. What I will do is I will copy this So I have my finder for my Drupal 9 site here open Drupal 9 And then I go to my module and So I'm gonna go to modules. I have nothing in there nothing at my slew I'm gonna drag this onto here. So now I have roll notices here on my Drupal 9 site the thing that I had the other thing besides the Drupal set message that I had to update was I Hope I didn't take it out of this one. I hope it copied it. I feel feeling it didn't It didn't I can show you it on this one. So we're gonna skip to Adam Okay, so I besides the Drupal set message thing I had to change this core version requirement to this is a composer constraint that to the right of the colon So I could be more fine grain, but I'm basically saying any 8 or 9 that this should work on and then if we look at the form then Down here we have the I get the messenger And then I add messages and this is just from form base gives me a little helper to get the message service and Let's go to Drupal 9. This is my Drupal 9 version Go to go to extend Hopefully I didn't already enable it roll So roll notices so it recognizes it be basically it recognizes it now because the info dot YAML file I enable it and Voila, I should Everything should work So I'm out of time, but I've already had this enabled on the site. That's we have the one two three So basically That's it. So it's not really like the big takeaway is Drupal most in most cases Drupal your Drupal 8 module is very very close to being Drupal 9 compatible So I don't think do I have time for questions? Anyways, I don't think I do but feel free to come up and ask me questions. So thank you