Forms Example
This example demonstrates how to work with forms in the DotApp PHP Framework
using a RAW approach, without relying on the dotapp.js
frontend library. It explains how to create and handle named forms, which are automatically protected against attacks, as outlined in the framework’s philosophy.
Introduction
Important: To fully understand this example, you must first familiarize yourself with the DotApp framework’s philosophy and core concepts. Please review the following documentation pages before proceeding:
These articles explain the framework’s philosophy, modularity, and best practices, which are essential for understanding the code in this and other examples. Without this background, the example may be difficult to follow.
Creating the Examples Module
First, create a new module named Examples
using the DotApper CLI
. Run the following command:
php dotapper.php --create-module=Examples
The output will be:
Module successfully created in: ./app/modules/Examples
Next, configure the module to activate only for URLs starting with /documentation/examples/run
. This avoids unnecessary module loading, which is a best practice. Edit /app/modules/Examples/module.init.php
and update the initializeRoutes
function:
public function initializeRoutes() {
return ['/documentation/examples/run*']; // Module activates only for /documentation/examples/run*
}
This ensures the module is loaded only for relevant routes, improving performance.
Creating the Forms Controller
Create a controller named Forms
for the Examples
module using the CLI:
php dotapper.php --module=Examples --create-controller=Forms
The output will be:
Controller 'Forms' successfully created in: /app/modules/Examples/Controllers
This creates the Forms.php
controller file in /app/modules/Examples/Controllers
.
Configuring Routes
Define routes for both GET and POST requests to handle form display and submission. Edit /app/modules/Examples/module.init.php
and update the initialize
function:
public function initialize($dotApp) {
// Display the forms page
Router::get(['/documentation/examples/run/forms', '/documentation/examples/run/forms/'], "Examples:Forms@index", Router::STATIC_ROUTE);
// Handle form submission
Router::post(['/documentation/examples/run/forms', '/documentation/examples/run/forms/'], "Examples:Forms@submit", Router::STATIC_ROUTE);
}
These routes map /documentation/examples/run/forms
to the index
method for displaying the forms and the submit
method for processing form submissions.
Creating View and Layout
Create a view file at /app/modules/Examples/views/html.view.php
to serve as the base template:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ var: $seo['title'] }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="{{ var: $seo['description'] }}"/>
<meta name="keywords" content="{{ var: $seo['keywords'] }}"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
{{ content }}
</body>
</html>
Note: This view includes SEO metadata to ensure proper indexing by search engine crawlers, as the module is publicly accessible. Users are not required to include SEO data, but it’s shown here as an example. The {{ content }}
placeholder is replaced by the rendered layout code.
Create a layout file at /app/modules/Examples/views/layouts/forms.layout.php
to define three named forms:
{{ if $formNumber }}
Using form {{ var: $formNumber }}, you submitted the text: {{ var: $formText }}
<br><br><br>
{{ /if }}
<b>Form 1</b><br>
<form method="POST">
<input type="text" placeholder="Enter text to display" name="textfrom1">
{{ formName(Form1) }}
<button type="submit" class="btn search-btn">{{ var: $btnName }}</button>
</form>
<br><br><br>
<b>Form 2</b><br>
<form method="POST">
<input type="text" placeholder="Enter text to display" name="textfromanother">
{{ formName(Form2) }}
<button type="submit" class="btn search-btn">{{ var: $btnName }}</button>
</form>
<br><br><br>
<b>Form 3</b><br>
<form method="POST">
<input type="text" placeholder="Enter text to display" name="textfromanother">
{{ formName(Form3) }}
<button type="submit" class="btn search-btn">{{ var: $btnName }}</button>
</form>
Each form uses {{ formName(
to assign a unique name and enhance security against attacks (stronger than @csrf
). All forms submit to the same URL (/documentation/examples/run/forms
) and are distinguished by their names. Note that Form 2 and Form 3 share the same input name (textfromanother
), but the form names ensure proper differentiation.
Handling Forms
Update the Forms
controller at /app/modules/Examples/Controllers/Forms.php
to handle form display and submission. Below is the complete controller code, including SEO variables and form processing logic:
class Forms extends \Dotsystems\App\Parts\Controller {
private static function seoVar() {
$seo = [];
$seo['title'] = "Example of Working with Forms in the DotApp Framework";
$seo['description'] = "This example demonstrates how to work with forms in the DotApp framework.";
$seo['keywords'] = "forms, form submission, form validation, DotApp framework";
return $seo;
}
public static function index($request, Renderer $renderer) {
$button = "Send";
$viewcode = $renderer
->module(self::modulename())
->setView("html")
->setViewVar("seo", static::seoVar())
->setViewVar("btnName", $button)
->setLayout("forms")
->renderView();
return $viewcode;
}
public static function submit($request, Renderer $renderer) {
$request->form(['POST'], "Form1", function($request) {
static::call("Examples:Forms@formDispatcher", 1, $request->data()['textfrom1']);
}, "Examples:Forms@errDrop");
$request->form(['POST'], "Form2", function($request) {
static::call("Examples:Forms@formDispatcher", 2, $request->data()['textfromanother']);
}, "Examples:Forms@errDrop");
$request->form(['POST'], "Form3", function($request) {
static::call("Examples:Forms@formDispatcher", 3, $request->data()['textfromanother']);
}, "Examples:Forms@errDrop");
}
public static function formDispatcher($form, $text, Renderer $renderer) {
$viewcode = $renderer
->module(self::modulename())
->setView("html")
->setViewVar("formText", $text)
->setViewVar("formNumber", intval($form))
->setViewVar("seo", static::seoVar())
->setViewVar("btnName", "Send")
->setLayout("forms")
->renderView();
echo $viewcode;
}
public static function errDrop() {
/*
* Drops error messages during form submission.
* The $request->form method requires an error callback to avoid throwing exceptions.
* Since we handle three forms but only one will match, this ensures code execution continues.
* This allows us to process three different forms at the same POST endpoint (/documentation/examples/run/forms) using formName to distinguish them.
*/
}
}
Explanation:
seoVar()
: Defines SEO metadata for the page, used in bothindex
andformDispatcher
methods.index()
: Renders the form page using thehtml
view andforms
layout, passing the button name ("Send") and SEO variables.submit()
: Handles form submissions by checking each form’s name (Form1
,Form2
,Form3
) using$request->form
. Each form triggers theformDispatcher
method viastatic::call
.formDispatcher()
: Renders the response, displaying the submitted form number and text.errDrop()
: Drops error messages for non-matching forms, ensuring execution continues. This is necessary because three forms submit to the same endpoint, but only one will match at a time. Security remains intact, asformName
provides protection.
Using static::call("Examples:Forms@formDispatcher", ...)
ensures the method is called within the Dependency Injection (DI) container, allowing Renderer
to be injected. Calling self::formDispatcher
directly would cause an error due to missing DI context.
Rendering Theory
Understanding how DotApp’s rendering system works is key to building dynamic pages. The Renderer
class handles views and layouts, with variables accessible across both.
Example of rendering a view with a layout:
$renderer->module(self::modulename())->setView("html")->setViewVar("seo", $seo)->setLayout("forms")->setLayoutVar("btnName", $button)->renderView();
This sets the html
view, assigns SEO variables, sets the forms
layout, and defines a button name variable. The renderView()
method renders the complete view, including the layout inserted into the {{ content }}
placeholder.
Key Concepts
- View Variables: Variables set via
setViewVar
(e.g.,seo
) are available in the view and all included layouts. - Layout Variables: Variables set via
setLayoutVar
(e.g.,btnName
) are specific to the layout. - Rendering View vs. Layout:
renderView()
: Renders the entire view with its layouts, using variables set bysetViewVar
.renderLayout()
: Renders only the layout, using variables set bysetLayoutVar
.
- Reusable Views: Using
{{ content }}
allows the same view (e.g.,html.view.php
) to be reused with different layouts, making it versatile for multiple examples.
Example of rendering only a layout:
$layoutcode = $renderer->module(self::modulename())->setLayout("forms")->setLayoutVar("btnName", $button)->renderLayout();
Example of rendering a full view with a layout:
$viewcode = $renderer->module(self::modulename())->setView("html")->setViewVar("seo", $seo)->setViewVar("btnName", $button)->setLayout("forms")->renderView();
By using {{ content }}
instead of directly including {{ layout:forms }}
, the html
view remains reusable across different examples.
Live Demo
Try the live demo of this forms example at /documentation/examples/run/forms. This interactive example lets you test the form submission and see how named forms are handled in DotApp.