Blog
- Details
- Written by R. Elizondo
- Category: Symfony Framework
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
- Details
- Written by R. Elizondo
- Category: Symfony Framework
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
- Details
- Written by R. Elizondo
- Category: Symfony Framework
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.
- Details
- Written by R. Elizondo
- Category: Symfony Framework
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:
Read more: How to deserialize and validate nested Dtos in Symfony 6+
Page 10 of 42