PHP code example of makraz / verify-email-change-bundle

1. Go to this page and download the library: Download makraz/verify-email-change-bundle 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/ */

    

makraz / verify-email-change-bundle example snippets


// config/bundles.php
return [
    // ...
    Makraz\Bundle\VerifyEmailChange\MakrazVerifyEmailChangeBundle::class => ['all' => true],
];



namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Makraz\Bundle\VerifyEmailChange\Model\EmailChangeableInterface;

#[ORM\Entity]
class User implements EmailChangeableInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 180, unique: true)]
    private string $email;

    // ... rest of your entity

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getEmail(): string
    {
        return $this->email;
    }

    public function setEmail(string $email): static
    {
        $this->email = $email;
        return $this;
    }
}



namespace App\Controller;

use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Routing\Attribute\Route;
use Makraz\Bundle\VerifyEmailChange\EmailChange\EmailChangeHelper;
use Makraz\Bundle\VerifyEmailChange\Exception\VerifyEmailChangeExceptionInterface;
use Doctrine\ORM\EntityManagerInterface;

class EmailChangeController extends AbstractController
{
    public function __construct(
        private readonly EmailChangeHelper $emailChangeHelper,
        private readonly EntityManagerInterface $entityManager,
        private readonly MailerInterface $mailer,
    ) {}

    #[Route('/account/email/change', name: 'app_email_change_request')]
    public function request(Request $request): Response
    {
        /** @var User $user */
        $user = $this->getUser();

        // Your form handling here...
        $newEmail = $request->request->get('new_email');

        try {
            // Generate the verification signature
            $signature = $this->emailChangeHelper->generateSignature(
                'app_email_change_verify',
                $user,
                $newEmail
            );

            $this->entityManager->flush();

            // Send verification email to the NEW address
            $email = (new Email())
                ->to($newEmail)
                ->subject('Verify your new email address')
                ->html(sprintf(
                    'Click here to verify: <a href="%s">%s</a><br>This link will expire in 1 hour.',
                    $signature->getSignedUrl(),
                    $signature->getSignedUrl()
                ));

            $this->mailer->send($email);

            $this->addFlash('success', 'Verification email sent! Check your new inbox.');
        } catch (VerifyEmailChangeExceptionInterface $e) {
            $this->addFlash('error', $e->getReason());
        }

        return $this->redirectToRoute('app_profile');
    }

    #[Route('/account/email/verify', name: 'app_email_change_verify')]
    public function verify(Request $request): Response
    {
        try {
            $user = $this->emailChangeHelper->validateTokenAndFetchUser($request);
            $oldEmail = $this->emailChangeHelper->confirmEmailChange($user);
            $this->entityManager->flush();

            $this->addFlash('success', 'Email changed successfully!');
        } catch (VerifyEmailChangeExceptionInterface $e) {
            $this->addFlash('error', $e->getReason());
            return $this->redirectToRoute('app_email_change_request');
        }

        return $this->redirectToRoute('app_profile');
    }

    #[Route('/account/email/cancel', name: 'app_email_change_cancel', methods: ['POST'])]
    public function cancel(): Response
    {
        /** @var User $user */
        $user = $this->getUser();

        $this->emailChangeHelper->cancelEmailChange($user);
        $this->entityManager->flush();

        $this->addFlash('success', 'Email change cancelled.');

        return $this->redirectToRoute('app_profile');
    }
}

public function generateSignature(
    string $routeName,
    EmailChangeableInterface $user,
    string $newEmail,
    array $extraParams = []
): EmailChangeSignature

public function validateTokenAndFetchUser(Request $request): EmailChangeableInterface

public function confirmEmailChange(EmailChangeableInterface $user): string

public function cancelEmailChange(EmailChangeableInterface $user): void

public function hasPendingEmailChange(EmailChangeableInterface $user): bool

public function getPendingEmail(EmailChangeableInterface $user): ?string

use Symfony\Component\Scheduler\Attribute\AsPeriodicTask;

#[AsPeriodicTask('1 day')]
class PurgeExpiredEmailChangeRequestsMessage
{
    // This message triggers the purge command
}

use Makraz\Bundle\VerifyEmailChange\Event\EmailChangeInitiatedEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener]
class EmailChangeListener
{
    public function __invoke(EmailChangeInitiatedEvent $event): void
    {
        $user = $event->getUser();
        $newEmail = $event->getNewEmail();
        $oldEmail = $event->getOldEmail();
        $verificationUrl = $event->getVerificationUrl();
    }
}

$signature = $this->emailChangeHelper->generateSignature(
    'app_email_change_verify',
    $user,
    $newEmail
);

// Send verification to the NEW email address
$this->sendEmail($newEmail, $signature->getSignedUrl());

// Also send confirmation to the OLD email address
if ($signature instanceof EmailChangeDualSignature) {
    $this->sendEmail($user->getEmail(), $signature->getOldEmailSignedUrl());
}

#[Route('/account/email/verify-old', name: 'app_email_change_verify_old')]
public function verifyOldEmail(Request $request): Response
{
    try {
        $user = $this->emailChangeHelper->validateOldEmailToken($request);
        // Check if both confirmations are done
        $oldEmail = $this->emailChangeHelper->confirmEmailChange($user);
        $this->entityManager->flush();
        $this->addFlash('success', 'Email changed successfully!');
    } catch (VerifyEmailChangeExceptionInterface $e) {
        $this->addFlash('info', $e->getReason());
    }

    return $this->redirectToRoute('app_profile');
}

