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 both index and formDispatcher methods.
  • index(): Renders the form page using the html view and forms 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 the formDispatcher method via static::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, as formName 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 by setViewVar.
    • renderLayout(): Renders only the layout, using variables set by setLayoutVar.
  • 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.