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 │ ├── config/ ← configuration files │ │ ├── config.neon │ │ └── config.local.neon │ ├── presenters/ ← presenter classes │ │ ├── HomepagePresenter.php ← Homepage presenter class │ │ └── templates/ ← templates directory │ │ ├── @layout.latte ← template of shared layout │ │ └── Homepage/ ← templates for Homepage presenter │ │ └── default.latte ← template for action `default` │ ├── router/ ← configuration of URL addresses │ └── bootstrap.php ← booting code ├── bin/ ← scripts for the command line ├── 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 index.php
. 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 public directory www/
can be changed without having to set anything else. In fact, it is common that due to
the specifics of your hosting, you will need to rename it or, conversely, set the so-called document-root to this directory in the
hosting configuration. If your hosting does not allow you to create folders one level above the public directory, we advise you to
look for another hosting. Otherwise, you would run a significant security risk.
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.php file.
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. When we get the container, we let it to 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/router/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
{
private $repository;
public function __construct(ProductRepository $repository)
{
$this->repository = $repository;
}
public function renderShow($id)
{
// 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 have to state which template to draw, he derives the path to it according to simple logic. In the case of
presenter Product
and action show
, it tries to see if one of these template files exists relative to the
directory where class ProductPresenter
is located:
templates/Product/show.latte
templates/Product.show.latte
It will also try to find the layout in file @layout.latte
and then it renders the template. Now the task of the
presenter and the entire application is completed. If the template does not exist, a page with error 404 will be returned. You
can read more about presenters on the Presenters page.
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
Homepage:default
- an
HomepagePresenter
object is created - method
renderDefault()
is called (if exists) - a template
templates/Homepage/default.latte
with a layouttemplates/@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. 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.
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 💙