Thursday, July 21, 2016

Using the Symfony Validator component as a standalone library

Sometimes I need to build some scripts without using a full framework like Symfony. The good news is that you can use individual components from Symfony. Most often I will be using the HTTPFoundation component, but for today I want to build a CLI script so no need for HTTPFoundation.
I  will be writing a small command line script to read and validate a  CSV file. For the  validation part I will be using the Symfony Validator Component,

Source of inspiration: https://blog.tinned-software.net/using-the-symfony-validator-as-a-standalone-component/


First step, install Symfony Validator using Composer:

                          composer require symfony/validator

and require the autoloader created by Composer.


<?php

require_once 'vendor/autoload.php';
require_once 'Loader.php';

use Symfony\Component\Validator\Validation;

$file = "someFile.csv";


$outputFile = "errorsLog" . "_" . time() . ".txt";
  
$validator = Validation::createValidatorBuilder()
 ->addMethodMapping('loadValidatorMetadata')
 ->getValidator()
;

$loader = new Loader($file, $outputFile, $validator);
$loader->load();

I will pass to my Loader class constructor the CSV file to be read, the file where I want to save the output and an instance of the Symfony validator.

The CSV file is read line by line, and from each line I will be creating a Row object. The validator will validate the Row object against the rules (constraints).
In the Row class I will add the method "loadValidatorMetada" mentioned when instantiating the Validator. I've added several validations for better examplification:


<?php

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Mapping\ClassMetadata;


class Row
{
    private $id;         
    private $name;   
    private $personalNumber;    
    private $CountryCode;  

    public function __construct($rowNumber, $data)
    {
        $this->id = $rowNumber;
        $this->name= $data[0];
        $this->personalNumber = $data[2];
        $this->countryCode = $data[1];
    }
 
 
    /**
     * This method is where you define your validation rules.
    */
    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        //Name 
        $metadata->addPropertyConstraint('name', new Assert\NotBlank());
 
        //Personal number
        $metadata->addPropertyConstraint('personalNumber', new Assert\NotBlank());
        $metadata->addPropertyConstraint('personalNumber', new Assert\Type(array(
            'type'    => 'digit',
            'message' => 'The value {{ value }} is not a valid personal number.',
         )));
  
         //Country code
         $metadata->addPropertyConstraint('countryCode', new Assert\NotBlank());
         $metadata->addPropertyConstraint('countryCode', new Assert\Choice(array(
            'choices' => array('DE', 'AT', 'LU', 'ES', 'FR', 'BE', 'NO', 'SI', 'SE', 'IT', 'DK'),
            'message' => '{{ value }} is not a valid country code!',
          )));
  
     }
}


When validating a Row object against these constraints an array of Errors will be returned by the Validator. Below is the Loader class.



<?php

require_once 'Row.php';


class Loader
{
 private $fileName;
 private $validator;
 private $outputFile;
 
 public function __construct($fileName, $outputFile, $validator)
 {
  $this->fileName = $fileName;
  $this->outputFile = $outputFile;
  $this->validator = $validator;

 }
 
 public function load()
 {
  $file_handle = fopen($this->fileName, "r");
  $fileOutputHandle = fopen($this->outputFile, "w");
  
  /* 
   * Keep track of the current row in .csv file
   */
  $rowNumber=1;
  
  /* 
   * Read the file line by line
   */
  while (!feof($file_handle) ) {

   $line_of_text = fgetcsv($file_handle, 0);
   
   if ($rowNumber === 1) {
    
      /*
       * Ignore the first row from file as it contains headers
       */
     
   } else {
       $row = new Row($rowNumber, $line_of_text);    
       $this->validateRow($row, $fileOutputHandle); 
   }
   
   $rowNumber++;
  }
  fclose($file_handle);
  fclose($fileOutputHandle);
 }
 
 
 public function validateRow(Row $row, $fileOutputHandle)
 {
  $errors = $this->validator->validate($row);
  
  foreach ($errors as $error) {
   $errorMsg = "At row:" 
      . $error->getRoot()->getId() 
      . "- Property: " 
      . $error->getPropertyPath()
      . ' - Message: ' 
      . $error->getMessage()
      . "\n";
   
   fwrite($fileOutputHandle, $errorMsg);

  }
 }
}

Tuesday, July 12, 2016

Symfony/Sonata ACL - search for the object owner

