Setting up simple html form with Symfony form validation and email notification using SwiftMailer.

index.php

<?php

use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Component\HttpFoundation\Session\Session;

require "vendor/autoload.php";

/**
 * Session start
 */
/** @var Session $session */
$session = new Session();
if(!$session->isStarted()){
    $session->start();
}

/**
 * Generate csrf security token for the form
 */
$csrfProvider = new CsrfTokenManager();
$csrf = $csrfProvider->getToken("main")->getValue();

?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="demo.php" method="POST">
        From:<br>
        <input type="text" name="email" id="email"><br>
        Subject:<br>
        <input type="text" name="subject" id="subject"><br>
        Message:<br>
        <textarea name="body" id="" cols="30" rows="10"></textarea><br>
        <input type="submit" name="submit" value="submit"><br>
        <input type="hidden" name="token" id="token" value="<?= $csrf ?>">
    </form>
    <?php
        /**
         * Print messages from falshbag
         */
        foreach ($session->getFlashBag()->get("message") as $key => $m) {
            echo "<p>{$m}</p>";
        }
    ?>
</body>
</html>

demo.php

<?php

use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\Validator\Validation;
use Devwl\Email\DataSanitizer;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Component\Security\Csrf\CsrfToken;

require "vendor/autoload.php";

// IDE VSCODE VS PHPSTORM -> https://www.youtube.com/watch?v=O9nIE-40uKk
// SYMFONY CONSTRAINTS -> https://symfony.com/doc/current/validation.html#string-constraints

/**
 * Get csrf form security token
 */
if($_POST){
    $csrfProvider = new CsrfTokenManager();
    $token = new CsrfToken("main", $_POST["token"]);
    // var_dump($csrfProvider->isTokenValid($token));
    if(!$csrfProvider->isTokenValid($token)){
        throw new \RuntimeException("Invalid token!");
    }
}

/**
 * Session var
 */
/** @var Session $session */
$session = new Session();

/**
 * Init Symfony Validator
 */
$validator = Validation::createValidator();
$error = [];

/**
 * Get current time
 */
$time = new \DateTime("now", new \DateTimeZone("Europe/Warsaw"));

// TODO - add form data
$email = DataSanitizer::userEmail($_POST['email']);

/** @var ConstraintViolationList $list */
$list = $validator->validate($email, 
    [
        new \Symfony\Component\Validator\Constraints\NotBlank(),
        new \Symfony\Component\Validator\Constraints\Length([
            'min' => 5,
            'max' => 100,
            'minMessage' => 'Your email must be at least {{ 5 }} characters long',
            'maxMessage' => 'Your email cannot be longer than {{ 100 }} characters',
        ]),
        new \Symfony\Component\Validator\Constraints\Email([
            "message" => "The email {{ {$email} }} is not a valid email.",
        ])
    ]);
if($list->count()){
    foreach ($list as $key => $cViolation) {
        /** @var ConstraintViolation $cViolation */
        $session->getFlashBag()->add("message", "{$cViolation->getMessage()}");
    }
}
$error[] = $list->count();


$subject = DataSanitizer::userString($_POST['subject']);
/** @var ConstraintViolationList $list */
$list = $validator->validate($subject, 
    [
        new \Symfony\Component\Validator\Constraints\NotBlank(),
        new \Symfony\Component\Validator\Constraints\Length([
            'min' => 2,
            'max' => 50,
            'minMessage' => 'Message subject must be at least {{ 2 }} characters long',
            'maxMessage' => 'Message subject cannot be longer than {{ 50 }} characters',
        ])
    ]);
if($list->count()){
    foreach ($list as $key => $cViolation) {
        /** @var ConstraintViolation $cViolation */
        $session->getFlashBag()->add("message", "{$cViolation->getMessage()}");
    }
}
$error[] = $list->count();

