Currency Change rates and Symfony with openexchangerates API

Share Button

In this post, we’ll explain how to create and maintain a database with currencies and change rates.

In order to update our changes rates, we will user openexchangerates.org solution. (Exists as free, premium and pro)

Get your own API Key. http://openexchangerates.org/signup/

Create the parameters

The first step is to register and get an API key on openexchangerates, and prepare Symfony2 to receive this parameters

#app/config/config.yml

obtao : 
  oer :
    app_id : YOUR_APP_ID
    base_url : http://openexchangerates.org/
    latest_uri : api/latest.json
    currencies_uri : api/currencies.json

Configure your bundle to use this parameters :
>?php

namespace Obtao\CurrencyBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;


class Configuration implements ConfigurationInterface
{
    /**
     * Generates the configuration tree.
     *
     * @return TreeBuilder
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('obtao');
        
        $rootNode
            ->children()
                ->arrayNode('oer')->isRequired()
                    ->children()
                        ->scalarNode('app_id')->isRequired()->cannotBeEmpty()->end()
                        ->scalarNode('base_url')->isRequired()->cannotBeEmpty()->end()
                        ->scalarNode('latest_uri')->isRequired()->cannotBeEmpty()->end()
                        ->scalarNode('currencies_uri')->isRequired()->cannotBeEmpty()->end()
                    ->end()
                ->end()
            −>end();

        return $treeBuilder;
    }
}

Create your bundle extension
<?php

namespace Obtao\CurrencyBundle\DependencyInjection;
 
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Definition\Processor;

class CurrencyExtension extends Extension
{
    
    protected $resources = array(
        'services' => 'services.yml'
    );
    
    public function load(array $configs, ContainerBuilder $container)
    {

        $processor = new Processor();
        $configuration = new Configuration();
        $config = $processor->processConfiguration($configuration, $configs);
        
        $this->loadDefaults($container);

        $container->setParameter('obtao.oer.app_id', $config['oer']['app_id']);
        $container->setParameter('obtao.oer.base_url', $config['oer']['base_url']);
        $container->setParameter('obtao.oer.latest_uri', $config['oer']['latest_uri']);
        $container->setParameter('obtao.oer.currencies_uri', $config['oer']['currencies_uri']);
        
    }
    
    
    /**
     * @codeCoverageIgnore
     */
    protected function loadDefaults($container)
    {
        $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));

        foreach ($this->resources as $resource) {
            $loader->load($resource);
        }
    }
    
}

Create the entity

The second step is to create your currency entity (you can also extend your own, just add some usefull methods).

For your entity, you can use ISO code as primary key.

<?php

namespace Obtao\CurrencyBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @author Obtao
 * @ORM\Table(name="ob_currency")
 * @ORM\Entity(repositoryClass="Obtao\ObtaoBundle\Repository\CurrencyRepository")
 */
class Currency {

    /**
    * @ORM\Id
    * @ORM\Column(type="string",length=3)
    */
    protected $iso;

    /**
    * @ORM\Column(type="string", length=100)
    *
    */
    protected $label;

    /**
     * @ORM\Column(type="float", name="exchange_rate")
     */
    protected $exchangeRate ;
    
    
    public function __construct()
    {

    }
    
    /**
     * Set label
     *
     * @param string $label
     */
    public function setLabel($label)
    {
        $this->label = $label;
    }

    /**
     * Get label
     *
     * @return string 
     */
    public function getLabel()
    {
        return $this->label;
    }

    /**
     * Set iso
     *
     * @param string $iso
     */
    public function setIso($iso)
    {
        $this->iso = $iso;
    }

    /**
     * Get iso
     *
     * @return string 
     */
    public function getIso()
    {
        return $this->iso;
    }

    /**
     * Set exchangeRate
     *
     * @param float $exchangeRate
     * @return Currency
     */
    public function setExchangeRate($exchangeRate)
    {
        $this->exchangeRate = $exchangeRate;
    
        return $this;
    }

    /**
     * Get exchangeRate
     *
     * @return float 
     */
    public function getExchangeRate()
    {
        return $this->exchangeRate;
    }
    
    public function __toString(){
        return $this->getLabel();
    }

    public function getChangeRateToUSD(){
       return $this->convertToUSD(1);
    }
    
    public function getChangeRateFromUSD(){
      return $this->convertFromUSD(1);
    }
    
