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.

 

Modern Enterprise Software Applications are implemented with a backend following the Http Rest Api pattern. Each request must include an "Authorization" header with a Bearer Token as value. JWT tokens are becoming the more secure way to authenticate requests. The most common way to get a JWT from the Backend Application is implementing an "authenticate" endpoint that receives the User's username and password. You can use the endpoint provided by the lexik/jwt-authentication-bundle or you can implement your own. Follow these steps to do this:

Prerequisites: How to generate a JWT json web token in symfony framework.

Step 1. Install the necessary packages:

composer require lexik/jwt-authentication-bundle
composer require symfony/security-bundle

 Step 2. Configure your security.yaml file by adding the following lines:

security:
    encoders:
        App\Entity\User:
            ### lib-sodium available in your machine
            ### If not use bcrypt
            algorithm: sodium

    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                ###This is the username
                property: email

    firewalls:
        login:
            pattern: ^/api/login
            stateless: true
            anonymous: true
            json_login:
                check_path: /api/login
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure

    access_control:
        - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }

 Step 3. Create a route for handling the login request in your routes.yaml file or you may better use the new recommended way with Symfony Annotations as shown in Step 4.:

login:
    path: /api/login
    controller: App\Controller\AuthController::login

 Step 4. Create an AuthController class and implement the login method:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class AuthController extends AbstractController
{
    /**
     * @Route("/api/login", methods={"POST"}, defaults={"_format": "json"})
     * @param Request $request
     * @param JWTTokenManagerInterface $jwtManager
     * @param UserPasswordEncoderInterface $passwordEncoder
     * @param EntityManagerInterface $em
     * @return JsonResponse
     */
    public function login(Request $request, JWTTokenManagerInterface $jwtManager, UserPasswordEncoderInterface $passwordEncoder, EntityManagerInterface $em)
    {
        $user = $em->getRepository(User::class)->findOneBy(['email' => $request->request->get('username')]);
        
        if (!$user) {
            throw new BadCredentialsException();
        }
        
        $isValid = $passwordEncoder->isPasswordValid($user, $request->request->get('password'));
        
        if (!$isValid) {
            throw new BadCredentialsException();
        }
        
        $token = $jwtManager->create($user);
        
        return new JsonResponse(['token' => $token]);
    }
}

Step 5.  Test your endpoint by sending a POST request to /api/login with a JSON payload containing the username and password fields:

{
  "username": "This email address is being protected from spambots. You need JavaScript enabled to view it.",
  "password": "password123"
}

 Important!: As best practice and to prevent security breaches, send hashed passwords with an algorithm that can be implemented in the frontend application that can be de-hashed by the backend.

 

JWT or Json Web Tokens are becoming the standard for Authentication in Http REST Api calls. In Symfony we have the lexik/jwt-authentication-bundle available as a package that allow us to manage jwt in a very easy way.

Step 1. Install the library using Composer:

composer require lexik/jwt-authentication-bundle

Step 2. Configure the bundle in your Symfony application by adding the following lines to your config/bundles.php file:

return [
    // ...
    Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
];

Step 3. Configure the JWT authentication in your config/packages/lexik_jwt_authentication.yaml file. Here's an example configuration:

lexik_jwt_authentication:
    secret_key: '%env(APP_SECRET)%'
    public_key: '%env(JWT_PUBLIC_KEY_PATH)%'
    pass_phrase: '%env(JWT_PASSPHRASE)%'
    token_ttl: 3600

This configuration defines the secret and public keys for JWT encryption, the passphrase for the private key (if using RSA), and the time-to-live (TTL) for the token.

Step 4. Generate the JWT token in your code:

use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;

// Inject the JWT token manager service into your controller or service
public function myAction(JWTTokenManagerInterface $jwtManager)
{
    // Generate the token for the user
    $user = $this->getUser(); // get the user object from your authentication system
    $token = $jwtManager->create($user);

    // Return the token as a JSON response
    return $this->json(['token' => $token]);
}

 

Read more about this lexik bundle.

 

 

 

Once we understand the concept of Software Objects taking physical real life examples as I explained in previous Article, then we need to dive into their characteristics and behavior before we can effectively write Object Oriented Programs.

Prerequisites:

Object Oriented Programming (abbreviated as OOP) has 4 base principles:

  • Encapsulation
  • Abstraction
  • Inheritance
  • Polymorphism

 

Encapsulation.

If we take a look at Mary's Lunch Box, it contains these "Objects":

Sandwich
Banana
Apple
Water Bottle

The Apple "Object" contains these Properties:

    Object Name: "Apple"
    Object Color: "Red"
    Object Type: "Food"
    Object has Seeds? "Yes"
    Object Need to be refrigerated? "No"

The Sandwich is an "Object" made of another "Objects" and also it has its own "Properties":

Object "Sandwich":

    Object "Ham":
        Name: "Honey Ham"
        Type: "Food"
        Need to be refrigerated? "Yes"
        Total Amount: 10
        Unit of Measure: Slice
    Object "Bread":
        Name: "Full Grain Bread"
        Type: "Food"
        Need to be refrigerated? "No"
        Total Amount: 25
        Unit of Measure: Slice
    Object "Cheese":
        Name: "Cheddar Cheese"
        Type: "Food"
        Need to be refrigerated? "Yes"
        Total Amount: 12
        Unit of Measure: Slice
    Object "Lettuce":
        Object Name: "Iceberg Lettuce"
        Object Color: "Green"
        Object Type: "Food"
        Object has Seeds? "No"
        Total Amount: 50
        Unit of Measure: Portion
    Ham Slices Count: "Not Defined Yet"
    Bread Slices Count: "Not Defined Yet"
    Cheese Slices Count: "Not Defined Yet"
    Has Mayo?: "Not Defined Yet"

 

In OOP we call "Encapsulation" to the characteristic of an "Object" of containing "Properties", "Methods" or even other "Objects". 

OOP is about writing code using "Objects" to "Encapsulate" pieces of "Data" or fragments of "Code" in a well organized manner, so we can easily have access to these from anywhere in the Program.

 

Abstraction.

If you think from Mary's stand point, she wants to get her Lunch Box out of her Backpack, and eat the food her Mom packed for her. She needs to execute these actions for eating the Sandwich:

- Get "Object" Lunch Box
- Open "Object" Lunch Box
- Get "Object" Sandwich
- Eat "Object" Sandwich

All the "Properties" and "Methods" used for making the "Object" Sandwich are not needed by Mary to eat the Sandwich, so all of them can be hidden or "Abstracted" in the "Object" Sandwich itself.

In most OOP Languages we declare these "Abstractions" using the word "private" and them are only accessible by the "Object" itself or we declare them as "protected" so them can be accessible to the object itself or their children.

 Because the purpose of OOP is to have code well organized and accessible everywhere, we use the concept of "Abstraction" to prevent misuse of "Objects'" "Properties" and "Methods" giving the Program consistency and integrity.

 

 Inheritance.

Mary's Mom packed her today a Banana and an Apple along with the Sandwich in her Lunch Box. Other days she packs other fruits, such like a Peach, a Pear, a Plum, a Tangerine, etc. Each of these Fruit "Objects" are having many of their "Properties" and "Methods" common to each other. So to not repeat many times the same "Property" or "Method" we place them in a Fruit "Object" that becomes the "Parent" and all fruits like the Apple and the Banana become the "Children". This way, the children may have only "Properties" or "Methods" that are exclusive to each one and have a "Parent" "Object" that shares the common "Properties" and "Methods" with them.

In most OOP Languages we declare a "Parent" using the word "extends", for example: "Object" Banana "extends" "Object" Fruit.

 

 Polymorphism.

The Apple and the Banana that Mary carries in her Lunch Box are both Fruits and we can make these as Children of the "Object" Fruit. We can use something called "Interface" that is like a Blue Print that says which "Methods" and "Properties" an "Object" has to "Implement" to be part of this "Interface".

Lets say we declare the "Interface" Fruits that says that all "Objects" "implementing" it must have a "Method" called "getFruitName".

interface Fruits
{
   ...
   function getFruitName();
   ...
} 

This way if we declare:

class Fruit implements Fruits
...


class Apple extends Fruit
...


class Banana extends Fruit
...

 

we know that all these has to have the "Method" called "getFruitName" regardless of which lines of code each one has within this "Method". These Apple and Banana "Objects" may use the "getFruitName" "Method" that its "Parent" must have or may "Override" it implementing their own "getFruitName" "Method".

So, in other part of the code we could have something like:

getTheSnacks(Fruits $fruit)
{
   ...
   print $fruit->getFruitName();
   ...

 We can pass to the "Method" "getTheSnacks" any $fruit "Object" that is "implementing" the "Fruits interface" and "getFruitName" will be there from it because we know it must be there, no matter the code that each fruit "Object" is using within its own implementation of the  "getFruitName" "Method".

Understanding these 4 OOP principles may take some time and lot of practice working with OOP programs, but is crucial for writing Enterprise Class Software Applications.