Monday, December 5, 2011

How to create a Service to embed business logic functions in Symfony2

Services are classes where you will write the Business Logic code or any other treatment that you want to make accessible to a controller or another service. The Logger delivered with Symfony2 is a service, like the Registry of Doctrine, which gives access to the EntityManager.

The example below shows how to create a service, to pass two services it can use as parameters.
1- We begin by defining our service in the configuration file for services of our bundle (AcmeDemoBundle) :
src/Acme/DemoBundle/Resources/config/services.xml
<?xml version="1.0" ?>
 

 
    
        
          
        
 
        
          
        
 
        
        
            
            
            
        
        
    
 

Was added a node , passed the service name in the id attribute and PHP class in class attribute.
As parameters we added 2 of type "service" : the Doctrine registry service (id = "doctrine") and logger (id = "logger").
We added one more to which we return later ...


2- We create the MyService PHP class containing the code of our service "my_service".
src/Acme/DemoBundle/Service/MyService.php
<?php
 
namespace Acme\DemoBundle\Service;
 
use Symfony\Bundle\DoctrineBundle\Registry;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
 
class MyService
{
 
   //default EntityManager
   protected $em;
   //Logger
   protected $logger;
 
   public function __construct(Registry $doctrine, LoggerInterface $logger)
   {
      $this->em = $doctrine->getEntityManager();
      $this->logger = $logger;
   }
 
   public function insertTime($message = 'Default message :)')
   {
      $time = new \DateTime;
 
      $myRecord = new \Acme\DemoBundle\Entity\MyServiceTable();
      $myRecord->setMessage($message);
      $myRecord->setCallTimer($time);
 
      $this->em->persist($myRecord);
      $this->em->flush();
 
      $this->logger->info('Insert one record in my_service_table  (' . $time->format('Y-m-d H:i:s') . ') !');
   }
 
   public function getTimeSinceLastCall()
   {
      $calls = $this->em->getRepository('AcmeDemoBundle:MyServiceTable')
         ->findBy(array (), array ('callTimer' => 'DESC'), 2);
 
      if (count($calls) == 2)
      {
         $diff = $calls[0]->getCallTimer()->getTimestamp() - $calls[1]->getCallTimer()->getTimestamp();
      }
      else
      {
         $diff = 'N/A';
      }
 
      return $diff;
   }
 
}
We first wrote the constructor that takes as parameters the that was defined in the services.xml file. EntityManager has been affected to $em property, and Logger to $logger.
Then we created two public methods :
  • insertTime that writes a database record on each call, with a timestamp (my_service_table.call_timer)
  • getTimeSinceLastCall that returns the duration (in seconds) since the last call.
Here is the code of the MyServiceTable entity used for the occasion :
src/Acme/DemoBundle/Entity/MyServiceTable.php
<?php
 
namespace Acme\DemoBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
 
/**
 * Acme\DemoBundle\Entity\MyServiceTable
 *
 * @ORM\Table(name="my_service_table")
 * @ORM\Entity
 */
class MyServiceTable
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;
 
    /**
     * @var datetime $callTimer
     *
     * @ORM\Column(name="call_timer", type="datetime", nullable=false)
     */
    private $callTimer;
 
    /**
     * @var string $message
     *
     * @ORM\Column(name="message", type="string", length=255, nullable=false)
     */
    private $message;
 
 
 
    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }
 
    /**
     * Set callTimer
     *
     * @param datetime $callTimer
     */
    public function setCallTimer($callTimer)
    {
        $this->callTimer = $callTimer;
    }
 
    /**
     * Get callTimer
     *
     * @return datetime 
     */
    public function getCallTimer()
    {
        return $this->callTimer;
    }
 
    /**
     * Set message
     *
     * @param string $message
     */
    public function setMessage($message)
    {
        $this->message = $message;
    }
 
    /**
     * Get message
     *
     * @return string 
     */
    public function getMessage()
    {
        return $this->message;
    }
}
SQL script of the table my_service_table :
CREATE TABLE IF NOT EXISTS `my_service_table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `call_timer` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `message` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Test table for MyService';
3- We test our service by calling it from the demo controller of AcmeDemoBundle : src/Acme/DemoBundle/Controller/DemoController.php
<?php
 
namespace Acme\DemoBundle\Controller;
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Acme\DemoBundle\Form\ContactType;
 
// these import the "@Route" and "@Template" annotations
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
 
class DemoController extends Controller
{
    /**
     * @Route("/my_service", name="_demo_my_service")
     * @Template()
     */
    public function myServiceAction()
    {
       //Call the CallTimer insertion from MyService
       $this->get('acme.demo.my_service')->insertTime('Call depuis le controlleur...');
       //Call the last 2 calls interval from MyService
       $diff = $this->get('acme.demo.my_service')->getTimeSinceLastCall();
 
       //Send time interval to the view
       return array('diff' => $diff);
    } 
}
For each call of the URL /demo/my_service, we call our service's method insertTime that writes a record in the table my_service_table and logs a message. Then we call getTimeSinceLastCall that returns the amount of seconds since the last call of the URL, and returns the result to the view.
src/Acme/DemoBundle/Resources/view/Demo/myService.html.twig
{% extends "AcmeDemoBundle::layout.html.twig" %}
 
{% block title "My Service" %}
 
{% block content %}
    <h1>My Service !</h1>
 
    <div>
       <span>Last Call was {{ diff }} seconds ago ...</span>
    </div>
{% endblock %}
 
{% set code = code(_self) %}
Result :


4- Back on the defined in the service configuration :
        
        
            
            
            
        
        
It defines the channel used by the logger Monolog (default Symfony2 Logger) when it writes in the log files, as shown below:
[2011-11-30 02:06:39] request.INFO: Matched route "_demo_my_service" (parameters: "_controller": "Acme\DemoBundle\Controller\DemoController::myServiceAction", "_route": "_demo_my_service") [] []
[2011-11-30 02:06:39] my_service.INFO: Insert one record in my_service_table (2011-11-30 02:06:39) ! [] []
[2011-11-30 02:06:39] request.INFO: Matched route "_demo_my_service" (parameters: "_controller": "Acme\DemoBundle\Controller\DemoController::myServiceAction", "_route": "_demo_my_service") [] []
[2011-11-30 02:06:39] my_service.INFO: Insert one record in my_service_table (2011-11-30 02:06:39) ! [] []
[2011-11-30 02:06:39] request.INFO: Matched route "_demo_my_service" (parameters: "_controller": "Acme\DemoBundle\Controller\DemoController::myServiceAction", "_route": "_demo_my_service") [] []
[2011-11-30 02:06:39] my_service.INFO: Insert one record in my_service_table (2011-11-30 02:06:39) ! [] []
[2011-11-30 02:06:39] request.INFO: Matched route "_demo_my_service" (parameters: "_controller": "Acme\DemoBundle\Controller\DemoController::myServiceAction", "_route": "_demo_my_service") [] []
[2011-11-30 02:06:39] my_service.INFO: Insert one record in my_service_table (2011-11-30 02:06:39) ! [] []
More on these Dependency Injection Tags that can be used for many other things : Official tags documentation
All about Symfony2 ServiceContainer : Official ServiceContainer documentation

1 comment :

Hendra-1 said...

cool explanation
separtion the business logic into symfony service.

Thanks for sharing the tutorial

Post a Comment

Comments are moderated before being published.