Lately I am wrestling with the ACL in a project using Symfony with Sonata Admin Bundle and Sonata User Bundle. When editing objects from the admin panel offered by Sonata Admin Bundle things go smoothly, but the issues is how to set ACL permissions for objects I create in my own controllers.

Situation:

 An Admin1 user --> creates a Regular1 user --> which creates Objects and save them to database.

Problem:

Make Regular1 user and Admin1 user  owners of the newly created Object.

Note: Admin user is not ROLE_SUPER_ADMIN, just a custom role I have in my app

Solution:

First step, in the createAction controller method, after persisting my object, set the current user (Regular user) as owner of the object.


<?php 
    public function createAction(Request $request)
    {
        .....
        $em->persist($myObject);
        $em->flush();
            
        // retrieve services and get current user
        $adminSecurityHandler = $this->container->get('sonata.admin.security.handler');
        $modelAdmin = $this->container->get('admin.sites');
        $user = $this->getUser();

        $securityIdentity = UserSecurityIdentity::fromAccount($user);

        $objectIdentity = ObjectIdentity::fromDomainObject($myObject);
        $acl = $adminSecurityHandler->getObjectAcl($objectIdentity);
        
        if (is_null($acl)) {
            $acl = $adminSecurityHandler->createAcl($objectIdentity);
        }
        $adminSecurityHandler->addObjectClassAces($acl, $adminSecurityHandler->buildSecurityInformation($modelAdmin));
        $adminSecurityHandler->addObjectOwner($acl,$securityIdentity);  // set current user as owner in ACL

        $adminSecurityHandler->updateAcl($acl);

Second part is to search for the Admin user, owner of the regular user (which is currently logged).
Looking into the list of ACE associated to the object identity, I am searching for the one with  Mask equal to 128 (owner mask).


<?php

        //search for the owner (admin user) of the current user and give him privileges on the "myObject" object
        $userObjectIdentity = ObjectIdentity::fromDomainObject($user);
        $userObjectACL = $adminSecurityHandler->getObjectAcl($userObjectIdentity);

        $aces= $userObjectACL->getObjectAces();    
       
        /*
         *  $aces is an array containing ACEs, objects from this class: 
         *  http://api.symfony.com/2.7/Symfony/Component/Security/Acl/Domain/Entry.html
         */
        foreach($aces as $ace){
            if(128 === $ace->getMask()){
                $adminSecurityIdentity = $ace->getSecurityIdentity();
                $adminSecurityHandler->addObjectOwner($acl,$adminSecurityIdentity);  
                $adminSecurityHandler->updateAcl($acl);
            }
        }



Friday, July 1, 2016

Symfony DIC: setter injection

Most of the time, when defining a service I inject into constructor parameters or other services. But it is possible to use the Symfony DIC to inject parameters/services into setter methods:

     newsletter_manager:
         class:     NewsletterManager
         calls:
             - [setMailer, ['@my_mailer']]

http://symfony.com/doc/current/components/dependency_injection/types.html
 

Where:

setMailer  - is the name of the method
['@my_mailer'] - is the name of the service being injected

Practical example with Sonata

If you are using Sonata Admin Bundle and Sonata User Bundle, you may want to access the Entity Manager for some reasons. For this we will inject the service ''@doctrine.orm.entity_manager" using a setter method.

I will create a bundle named ApplicationSonataAdminUserBundle where I will place my new Admin class. In the project's services.yml I will add the new service definition:


sonata.user.admin.user:
        class: Application\Sonata\UserBundle\Admin\Model\UserAdmin
        arguments: [~,"%sonata.user.admin.user.entity%","SonataAdminBundle:CRUD"]
        calls:
            - [setUserManager, ['@fos_user.user_manager']]
            - [setTranslationDomain, ['%sonata.user.admin.user.translation_domain%']]
             - [setEntityManager, ["@doctrine.orm.entity_manager"]]
        tags:
            - { name: sonata.admin, manager_type: orm, group: "sonata_user", label: "users", label_catalogue: "SonataUserBundle",label_translator_strategy: "sonata.admin.label.strategy.underscore",icon: "<![CDATA[<i class='fa fa-users'></i>]]>" }


And of course in the class I will add the setter method "setAuthorizationChecker":

use Sonata\AdminBundle\Admin\AbstractAdmin;

class UserAdmin extends AbstractAdmin
{


    protected $entityManager;

    public function setEntityManager(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }


...
}

Tuesday, June 28, 2016

Vagrant and ScotchBox

Developing on local machine brings problems like:
-  different software version on local machine than production(PHP, MySQL etc)
-  if you are messing with php.ini or other configuration file you need to uninstall it and install it from scratch.

Solution: use a virtual machine

Advanced solution: use a predefined virtual machine

For the advanced solution we need:

- VirtualBox : An x86 virtualization software package distributed under either the GNU GPL
- Vagrant : enables users to create and configure lightweight, reproducible, and portable development environments.
- ScotchBox - is a preconfigured Vagrant Box with a full array of LAMP Stack ( box.scotch.io )
-----------------------------------------------------------------------------------------------------------------------
You can search for other Vagrant boxes here: https://atlas.hashicorp.com/boxes/search
------------------------------------------------------------------------------------------------------------------------
Install VirtualBox and Vagrant on your machine.
Clone ScotchBox on your local machine:
    
git clone https://github.com/scotch-io/scotch-box.git my-project
 
Start the box! If this is your first time, the box will need to download. After that, everything should be ultra-fast to start:

vagrant up

 You can access the new web server on your virtual machine at this address:
 http://192.168.33.10/

Check the official website  https://box.scotch.io/  for details on how to connect with MySQL and SSH.

How to enable Zend OPCache on ScotchBox:

I lost some time until I found that I need to put opcache.enable = 1 also in this file: /etc/php5/apache2/conf.d/user.ini

https://github.com/scotch-io/scotch-box/issues/163

Monday, June 13, 2016

Symfony - How to apply validation based on user input using validation groups

In Symfony applications validation constraints are applied to the Entity and not to the Form. In some cases, however, you'll need to validate an object against only some constraints on that class. To do this, you can organize each constraint into one or more "validation groups", and then apply validation against just one group of constraints.

http://symfony.com/doc/current/book/validation.html#book-validation-validation-groups

Even better, you can determine which validation group should be applied based on the value filled in form by user:

http://symfony.com/doc/current/book/forms.html#groups-based-on-the-submitted-data

Let's say we have a   Product entity. Product entity has a 2 properties: description and category.
Depending on the "category "property different validation groups can be applied to 'description'. Symfony defines a group called "Default" in which are included all the validation which are not marked as part of any group.


<?php

class Product
{
    /**
     * @var string
     *
     * @ORM\Column(name="description", type="text",  nullable=false)
     * @Assert\NotNull(groups={"imported"})
     */
    protected $description;

    protected $category;
}

I marked the NotNull validation as being part of "imported" group.

Now in the form class using the Product entity I need to put the logic based on which the validation groups are used:


<?php

use AppBundle\Entity\Product;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ProductType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
       ....
    }
    // ...
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'validation_groups' => function (FormInterface $form) {
                $data = $form->getData();

                if ($data->getCategory() === 'Exotic fruits' ) {
                    return array('Default', 'imported');
                }

                return array('Default');
                },
        ));
    }
}

