How Do Applications Work?
You are currently reading the basic document of the Nette documentation. You will learn all the principle of web applications. Nice from A to Z, from the moment of birth until the last breath of the PHP script. After reading you will know:
- how it all works
- what is Bootstrap, Presenter and DI container
- what the directory structure looks like
Directory Structure
Open a skeleton example of a web application called WebProject and you can watch the files being written about.
The directory structure looks something like this:
web-project/ ├── app/ ← directory with application │ ├── Core/ ← basic necessary classes │ │ └── RouterFactory.php ← configuration of URL addresses │ ├── UI/ ← presenters, templates & co. │ │ ├── @layout.latte ← template of shared layout │ │ └── Home/ ← Home presenter directory │ │ ├── HomePresenter.php ← Home presenter class │ │ └── default.latte ← template for action default │ └── Bootstrap.php ← booting class Bootstrap ├── bin/ ← scripts for the command line ├── config/ ← configuration files │ ├── common.neon │ └── services.neon ├── log/ ← error logs ├── temp/ ← temporary files, cache, … ├── vendor/ ← libraries installed by Composer │ ├── ... │ └── autoload.php ← autoloading of libs installed by Composer ├── www/ ← public directory, document root of project │ ├── .htaccess ← mod_rewrite rules etc │ └── index.php ← initial file that launches the application └── .htaccess ← prohibits access to all directories except www
You can change the directory structure in any way, rename or move folders, and then just edit the paths to log/
and temp/
in the Bootstrap.php
file and the path to this file in composer.json
in the
autoload
section. Nothing more, no complicated reconfiguration, no constant changes. Nette has a smart autodetection.
For slightly larger applications, we can divide folders with presenters and templates into subdirectories (on disk) and into namespaces (in code), which we call modules.
The www/
directory is the public directory or document-root of the project. You can rename it without having to
set anything else on the application side. You just need to configure the hosting so
that the document-root goes to this directory.
You can also download the WebProject directly, including Nette, using Composer:
composer create-project nette/web-project
On Linux or macOS, set the write
permissions for directories log/
and temp/
.
The WebProject application is ready to run, there is no need to configure anything else at all and you can view it directly in
the browser by accessing the folder www/
.
HTTP Request
It all begins when a user opens the page in a browser and browser knocks on the server with an HTTP request. The request goes
to a PHP file located in the public directory www/
, which is index.php
. Let's suppose that this is a
request to https://example.com/product/123
. Thanks to the appropriate server settings, this URL is also
mapped to the index.php
file and will be executed.
Its task is:
- initialize the environment
- get the factory
- launch the Nette application that handles the request
What kind of factory? We do not produce tractors, but websites! Hold on, it'll be explained right away.
By “initialize the environment” we mean, for example, that Tracy is activated, which is an amazing tool for logging or visualizing errors. It logs errors on the production server and displays them directly on the development server. Therefore, initialization also needs to decide whether the site is running in production or developer mode. To do this, Nette uses autodetection: if you run the site on localhost, it runs in developer mode. You don't have to configure anything and the application is ready for both development and production deployment. These steps are performed and described in detail in the chapter about Bootstrap class.
The third point (yes, we skipped the second, but we will return to it) is to start the application. The handling of HTTP
requests in Nette is done by the class Nette\Application\Application
(hereinafter referred to as the
Application
), so when we say “run an application”, we mean to call a method with the name run()
on
an object of this class.
Nette is a mentor who guides you to write clean applications by proven methodologies. And the most proven is called
dependency injection, abbreviated DI. At the moment we don't want to burden you with explaining DI, since there is a separate chapter, the important thing here is that the key
objects will usually be created by a factory for objects called DI container (abbreviated DIC). Yes, this is the factory
that was mentioned a while ago. And it also creates the Application
object for us, so we need a container first. We
get it using the Configurator
class and let it produce Application
object, call the method
run()
and this starts Nette application. This is exactly what happens in the index.php file.
Nette Application
The Application class has a single task: to respond to an HTTP request.
Applications written in Nette are divided into many so-called presenters (in other frameworks you may come across the term controller, which is the same), which are classes representing a specific website page: eg homepage; product in e-shop; sign-in form; sitemap feed, etc. The application can have from one to thousands of presenters.
The application starts by asking the so-called router to decide which of the presenters to pass the current request for
processing. The router decides whose responsibility it is. It looks at the input URL https://example.com/product/123
and, based on how it is set up, decides that this is a job, for example, for presenter Product
, who wants to
show
a product with id: 123
as an action. It is a good habit to write a pairs of presenter + action
separated by a colon as Product:show
.
So the router transformed the URL into a pair Presenter:action
+ parameters, in our case Product:show
+ id: 123
. You can see how a router looks like in file app/Core/RouterFactory.php
and we will describe
it in detail in chapter Routing.
Let's move on. The application already knows the name of the presenter and can continue. By creating an object
ProductPresenter
, which is the code of presenter Product
. More precisely, it asks the DI container for
creating the presenter, because producting objects is its job.
The presenter might look like this:
class ProductPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ProductRepository $repository,
) {
}
public function renderShow(int $id): void
{
// we obtain data from the model and pass it to the template
$this->template->product = $this->repository->getProduct($id);
}
}
The request is handled by the presenter. And the task is clear: do action show
with id: 123
. Which in
the language of presenters means that the method renderShow()
is called and in the parameter $id
it gets
123
.
A presenter can handle multiple actions, ie have multiple methods render<Action>()
. But we recommend
designing presenters with one or as few actions as possible.
So, the method renderShow(123)
was called, whose code is fictional example, but you can see on it how the data is
passed to the template, ie by writing to $this->template
.
Subsequently, the presenter returns the answer. This can be an HTML page, an image, an XML document, sending a file from disk,
JSON or redirecting to another page. Importantly, if we do not explicitly say how to respond (which is the case of
ProductPresenter
), the answer will be to render the template with an HTML page. Why? Well, because in 99% of cases we
want to draw a template, so the presenter takes this behavior as the default and wants to make our work easier.
That's Nette's point.
We don't even need to specify which template to render; the framework will deduce the path itself. In the case of the
show
action, it simply tries to load the show.latte
template in the directory with the
ProductPresenter
class. It also attempts to find the layout in the @layout.latte
file (more about template searching).
Subsequently, the templates are rendered. This completes the task of the presenter and the entire application, and the work is done. If the template did not exist, a 404 error page would be returned. You can read more about presenters on the page Presenters.
Just to be sure, let's try to recap the whole process with a slightly different URL:
- the URL will be
https://example.com
- we boot the application, create a container and run
Application::run()
- the router decodes the URL as a pair
Home:default
- an
HomePresenter
object is created - method
renderDefault()
is called (if exists) - a template
default.latte
with a layout@layout.latte
is rendered
You may have come across a lot of new concepts now, but we believe they make sense. Creating applications in Nette is a breeze.
Templates
When it comes to the templates, Nette uses the Latte template system. That's why the
files with templates ends with .latte
. Latte is used because it is the most secure template system for PHP, and at
the same time the most intuitive system. You don't have to learn much new, you just need to know PHP and a few Latte tags. You
will find out everything in the documentation.
In template we create a links to other presenters & actions as follows:
<a n:href="Product:show $productId">product detail</a>
Simply write the familiar Presenter:action
pair instead of the real URL and include any parameters. The trick is
n:href
, which says that this attribute will be processed by Nette. And it will generate:
<a href="/product/456">product detail</a>
The previously mentioned router is in charge of generating the URL. In fact, routers in Nette are unique in that they can perform not only transformations from a URL to a pair of presenter:action, but also vice versa generate a URL from the name of the presenter + action + parameters. Thanks to this, in Nette you can completely change the form of the URL in the whole finished application without changing a single character in the template or presenter just by modifying the router. And thanks to this, the so-called canonization works, which is another unique feature of Nette, which improves SEO (optimization of searchability on the internet) by automatically preventing the existence of duplicate content at different URLs. Many programmers find this amazing.
Interactive Components
We have one more thing to tell you about presenters: they have a built-in component system. Older of you may remember something similar from Delphi or ASP.NET Web Forms. React or Vue.js is built on something remotely similar. In the world of PHP frameworks, this is a completely unique feature.
Components are separate reusable units that we place into pages (ie presenters). They can be forms, datagrids, menus, polls, in fact anything that makes sense to use repeatedly. We can create our own components or use some of the huge range of opensource components.
Components fundamentally change the approach to application development. They will open up new possibilities for composing pages from pre-defined units. And they have something in common with Hollywood.
DI Container and Configuration
DI container (factory for objects) is the heart of the whole application.
Don't worry, it's not a magical black box, as it might seem from the previous words. Actually, it's one pretty boring PHP
class generated by Nette and stored in a cache directory. It has a lot of methods named as createServiceAbcd()
and
each of them creates and returns an object. Yes, there is also a method createServiceApplication()
that will produce
Nette\Application\Application
, which we needed in the file index.php
to run the application. And there
are methods for producing individual presenters. And so on.
The objects that the DI container creates are called services for some reason.
What is really special about this class is that it is not programmed by you, but by the framework. It actually generates the
PHP code and saves it to disk. You just give instructions on what objects the container should be able to produce and how exactly.
And these instructions are written in configuration files in the NEON format and therefore have the extension .neon
.
The configuration files are used purely to instruct the DI container. So, for example, if I specify the
expiration: 14 days
option in the session
section, the DI container when creating the Nette\Http\Session
object representing the session will call its method
setExpiration('14 days')
, and thus configuration becomes a reality.
There is a whole chapter ready for you, describing what can be configured and how to define your own services.
Once you get into the creation of services, you will come across the word autowiring. This is a gadget that will make your life incredibly easier. It can automatically pass objects where you need them (in the constructors of your classes, for example) without having to do anything. You will find that the DI container in Nette is a small miracle.
What Next?
We went through the basic principles of applications in Nette. So far, very superficially, but you will soon delve into the depths and eventually create wonderful web applications. Where to continue? Have you tried the tutorial Create Your First Application?
In addition to the above, Nette has a whole arsenal of useful classes, database layer, etc. Try purposely just click through documentation. Or visit blog. You will discover a lot of interesting things.
Let the framework bring you a lot of joy 💙