$body = DataSanitizer::userHTML($_POST['body']);
/** @var ConstraintViolationList $list */
$list = $validator->validate($body, 
    [
        new \Symfony\Component\Validator\Constraints\NotBlank(),
        new \Symfony\Component\Validator\Constraints\Length([
            'min' => 6,
            'max' => 1500,
            'minMessage' => 'Message body must be at least {{ 6 }} characters long',
            'maxMessage' => 'Message body cannot be longer than {{ 1500 }} characters',
        ])
    ]);
if($list->count()){
    foreach ($list as $key => $cViolation) {
        /** @var ConstraintViolation $cViolation */
        $session->getFlashBag()->add("message", "{$cViolation->getMessage()}");
    }
}
$error[] = $list->count();

echo "$email, $subject, $body, ";

/**
 * Read email and pass from json file (not commited to git repo)
 * create own json file config.json or raname demo tempconfig.json with "email" and "pass" keys with values
 */
$string = file_get_contents("config.json");
if ($string === false) {
    throw new Error("Missing config file. Create {{ ".__DIR__."".DIRECTORY_SEPARATOR."config.json }} from {{ ".__DIR__."".DIRECTORY_SEPARATOR."tempconfig.json }} file in " . __DIR__, 1);
    die();
}

$json = json_decode($string);
if ($json === null) {
    throw new Error("config.json can not be {{ null }}. Check json " . __DIR__ . "/config.json", 1);
    die();
}

// Send form
if($_POST["submit"] && array_sum($error) == 0){
    $transport = new Swift_SmtpTransport($json->smtp, 465, "ssl");
    $transport->setUsername($json->email);
    $transport->setPassword($json->pass);
    $mailer = new Swift_Mailer($transport);

    $message = new Swift_Message();
    $message
        ->addFrom($email)
        ->addTo($transport->getUsername())
        ->addReplyTo($email)
        ->setCharset("utf8")
        ->setSubject($subject)
        ->setContentType("text/html")
        ->setBody($body);

    $success = $mailer->send($message);

    /**
     * Display message about delivary status
     */
    if($success){
        $session->getFlashBag()->add("message","Message sent and recived! {$time->format('H-i-s')}");
    }else{
        $session->getFlashBag()->add("message","Something went wrong! Message not sent. Try again. {$time->format('H-i-s')}");
    }
}

/**
 * Debug - Display num of errors in each ConstraintViolationList
 */
// print_r($error);



header("Location: ./index.php");
# src/DataSerializer.php
<?php

namespace Devwl\Email;

use Symfony\Component\HttpFoundation\Session;

/**
 * User input / string manipulation static helpers class
 */
class DataSanitizer{

    /**
     * Removes HTML from string
     *
     * @param string $string
     * @return string
     */
    static function userString($string){
        $string = filter_var($string, FILTER_SANITIZE_STRING);
        return $string;
    }    

    /**
     * Removes unwanted chars from email / validating email data
     *
     * @param string $string
     * @return string
     */
    static function userEmail($string){
        $string = filter_var($string, FILTER_SANITIZE_EMAIL);
        return $string;
    }    

    /**
     * Removes js from user input strings. Preservs HTML tags.
     *
     * @param string $string
     * @return string
     */
    static function userHTML($string){
        // $string = filter_var($string, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
        $string = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', "", $string);
        return $string;
    }  
}
# config.json
{
    "email": "youremailhere@website.pl",
    "pass": "yourpasshere",
    "smtp": "mail.website.pl"
}
# composer.json
{
    "name": "devwl/email",
    "type": "project",
    "require": {
        "swiftmailer/swiftmailer": "^6.3",
        "symfony/validator": "^5.3",
        "symfony/http-foundation": "^5.3",
        "symfony/security-csrf": "^5.3"
    },
    "autoload": {
        "psr-4": {
            "Devwl\\Email\\": "src/"
        }
    },
    "authors": [
        {
            "name": "DevWL",
            "email": "w.liszkiewicz@gmail.com"
        }
    ]
}
# .gitignore
vendor
config.json

Github at: https://github.com/DevWL/SecureForm using Symfony Components (with email notification and flashMessages)

0
Would love your thoughts, please comment.x
()
x