The code above basically says :
 - if category is "Exotic fruits" than apply "Default" and "imported" validations.
- otherwise apply only "Default" validations

Sometimes you need advanced logic to determine the validation groups. If they can't be determined by a simple callback, you can use a service:   http://symfony.com/doc/current/cookbook/validation/group_service_resolver.html

Tuesday, May 24, 2016

Explaining Mediator pattern to myself

NOTE: This is how I understand  this concept now, it may not be the right way

I used the events system from Doctrine recently and I would like to understand more in depth the Mediator pattern.

From Wikipedia: "With the mediator pattern, communication between objects is encapsulated with a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces the dependencies between communicating objects, thereby lowering the coupling."

Good, let's see some code without mediator pattern.


I have a "Person" class and I would want to log all the modifications that happen on those class properties. We can say the "Person" class is the producer of events and the "Logger" class is the consumer.


<?php

class Logger
{
 public function addMessage($msg)
 {
  echo $msg;
 }
}

class Person
{
 private $name;
 private $logger;
 
 public function __construct($logger)
 {
  $this->logger = $logger;
 }
 
 public function getLogger()
 {
  return $this->logger;
 }
 
 public function setName($name)
 {
  $this->name = $name;
  $msg = "Name was set to: {$name}";
  $this->logger->addMessage($msg);
 }
 
 public function getName()
 {
  return $this->name;
 } 
}

$logger = new Logger();

$first = new Person($logger);
$first->setName('Ion');

I am injecting an object of class Logger into Person and in the setName() method I am calling (call the  "Consumer")  $logger->addMessage().

The problem is if I want to modify the Logger class, like change addMessage() to addLogMessage() or whatever, I will need to modify also the Person class, the two classes are strongly coupled.

Mediator

Image from servergrove.com

When using the mediator pattern between the Producer and the Consumer of the event there is another class: the mediator. I will call my class "Dispatcher".

The Dispatcher class has two methods:
  • addListener($eventName, $callback) 
  • notify($eventName)  - in my case I added also some parameters to better exemplify
With the "addListener" method I am building an array containing as keys the event names  and as value the callbacks.

Now I will be injecting the Dispatcher into the Person constructor, and no direct relation will exist between Person and Logger class (producer and consumer).

When setting a new value for property Name, the dispatcher will notify (read this call) all callbacks associated with that event name.

I will create a listener for the 'property.changed' event which will call $logger->addMessage() method. Here is where the connection between Producer and Consumer is made.


<?php

class Logging
{
 public function addMessage($msg)
 {
  echo $msg;
 }
}

class Dispatcher
{
 private $listeners=array();
 
 public function addListener($eventName, $callback)
 {
  $this->listeners[$eventName]=$callback;
 }
 
 public function notify($eventName, $parameter1, $parameter2)
 {
  $this->listeners[$eventName]($parameter1, $parameter2);
 }
}

class Person
{
 private $name;
 private $dispatcher;
 
 public function __construct($dispatcher)
 {
  $this->dispatcher = $dispatcher;
 }
 
 public function setName($name)
 {
  $this->name = $name;
  $this->dispatcher->notify('property.changed', 'Name', $name);
 }
 
 public function getName()
 {
  return $this->name;
 }
}



$log = new Logging();

$dispatcher = new Dispatcher;
$dispatcher->addListener('property.changed', function ($propertyName, $value) use ($log) {
   $log->addMessage(
    "Property - {$propertyName} - was set to: {$value}"
   );
  });


$first = new Person($dispatcher);
$first->setName('Ion');

Of course this is a very basic implementation of the concept, real life example are EventDispatcher from Symfony or EventManager from Doctrine.

Some source of inspiration for this article:

http://blog.ircmaxell.com/2013/01/mediators-programming-with-anthony.html

http://blog.servergrove.com/2013/10/23/symfony2-components-overview-eventdispatcher


Monday, May 23, 2016

Customize color codes for visual elements using a Twig Extension

Situation:

In a ticketing system, next to each ticket title I need to display a status, each status with a certain CSS style. I will be using Bootstrap  labels for displaying a certain color for each status.

I would want to avoid to have the entire logic (in Twig)  in each view where I display these statuses,  like this:


<?php

{% if ticket.status == 'start' %}
    <span class="label label-default">{{ ticket.status|trans }}</span>
{% elseif ticket.status == 'working' %}
    <span class="label label-primary">{{ ticket.status|trans }}</span> 
 
...
 
{% endif %}


Solution:

Twig Extension "The main motivation for writing an extension is to move often used code into a reusable class"

Implementation:

Start by creating a directory TwigExtension, and inside create a PHP file with a class. This class will extend \Twig_Extension.

I will create a filter which will return a certain CSS class depending on the status string, the name of the filter is "statusLabel" . I believe the code below is self explanatory.


<?php

namespace MyBundle\TwigExtensions;

/**
 * This extension handles how are displayed the statuses.
 * The status is displayed using Boostrap labels, for each status there is a certain CSS label class
 *    <span class="label label-info">Info Label</span>
 * 
 * @author Cristian Pana
 */
class StatusLabelExtension extends \Twig_Extension
{
    public function getFilters()
    {
        return array(
            new \Twig_SimpleFilter('statusLabel', array($this, 'statusLabel')),
        );
    }

    public function statusLabel($status)
    {

        switch($status){
            case 'created':
                $label = 'label label-default';
                break;
            case 'start_working':
                $label = 'label label-info';
                break;
            case 'waiting_feedback':
                $label = 'label label-primary';
                break;
            case 'closed':
                $label = 'label label-success';
                break;
            default:
                $label = 'label label-danger';    
        }

        return $label;
    }

    public function getName()
    {
        return 'statusLabel_extension';
    }
}


This extension will be registered as a service tagged with 'twig.extension' in your service.yml file:


    status.label.twig_extension:
        class: MyBundle\TwigExtensions\StatusLabelExtension
        public: false
        tags:
            - { name: twig.extension }

Now I can use the newly created Twig filter in my views:


  <span class="{{ ticket.status|statusLabel }}">{{ ticket.status|trans }}</span>