use Makraz\Bundle\VerifyEmailChange\Security\CsrfTokenHelper;

class EmailChangeController extends AbstractController
{
    public function __construct(
        private readonly EmailChangeHelper $emailChangeHelper,
        private readonly CsrfTokenHelper $csrfHelper,
    ) {}

    #[Route('/account/email/cancel', name: 'app_email_change_cancel', methods: ['POST'])]
    public function cancel(Request $request): Response
    {
        if (!$this->csrfHelper->isTokenValid($request)) {
            throw $this->createAccessDeniedException('Invalid CSRF token.');
        }

        $this->emailChangeHelper->cancelEmailChange($this->getUser());
        $this->addFlash('success', 'Email change cancelled.');
        return $this->redirectToRoute('app_profile');
    }
}

use Makraz\Bundle\VerifyEmailChange\EmailChange\EmailChangeHelper;
use Makraz\Bundle\VerifyEmailChange\Notifier\EmailChangeNotifier;

class EmailChangeController extends AbstractController
{
    public function __construct(
        private readonly EmailChangeHelper $emailChangeHelper,
        private readonly EmailChangeNotifier $notifier,
    ) {}

    public function request(Request $request): Response
    {
        $user = $this->getUser();
        $newEmail = $request->request->get('new_email');

        $signature = $this->emailChangeHelper->generateSignature(
            'app_email_change_verify',
            $user,
            $newEmail
        );

        // Sends verification to new email (and old email in dual mode)
        $this->notifier->sendVerificationEmail($user, $newEmail, $signature);

        return $this->redirectToRoute('app_profile');
    }

    public function verify(Request $request): Response
    {
        $user = $this->emailChangeHelper->validateTokenAndFetchUser($request);
        $oldEmail = $this->emailChangeHelper->confirmEmailChange($user);

        // Notify old email address about the change
        $this->notifier->sendEmailChangeConfirmation($user, $oldEmail, $user->getEmail());

        return $this->redirectToRoute('app_profile');
    }
}

use Makraz\Bundle\VerifyEmailChange\Api\EmailChangeResponseFactory;
use Makraz\Bundle\VerifyEmailChange\Exception\VerifyEmailChangeExceptionInterface;

class ApiEmailChangeController
{
    public function __construct(
        private readonly EmailChangeHelper $emailChangeHelper,
        private readonly EmailChangeResponseFactory $responseFactory,
    ) {}

    #[Route('/api/email/change', methods: ['POST'])]
    public function request(Request $request): JsonResponse
    {
        try {
            $user = $this->getUser();
            $newEmail = $request->toArray()['new_email'];

            $signature = $this->emailChangeHelper->generateSignature(
                'api_email_change_verify', $user, $newEmail
            );

            return $this->responseFactory->initiated($newEmail, $signature->getExpiresAt());
        } catch (VerifyEmailChangeExceptionInterface $e) {
            return $this->responseFactory->error($e);
        }
    }

    #[Route('/api/email/status', methods: ['GET'])]
    public function status(): JsonResponse
    {
        $user = $this->getUser();
        return $this->responseFactory->pendingStatus(
            $this->emailChangeHelper->getPendingEmail($user)
        );
    }
}

use Makraz\Bundle\VerifyEmailChange\Otp\OtpEmailChangeHelper;

class OtpEmailChangeController
{
    public function __construct(
        private readonly OtpEmailChangeHelper $otpHelper,
    ) {}

    #[Route('/api/email/otp/request', methods: ['POST'])]
    public function request(Request $request): JsonResponse
    {
        $user = $this->getUser();
        $newEmail = $request->toArray()['new_email'];

        $result = $this->otpHelper->generateOtp($user, $newEmail);

        // Send the OTP to the new email address
        // $this->mailer->send(...$result->getOtp()...)

        return new JsonResponse([
            'message' => 'OTP sent to new email address.',
            'expires_at' => $result->getExpiresAt()->format(\DateTimeInterface::ATOM),
        ]);
    }

    #[Route('/api/email/otp/verify', methods: ['POST'])]
    public function verify(Request $request): JsonResponse
    {
        $user = $this->getUser();
        $otp = $request->toArray()['otp'];

        $oldEmail = $this->otpHelper->verifyOtp($user, $otp);

        return new JsonResponse([
            'message' => 'Email changed successfully.',
            'old_email' => $oldEmail,
            'new_email' => $user->getEmail(),
        ]);
    }
}

use Makraz\Bundle\VerifyEmailChange\Event\EmailChangeAuditEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener]
class EmailChangeAuditListener
{
    public function __invoke(EmailChangeAuditEvent $event): void
    {
        $action = $event->getAction(); // 'initiated', 'confirmed', 'failed_verification', etc.
        $user = $event->getUser();
        $ip = $event->getIpAddress();
        $metadata = $event->getMetadata();

        // Log to your audit system
    }
}

use Makraz\Bundle\VerifyEmailChange\Exception\VerifyEmailChangeExceptionInterface;

try {
    // Your email change logic
} catch (VerifyEmailChangeExceptionInterface $e) {
    $this->addFlash('error', $e->getReason());
}
bash
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
bash
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate