This chapter needs an extra package
composer require naf/view
Your first application¶
Start with Installation. The skeleton already
includes naf/view, so you do not need to run the package command above again.
The form recipes use naf/form 0.2.1 or newer; this command also updates older starter
installations that still contain 0.2.0:
composer require 'naf/form:^0.2.1'
This tutorial replaces the starter's demonstration routes with an HTML home page and a JSON
endpoint. Each titled code block is the complete contents of that file. Create missing
directories; keep the capital C in app/Controllers on every operating system.
Files and bootstrap¶
Your application will use:
nafphp-demo/
├── app/
│ ├── Controllers/HomeController.php
│ ├── views/home.phtml
│ ├── config.php
│ └── routes.php
├── public/index.php
├── vendor/
├── .env
├── bootstrap.php
└── composer.json
The starter's composer.json maps App\ to app/. Composer can therefore load
App\Controllers\HomeController from app/Controllers/HomeController.php.
<?php
define('BASE_PATH', __DIR__);
require __DIR__ . '/vendor/autoload.php';
use Naf\Core\Event;
use Psr\Http\Message\ResponseInterface;
use function Naf\{app, event, request};
// Register application services here, before run() handles the request.
// naf/framework 0.2.1 redirects carry HTTP/2; PHP's local server needs HTTP/1.x.
event()->listen(Event::RESPONSE_HEADER, static fn(ResponseInterface $response) =>
$response->withProtocolVersion(request()->getProtocolVersion()));
app()->run();
The response listener keeps the outgoing protocol consistent with the request. In
naf/framework 0.2.1, redirect() creates an HTTP/2 response; emitted by php -S this
produces an invalid HTTP/1.x status line. The listener makes redirects usable with the
documented development server.
<?php
require dirname(__DIR__) . '/bootstrap.php';
APP_ENV=dev
<?php
return [];
If a .env.local exists, NAF loads it instead of .env. Remove that ambiguity for this
exercise, or put APP_ENV=dev there too.
Register two named routes¶
<?php
use App\Controllers\HomeController;
use function Naf\route;
route()->add('GET', '/', [HomeController::class, 'index'], 'home');
route()->add('GET', '/hello/{name}', [HomeController::class, 'hello'], 'hello');
Every route has a unique name. The {name} placeholder matches the controller argument $name.
Write the controller¶
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface;
use function Naf\json;
use function Naf\View\render;
final class HomeController
{
public function index(): ResponseInterface
{
return render('home', ['name' => 'World']);
}
public function hello(string $name): ResponseInterface
{
return json(['hello' => $name]);
}
}
Render the page¶
<?php
use function Naf\route;
use function Naf\View\s;
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My first NAF application</title>
</head>
<body>
<h1>Hello, <?= s($name) ?>!</h1>
<a href="<?= s(route('hello', ['name' => 'Ada'])) ?>">Try the JSON endpoint</a>
</body>
</html>
The s() call escapes text for HTML. PHP templates do not escape values automatically.
Functions must be imported in each PHP file that uses them, including templates.
Run and verify¶
From nafphp-demo/:
composer dump-autoload
php -S 127.0.0.1:8000 -t public
Open http://127.0.0.1:8000/: the page says Hello, World!. In another terminal:
curl -i http://127.0.0.1:8000/hello/Ada
Expect HTTP 200, Content-Type: application/json; charset=UTF-8 and a body containing
"hello": "Ada". An unknown path, such as /does-not-exist, should return 404.
These commands run the application through HTTP. Executing php bootstrap.php alone does
not serve a page: app()->run() returns immediately under CLI.
Continue building¶
- Handling a POST request explains body data, validation and CSRF.
- A contact form adds an HTML form.
- A login form signs in a user.
- A JSON API adds persistent data and JSON error responses.