Since the first article I submitted to Digg was voted onto the front page a couple of days ago, we're going to add onto the script and make it update the conversion rates from a website that keeps track of them all. This is quite a jump from the first tutorial and so buckle your seatbelt, Dorothy, 'cause Kansas... OK, 'nuff of the Matrix quotes. Let's begin!
If you haven't read part 1 then you'll be
well advised to give it a read through. I'm not going to re-explain all the code from the first part because that would be out-right silly.
First we are going to turn our script into a class, we also have to think about memory here - something I'm sure many PHP programmers don't give a damn about. I was considering using singletons but I haven't for 2 obvious reason:
- What if we wanted to modify the source and target currencies?
- It's a bit advanced for this tutorial. Read guide on singletons.
As the first tutorial was aimed for the neophytes of the community, I'll also be keeping this tutorial simple. If I lose you at any point, please don't hesitate to ask for clarification.
First we are going to define our class:
php Code:
class TalkPHP_CurrencyConverter
{
}
Simple enough, but she's looking rather empty. Let's fill her up to the brink with various members - variables and constants. First the variables we are going to be using:
php Code:
private $m_fSourceRate;
private $m_fSourceCurSymbol;
private $m_fSourceIntlSymbol;
private $m_fTargetRate;
private $m_fTargetCurSymbol;
private $m_fTargetIntlSymbol;
...And then we'll want a couple of class constants to be used and shared. The constant
OPT_FILE will be the location where our file will be kept, whereas
OPT_URL is the XML resource containing all the conversion rates based against the Euro.
php Code:
const OPT_FILE = 'talkphp_conversion_rates.json';
const OPT_URL = 'http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml';
If you are a little unsure about class constants then please refer yourself to
this guide on PHP constants. I hope I'm not going too fast, too quickly.
Next we are going to create our construct. A construct function is the function which is called when the object class is instantiated. In PHP4 this function was simply called the same as the class, and thus if we had our class called
TalkPHP_CurrencyConverter then our construct function name would be
TalkPHP_CurrencyConverter(). However, because we're lucky enough to have the immense power of PHP5, our function is called
__construct():
php Code:
public function __construct($szCurrencyFrom =
'',
$szCurrencyTo =
''){}
We're giving the 2 arguments,
$szCurrencyFrom and
$szCurrencyTo so that we can call another function,
updateRates(), which we will get to later on, if we do not wish to convert anything. Our next code will check for the variables being empty, and if they are, call the update function which we will write later on:
php Code:
if(empty($szCurrencyFrom) ||
empty($szCurrencyTo)){ $this->
updateRates();
return;
}
We can then procure the details of the 2 currencies - the source and the target. From those we need the international currency symbol and the locale currency symbol. Although we don't
technically require the currency symbol for our source currency, it's good practice to return it nevertheless just in case you wish to extend onto the class at a later date.
php Code:
setlocale(LC_MONETARY,
$szCurrencyFrom);
$aSource =
localeconv();
$this->
m_fSourceCurSymbol =
$aSource[
'currency_symbol'];
$this->
m_fSourceIntlSymbol =
$aSource[
'int_curr_symbol'];
setlocale(LC_MONETARY,
$szCurrencyTo);
$aTarget =
localeconv();
$this->
m_fTargetCurSymbol =
$aTarget[
'currency_symbol'];
$this->
m_fTargetIntlSymbol =
$aTarget[
'int_curr_symbol'];
OK, long list, long list! It may seem a little bit harsh to throw so much code out at you, but it's really just code repetition, we set the locale and return the 2 symbols for our source currency, and then we repeat the process for the second currency. I won't go into how this works because I covered that in the first part.
Last but not least we'll want to obtain the rates from our cache file. We're using PHP's list construct. This essentially works by taking the first offset of the array and assigning it to the left most variable in our list construct.
php Code:
list($this->m_fSourceRate, $this->m_fTargetRate) = $this->getRates();
It may be wise now to crack open the
getRates function to have a peak. We'll be using exceptions extensively from now on, this will allow us to return various messages from our class to the front-end, thus we can avoid simply returning an equivocal boolean.
php Code:
private function getRates()
{
}
We'll be setting a scope of private for this function because we are calling it from inside our class construct. We will first check to see if the cache file currency exists, if it doesn't then we'll call the update function to create it for us:
php Code:
if(!
file_exists(self::
OPT_FILE)){ $this->
updateRates();
}
Nothing overly complicated about that code, so we'll move swiftly onto the next snippet. Enjoying yourself yet?
"Woah!" you say? Yes, we'll be using JSON because I'm quite fond of its storage technique. If you're a little unfamiliar with JSON then by all means please
read our article on JSON to enlighten yourself.
Fundamentally speaking, however, what we are doing is reading the contents of our JSON cache file and then decoding it using PHP5's native
json_decode function. Next we need to ensure we've got back the correct data to continue on with the script, if not then we're throwing an exception to really put our foot down!
php Code:
if(!isset($aContent->{$this->m_fSourceIntlSymbol}))
{
throw new Exception('Invalid source currency supplied.');
}
if(!isset($aContent->{$this->m_fTargetIntlSymbol}))
{
throw new Exception('Invalid target currency supplied.');
}
If you've read meticulously over the JSON article then you'll realise, unless you explicitly specify otherwise, the
json_decode function will return the data back as an object - I suppose you could draw some parallels between storage objects and arrays, though if were to be pedantic, they are quite dissimilar. We use the arrow (->) to access members of the object which relate to the items in our file which were JSON encoded. Thus:
php Code:
return array($aContent->
{$this->
m_fSourceIntlSymbol},
$aContent->
{$this->
m_fTargetIntlSymbol});
We're using the curly braces as we're saying we want to resolve the value of the variable and use that, instead of using the variable name literally. However, PHP would cause an error anyway because we've left the dollar sign in there which results in an incorrect syntax.
Now you can see why we're using PHP's list construct to crack open the array and assign it to 2 different variables - because we're returning an array with the 2 conversion rates in it that we need.
Before we jump into the
toCurrency function, which if you've used Zend Framework before which comes highly recommended from me, then you'll realise I've temporarily borrowed the function's name, we'll create our update function which uses the SimpleXML class - now native to PHP since PHP5, to parse the XML document and return the conversion rates. We're getting perhaps a little advanced here but I've got faith in your abilities!
Again we start with an empty function with a private scope:
php Code:
private function updateRates()
{
}
...And again we're going to fill it with juicy code. As we're obtaining data from an unreliable source - a source which we have no control over, we'll be using exceptions to ensure it has returned from the server:
php Code:
$szContent =
file_get_contents(self::
OPT_URL);
if(!
$szContent){ throw
new Exception
('XML resource unavailable.');
}
Not a lot new here. We'll then want to create a new instance of SimpleXML and create an empty array which we'll use to store the items from the XML document:
php Code:
$pXML =
new SimpleXMLElement
($szContent);
$aRates =
array();
Now, as you may be aware, XML has come on in leaps and bounds over the past couple of years. The predominant reason for XML's rise to power is due to the requirement of needing some standardisation in data that is passed from one place to another. XML, like HTML, but more closely related to XHTML, has nodes that may have attributes, even namespaces, and inevitably, data inside the nodes. If our XML document looked something like so:
xml Code:
<cars>
<car maker="Ferrari">Enzo</car>
<car maker="Lamborghini">LP640</car>
</cars>
We would be able to access the data by traversing 2 nodes, namely,
cars and
car. In PHP object style this would be:
php Code:
echo $pXML->
cars->
car[
0];
Which would in turn echo out our first car, the Enzo. And so, once we have initiated a new SimpleXML instance, we can begin to parse the XML document to return the items we require for our script to work perfectly.
php Code:
foreach($pXML->Cube->Cube->Cube as $pChildren)
{
$aRates[(string) $pChildren['currency']] = (float) $pChildren['rate'];
}
This will return every item as:
Currency Symbol => Conversion Rate against Euro. All that's left to do now for our update function is to encode its contents using
json_encode and then save it into its designated file. I'm sure the
fwrite process is of no mystery to yourself, but for argument's sake I present you:
php Code:
$pFile =
fopen(self::
OPT_FILE,
'w+');
fwrite($pFile, json_encode
($aRates));
fclose($pFile);
Let us track back to where we were. If you remember, we'd just about finished our class to convert to a currency, but we just needed the function to perform the last calculations and to return it to use with the currency symbol automatically prepended. I may as well throw this function at you in 1 go because in all honesty, everything declared and on the table, there's not a lot to it.
php Code:
public function toCurrency
($fAmount){ $fTotal =
($fAmount /
$this->
m_fSourceRate) *
$this->
m_fTargetRate;
return $this->
m_fTargetCurSymbol .
number_format($fTotal,
2,
'.',
',');
}
So, was ist das? Pardon me, German never was my most favoured second language. Furthermore, mathematics was never my most favoured subject and so excuse me while I get Karl to type up an explanation. This calculation converts a given amount
(in any currency), divides that amount by the base rate for that currency
(for example, the EUR to USD base rate) and then multiplies it by the target rate
(for example EUR to GBP base rate).
Once we've done all the final calculations we return the total back to the front-end with the currency's symbol prepended. And because exceptions are prevalent in our script, we use the try and catch block to try the script and catch any exceptions:
php Code:
try
{ $pCurrency =
new TalkPHP_CurrencyConverter
(CURRENCY_GBP, CURRENCY_USD
);
echo $pCurrency->
toCurrency(50);
}catch
(Exception
$pEx){ echo $pEx->
getMessage();
}
Based on today's conversion rates, 50 GBP into USD comes to $103.48. And not one defined constant which requires us to change everyday! Naturally I would have done this in the first part of the guide, but I didn't want you to get too far ahead of yourself. People learn a step at a time!
If we want to update the conversion rate cache file then we merely call the class, like so:
php Code:
new TalkPHP_CurrencyConverter();
To sum this guide up, because conversion rates are fluctuating constantly throughout the day as billion dollar deals are sealed, and as managing directors pay for their McWhoppas
(or whatever they're called), McDonalds accumulate more money. So what better way than to give our script the awe-inducing ability to monitor and update its
own conversion rates? That way you can code it and then let it fly, fly, fly, all on its own. No hands, no hands!