1. Go to this page and download the library: Download polidog/relayer library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
// trap: bakes "" if API_TOKEN is unset at compile time
$container->setParameter('app.api_token', $_ENV['API_TOKEN'] ?? '');
// resolves at boot, not at compile — runtime-injected secrets work
$container->setParameter('app.api_token', '%env(API_TOKEN)%');
// src/Pages/users/[id]/page.psx
declare(strict_types=1);
namespace App\Pages\Users;
use App\Service\UserRepository;
use Polidog\UsePhp\Runtime\Element;
use Polidog\Relayer\Router\Component\PageComponent;
final class UserDetailPage extends PageComponent
{
public function __construct(private readonly UserRepository $users) {}
public function render(): Element
{
$user = $this->users->find($this->getParam('id'));
return <h1>{$user->name}</h1>;
}
}
// src/Pages/users/page.psx
declare(strict_types=1);
use App\Service\UserRepository;
use Polidog\Relayer\Router\Component\PageContext;
use Polidog\UsePhp\Runtime\Element;
return function (PageContext $ctx, UserRepository $users): Closure {
$ctx->metadata(['title' => 'Users']);
return function () use ($users): Element {
$list = $users->all();
return <ul>{...\array_map(fn($u) => <li>{$u->name}</li>, $list)}</ul>;
};
};
return function (PageContext $ctx) use ($posts): Closure {
$post = $posts->find($ctx->params['id']) ?? $ctx->notFound();
if ($post->isDraft && null === $ctx->user()) {
$ctx->abort(403);
}
return fn (): Element => <article>{$post->title}</article>;
};
// src/Pages/api/users/route.php
declare(strict_types=1);
use App\Service\UserRepository;
use Polidog\Relayer\Http\Request;
use Polidog\Relayer\Http\Response;
return [
'GET' => fn (UserRepository $users): Response => Response::json(['users' => $users->all()]),
'POST' => function (Request $req, UserRepository $users): Response {
$users->create($req->allPost());
return Response::json(['ok' => true], 201);
},
];
return function (PageContext $ctx): Closure {
$ctx->js('/assets/chart.js', defer: true);
return fn (): Element => <canvas id="chart"></canvas>;
};
final class Dashboard extends LayoutComponent
{
public function render(): Element
{
$this->addJs('/assets/dashboard.js', module: true);
return <div>{...$this->getChildren()}</div>;
}
}
// src/Pages/middleware.php
declare(strict_types=1);
use Polidog\Relayer\Http\Request;
return function (Request $request, Closure $next): void {
if (null === $request->header('x-api-key')) {
\http_response_code(401);
echo '{"error":"missing api key"}';
return; // route never runs
}
$next($request);
};
// src/Pages/middleware.php
use Polidog\Relayer\Http\Cors;
return Cors::middleware([
'origins' => ['https://app.example.com'], // or ['*']
// methods / headers / credentials / maxAge are optional
]);
public function render(): Element
{
return (
<form method="post">
<input type="hidden" name="_usephp_action" value={$this->action([$this, 'save'])} />
<input name="title" />
</form>
);
}
public function save(array $form): void
{
// ... handle $form['title']
header('Location: /dashboard', true, 303); // PRG
exit;
}
// src/Pages/users/page.psx
declare(strict_types=1);
use App\Service\UserRepository;
use Polidog\Relayer\Router\Component\PageContext;
use Polidog\UsePhp\Runtime\Element;
return function (PageContext $ctx, UserRepository $users): Closure {
$save = $ctx->action('save', function (array $form) use ($users, $ctx): void {
$users->create($form['name']);
$ctx->redirect('/users'); // 303 Post/Redirect/Get; unwinds here
});
return function () use ($save, $users): Element {
return (
<main>
<ul>{...\array_map(fn($u) => <li>{$u->name}</li>, $users->all())}</ul>
<form action={$save}>
<input name="name" />
<button>save</button>
</form>
</main>
);
};
};
// list → positional: handler($form, 42)
$delete = $ctx->action('delete', function (array $form, int $id) use ($repo): void {
$repo->delete($id);
}, [$user->id]);
// assoc → named args: handler(formData: $form, id: 42)
$ctx->action('delete', fn (array $formData, int $id) => $repo->delete($id), ['id' => $user->id]);
return function (PageContext $ctx) use ($schema): Closure {
$errors = [];
$save = $ctx->action('save', function (array $form) use ($schema, &$errors, $ctx): void {
$result = $schema->safeParse($form);
if (!$result->success) { $errors = $result->errors; return; }
// ... on success: $ctx->redirect('/users') (303 Post/Redirect/Get)
});
// $errors is mutated after the action runs → capture by reference
return function () use ($save, &$errors): Element { /* render $errors */ };
};
// src/PagesConfigurator.php
declare(strict_types=1);
namespace App;
use App\Service\UserRepository;
use App\Service\PdoUserRepository;
use Polidog\Relayer\AppConfigurator as BaseConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
final class AppConfigurator extends BaseConfigurator
{
public function configure(ContainerBuilder $container): void
{
$container->register(PdoUserRepository::class);
$container->setAlias(UserRepository::class, PdoUserRepository::class)
->setPublic(true);
}
}
Relayer::boot(__DIR__ . '/..', new App\AppConfigurator(__DIR__ . '/..'))->run();
// src/Components/TodoList.psx
declare(strict_types=1);
namespace App\Components;
use Polidog\Relayer\Db\Database;
use Polidog\Relayer\Relayer;
use Polidog\UsePhp\Runtime\Element;
return function (array $props): Element {
$db = Relayer::container()->get(Database::class);
$todos = $db->fetchAll('SELECT id, title, done FROM todos ORDER BY id');
return (
<ul>
{array_map(fn($t) => <li>{$t['title']}</li>, $todos)}
</ul>
);
};
// src/Pages/users/page.psx
declare(strict_types=1);
namespace App\Pages\Users;
use App\Service\UserRepository;
use Polidog\UsePhp\Runtime\Element;
use Polidog\Relayer\Router\Component\PageComponent;
final class UsersPage extends PageComponent
{
public function __construct(private readonly UserRepository $users)
{
}
public function render(): Element
{
$users = $this->users->all();
// ...
}
}
// src/Pages/signup/page.psx
declare(strict_types=1);
use Polidog\Relayer\Http\Request;
use Polidog\Relayer\Router\Component\PageContext;
use Polidog\UsePhp\Runtime\Element;
return function (PageContext $ctx, Request $req): Closure {
$errors = [];
if ($req->isPost()) {
$email = $req->post('email') ?? '';
if (!\filter_var($email, \FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Invalid email';
}
if ([] === $errors) {
$ctx->redirect('/thanks'); // 303 Post/Redirect/Get; unwinds here
}
}
return function () use ($errors, $req): Element {
// ... render form, echoing $req->post('email') back into the input
};
};
declare(strict_types=1);
namespace App\Auth;
use Polidog\Relayer\Auth\Credentials;
use Polidog\Relayer\Auth\Identity;
use Polidog\Relayer\Auth\UserProvider;
final class PdoUserProvider implements UserProvider
{
public function __construct(private readonly \PDO $pdo) {}
public function findByIdentifier(string $identifier): ?Credentials
{
$stmt = $this->pdo->prepare(
'SELECT id, name, password_hash, roles FROM users WHERE email = ?'
);
$stmt->execute([\strtolower(\trim($identifier))]);
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
if (false === $row) {
return null;
}
return new Credentials(
identity: new Identity(
id: (int) $row['id'],
displayName: (string) $row['name'],
roles: \json_decode((string) $row['roles'], true) ?: [],
),
passwordHash: (string) $row['password_hash'],
);
}
}
// src/Pages/login/page.psx
declare(strict_types=1);
use Polidog\Relayer\Auth\Authenticator;
use Polidog\Relayer\Router\Component\PageContext;
use Polidog\UsePhp\Runtime\Element;
return function (PageContext $ctx, Authenticator $auth): Closure {
$error = null;
$login = $ctx->action('login', function (array $form) use ($auth, &$error, $ctx): void {
$identity = $auth->attempt(
(string) ($form['email'] ?? ''),
(string) ($form['password'] ?? ''),
);
if (null === $identity) {
$error = 'Invalid email or password.';
return;
}
$ctx->redirect('/dashboard'); // 303 Post/Redirect/Get; unwinds here
});
return function () use ($login, $error): Element {
// ... render form, surface $error as a single generic message
};
};
namespace App\Pages;
use Polidog\Relayer\Auth\Auth;
use Polidog\Relayer\Router\Component\PageComponent;
#[Auth] // any authenticated user
final class DashboardPage extends PageComponent { /* ... */ }
#[Auth(roles: ['admin'])] // role-gated; non-admin gets 403
final class AdminPage extends PageComponent { /* ... */ }
#[Auth(redirectTo: '')] // empty redirect -> 401 instead of 302 (JSON / API)
final class ApiEndpoint extends PageComponent { /* ... */ }
// src/Pages/dashboard/page.psx
declare(strict_types=1);
use Polidog\Relayer\Router\Component\PageContext;
use Polidog\UsePhp\Runtime\Element;
return function (PageContext $ctx): Closure {
$user = $ctx->
#[Auth(redirectTo: '')] // 401 for an absent/bad token (no redirect)
final class ApiEndpoint extends PageComponent { /* ... */ }
#[Auth(roles: ['admin'])] // roles read from the token's claims
final class AdminApi extends PageComponent { /* ... */ }
// src/Pages/auth/token/route.php — POST { } with Authorization: Bearer <jwt>
declare(strict_types=1);
use Polidog\Relayer\Auth\Authenticator;
use Polidog\Relayer\Auth\Token\BearerToken;
use Polidog\Relayer\Auth\Token\TokenVerifier;
use Polidog\Relayer\Auth\Token\AuthorizationHeader;
use Polidog\Relayer\Http\Response;
return [
'POST' => static function (
TokenVerifier $verifier,
Authenticator $auth,
AuthorizationHeader $header,
): Response {
$identity = $verifier->verify(
BearerToken::parse($header->value()) ?? '',
);
if (null === $identity) {
return Response::json(['error' => 'invalid token'], 401);
}
$auth->login($identity); // rotates the session id
return Response::json(['user' => $identity->toArray()]);
},
];
// src/Pages/page.psx
declare(strict_types=1);
namespace App\Pages;
use Polidog\Relayer\Router\Component\PageComponent;
use Polidog\Relayer\Http\Cache;
use Polidog\UsePhp\Runtime\Element;
#[Cache(
maxAge: 3600,
sMaxAge: 86400,
public: true,
vary: ['Accept-Language'],
etag: 'home-v1',
)]
final class HomePage extends PageComponent
{
public function render(): Element { /* ... */ }
}
// src/Pages/feed/page.psx
declare(strict_types=1);
use Polidog\Relayer\Http\Cache;
use Polidog\Relayer\Router\Component\PageContext;
use Polidog\UsePhp\Runtime\Element;
return function (PageContext $ctx): Closure {
// Lightweight setup: declare cache, read params. NO DB queries here.
$ctx->cache(new Cache(maxAge: 60, public: true, etagKey: 'feed'));
return function () use ($ctx): Element {
// Heavy work goes here — only runs on cache miss.
// ... query DB, build the page
};
};
#[Cache(maxAge: 60, public: true, etagKey: 'user-list')]
final class UsersPage extends PageComponent { /* ... */ }
final class UserRepository
{
public function __construct(private readonly EtagStore $etags) {}
public function save(User $user): void
{
// ... persist
$this->etags->set('user-list', \sha1((string) \microtime(true)));
}
}
final class RedisEtagStore implements EtagStore
{
public function __construct(
private readonly \Redis $redis,
private readonly string $prefix = 'etag:',
) {}
public function get(string $key): ?string
{
$value = $this->redis->get($this->prefix . $key);
return \is_string($value) && $value !== '' ? $value : null;
}
public function set(string $key, string $etag): void
{
$this->redis->set($this->prefix . $key, $etag);
}
public function forget(string $key): void
{
$this->redis->del($this->prefix . $key);
}
}
use Polidog\Relayer\Db\Database;
final class UserPage extends PageComponent
{
public function __construct(private readonly Database $db) {}
public function render(): string
{
$user = $this->db->fetchOne(
'SELECT id, name FROM users WHERE id = :id',
['id' => 42],
);
// ...
}
}
$db->transactional(function (Database $tx): void {
$tx->perform('INSERT INTO orders (user_id) VALUES (?)', [$userId]);
$tx->perform('UPDATE users SET order_count = order_count + 1 WHERE id = ?', [$userId]);
});
use Polidog\Relayer\Http\Client\HttpClient;
final class WeatherPage extends PageComponent
{
public function __construct(private readonly HttpClient $http) {}
public function render(): string
{
$res = $this->http->get('https://api.example.com/weather?city=tokyo', [
'Accept' => 'application/json',
]);
if (!$res->ok()) {
// 4xx/5xx is not an exception — a normal HttpResponse to branch on
return 'Could not fetch (' . $res->status . ')';
}
$data = $res->json();
// ...
}
}
$schema = Validator::object([
'name' => Validator::string()->trim()->min(1, 'Name is )->min(8, 'Password must be at least 8 characters.'),
]);
$signup = $ctx->action('signup', function (array $form) use ($schema, &$errors): void {
$result = $schema->safeParse($form);
if (!$result->success) {
$errors = $result->errors; // hand field errors to the view
return;
}
// $result->data is coerced
});
use Psr\Log\LoggerInterface;
final class CheckoutPage extends PageComponent
{
public function __construct(private readonly LoggerInterface $log) {}
public function render(): string
{
$this->log->info('checkout started for {user}', ['user' => $userId]);
// ...
}
}
use Polidog\Relayer\Profiler\Profiler;
public function __construct(private readonly Profiler $profiler) {}
// one-shot event
$this->profiler->collect('app', 'cache warmed', ['keys' => 12]);
// timed span (finalized by stop())
$span = $this->profiler->start('app', 'heavy compute');
$result = $this->compute();
$span->stop(['rows' => \count($result)]);
// timed span around a call, finalized for you (returns the call's value,
// records an `error` payload and rethrows if it throws)
$user = $this->profiler->measure('lib', 'sdk.fetchUser',
fn () => $this->thirdPartySdk->fetchUser($id));
final class TraceableWeatherApi implements WeatherApi
{
public function __construct(
private readonly WeatherApi $inner,
private readonly Profiler $profiler,
) {}
public function forecast(string $city): Forecast
{
// Choose the label/payload deliberately — record the city, not an
// API key the real client might also take.
return $this->profiler->measure('lib', 'weather.forecast',
fn () => $this->inner->forecast($city));
}
}
// config: AppConfigurator::configure()
if ($_ENV['APP_ENV'] === 'dev') {
$container->register(TraceableWeatherApi::class)
->setArguments([new Reference(HttpWeatherApi::class), new Reference(Profiler::class)])
->setPublic(true);
$container->setAlias(WeatherApi::class, TraceableWeatherApi::class)->setPublic(true);
}
your-app/
.env # loaded automatically if present
composer.json
config/
services.yaml # auto-loaded if present (also services.php / .yml)
public/
index.php
src/
Pages/ # AppRouter file-based routes live here
layout.psx
page.psx
about/
page.psx
AppConfigurator.php # your service registrations (extends Polidog\Relayer\AppConfigurator)
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.