May 14, 2014
Table of contents:
Computers are the perfect tool for running calculations on massive numbers to within a high degree of accuracy. However this can break down in situations where the required digits of precision exceed the available memory of the computer.
In PHP we have the BCMath extension. These functions allow you to run calculations on numbers of any size and precision because they are represented as strings.
The BCMath extensions will work natively out of the box as regular PHP functions, however it provides us with an excellent opportunity to work on a dedicated package for working with BCMath in an object oriented way.
Over the last couple of weeks I’ve been looking at some of the integral parts of building PHP packages. Now that we’ve established an understanding of some of these important concepts, we can use that knowledge to start building PHP packages that solve our problems but can also be made available to the wider PHP community.
In this tutorial I’m going to walk you though how to build a PHP package for working with the BCMath functions in an object oriented way.
Before I get into writing the code of the package, first I will describe how I intend for it to work.
BCMath is set of PHP functions that allow to you to use Arbitrary-precision arithmetic. This package is basically going to be a wrapper for these functions so that they can be used in an object oriented way.
The class will allow you to input certain values and run one of the BCMath commands. This will return a Value Object in the form of a Number
object (What is the difference between Entities and Value Objects?).
The package will have a custom exception incase a developer who uses the package tries to use an input that is not a valid number (When should you use an Exception?).
Each command will extend an Abstract class (What are Abstract classes?) and implement an interface (When should I code to an Interface?).
Last week I walked you through How to create a PSR-4 PHP package. I won’t cover the same ground again so I will be skipping setting the files and directories up and instead jumping straight into the code.
For this package I will be using the namespace PhilipBrown\Math
and I will be using PSR-4 autoloading.
An important aspect of this package is ensuring that only valid inputs types are given to the class to work with. There is no point in trying to do calculations on "hello world"
because it’s just not going to work. When the package receives an invalid input it is a good opportunity to throw an Exception
because this is an exceptional circumstance that cannot be recovered from and it is caused by an external input.
In order to separate the custom exceptions from the rest of the package’s code, I usually create an Exceptions
directory under the main src
directory.
For this package I will only require one custom exception for when an invalid input has been given to the class:
<?php namespace PhilipBrown\Math\Exceptions;
use Exception;
class InvalidNumberException extends Exception
{
}
As you can see this is a really simple class as we inherit all of the methods from the Exception
class and we don’t need any custom methods. Now that this custom exception class has been created, the consumers of this package can target this specific exception by type hinting it.
This package will revolve around a Number
Value Object that will be used to run the calculations. I will also need to define a specific PositiveNumber
Value Object to ensure certain numbers are not of a negative value.
For each of these Value Objects, I want to have a consistent way of getting the value of the object. This is a perfect opportunity to use an Interface contract that both of the objects will implement:
<?php namespace PhilipBrown\Math;
interface NumberInterface
{
/**
* Get the value
*
* @return string
*/
public function getValue();
}
Now that I’ve got the NumberInterface
defined I can create the Number
Value Object:
<?php namespace PhilipBrown\Math;
use PhilipBrown\Math\Exceptions\InvalidNumberException;
class Number implements NumberInterface
{
/**
* The value of the Number
*
* @var string
*/
protected $value;
/**
* Create a new instance of Number
*
* @param string $value
* @param void
*/
public function __construct($value)
{
if (!$this->isValid($value)) {
throw new InvalidNumberException("$value is not a valid number.");
}
$this->value = $value;
}
/**
* Get the value
*
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* Check to see if the number is valid
*
* @param string $number
* @return bool
*/
protected function isValid($number)
{
return (bool) preg_match('/^\-?\d+(\.\d+)?$/', $number);
}
/**
* Return the value when cast as a string
*
* @return string
*/
public function __toString()
{
return $this->value;
}
}
When a new Number
object is created, the value of the number is passed in through the constructor and passed to the isValid()
method. If the isValid()
method returns false
, the InvalidNumberException
will be thrown.
The getValue()
method is implemented by simply returning the $this->value
class property.
Also notice I’ve implemented the __toString()
magic method so when this number is cast as a string it will return $this->value
.
Next I need a Value Object that will ensure that the value is a positive number:
<?php namespace PhilipBrown\Math;
class PositiveNumber extends Number implements NumberInterface
{
/**
* Check to see if the number is valid
*
* @param string $number
* @return bool
*/
public function isValid($number)
{
return (bool) preg_match('/^\d+$/', $number);
}
}
The PositiveNumber
class can extend the Number
class so all of the methods are inherited. The only thing we need to overwrite is the method to ensure that the value is valid.
The main entry point to this package will be the Math
class. This class will be do the majority of the leg work coordination and will be used as the central API:
<?php namespace PhilipBrown\Math;
class Math
{
/**
* The scale to use
*
* @var integer
*/
protected $scale;
/**
* Create a new instance of Math
*
* @return void
*/
public function __construct()
{
$this->scale = new PositiveNumber(0);
}
/**
* Create a new Number instance
*
* @param $value int
* @return PhilipBrown\Math\Number
*/
public function create($value)
{
return new Number($value);
}
/**
* Set default scale parameter for all bc math functions
*
* @param $scale int
* @return int
*/
public function setScale($scale)
{
if (!$scale instanceof PositiveNumber) {
$scale = new PositiveNumber($scale);
}
return $this->scale = $scale;
}
}
When a new instance of the Math
class is created, the $this->scale
class property defaults to 0. I’ve also added a helper method to allow a consumer of this package to easily create()
a new Number
instance.
The setScale()
method allows the developer to override the $this->scale
property. The developer can either pass in an instance of PositiveNumber
, or if not, the Value Object will be created from the passed in value.
Each of the bc_math
functions accept arguments and return a value. In order to standardise how these different functions are implemented in the class, I will turn them into “commands” that can be called through the Math
object.
First I will define an interface for each of the commands:
<?php namespace PhilipBrown\Math\Command;
interface CommandInterface
{
/**
* Run the command
*
* @return PhilipBrown\Math\Number
*/
public function run();
}
Next I will define an abstract class that each command will inherit from:
<?php namespace PhilipBrown\Math\Command;
use PhilipBrown\Math\Number;
abstract class AbstractCommand
{
/**
* Ensure the value is an instance of Number
*
* @param $number integer
* @return PhilipBrown\Math\Number
*/
public function isNumber($number)
{
if ($number instanceof Number) {
return $number;
}
return new Number($number);
}
}
Each of the commands will require a method to ensure that the numbers we are working with are instances of the Number
Value Object. It makes sense to abstract this method to an abstract class instead of repeating it in every class.
Next I can create the Add
command class:
<?php namespace PhilipBrown\Math\Command;
use PhilipBrown\Math\Number;
class Add extends AbstractCommand implements CommandInterface
{
/**
* The left operand, as a string
*
* @var string
*/
protected $left;
/**
* The right operand, as a string.
*
* @var string
*/
protected $right;
/**
* This optional parameter is used to set the number
* of digits after the decimal place in the result.
*
* @var int
*/
protected $scale;
/**
* Create a new instance of the Add command
*
* @param $left string
* @param $right string
* @param $scale integer
*/
public function __construct($left, $right, $scale)
{
$this->left = $left;
$this->right = $right;
$this->scale = $scale;
}
/**
* Run the command
*
* @return PhilipBrown\Math\Number
*/
public function run()
{
$left = $this->isNumber($this->left);
$right = $this->isNumber($this->right);
return new Number(
bcadd(
$left->getValue(),
$right->getValue(),
$this->scale->getValue()
)
);
}
}
As you can see, the Add
class extends the AbstractCommand
class and implements the CommandInterface
.
The values that we are interested in are passed in through the constructor.
The run()
method is where the magic happens. First each value is checked to ensure that they are instances of the Number
Value Object.
The values are then passed into the bcadd()
function which is then passed into a new Number
Value Object. Remember, when the properties of a Value Object change, the Value Object must be destroyed.
This new Number
Value Object is then returned.
In order to provide a clean API, I will then add the add()
method to the Math
object like this:
/**
* Add two arbitrary precision numbers
*
* @param $left int
* @param $right int
* @return PhilipBrown\Math\Number
*/
public function add($left, $right)
{
$command = new Add($left, $right, $this->scale);
return $command->run();
}
Each of the remaining BCMath functions can be implemented by creating child command classes and implementing them using the same blueprint of the Add
class. By creating this blueprint, we’ve made it really easy to add additional “commands” whilst also making the code really easy to understand.
I won’t go through each of the commands because there is not much difference between them. If you want to have a go at implementing, take a look at the list of BCMath functions to see how you would use this blueprint to create each of them.
In all honesty, there is nothing much to test in this package. We are simply creating a wrapper around an existing set of functions, so we can be pretty confident that the return values will be correct.
However, we can write some simply tests to ensure our API is acting as it should:
use PhilipBrown\Math\Math;
class MathTest extends PHPUnit_Framework_TestCase {
public function m($scale = 0)
{
$m = new Math;
$m->setScale($scale);
return $m;
}
public function testSetScale()
{
$this->assertInstanceOf('PhilipBrown\Math\PositiveNumber', $this->m()->setScale(2));
}
public function testCreatingNumber()
{
$this->assertInstanceOf('PhilipBrown\Math\Number', $this->m()->create(2));
}
/**
* @expectedException PhilipBrown\Math\Exceptions\InvalidNumberException
* @expectedExceptionMessage what up is not a valid number.
*/
public function testCreateInvalidNumber()
{
$this->m()->create('what up');
}
public function testAdd()
{
$this->assertEquals(3, $this->m()->add(1, 2)->getValue());
$this->assertEquals(3.4, $this->m(1)->add(1, 2.4)->getValue());
$this->assertEquals(3.44, $this->m(2)->add(1.04, 2.4)->getValue());
$this->assertEquals(3.446, $this->m(4)->add(1.04, 2.406)->getValue());
$this->assertEquals(3.4467, $this->m(4)->add(1.0407, 2.406)->getValue());
}
}
Now that I’ve finished creating the package, I can use it to run arbitrary precision mathematic calculations:
$m = new PhilipBrown\Math\Math();
// $n is an instance of PhilipBrown\Math\Number
$n = $m->add(2, 2);
The returned value $n
is an instance of PhilipBrown\Math\Number
and can therefore by used safely within your application as a Value Object.
To see the full source code of the package, take a look at the GitHub repository.
In this tutorial we’ve taken the theory from the last couple of weeks and applied it to a real world package. Hopefully as you can see, by implementing abstract classes and interfaces, using custom exceptions and value objects, we can create a robust and reliable PHP package that solves a problem and is easy to understand and use.
This PHP package only uses the BCMath functions, but it could be easily extended to offer additional mathematical “commands”. By keeping each individual component small and concise, we can use these individual building blocks to build powerful packages that solve a real problem.