nette/application
Nette Application is a lightweight MVC layer for PHP web apps, offering presenters, flexible routing, HTTP request/response handling, and reusable UI components. It integrates smoothly with Nette Framework and supports clean, testable application structure.
nette/application is the Nette MVC kernel, not a Laravel package. It will not slot into a Laravel app as a replacement for controllers, Blade, or Eloquent. Its natural home is a Nette application with Nette DI, nette/routing, nette/http, and Latte. If you are evaluating it from a Laravel background, treat it as a different framework layer: Presenter is roughly controller + view model + lifecycle, Control is a reusable widget/component, and signal is a component-scoped request handler.
Start with the Nette sandbox rather than assembling dependencies manually:
composer create-project nette/web-project my-app
# or, in an existing Nette project:
composer require nette/application
A minimal entry point looks like this:
// www/index.php
require __DIR__ . '/../vendor/autoload.php';
$configurator = new Nette\Bootstrap\Configurator;
$configurator->setDebugMode(true);
$configurator->enableTracy(__DIR__ . '/../log');
$configurator->setTempDirectory(__DIR__ . '/../temp');
$configurator->createRobotLoader()
->addDirectory(__DIR__ . '/../app')
->register();
$configurator->addConfig(__DIR__ . '/../app/config/common.neon');
$container = $configurator->createContainer();
$container->getByType(Nette\Application\Application::class)->run();
Then create your first presenter:
namespace App\Presenters;
use Nette\Application\UI\Presenter;
final class HomepagePresenter extends Presenter
{
public function renderDefault(): void
{
$this->template->title = 'Home';
}
}
Place the template at app/Presenters/templates/Homepage/default.latte:
{block content}
<h1>{$title}</h1>
<p>Hello from Latte.</p>
{/block}
Where to look first:
render<Action>, action<Action>, beforeRender, afterRender).Nette\Application\UI\Control and createComponent<Name>().{link}, n:href, handle<Signal>().RouteList, route masks, presenter/action mapping.{control}, {snippet}, {link}).First real use case: build one page with a layout, a presenter, a reusable control, and a signal that mutates state and redirects or invalidates a snippet. That single flow exercises the majority of what you will use day to day.
Mental model for Laravel developers:
| Laravel | Nette Application |
|---|---|
| Controller | Presenter |
| Blade view | Latte template |
route('name') |
{link Presenter:action} / LinkGenerator |
| Reusable view component | Control with its own template and signals |
| Form request + controller action | nette/forms component + onSuccess callback |
| Service container constructor injection | Nette DI constructor injection |
Presenter lifecycle is the primary workflow. Use startup() for shared setup, beforeRender() for data needed by the template, render<Action>() for GET-style page rendering, and handle<Signal>() for state-changing requests. Keep action parameters in method signatures rather than reading them manually.
use Nette\Application\Attributes\Persistent;
final class ArticlePresenter extends Presenter
{
#[Persistent]
public int $page = 1;
public function renderDetail(int $id): void
{
$this->template->article = $this->articles->get($id);
}
public function handleDelete(int $id): void
{
$this->articles->delete($id);
$this->flashMessage('Article deleted.');
$this->redirect('this');
}
}
Use components for anything reusable. A component is a full sub-request with its own template, signals, and lifecycle. This is the package’s main architectural advantage over controller-only frameworks.
protected function createComponentCommentForm(): Nette\Application\UI\Form
{
$form = new Nette\Application\UI\Form;
$form->addText('name')->setRequired();
$form->addTextArea('text')->setRequired();
$form->addSubmit('send', 'Post comment');
$form->onSuccess[] = [$this, 'commentFormSucceeded'];
return $form;
}
public function commentFormSucceeded(Nette\Application\UI\Form $form, array $values): void
{
$this->comments->add($values['name'], $values['text']);
$this->flashMessage('Comment posted.');
$this->redirect('this');
}
Template usage:
{control commentForm}
Use signals for component-scoped actions. Signal links use an exclamation mark:
<a n:href="delete! $article->id">Delete</a>
The corresponding handler is handleDelete(int $id). For AJAX-driven components, update a snippet and invalidate it:
public function handleIncrement(): void
{
$this->count++;
$this->invalidateControl('counter');
}
{snippet counter}
<strong>{$count}</strong>
{/snippet}
Generate links through the presenter/LinkGenerator, not by hand. Inside presenters and templates, use {link} or $this->link(). For emails, CLI jobs, or URLs generated outside a request context, inject Nette\Application\LinkGenerator and use withReferenceUrl() where appropriate. Link aliases added in 3.2.x are useful for keeping URLs stable while renaming presenters.
Use attributes for cross-cutting request requirements. Recent 3.2.x releases add #[Requires], #[Deprecated], #[Parameter], and #[TemplateVariable]. They are useful for login/role/method checks, deprecating actions, parameter metadata, and typed template properties. Do not mistake #[Requires] for a full authorization system: pair it with nette/security and a real authorizator.
Inject services through constructors. Since 3.2.1, injected presenter services are readonly. Prefer explicit constructor injection over $this->context-style service location.
public function __construct(
private readonly ArticleRepository $articles,
) {
parent::__construct();
}
Handle errors with the application’s error presenter. Use $this->error('Not found', 404) for aborting a request, and configure application.error4xxPresenter / errorPresenter for user-facing error pages. Let catchExceptions remain enabled in production; it is silently deprecated as a manual toggle in newer releases.
Test at the boundaries. Use Nette Tester for presenters/control logic, and test link generation through LinkGenerator. For forms and components, test the callbacks and services rather than trying to emulate the entire HTTP kernel. Keep presenter tests small: one action render, one signal, one form success path.
nette/application and Latte versions in composer.json.NOASSERTION. Nette packages are typically BSD-3-Clause, but verify the actual license file before adopting it in a commercial or redistributed product.#[Requires] with application-level authorization.redirect() terminates the request via an exception. Any earlier output or partially rendered template will cause problems.#[Persistent] properties should be initialized or safely typed. Defaults on #[Parameter] are not passed to link generation in 3.2.8, which can produce unexpected URLs. Prefer action method arguments for ordinary request parameters.LinkGenerator throws when linking to #[Requires(forward: true)] targets (3.2.9). Check invalidLinkMode when debugging link exceptions, and prefer {link}/n:href over manually concatenated URLs.render<Action>() maps to templates/<Presenter>/<action>.latte; custom template classes and findTemplateFile() can change this. In 3.2.7, a template’s default property is not assigned if it does not match the expected type. Type your template properties carefully.Template is abstract in 3.2.7+. If you subclassed it directly, adjust to the current hierarchy (Template → DefaultTemplate). Also note the 3.1 change from magic __get/__set to real object properties.LatteFactory, add the UI extension or pass the control so {control}, {link}, and snippets work. UI macros such as {control} need the Latte UI extension; xhtml is deprecated.{snippet}/{snippetArea} and invalidateControl(). Multiple-template snippet rendering was fixed in 3.2.7; if you render more than one template, upgrade rather than debugging stale snippet state.application.mapping and the scanDir/scanFilter pattern (*Presenter by default). The ** mapping supports Foo/FooPresenter layouts. Clear the temp/cache directory after changing DI or mapping configuration.{templatePrint} / the Latte panel to inspect template parameters. Most link and routing issues are visible there before you add logging.startup() light, avoid expensive work in every presenter, and ensure opcache and the temp directory are configured correctly in production.How can I help you explore Laravel packages today?