Event Driven Architecture is a very powerful pattern for modern PHP Enterprise Systems Applications. In Symfony is very easy to implement actions that are executed when a particular type of event happens using listeners.

Let's say you want to log every time a user logs in to your application. You can achieve this by creating a listener that listens to the security.interactive_login event, which is dispatched when a user logs in using the Symfony Security component.

- First, create a new listener class (e.g. UserLoginListener) and implement the EventSubscriberInterface interface. This interface requires you to define a static method getSubscribedEvents() that returns an array of events that the listener should subscribe to:

<?php

namespace App\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

class UserLoginListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            InteractiveLoginEvent::class => 'onUserLogin',
        ];
    }

    public function onUserLogin(InteractiveLoginEvent $event)
    {
        // TODO: Implement the code to log the user login event
    }
}

- In the onUserLogin() method, implement the code to log the user login event. You can use any logging library or service of your choice (e.g. Monolog or Psr\Log), or simply write the log to a file or database. For example, you can use the LoggerInterface service provided by Symfony to log the event to a file:

use Psr\Log\LoggerInterface;

public function onUserLogin(InteractiveLoginEvent $event, LoggerInterface $logger)
{
    $user = $event->getAuthenticationToken()->getUser();
    $logger->info('User ' . $user->getUsername() . ' has logged in');
}

- Finally, register the listener as a service in your Symfony application's configuration file (e.g. config/services.yaml):

services:
    App\EventListener\UserLoginListener:
        tags:
            - { name: kernel.event_subscriber }

The kernel.event_subscriber tag tells Symfony to automatically register the listener as an event subscriber, based on the events returned by the getSubscribedEvents() method.

With this implementation, every time a user logs in to your application, the onUserLogin() method of the UserLoginListener class will be called, and the user login event will be logged to the specified log file.

Further reading:

What is a listener in Symfony Framework

Symfony Events and Listeners.

 

Most modern Enterprise Systems require to generate reports, agreements, business plans or a variety of documents that can be downloaded in PDF format. Pdf is one of the most popular file formats used for document sharing as it has a small file size, can be viewed and edited on a computer and also it can be printed with great quality. Follow these steps to prepare your Symfony project for Pdf generation:

Prerequisites

 

Step 1. Install wkhtmltopdf1 package in your server:

### This is the suggested installation procedure in CentOS 7
$> cd /tmp
### Get most recent package
$> wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox-0.12.6-1.centos7.x86_64.rpm
### Install it along with dependencies
$> sudo yum -y localinstall wkhtmltox-0.12.6-1.centos7.x86_64.rpm

 Step 2. Install Symfony package2

$> cd /var/www/symfony-root
$> composer require mikehaertl/phpwkhtmltopdf

 Step 3. Create a new action method in your Symfony Controller to download the pdf:

