In this video, we’re going to look at the Craft application bootstrapping process, in other words how a web request is handled by Craft, and how it ultimately determines which action to call, or which template to render. In this case, we’re visiting the homepage of the site, so our URI is a blank string, and we expect the request to be handled by rendering the
Every web request that the server receives is going to end up at the
index.php file, which exists in the public
web folder. And we can see that this is the case by opening up the
.htaccess file that Craft ships with. Here we can see that all requests that are not files or directories, or some sort of favicon, end up at this rewrite rule which redirects the request to the
index.php file, appending whatever comes after the base URL to the path parameter,
So let’s open up the main
index.php file and take a look. Now, depending on when you originally installed Craft, the
index.php file in your project might look like this, or it might not. This is a fresh install of Craft, but earlier Craft versions shipped with a slightly different
index.php file. When you first set up your Craft project, you very likely did so using
composer create-project followed by the
craftcms/craft repository, or perhaps a starter Craft project, but either way, running that command pulls down all of the files and folders that Craft requires, into your project’s root directory. Now, whenever you update Craft, you’re updating the
craftcms/cms repository, so only files in the
vendor/craftcms/cms directory actually get updated. Updating your version of Craft has no effect on the files in your root project, which makes perfect sense since you don’t want any of your project-specific files being overwritten. Your
composer.json file, for example, lists all of the plugins and packages that your project requires. Similarly, your
.env file contains all of your project’s environment variables, the
config folder contains your configuration and the
templates folder, your hand-crafted Twig templates. So the version of the
index.php file that you’ll have, depends on what that file looked like at the time that you set up your Craft project.
As we previously saw, the
index.php file is the main entry point for all web requests, and as we can see here, on the surface at least, there’s not much to it. The first thing that happens is that a shared bootstrap file is loaded, which also exists in the root of the project. Bootstrapping generally consists of setup tasks such as performing checks, defining constants, aliases and paths, autoloading required packages and loading configuration files.
Here, for example, is where Composer’s autoloading process kicks off. This is somewhat off-topic, but since we’re so heavily dependent on Composer for managing packages, I find it really interesting to get an idea of how Composer works under the hood. If we open up this
autoload.php file, which is generated when you first run Composer, and if we jump into the
getLoader method, we’ll see a few interesting things. Firstly, Composer keeps track of every single PHP file that your project depends on, literally thousands of files. And secondly, Composer registers itself as an autoloader, meaning that it becomes responsible for autoloading PHP files on demand during runtime. Fortunately, PHP has something called OPcache, which stores compiled bytecode, known as Opcodes, in memory, just like a caching layer, significantly improving performance when executing PHP requests.
The last thing that happens in this bootstrap file is that the environment variables are loaded in from the
.env file using Dotenv. The version of the Dotenv package that Craft uses is also something that has changed over time. Craft currently requires Dotevn version 5, however previous versions of Craft required version 3. I always recommend keeping software up-to-date, so I’d advise you to go check what version of Dotenv your project is using. If you choose to update from version 3 to 5, be aware that how Dotenv is initialised has also changed, so at the very least you’ll need to update your
index.php file and the
bootstrap.php file, which you’ll find in the
So we’ve come to the end of this file, which means that we can close it and come back to the
index.php file. Now that we’ve required and executed the
bootstrap.php file, we can proceed to load and run Craft. The file that we’re requiring here, which returns an instance of the Craft web application class loaded into the variable
$app, exists inside the
craftcms/cms repo. And you might also notice that this file exists inside the bootstrap directory, meaning that this is a web request specific bootstrap file. Let’s take a quick look at this file, which remember, is part of the CMS repo, meaning that you will automatically receive updates to it. Very briefly, we can see a check for the minimum required PHP version, a check for a required PHP extension and environment normalisation, your average bootstrap stuff, that in this case is specific to web requests. Once this is complete we send back the result of another bootstrap file, which exists alongside the web bootstrap file, but which is shared between both web and console requests. So looking in the console bootstrap file, we can see that it also requires the shared
Skimming over the comment headers in the shared bootstrap file, we can identify things such as general setup, determining paths, validating paths, loading general config settings, determining if Craft is running in Dev Mode, load Composer dependencies, and finally initialising the application and returning it. The app is going to be either an instance of
craft/console/application, depending on where the request originated – in our case, it originated from a web request. So that rather long-winded process is what’s involved in returning to us an instance of a
$app, that we can finally run.
The lifecycle process of a web request is rather involved. Fortunately, both Craft and Yii, the underlying PHP framework, take care of a lot of low-level detail for us. So we’re going to look at just a few aspects of the request handling process, and don’t worry if you feel a bit lost along the way, many plugin developers won’t even be familiar with this. But I find it very useful, even important, to have a basic understanding of how requests are handled in Craft.
You’ve probably noticed how seamless I’m navigating through the source code with my IDE, and I’m using PhpStorm which is a phenomenal tool for doing this. Just by holding the Command key and clicking on a method’s usage, I’m immediately taken to the definition of the method. Inversely, holding the Command key and clicking on a method’s definition will either take me directly to its usage, or if it is used in multiple places, then it will show me each instance where the method is called, and take me to the one I select.
So note how we’re now in the
run method, but somehow we’ve found ourselves in the
yii/base/application class, even though we just clicked on the usage of an instance of
craft/web/application. Well, this is as a result of how inheritance works in object-oriented programming. The
craft/web/application class extends the
yii/web/application class, which itself extends the
yii/base/application class. Since the
craft/web/application class does not contain a
run method, the closest ancestor class, or superclass, that does contain a
run method, in this case
yii/base/application, is what is called. So we’re firmly in Yii land now, we’re allowing the framework to handle the running of this part of the request. There are two things of interest to us here, the first being that the
handleRequest method is called on
$this. Remember, however, that
$this still represents an instance of
craft/web/application, so when I want to navigate to the
handleRequest method, I need to ensure that I either go to the method in the
craft/web/application class, if one exists, otherwise to the closest superclass that contains one. Command-click takes me to an abstract method definition inside the
yii/base/application class. An abstract method is a method signature, like a placeholder, that must be implemented by its subclasses. This icon here shows me which subclasses implement the
handleRequest method. We, of course, are interested in
craft/web/application, so that’s where we’ll head to next.
One of the things that’s great about Craft, and Yii to an extent, is that stepping through and navigating the source code is so straightforward. There are magic methods used around the place, but not so many as to break the control flow or leave you guessing as to where to go to next, which is the case in many other frameworks. Also, the extensive use of DocBlocks that provide high-level descriptions of methods and their parameters, are wonderful to have. Here, for example, we can see that this method accepts two parameters, the request to be handled, as well as a boolean that specifies whether to skip the special case request handling stuff and go straight to the normal routing logic, which is
If we inspect the usages of the
handleRequest method then we can see where it is used with
$skipSpecialHandling set to
PreviewController class is one example. It contains a
preview action for live previewing entries and other elements, and it would make sense that when previewing an entry, we might not require so-called “special handling” of the request.
I’m going to collapse this code block, as it’s over a hundred lines of code that performs various checks, leaving us with two other things of interest. Firstly, if we determine that this is an action request then we process it. Notice how the logic for doing this has been extracted into its own method. This is a refactoring technique that is commonly used for reducing cognitive load when reading source code, as the logic is abstracted away while the method name is nice and descriptive, but it can also be used when running the same logic from multiple places to keep our code DRY, as is the case here. Assuming this is a web request to the homepage, and therefore not an action request, we’re going to carry on to this line, in which the
handleRequest method of this class’ parent class is called. So again, this works due to inheritance, by calling the closest ancestor class that contains a
handleRequest method. Clicking on this takes us to the
yii/web/application class which continues handling the request. What’s slightly unusual here, is that this instructs the request being handled, to resolve itself. It’s starting to sound like everyone is just passing the baton along, right? We have to be careful here, as clicking into the
resolve method takes us into the
yii/web/request class, when in fact we’re interested in the
craft/web/request subclass, it’s just that PhpStorm doesn’t know this. Later in this video, we’ll use Xdebug to step through the code, which will help to eliminate this inconsistency.
Here, we get the result of the request by telling the URL manager to parse the request. This, in turn, calls the
_getRequestRoute method, which checks whether there is a token in the URL, whether this is an element request, whether there is a URL route that matches the request, whether this is a “well-known” request, and finally, if none of the above match, we fetch the template route. Public template paths are paths that do not begin with an underscore, so
$matches will evaluate to
true, and the
$path will evaluate to an empty string. And so this method will return an array of two values – the controller route and a set of parameters, which in this case will be the template with a value of an empty string, which will resolve to the
Ok, so I promised that we would go through this process again using Xdebug, which, used together with PhpStorm, is an incredible tool that I absolutely encourage you to start using if you don’t already. I’m using DDEV as my local development environment so all I need to do is run the
ddev xdebug command, start listening for PHP debug connections, and now I can set some breakpoints in my code. Let me set one here, just so we can verify that the request does indeed go through the
index.php file in the
web folder. And I’ll set another one here so that we can see the result of parsing the request route.
Now, refreshing the page should prompt Xdebug to intercept the request at the first breakpoint that is encountered, and, as expected, we’re at the breakpoint that we set in the
index.php file. Let’s resume execution, which brings us to the second breakpoint. Notice how the web browser is waiting for the response to come back. Xdebug has essentially paused runtime execution of the request, and in the debugger, we can see the stack trace leading up to this breakpoint, as well as the entire context, so all of the variables that are defined in the current scope, and we can inspect each and every one of them.
Two very useful functions in Xdebug are “step over” and “step into”. “Step over” will step over the current line and onto the next line, whereas “step into” will step into any methods that are called in the current line. I’d like to see how the request route is parsed so let’s step into the method, and now I’ll step over this line of code. Since we skipped over this
return statement, the condition must have evaluated to
false. This time, however, we don’t skip over this return statement, meaning that the
_getMatchedElementRoute function did not return
false. So this gives us a more accurate representation of what is actually happening during the execution of this request, as opposed to what we assumed was happening when we stepped through the code manually. If we inspect the
$route variable, we find the controller route of
template/render, but this time the template parameter is set to
index.twig and we have an additional parameter called
variables, which contains the “Homepage” entry. So it’s not the case that visiting the homepage automatically renders the index template, as presupposed. Instead, the “Homepage” section, which I was very sneaky in failing to mention earlier, is set up to route homepage requests to the
index.twig template, and that’s exactly what we’re seeing.
If we “step out” of this method, that is the opposite of stepping into a method, and out again, and again, and one final time, then we find ourselves back in the
run method, however, now we have a
$response that we can inspect. The response has a format of type “template”, but the content is still
null, as there is one step remaining. So let’s set one more breakpoint, just after the
send method is called on the response, and resume execution. Now the response content is populated with the raw HTML that will be sent back to the browser. We can resume execution and since we have no more breakpoints set, the request will complete.
Xdebug is incredibly useful for debugging and code exploration, and while you can get quite far using “dump-and-die”, it doesn’t give you anywhere as much control and insight into the lifecycle of requests. Let me give you another quick example.
Remember that one of the things that special request handling did was add some headers for us. Well, one of those headers is the
X-Powered-By header, which Craft sets by default, but which you can disable using the
sendPoweredByHeader general config setting. What if we wanted to test the effect of changing the value that is set, as a trivial example. We could set a breakpoint in the
set method, which will break every time any header is set. But since we’re only interested in the
X-Powered-By header, we can set a conditional breakpoint by right-clicking here and adding our condition,
X-Powered-By. Let me just remove the other breakpoints I had set, and I should open the “Network” tab so we can see requests.
So here we are at our breakpoint, in which the
X-Powered-By header is about to be set to “Craft CMS”. We can modify any variable, just like this, or we can even evaluate expressions, so let’s set
$value to “CMS” replaced with “App”. And there we go – we’ve modified the state of the applicate during runtime execution. Back in my browser, I can inspect the response and there’s the
X-Powered-By header set to “Cruft App”.
So if you’re still watching this video then congratulations, you’ve gotten an insight into the bootstrapping process of the Craft application and that puts you further ahead in the game than many other Craft developers. Perhaps you didn’t understand everything, and that’s ok, you’re already becoming more familiar with navigating the source code and I encourage you to practice doing so yourself as well. Reading source code is a skill that anyone can learn with practice and persistence.
Everything we’ve looked at until now was initialised by a web request. Craft also comes with console commands, which result in console requests and I’ll show you the entry point to those in just a moment. First, though, I mentioned that the
.htaccess file is what is responsible for redirecting web requests to the
index.php file, the entry point into the web application. That is the case if your site is hosted on an Apache web server, if your site runs on Nginx then there’ll be an Nginx configuration file with an equivalent redirect rule, albeit with a different syntax.
And on the topic of web hosting, I’d like to share with you one of my recommendations when it comes to managed hosting, and that’s ArcusTech. This video has been made freely available to you thanks to financial support from ArcusTech, so bear with me while I highlight some of the benefits of using managed hosting for your Craft sites.
ArcusTech are well known in the Craft CMS space and they offer Craft optimised VPS plans. These are fully managed servers, in other words, you don’t have to know anything about Devops or server administration or have experts in-house, it’s almost like having ArcusTech provide your Devops expertise for you, handling things such as server updates, security patches and generally keeping everything running smoothly. One really nice feature is that Craft support engineers have direct access to your server if necessary, in case you need urgent support from Pixel & Tonic. Managed hosting plans start at $10 per month, which is incredibly affordable, and you can scale up or down any time, as needed. Where ArcusTech really shines is that their technical support is open 24 hours a day, 7 days a week, so you can email them or open a support request and get a prompt response from a real person. Data centres are available in the US and the EU, with Australia and other locations on the roadmap. So I encourage you to give arcustech.com a look and if you prefer to outsource server administration and instead just build sites and do what you’re good at, then consider them for your next managed hosting project.
So back to our source code, I said that we would look at how console requests are initiated and that is through console commands, which go via the
craft file, in the root of our project. This file is not too dissimilar from the
index.php file, if you look closely. It’s also another file that has changed over time. As before, we load the shared bootstrap file, but instead of requiring the
web bootstrap file, we require the
console bootstrap file, which gives us an instance of the
craft/console/application class, which we run, before exiting.
One of the nice things here is that I can still use Xdebug, even though we’re going through the console. So I can set a breakpoint here and initiate a console request by running a console command. There are a few ways to do this – we can run
./craft to execute the file directly. The shebang at the top of the file tells the terminal to run the file using PHP. We can also run the file through PHP using
php craft, but since I’m using DDEV I’ll use
ddev craft to run it inside the Docker container. And there we are, runtime execution has been paused and we can see that the exit code is set to zero, to mean success.
So that’s it for this video, I hope you learned a thing or two and I’ll see you in the next one.