Lesson 1
Implementing User Registration
Implementing User Registration

Welcome to the first lesson in our course "Securing Your Symfony MVC App". In this lesson, we'll focus on implementing user registration, a crucial part of any web application.

User registration helps in:

  • Creating unique user accounts for personalized experiences.
  • Enhancing security by controlling access to different parts of the application.
  • Tracking user interactions and preferences.

By the end of this lesson, you will be able to set up a user registration feature in your Symfony application. This foundational knowledge will be valuable as we explore more advanced authentication strategies in future lessons.

Setting Up the User Entity

Let's start by creating the User entity, which will represent the users of our application.

php
1<?php 2namespace App\Entity; 3 4use Doctrine\ORM\Mapping as ORM; 5 6#[ORM\Entity(repositoryClass: "App\Repository\UserRepository")] 7#[ORM\Table(name: "users")] 8#[ORM\UniqueConstraint(name: "username_unique", columns: ["username"])] 9class User 10{ 11 #[ORM\Id] 12 #[ORM\GeneratedValue] 13 #[ORM\Column(type: "integer")] 14 private $id; 15 16 #[ORM\Column(type: "string", length: 255, unique: true)] 17 private $username; 18 19 #[ORM\Column(type: "string", length: 255)] 20 private $password; 21 22 // Getter and Setter methods... 23}

In this code, we define a User entity class using Doctrine ORM annotations to specify how it maps to the underlying database. The #[ORM\Entity] annotation indicates that this is a Doctrine entity, and the #[ORM\Table] annotation specifies the table name and constraints.

The entity has three main properties: id, username, and password. The id is the primary key and is auto-generated. The username is a unique string field, and the password is also stored as a string. The class should also include getter and setter methods to interact with these properties.

Creating the User Repository

Next, we need a repository to interact with the user data in the database.

php
1<?php 2namespace App\Repository; 3 4use App\Entity\User; 5use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 6use Doctrine\Persistence\ManagerRegistry; 7 8class UserRepository extends ServiceEntityRepository 9{ 10 public function __construct(ManagerRegistry $registry) 11 { 12 parent::__construct($registry, User::class); 13 } 14 15 public function findByUsername(string $username): ?User 16 { 17 return $this->findOneBy(['username' => $username]); 18 } 19}

The UserRepository extends Doctrine's ServiceEntityRepository, which provides convenient methods for database operations. It includes a custom method findByUsername to retrieve a user by their username, allowing us to encapsulate query logic within the repository.

Building the User Service

The service layer provides a clean way to handle the application logic related to users. This allows for a clean separation between the controller logic and database operations.