    public function convertToUSD($nbToConvert)
    {
        return $nbToConvert/$this->getExchangeRate();
    }
    
    public function convertFromUSD($nbToConvert){
        return $nbToConvert*$this->getExchangeRate();
    }
}

Create the first command : Import currencies

<?php

namespace Obtao\CurrencyBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputInterface;
use Obtao\CurrencyBundle\Entity\Currency;

/**
 * Export database datas
 */
class CurrenciesImportCommand extends ContainerAwareCommand
{
    /**
     * @see Command
     */
    protected function configure()
    {
        $this
            ->setName('obtao:currency:import')
            ->setDescription('Import currencies')
            ->setHelp(<<obtao:currency:import get current currencies on openexchange and update the database
              EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('Get currencies');
        
        $em = $this->getEm();
        
        $base_url = $this->getContainer()->getParameter('obtao.oer.base_url');
        $app_id= $this->getContainer()->getParameter('obtao.oer.app_id');
        $currencies_uri = $this->getContainer()->getParameter('obtao.oer.currencies_uri');
        
        $dbCurrencies = $em->getRepository("ObtaoBundle:Currency")->findAll();
        
        $c = curl_init();
        
        curl_setopt($c, CURLOPT_URL, $base_url.$currencies_uri."?app_id=".$app_id);
        curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($c, CURLOPT_HEADER, false);
        $content = curl_exec($c);
        
        $output->writeln("->OK");
        $output->writeln(" ");
        $output->writeln("Parsing JSON");
        $oerCurrencies = json_decode($content,true);
        
        $output->writeln("->OK");
        $output->writeln(" ");
        $output->writeln("Update currencies");
        
        $newCodes = array();
        $updatedCodes = array();
        
        foreach($dbCurrencies as $dbCurrency){
          $iso = $dbCurrency->getIso();
          if(key_exists($iso,$oerCurrencies)){
            $dbCurrency->setLabel($oerCurrencies[$iso]);
            unset($oerCurrencies[$iso]);
            $em->persist($dbCurrency);
            $updatedCodes[] = $iso;
          }
          
        }
        
        foreach($oerCurrencies as $oerCode => $oerLabel){
          $currency = new Currency();
          $currency->setIso($oerCode);
          $currency->setLabel($oerLabel);
          $em->persist($currency);
          $newCodes[] = $oerCode;
        }
        
        $em->flush();
        
        $output->writeln("OK Done");
        $output->writeln("Updated labels : [". implode(',',$updatedCodes) . "]");
        $output->writeln("New labels : [". implode(',',$newCodes) . "]");
    }

    private function getEm(){
      return $this->getContainer()->get('doctrine.orm.entity_manager');
    }

}

Your database is now filled with the Openexchangerates.org labels/currencies, but every change rate equals to 1.

Update change rates values

<?php

namespace Obtao\CurrencyBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputInterface;

/**
 * Export database datas
 */
class CurrenciesRatesCommand extends ContainerAwareCommand
{
    /**
     * @see Command
     */
    protected function configure()
    {
        $this
            ->setName('obtao:currency:update')
            ->setAliases(array('obtao:rates:update'))
            ->setDescription('Updates currencies rates')
            ->setHelp(<<obtao:currency:update get current rates on openexchange and update the database
               EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('Get currencies');
        
        $em = $this->getEm();
        
        $app_id= $this->getContainer()->getParameter('obtao.oer.app_id');
        $base_url = $this->getContainer()->getParameter('obtao.oer.base_url');
        $lastest = $this->getContainer()->getParameter('obtao.oer.latest_uri');
        
        $dbRates = $em->getRepository("ObtaoBundle:Currency")->findAll();
        
        $c = curl_init();
        curl_setopt($c, CURLOPT_URL, $base_url.$lastest."?app_id=".$app_id);
        curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($c, CURLOPT_HEADER, false);
        $content = curl_exec($c);
        
        $output->writeln("->OK");
        $output->writeln(" ");
        $output->writeln("Parsing JSON");
        $oerValues = json_decode($content,true);
        
        $output->writeln("->OK");
        $output->writeln(" ");
        $output->writeln("Update currencies rates values ");
        
        $oerRates = $oerValues["rates"];

        $nbRates = 0;
        $ok = 0;
        $notFound = array();
        
        foreach($dbRates as $dbRate){
          $code = $dbRate->getIso();
          if(key_exists($code, $oerRates)){
            $dbRate->setExchangeRate($oerRates[$code]);
            $em->persist($dbRate);
            $ok++;
          }else{
            $notFound[] = $code;
          }
          $nbRates++; 
        }
        $em->flush();
        
        $output->writeln("Done. $ok/$nbRates ( = ok / nb of rates in oer)");
        if(count($notFound) > 0){
          $output->writeln("Not found in openexchangerates API : " . implode(",",$notFound));
        }
    }

    private function getEm(){
      return $this->getContainer()->get('doctrine.orm.entity_manager');
    }

}

Here we are, you database is up to date and currencies exchange rates too (in USD).

Create a Service for currencies

In order to use our new database, we can create a simple currency converter service with some inner cache.

It provides 2 simple methods :

- Get exchange rate from one currency to another based on the iso currency string.

public function getExchangeRate($isoFrom, $isoTo)

- Convert an amount from a currency to another
public function convertAmount($amount ,$isoFrom, $isoTo)

<?php

namespace Obtao\CurrencyBundle\DependencyInjection;

use Doctrine\ORM\EntityManager;

class CurrencyConverter{

