Table of Contents
Video 12: Code Generators, Part 2: Creating a Controller
The twelfth VuFind® instructional video discusses VuFind®'s model-view-controller architecture and demonstrates how to use VuFind's code generation tools to build a custom controller and customize URL routes.
This is a raw machine-generated transcript; it will be cleaned up as time permits.
welcome to the second video on code generators this month we are going to talk about customizing routes and controllers in order to add new functionality to viewfind a viewfinder is built using the model view controller model which essentially means that the software is divided into a few distinct portions which allows for separation of concerns and can make it easier to customize so there are the views which are for displaying data that's our theme system there are the controllers which are the the pieces of code that contain the business logic of viewfind that actually make things happen and then there are the models uh which represent the data within the system and there are of course some sort of services that help bring all of this together but the the important point is the controllers are make think the controllers are what make things happen the views are what display things to the user uh and today we're going to make some new things happen and display some new things to users and we're going to use code generators to make the job easier another concept that's important for understanding all of this is uh the router so viewfind uses laminas's router for mapping urls to controllers so whenever you access a url under viewfind's control the router picks apart the pieces of that url and uses them to decide which controller code to run and that's really how everything happens in viewfind routes are defined through configuration uh which can be put anywhere in the the frameworks configuration stack but currently most of you finds routes are found inside the viewfind code module and i'm just going to show you in the code under module viewfind config and module.config.php at the very top of the configuration there's this router routes section and this contains a number of predefined routes you can override these or add to them in your own local module if you need to and we'll get to that a little later but i just wanted to show you as a useful example the default route which is what viewfind will use to match urls if it finds no more specific route later in the configuration this is using a segment routing method which is what allows us to define the route with these little named placeholders brackets indicate optional portions so what this is saying is that if you have a url that starts with a slash and then optionally has some more text and maybe optionally has another slash and some even more text that first chunk of text between slashes is going to be used as a controller variable and that second part is going to be used as an action we have some constraints on these parameters so the route will only match if we have alphanumeric or underscores or dashes in the in the values and we also have some defaults so if one of these sections is omitted uh it'll use a default instead so what this is showing us is that when you go to view finds base url you go to the home action of the index controller but if you put names into the url you override these defaults and you affect which controller and which action get executed so for today i'm going to create a new controller called tutorial and put some functionality into that so based on this route you can see that if i were to just add slash tutorial to a viewfinder url what's going to happen is action is going to default to home because i omitted it and controller is going to get overridden with tutorial so let's just try that and see what happens not surprisingly i get an error message telling me controller is not found this is to be expected because we don't have a tutorial controller yet uh i should also note that i'm seeing this detailed error message uh because i've turned on development mode in viewfind if i had not turned on development mode i would see a generic message that would be far less informative it's always a good idea to have development mode on while you're developing because it helps with troubleshooting like this that all being said let's go ahead and make ourselves a tutorial controller so that we can start attaching some behavior to this url i'm going to assume that you already have a local module called tutorial and that you're somewhat familiar with the plug-in code generator because we talked about both of those things last month so i'm going to go to my viewfinder home directory and i am going to run the plugin generator to build myself a new controller inside my tutorial module so i do this by saying php public index.php generate plugin and i'm going to call my controller tutorial backslash backslash controller backslash backslash tutorial controller so the first part of the namespace will tell the plugin generator to generate code in the tutorial module the fact that the second part says controller will tell it that we're building a controller and then it will create a tutorial controller class uh one important feature of the plug-in generator that i'm going to take advantage of here is that the last parameter you can specify is the name of an existing factory for building your new class if i were to omit this parameter it would just create a new empty factory that i could fill in but because all of viewfind's controllers extend an abstract base class that provides some helpful functionality and requires some specific constructor parameters i'm going to use viewfinder's existing default controller factory which is named viewfind backslash backslash controller backslash backslash abstract base factory so this will tell the generator to reuse that existing factory to build my new class instead of building a new empty factory that i would have to fill in and also a reminder that i have to double up all of these backslashes because of the quirks of the command line but what i'm actually generating are class names with single backslashes in them as one would expect so there we go i have just created a tutorial controller and configured it in my local module so now if i go back over here and i refresh this page i have a new error message it now says error controller cannot dispatch this is because i created a controller and viewfind was able to find it but i haven't defined the home action that the route is trying to execute so my next task is to define a home action so i'm going to go over to my editor and go into my tutorial module where i will see that the code generator has created this controller folder under my source code and built an empty tutorial controller so in lobinos mvc the framework that we're using controllers are organized as a class containing methods whose names end with action so because we want to route to a home action i need to create a public function called home action and put something into it uh as i mentioned we're using a model view controller system where the views are separated from the controllers and the way that these two pieces of the system communicate with each other is that every action returns what's called a view model which is just a container full of values that the view will have access to uh to get started with we don't need to send any values at all we're just going to go with the default template render to begin so we can return an empty view model with no special data and as i mentioned viewfinder controllers generally extend this abstract base class which includes a helper function called create view model which will give you an empty view model automatically with some useful values pre-populated so i'm just going to start out here with this simple uh public function home action that simply returns this create view model with no parameters so i've now defined an action that returns the right kind of value so let's refresh this page and see what our next error message is all right we've got a new error message we were now able to find a controller and dispatch an action but now uh the framework is trying to render the template associated with our action which by naming convention is going to be tutorial for the controller slash home for the action but we've created no such template yet so viewfind couldn't find it so it created yet another error so let's go build a template in our theme so that we can resolve this latest problem so if i go to my tutorial theme and my templates directory i just need to create a folder in here called tutorial to match my controller and then i want to create a file in here called home.phtml to match the name of my action and the phtml extension just represents a php html template and for now all i'm going to put in here is hello world so now all of my pieces are in place i have uh created a controller and a route and a template so if i refresh the page hello world i have a message displayed so this is how you create a new action but you know displaying a piece of text is not that exciting why don't we look into how we can actually interact with this a little bit so suppose rather than saying hello world i wanted to be able to specify my name uh as a parameter on the command line so i could add question mark name equals damien and then i would like this to say hello damien instead of hello world the framework makes this quite easy to achieve first i need to go up to my tutorial controller and add some some logic so every controller in the framework has access to a helper called params which you can access uh by calling this params with parentheses and the params helper has various methods on it you can use for extracting values from the incoming request there is a from query method where you can specify the name of a parameter and also a default value so if i say name equals this params from query name comma world that means it will set the name variable equal to the value of the name parameter in the query and if that is undefined it'll just use world instead so i also mentioned of course that view models are used for passing values to the view and this create view model helper function that you have access to in viewfinds controllers lets you specify an array of values to pass so i'm just going to create an array that includes this name value i've just pulled out of the request and passes it through as name having done this uh i will now have access to a name variable in my template so i can replace the word world here with a php echo tag that displays this escape html name note that it's always important to remember to html escape values that you display to prevent injection attacks or simply garbled displays so now just by pulling a value from the query in the controller passing it to the view displaying it in the view i have some interaction in place and sure enough when i refresh the page it says hello damian and if i take my get parameter back off again the default kicks in and i still get hello world since we've made some nice progress here i'm just going to take a moment and do a git commit to save all of this progress and so that when i do some subsequent work i can more easily show you what has changed since this initial baseline was set so it's nice to be able to specify get parameters but suppose you want to actually seamlessly integrate a value into the url itself so say for example we want to be able to go to tutorial slash home slash custom location and have the display tell us that we have entered custom location if i do this right now i get a no route match instance provided because i've created this three part route with multiple separated values and there is no route configuration in viewfind that knows what to do with this the default route expects only two pieces and we've just provided three so if we want this kind of a route to work we need to generate a custom route fortunately we have a command line generator tool that will do exactly that so if i go back to the command line where i'm in the viewfinder home directory i can run php public index.php generate dynamic route and then i need to specify the name of this route which i'm going to call tutorial dash home uh the route name is important both because it's used as the key in the configuration array so that you can potentially override it by name in a later part of the configuration process it's also important because in templates you can use the url view helper to convert a route name into an actual working url which is a great way to abstract your urls and make it possible to override and customize them anyway i've named my route tutorial home here then i need to say which module or rather i need to say which controller i'm using the tutorial controller and then i need to provide uh the remainder of the route that i want to match so i'm going to say home slash colon location this is using the router's segment syntax where you can put any name beginning with a colon to use as a placeholder that will be passed through to your controller as a route parameter so the idea here is that we want to allow a location name to be appended after the home part of the url finally i say tutorial for the name of the module where i want this configuration to be added so when i hit enter here it automatically makes some changes to my tutorialsmodule.config.php i should also note that dynamic route is just one of several uh route generating tools that viewfind includes you can also create static routes for simple pages that don't take segment parameters uh and you can create record routes for linking to things that act like you find standard record view you can find more details on all of this on the generator page of the wiki but for now let's just take a look using git diff at what the generator just did for us and as you can see it created a route named tutorial home like i asked it to it's a segment route because i used the appropriate generator to create segment routes and the full route is slash tutorial slash home slash location placeholder it got the tutorial part from the controller name i provided and the rest from the action i specified and then it has defaults of tutorial controller and home action because we want those values to be hard-coded in because we don't have controller or action segments in the route match so let's see what that did for us if i go back over here and refresh the page sure enough uh i no longer have an error because there's now a valid route that understands what to do with tutorial slash home custom location but i just have my default hello world message because i haven't done anything with that route parameter yet thus i need to go back to my code again and back to my controller and as i mentioned the controller's params helper can get values from a variety of places and from the route is one of those places so i can say location equals this params from route specify location which matches that segment name uh in the route configuration we just looked at and just with uh just as with query parameters i can also provide a default value should that be omitted so i'll just say default location here and i need to pass that location value along to the view through the view model so i'm also going to add down here location key to point to the location variable save that go over to my template and now i can just say you are in and again i use a php echo tag to display an escaped version of the location that comes through from the route now if i go back and refresh again hello world you are in custom location i can add my name to this and it will say hello damien you are in custom location pretty neat so these are two different ways you can provide values from the url and communicate them to your controller and this explains how a lot of the urls in viewfind actually work so let's just do uh one more thing what if we wanted to have a less complicated url we don't like this home portion so we'd like to just be able to say tutorial custom location right now if i do that of course i get cannot dispatch because there's no such thing in my controller as a custom location action but we can take advantage of the router to just change the way the tutorial controller behaves and make this a little simpler to build our urls all i need to do is go into my tutorial module and edit the configuration there so let's find my route down here so right now this route is very specific it has to have slash tutorial slash home slash in it or it won't match and it also has to have some kind of a location placeholder but let's make more things optional so first of all we don't want to say home anymore that's not interesting to us uh next let's make the location value optional so that if you leave it off you can have a default let's also make this trailing slash optional we'll wrap another pair of brackets around so now if you go to [Music] tutorial by itself you're going to get the home action if you go to tutorial slash something you're still going to get the home action because it's set down here as the default but what we need to do now is add a default value for this location placeholder so that something gets passed through if it is omitted i'm going to say default route location because this will be useful for demonstrating something helpful uh the other thing that we might consider doing is adding a constraint for our location placeholder these default controller and action placeholders are actually meaningless in this context because these values can't be passed in but they're included in all of youfind's routes just as sort of a safety mechanism and example but since location is something that users can provide input to we might want to control what characters they're allowed to pass through in that position to protect against attacks or unexpected behavior so i'm just going to put the same constraint on location that is used for controller and action uh to require it to start with a letter and then follow that with the sequence of letters numbers and separators so with all of this saved if i go back into the browser and refresh my page now i'm at my shorter version of the url and it's working again because the router matched this pattern and took us to the home action and passed through all of the values the other thing i wanted to show you is that if i leave off this location placeholder it now says i'm in default route location that is because i specified a default value for location in the route and even though my controller when it reads the location from the route uses this default location value if the placeholder is missing the router default is going to get applied first and that's going to take precedence over the default in my controller so that's all i wanted to show you today you should now know how to build routes and how to build controllers and also perhaps understand a little bit better about how user input from the browser flows through into viewfind's internals and how data gets displayed uh if you don't want to build a whole new controller there are also other generators you can consider using such as the extend class generator that you can use to override an existing controller in your custom module so you could override just one method or you could add a method to an existing controller you can also as i mentioned use route names to override specific routes in your local module if for example you wanted to change the text that displays in the url for certain kinds of functionality these are all things i'd be happy to demonstrate on a future video if there's interest so let me know in any case thanks again for your attention and i hope this has been helpful