php
1<?php 2namespace App\Service; 3 4use App\Entity\User; 5use App\Repository\UserRepository; 6use Doctrine\ORM\EntityManagerInterface; 7 8class UserService 9{ 10 private $entityManager; 11 private $userRepository; 12 13 public function __construct(EntityManagerInterface $entityManager, UserRepository $userRepository) 14 { 15 $this->entityManager = $entityManager; 16 $this->userRepository = $userRepository; 17 } 18 19 // Method to create a new user 20 public function create(string $username, string $password): User 21 { 22 $user = new User(); 23 $user->setUsername($username); 24 $user->setPassword($password); // No password hashing yet. 25 26 $this->entityManager->persist($user); 27 $this->entityManager->flush(); 28 29 return $user; 30 } 31 32 // Method to find a user by their username 33 public function findByUsername(string $username): ?User 34 { 35 return $this->userRepository->findByUsername($username); 36 } 37}

Here, the UserService class uses dependency injection to receive the EntityManagerInterface and UserRepository instances. It has two main methods: create, which creates a new user and saves it to the database, and findByUsername, which finds a user by their username. This service layer separates the business logic from the controller, making the code more modular and maintainable.

Setting Up the User Controller

The controller will handle HTTP requests and responses related to user registration.

php
1<?php 2namespace App\Controller; 3 4use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 5use Symfony\Component\HttpFoundation\Response; 6use Symfony\Component\HttpFoundation\Request; 7use Symfony\Component\Routing\Annotation\Route; 8use App\Service\UserService; 9 10class UserController extends AbstractController 11{ 12 private $userService; 13 14 public function __construct(UserService $userService) 15 { 16 $this->userService = $userService; 17 } 18 19 #[Route("/register", name: "user_register", methods: ["POST"])] 20 public function register(Request $request): Response 21 { 22 // Retrieve username and password from the request 23 $username = $request->request->get('_username'); 24 $password = $request->request->get('_password'); 25 26 // Check if username and password are provided 27 if (!$username || !$password) { 28 $this->addFlash('error', 'Username and password are required.'); 29 return $this->redirectToRoute('user_auth'); 30 } 31 32 // Check if the username already exists 33 if ($this->userService->findByUsername($username)) { 34 $this->addFlash('error', 'Username already exists. Please choose a different one.'); 35 return $this->redirectToRoute('user_auth'); 36 } 37 38 // Create the user 39 $this->userService->create($username, $password); 40 41 // Add a success message and redirect to the auth page 42 $this->addFlash('success', 'Registration successful! Please log in.'); 43 44 // Refresh the page 45 return $this->redirectToRoute('user_auth'); 46 } 47 48 #[Route("/", name: "user_auth")] 49 public function auth(): Response 50 { 51 return $this->render('user/auth.html.twig'); 52 } 53}

In this code, we define a route for user registration that handles POST requests. The register method validates the request data, checks if the username is unique, creates the user, and provides feedback through flash messages. Additionally, the auth method renders the authentication template.

Creating the Registration Page

Finally, we will create a simple registration page using Twig.

HTML, XML
1<!-- templates/user/auth.html.twig --> 2<!DOCTYPE html> 3<html> 4<head> 5 <title>User Authentication</title> 6</head> 7<body> 8 <h1>User Authentication</h1> 9 10 <!-- Success Messages --> 11 {% for message in app.flashes('success') %} 12 <div class="flash-success"> 13 {{ message }} 14 </div> 15 {% endfor %} 16 17 <!-- Error Messages --> 18 {% for message in app.flashes('error') %} 19 <div class="flash-error"> 20 {{ message }} 21 </div> 22 {% endfor %} 23 24 <!-- Registration Form --> 25 <form action="{{ path('user_register') }}" method="post"> 26 <label for="username">Username:</label> 27 <input type="text" id="username" name="_username" required><br><br> 28 29 <label for="password">Password:</label> 30 <input type="password" id="password" name="_password" required><br><br> 31 32 <button type="submit">Register</button> 33 </form> 34</body> 35</html>
Registration Page: Success Messages

Using a Twig loop, we iterate over success messages that are stored in the session flash bag and display them inside a div with the class flash-success. These messages inform the user about successful operations, such as successful registration.

HTML, XML
1{% for message in app.flashes('success') %} 2 <div class="flash-success"> 3 {{ message }} 4 </div> 5{% endfor %}
Registration Page: Error Messages

Similarly, we iterate over error messages and display them inside a div with the class flash-error. These messages inform the user of any errors, such as missing fields or a duplicate username.

HTML, XML
1{% for message in app.flashes('error') %} 2 <div class="flash-error"> 3 {{ message }} 4 </div> 5{% endfor %}
Registration Page: Form

The form includes fields for username and password, both marked as required to ensure users provide this information. It directs the data to the correct endpoint using the action attribute set to the user_register route. Additionally, the method is set to post, which ensures the data is securely transmitted upon form submission.

HTML, XML
1<form action="{{ path('user_register') }}" method="post"> 2 <label for="username">Username:</label> 3 <input type="text" id="username" name="_username" required><br><br> 4 5 <label for="password">Password:</label> 6 <input type="password" id="password" name="_password" required><br><br> 7 8 <button type="submit">Register</button> 9</form>
Component Interaction Flow

Here's a simplified flow of how user registration works and how the various components interact:

  1. Form Submission:

    • User fills out and submits the registration form, triggering an HTTP POST request to the /register endpoint.
  2. Controller Handling:

    • The UserController's register method processes the request, retrieving the username and password from the form data.
  3. Service Logic:

    • UserController calls UserService::create to handle the creation of the new user.
    • UserService::create initializes a new User entity, sets the username and password, and uses the entityManager to persist and flush the user to the database.
  4. EntityManager and Repository:

    • entityManager manages and tracks entities, saving the new user to the database upon calling flush().
    • UserRepository, extending ServiceEntityRepository, depends on entityManager to execute database queries. Once entityManager->flush() is called, UserRepository can access the newly created user.

This flow ensures that when a user entity is created and persisted by the entityManager, the UserRepository can immediately query for this user, thanks to their integrated system via Doctrine ORM.

Conclusion and Summary

In this lesson, we have covered:

  • Entity Creation: We created the User entity with Doctrine ORM.
  • Repository Setup: We set up the UserRepository for database interactions.
  • Service Implementation: We built the UserService for handling business logic.
  • Controller Development: We created the UserController to handle registration.
  • Form Design: We designed a registration form using Twig.

Now that you have a foundational understanding of how to implement user registration in Symfony, it's time to put this knowledge into practice. Use the provided examples and explanations to complete the exercises and reinforce your learning.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.