    protected $em;
    protected $rates;
    
    public function __construct(EntityManager $em)
    {
      $this->em = $em;
      $this->rates = array();
    }
    
    public function getExchangeRate($isoFrom, $isoTo){
      
      if(! key_exists($isoFrom,$this->rates)){
        $this->rates[$isoFrom] = $this->em->getRepository("ObtaoBundle:Currency")->findOneByIso($isoFrom);
      }
      
      if(! key_exists($isoTo,$this->rates)){
        $this->rates[$isoTo] = $this->em->getRepository("ObtaoBundle:Currency")->findOneByIso($isoTo);
      }
      
      if(! key_exists($isoFrom,$this->rates)){
        throw new \Exception("$isoFrom does not exist in Obtao currency database");
      }
      if(! key_exists($isoTo,$this->rates)){
        throw new \Exception("$isoTo does not exist in Obtao currency database");
      }
      
      return $this->rates[$isoFrom]->getChangeRateToUSD() * $this->rates[$isoTo]->getChangeRateFromUSD();
      
    }
    
    public function convertAmount($amount ,$isoFrom, $isoTo){
      return $amount * $this->getExchangeRate($isoFrom, $isoTo);
    }
}

Declare your service in services.yml file
#Obtao\CurrencyBundle\Resources\config\services.yml
services:
  # ...
  obtao.currency_converter:
    class: Obtao\CurrencyBundle\DependencyInjection\CurrencyConverter
    arguments : [@doctrine.orm.entity_manager]

As a bonus we also created a Twig filter.
#Obtao\CurrencyBundle\Resources\config\services.yml
services:
  # ...
  twig.extension.obtao_currency:
    class: Obtao\CurrencyBundle\Twig\Extensions\ObtaoTwigCurrency
    tags:
        - { name: 'twig.extension' }
    arguments : [ @obtao.currency_converter ]

<?php

namespace Obtao\CurrencyBundle\Twig\Extensions;

use Obtao\CurrencyBundle\\DependencyInjection\CurrencyConverter;

class ObtaoTwigCurrency extends \Twig_Extension{

    private $converter;
  
    public function __construct(CurrencyConverter $converter) {
      $this->converter = $converter;
    }
  
    public function getName()
    {
        return 'obtao_currency';
    }
    
    public function getFilters()
    {
        return array(
            'convertCurrency' => new \Twig_Filter_Method($this, 'getConversionBetween')
        );
    }
 
    public function getConversionBetween($amount, $isoFrom, $isoTo="USD")
    {
        try{
          $value = $this->converter->convertAmount($amount, $isoFrom, $isoTo);
          return round($value,2);
        }catch(\Exception $e){
          return "?";
        }
    }
}

You can now use it in an efficient and simple way :

Convert amount in USD to amount in EUR :

{{ amount | convertCurrency('USD','EUR')}}

Convert amount in EUR to amount in USD :
{{ amount | convertCurrency('EUR')}}

Share Button

2 thoughts on “Currency Change rates and Symfony with openexchangerates API

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Protected by WP Anti Spam