public function getCdpPdfForLineItemId($lineItemId, CdpService $cdpService): Response
    {
        $cdpData = $cdpService->getCdpData($lineItemId);//Gather all data
        $htmlPages = $cdpService->getHtmlPages($cdpData);//Get html pages with this data
        $pdf = $cdpService->getPdf($htmlPages);//Generate Pdf
        if (!$pdf->send()) {
            throw new PdfDownloadException($pdf->getError());
        } else {
            return new Response('ok');
        }
    }

 Step 4. In this example $cdpData variable is an array that will be passed to the getHtmlPages method in this way:

    public function getHtmlPages(array $cdpData): array
    {
        try {
            $cdpData['images'] = $this->getEncodedImages($cdpData);
            $htmlPages = [];
            $htmlPages['cover'] = $this->getHtmlForTemplate(self::TITLE_PAGE_TEMPLATE, $cdpData);
            $cdpData['pageNumber'] = 3;
            $htmlPages['pages'][] = $this->getHtmlForTemplate(self::OVERVIEW_PAGE_TEMPLATE, $cdpData);
            $cdpData['pageNumber'] += 1;

            ...




### Each page is using a twig template that is rendered this way
    public function getHtmlForTemplate(string $templateName, array $cdpData = []): string
    {
        try {
            return $this->twig->render(self::CDP_TEMPLATE_DIR . $templateName, $cdpData);
        } catch (Throwable $e) {
            throw new SystemException('Template Rendering failed');
        }
    }

In each Twig file you will use the $cdpData keys that corresponds to the twig template variables defined

Step 5. In this example once we get the $htmlPages we use the getPdf method to generate the Pdf Document:

    /**
     * @param array $htmlPages
     * @return Pdf
     * @throws LoaderError
     * @throws RuntimeError
     * @throws SyntaxError
     */
    public function getPdf(array $htmlPages): Pdf
    {
        try {
            $pdf = new Pdf(array(
                'no-outline',         // Make Chrome not complain
                'margin-top' => '0',
                'margin-left' => '0',
                'margin-right' => '0',
                'margin-bottom' => '0',
                'ignoreWarnings' => true,
                'footer-spacing' => '0',
                'no-outline',
                'commandOptions' => [
                    'useExec' => true,
                    'procEnv' => [
                        'LANG' => 'en_US.utf-8',
                    ],
                    'escapeArgs' => false,
                    'procOptions' => [
                        'bypass_shell' => true,
                        'suppress_errors' => true,
                    ],
                ],
                // Default page options
                'disable-smart-shrinking',
                //'user-style-sheet' => '/path/to/pdf.css',
            ));
            //Add cover
            $pdf->addCover($htmlPages['cover']);
            //Create Table of Contents
            $pdf->addToc(['xsl-style-sheet' => $this->getHtmlForTemplate('pageHeaderGeneric.html.twig')]);
            //Add Pages to Pdf
            foreach ($htmlPages['pages'] as $htmlPage) {
                $pdf->addPage($htmlPage, null, 'html');
            }

            return $pdf;
        } catch (Throwable $e) {
            throw new SystemException('Template Rendering failed');
        }
    }

With this code, the Pdf will also have a nice Cover Page and a Table of Contents Page along with all other required pages.

 

 

1 Refer to wkhtmltopdf website for more downloads

2 Refer to the packagist website for more information

Sending emails is a very common task in modern Enterprise Software Applications. If your application is built on top of PHP Symfony Framework, just follow these simple steps:

Prerequisites:

Step 1. Make sure you have the Symfony mailer component by doing:

composer require symfony/mailer

 Step 2. Configure your mailer in the config/packages/mailer.yaml file:

# config/packages/mailer.yaml

framework:
    mailer:
        dsn: '%env(MAILER_DSN)%'

 Notice that MAILER_DSN is the connection string to the mailing transport. For more information on this refer to  Symfony Mailer Component

 Step 3. Inject the Symfony\Component\Mailer\MailerInterface in the Controller or Service Class from where you are going to send emails and implement something like this:

use App\ApiBundle\Exceptions\SystemException;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Mime\Email;
use Twig\Environment;

class MailService
{
    /**
     * @var MailerInterface
     */
    private MailerInterface $mailer;
    
    /**
     * @var Environment
     */
    private Environment $twig;
    
    /**
     * @param MailerInterface $mailer
     * @param Environment $twig
     */
    public function __construct(MailerInterface $mailer, Environment $twig)
    {
        $this->mailer = $mailer;
        $this->twig = $twig;
    }
    
    /**
     * @param SomeDtoClass $forThis
     * @return array
     */
    public function getEmailPayload(SomeDtoClass $forThis): array
    {
        //...Get Email Twig Template Name
        $payload['twigTemplateName'] = $forThis->templateName;
        //...Get  Data to put in Html Text
        $payload['HtmlBody'] = $this->getHtmlBody($forThis->data);
        //...Get Text To put in no Html format Email
        $payload['rawTextBody'] = $this->getRawTextBody($forThis->data);
        //...Get Sender
        $payload['sender'] = $this->getEmailSenser($forThis);
        //...Get Recipient(s) email address
        $payload['sendTo'] = $this->getSendTo($forThis->user);
        //...Get Email Subject Text string
        $payload['subject'] = $this->getEmailSubject($forThis);
        //...Get Email Attachments if any
        $payload['attachments'] = $this->getEmailAttachments($forThis->attachments);
        
        return $payload;
    }
    
    /**
     * @param array $payload
     * @return bool
     */
    public function sendEmail(array $payload): bool
    {
        try {
            //Use this in case you prefer to have all sensitive data stored somewhere else like in a Schema table no accesible by anyone but the sys admin
            $dsn = $this->getParamFromDb('MAILER_DSN');
            $transport = Transport::fromDsn($dsn);
            $mailer = new Mailer($transport);
            $message = new Email();
            
            $message->getHeaders()
                // this non-standard header tells compliant autoresponders ("email holiday mode") to not
                // reply to this message because it's an automated email
                ->addTextHeader('X-Auto-Response-Suppress', 'OOF, DR, RN, NRN, AutoReply');
            
            $message->from($payload['sender'])
                ->to($payload['sendTo'])
                ->subject($payload['subject'])
                ->text($this->twig->render('emails/' . $payload['twigTemplateName'] . '.txt.twig', $payload['rawTextBody']))
                ->html($this->twig->render('emails/' . $payload['twigTemplateName'] . '.html.twig', $payload['HtmlBody']));
            //Most Email services like Google and Yahoo accept up to 10 attachments and total size up to 20 MB
            foreach ($payload['attachments'] as $attachment) {
                $message->attach($attachment, $attachment->name, $attachment->contentType);
            }
            //Send Email
            $mailer->send($message);
            
        } catch (\Throwable $e) {
            throw new SystemException($e->getMessage());
        }
        
        return true;
    }
    
}

 It is always a good idea to use Twig Templates for the email content. This way, you can have very nice formatted text and a template that can be reused over and over again.

Refer to Symfony Documentation for more details and more options.

 

 

 

 

 

 

DTOs (Data Transfer Objects) are very useful for handling complex data structures. In PHP Symfony is very easy to deserialize and validate complex deep nested json or xml strings passed in the body of an Api Http Request.

Prerequisites:

Step 1. Make sure you have set the Request Param Converter as explained in the articles above and enabled it as an event listener: