Sunday, April 15, 2012

How to set a related entity with Doctrine2

When you want to set a related entity to another one, no need request the related object !

Doctrine ORM provides EntityManager::getReference($entityName, $id) method that only returns a reference to a record, not the entity itself... Let's see it in action :

<php

// ...

//This line gets the reference to the Comment record with primary key is #5
$comment = $em->getReference('AlterPHPDemoBundle:Comment', 5);

$article->addComment($comment);

// ...

Wednesday, April 11, 2012

Set up a specific log file for connections with Symfony2

Symfony2 basically embeds a full-featured logger, Monolog. A default implementation is available as 'logger' service. But you may need to log to a specifi file instead of traditional dev/prod/test.log... This article shows how to define a specific logger for connections.

I don't need to re-code a logger, the default one perfectly works. I just need to define a new file to log in, so a Monolog handler and to instanciate a new logger service based on Monolog Logger class. Let's have a look at this services.yml file :

parameters:
    connections_logs_path: %kernel.logs_dir%/connections.log

services:

    alterphp.connection.logger:
        class: Symfony\Bridge\Monolog\Logger
        arguments: [CONNECTION]
        calls:
            - [pushHandler, ['@alterphp.connection.logger_handler']]

    alterphp.connection.logger_handler:
        class: Monolog\Handler\StreamHandler
        arguments: [%connections_logs_path%, 200]

Note that connections_logs_path parameter can be directly set in app/configparameters.ini !

Then, let's see how I will use this logger to log users connections. I pass alterphp.connection.logger service in my AuthenticationSuccessHandler constructor (Read a past article about AuthenticationSuccessHandler implementation).

Service configuration file defining my AuthenticationSuccessHandler, services.yml :

services:

    security.authentication.success_handler:
        class: %security.authentication.success_handler.class%
        public: false
        tags:
            - { name: monolog.logger, channel: security }
        arguments:  ['@router', '@security.user.entity_manager', '@logger', '@alterphp.connection.logger']

And my AuthenticationSuccessHandler :

<php

namespace Acme\DemoBundle\Security\Http\Authentication;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpKernel\Log\LoggerInterface;

/**
 * Custom authentication success handler
 */
class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface
{

   private $router;
   private $em;
   private $logger;
   private $connectionLogger;

   /**
    * AuthenticationSuccessHandler constructor
    * @param RouterInterface   $router
    * @param EntityManager     $em
    * @param LoggerInterface   $logger
    * @param LoggerInterface   $connectionLogger
    */
   public function __construct(RouterInterface $router, EntityManager $em, LoggerInterface $logger, LoggerInterface $connectionLogger)
   {
      $this->router = $router;
      $this->em = $em;
      $this->logger = $logger;
      $this->connectionLogger = $connectionLogger;
   }

   /**
    * This is called when an interactive authentication attempt succeeds. This
    * is called by authentication listeners inheriting from AbstractAuthenticationListener.
    * @param Request        $request
    * @param TokenInterface $token
    * @return Response The response to return
    */
   function onAuthenticationSuccess(Request $request, TokenInterface $token)
   {
      //On logge spécialement les connexions à Sellermania (login principal)
      $logMess = 'UserID : ' . $token->getUser()->getId();
      $logMess .= '; Username : ' . $token->getUser()->getUsername();
      $logMess .= '; IP : ' . $request->getClientIp();

      $this->connectionLogger->info($logMess);

      return new RedirectResponse($this->router->generate('_home');
   }

And here is what we can read in app/logs/connections.log :


[2012-04-04 16:30:14] CONNECTION.INFO: UserID : 6523; Username : alterphp; IP : 127.0.0.1 [] []

Custom form validation constraint with Callback in Symfony2

Symfony2 forms provides many embedded validators to constrain data passed in form fields. Here is the way to define a constraint in a callback function, useful to determine a data validity relative to another field...

Given a form asking the user to chose its favorite payment ways, in a shopping application :
<?php

namespace Acme\DemoBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

/**
 * Selection of preferred payment ways form
 */
class PreferredPaymentWays extends AbstractType
{

   public function buildForm(FormBuilder $builder, array $options)
   {
      $builder
         ->add(
            'paypal', 'checkbox',
            array (
                 'label' => 'PayPal',
                 'required' => false,
            )
         )
         ->add(
            'cc', 'checkbox',
            array (
                 'label' => 'CreditCard',
                 'required' => false,
            )
         )
         ->add(
            'banktransfer', 'checkbox',
            array (
                 'label' => 'Bank Transfer',
                 'required' => false,
            )
         )
         ->add(
            'check', 'checkbox',
            array (
                 'label' => 'Personal Check',
                 'required' => false,
            )
         )
         ->add(
            'paypalEmailAddress', 'email',
            array (
                 'label' => 'PayPal email',
                 'required' => false
            )
         )
      ;
   }

   /**
    * Returns the name of this type.
    *
    * @return string The name of this type
    */
   public function getName()
   {
      return 'acme_demo_payment_ways';
   }

}