Commit 8a8f21b8 authored by Thorsten Buss's avatar Thorsten Buss

inital commit

parents
vendor
\ No newline at end of file
{
"name": "bnet/money-datatype",
"description": "Package for a simple money datatype",
"minimum-stability": "stable",
"license": "proprietary",
"authors": [
{
"name": "Thorsten Buss",
"email": "info@buss-networks.de"
}
],
"require-dev": {
"phpunit/phpunit": "5.4.6"
},
"autoload": {
"psr-4": {
"Bnet\\": "src",
"Tests\\Bnet\\": "tests"
}
},
"require": {
}
}
This diff is collapsed.
# PHP MoneyDataType - use Money as that what it is - an amount with currency
A simple Money Datatype Implementation
## Main Features
* Contains amount AND currency as package
* calculate with the amount
* format with the currency
* parse string to money datatype
##INSTALLATION
Install the package through [Composer](http://getcomposer.org/). Edit your project's `composer.json` file by adding:
"require": {
"bussnet/money-datatype": "*"
}
then run: $ composer update
or execute
composer require "bussnet/money-datatype"
## Usage
### Manual Currency Attributes
```php
$a = [
'code' => 'EUR',
'iso' => 978,
'name' => 'Euro',
'symbol_left' => '',
'symbol_right' => '€',
'decimal_place' => 2,
'decimal_mark' => ',',
'thousands_separator' => '.',
'unit_factor' => 100
];
$c = new \Bnet\Money\Currency('EUR', $a);
```
### Currency with ArrayRepository
```php
$repository = new ArrayRepository([
'EUR' => [
'code' => 'EUR',
'iso' => 978,
'name' => 'Euro',
'symbol_left' => '',
'symbol_right' => '€',
'decimal_place' => 2,
'decimal_mark' => ',',
'thousands_separator' => '.',
'unit_factor' => 100
]
]);
\Bnet\Money\Currency::registerCurrencyRepository($repository);
$c = new \Bnet\Money\Currency('EUR');
```
### Currency with CallbackRepository
```php
$repository = new \Bnet\Money\Repositories\CallbackRepository(function($code) {
// request the data from Database and return it
});
\Bnet\Money\Currency::registerCurrencyRepository($repository);
$c = new \Bnet\Money\Currency('EUR');
```
### Money
```php
// assign own currency
$m = new Money(123456, 'EUR');
$m = new Money(123456, new Currency('EUR'));
// use systemwide default currency
$m = new Money(123456);
// int 123456
$m->amount();
// float 1234.56
$m->normalize();
// 1234,56€
$m->format();
// 1.234,56€ (with thousand mark)
$m->format(true);
// 1.234,56 EUR (with thousand mark ans code instead of sign)
$m->format(true, true);
// EUR 1.234,56 (with thousand mark ans code instead of sign swap before/after part)
$m->format(true, true, true);
// 1234,56€ with html spans arround the parts
$m->html(/* use the same params as format() above */);
// parse Moneystrings -> 123456 cents
$m = Money::parse('1.234,56');
// +/- sign before is allowed, currency sign is not allowed
// the first ./, is interpreted as thousand mark, the second as decimal makr - more than two are not allowed
```
## Changelog
**0.1
- create the basic functionality, prepared with Array and Callback CurrencyRepository
## License
The MoneyDatatype Package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)
### Disclaimer
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR, OR ANY OF THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<?php
/**
* User: thorsten
* Date: 07.07.16
* Time: 12:14
*/
namespace Bnet\Money;
use Bnet\Money\Repositories\CurrencyRepositoryInterface;
use Bnet\Money\Repositories\Exception\CurrencyRepositoryException;
class Currency {
public static $default_currency = 'EUR';
/**
* @var string iso code
*/
public $code;
/**
* @var int numeric iso code
*/
public $iso;
public $name;
public $symbol_left;
public $symbol_right;
public $decimal_place;
public $decimal_mark;
public $thousands_separator;
/**
* @var float the value relative to USD to convert amounts
*/
public $value;
public $unit_factor = 100;
/**
* @var CurrencyRepositoryInterface
*/
protected static $repository;
/**
* Currency constructor.
* @param string $code
* @param array $attributes
*/
public function __construct($code = null, array $attributes = []) {
$this->code = @$code ?: self::$default_currency;
$this->loadAttributes($this->code, $attributes);
}
/**
* register a repository for loading the currency attributes
* @param CurrencyRepositoryInterface $repo
*/
public static function registerCurrencyRepository(CurrencyRepositoryInterface $repo) {
self::$repository = $repo;
}
/**
* @param $code
* @param $attributes
* @return mixed
*/
protected function loadAttributes($code, $attributes=[]) {
if (empty($attributes)) {
$this->assertRepository();
$attributes = self::$repository->get($code);
}
foreach ($attributes as $k => $v) {
if (property_exists(self::class, $k))
$this->{$k} = $v;
}
}
/**
* @return string
*/
public static function getDefaultCurrency() {
return self::$default_currency;
}
/**
* @param string $currency
*/
public static function setDefaultCurrency($currency) {
self::$default_currency = $currency;
}
/**
* @throws CurrencyRepositoryException
*/
protected function assertRepository() {
if (!self::$repository instanceof CurrencyRepositoryInterface)
throw new CurrencyRepositoryException;
}
}
\ No newline at end of file
<?php
/**
* User: thorsten
* Date: 07.07.16
* Time: 12:12
*/
namespace Bnet\Money;
use Illuminate\Support\Str;
class Money {
/**
* @var int cents of currency
*/
protected $amount;
/**
* @var Currency
*/
protected $currency;
/**
* Money constructor.
* @param int $amount
* @param Currency $currency
* @throws MoneyException
*/
public function __construct($amount, $currency=null) {
if (intval($amount) != $amount) {
throw new MoneyException('Amount must be an integer');
}
$this->amount = $amount;
if (!$currency instanceof Currency)
$currency = new Currency($currency);
$this->currency = $currency;
}
public function value() {
return $this->amount();
}
/**
* @return int
*/
public function amount() {
return $this->amount;
}
/**
* amount as decimal with . as decPoint
* @return float
*/
public function normalize() {
return bcdiv($this->amount(), $this->currency->unit_factor, $this->currency->decimal_place);
}
/**
* amount as decimal with localized dec and thousand points
* @param bool $with_thousand_point
* @return string
*/
public function localize($with_thousand_point=false) {
$c = $this->currency;
return number_format($this->normalize(), $c->decimal_place, $c->decimal_mark, $with_thousand_point?$c->thousands_separator:'');
}
/**
* amount as decimal with localized dec and thousand points with currency sign or code
* @param bool $with_thousand_point
* @param bool $code_instead_of_sign
* @param bool $swap_left_and_right
* @return string
*/
public function format($with_thousand_point = false, $code_instead_of_sign=false, $swap_left_and_right=false) {
$c = $this->currency;
$amount = $this->localize($with_thousand_point);
if ($code_instead_of_sign)
return $swap_left_and_right
? $c->code . ' ' . $amount
: $amount .' '.$c->code;
return $swap_left_and_right
? $c->symbol_right . $amount . $c->symbol_left
: $c->symbol_left . $amount . $c->symbol_right;
}
/**
* amount as decimal with localized dec and thousand points with currency sign or code
* @param bool $with_thousand_point
* @param bool $code_instead_of_sign
* @param bool $swap_left_and_right
* @return string
*/
public function html($with_thousand_point = false, $code_instead_of_sign=false, $swap_left_and_right=false) {
$c = $this->currency;
// prepare parts with html wrapper
$amount = '<span class="amount">'.$this->localize($with_thousand_point).'</span>';
$code = '<span class="code">'.$c->code.'</span>';
$left = empty($c->symbol_left)
? ''
: '<span class="symbol">'. $c->symbol_left .'</span>';
$right = empty($c->symbol_right)
? ''
: '<span class="symbol">'. $c->symbol_right .'</span>';
// code/symbol
if ($code_instead_of_sign) {
// check swap
$html = $swap_left_and_right
? $code . ' ' . $amount
: $amount .' '.$code;
} else {
// check swap
$html = $swap_left_and_right
? $right . $amount . $left
: $left . $amount . $right;
}
return '<span class="money currency_' . Str::lower($c->code) . '">'.$html.'</span>';
}
/**
* Parse a Moneystring
* @param string $money
* @param string|Currency $currency
* @return static
* @throws MoneyException
*/
public static function parse($money, $currency=null) {
if (!is_string($money)) {
throw new MoneyException('Formatted raw money should be string, e.g. $1.00');
}
if (!$currency instanceof Currency)
$currency = new Currency($currency);
$sign = "(?P<sign>[-\+])?";
$digits1 = "(?P<digits1>\d*?)";
$separator1 = '(?P<separator1>[.,])??';
$digits2 = "(?P<digits2>\d*)";
$separator2 = '(?P<separator2>[.,])?';
$decimals = "(?P<decimal1>\d)?(?P<decimal2>\d)?";
$pattern = '/^' . $sign . $digits1 . $separator1 .$digits2 . $separator2 . $decimals . '$/';
if (!preg_match($pattern, trim($money), $matches)) {
throw new MoneyException('The value could not be parsed as money');
}
$units = $matches['sign'] === '-' ? '-' : '';
$units .= $matches['digits1']. $matches['digits2'];
$units .= isset($matches['decimal1']) ? $matches['decimal1'] : '0';
$units .= isset($matches['decimal2']) ? $matches['decimal2'] : '0';
if ($matches['sign'] === '-') {
$units = '-' . ltrim(substr($units, 1), '0');
} else {
$units = ltrim($units, '0');
}
if ($units === '' || $units === '-') {
$units = '0';
}
return new static($units, $currency);
}
}
\ No newline at end of file
<?php
/**
* User: thorsten
* Date: 08.07.16
* Time: 22:50
*/
namespace Bnet\Money;
class MoneyException extends \Exception{
}
\ No newline at end of file
<?php
/**
* User: thorsten
* Date: 07.07.16
* Time: 21:18
*/
namespace Bnet\Money\Repositories;
use Bnet\Money\Repositories\Exception\CurrencyNotFoundException;
class ArrayRepository implements CurrencyRepositoryInterface {
/**
* @var array List of currencies with alpha Iso Code as Key
*/
protected $currencies;
/**
* ArrayRepository constructor.
* @param array $currencies
*/
public function __construct(array $currencies) {
$this->currencies = $currencies;
}
/**
* load the attributes for this currency and return it
* @param string $code the iso alphanumeric currency code
* @return array attributes for this currency
* @throws CurrencyNotFoundException
*/
public function get($code) {
$code = strtoupper($code);
$this->assertCurrency($code);
return $this->currencies[$code];
}
/**
* check if the currency exists
* @param string $code the iso alphanumeric currency code
* @return bool currency exists or not
*/
public function has($code) {
return array_key_exists(strtoupper($code), $this->currencies);
}
/**
* @param $code
* @throws CurrencyNotFoundException
*/
protected function assertCurrency($code) {
if (!$this->has($code))
throw new CurrencyNotFoundException(static::class.": Currency $code not found");
}
}
\ No newline at end of file
<?php
/**
* User: thorsten
* Date: 07.07.16
* Time: 23:03
*/
namespace Bnet\Money\Repositories;
use Bnet\Money\Repositories\Exception\CurrencyNotFoundException;
class CachedCallbackRepository extends CallbackRepository {
/**
* @var string prefix for cache key
*/
protected $cache_prefix = 'bnet.currency.';
/**
* load the attributes for this currency and return it
* @param string $code the iso alphanumeric currency code
* @return array attributes for this currency
* @throws CurrencyNotFoundException
*/
public function get($code) {
$code = strtoupper($code);
$this->assertCurrency($code);
return \Cache::rememberForever($this->cache_prefix . $code, function () use ($code) {
return parent::get($code);
});
}
/**
* check if the currency exists
* @param string $code the iso alphanumeric currency code
* @return bool currency exists or not
*/
public function has($code) {
$code = strtoupper($code);
return \Cache::has($this->cache_prefix . $code) || parent::has($code);
}
}
\ No newline at end of file
<?php
/**
* User: thorsten
* Date: 07.07.16
* Time: 22:05
*/
namespace Bnet\Money\Repositories;
use Bnet\Money\Repositories\Exception\CurrencyNotFoundException;
use Bnet\Money\Repositories\Exception\CurrencyRepositoryException;
class CallbackRepository extends ArrayRepository{
/**
* @var \Closure
*/
protected $resource;
/**
* CallbackRepository constructor.
* @param \Closure $resource
*/
public function __construct($resource) {
$this->resource = $resource;
}
/**
* load the attributes for this currency and return it
* @param string $code the iso alphanumeric currency code
* @return array attributes for this currency
* @throws CurrencyNotFoundException
* @throws CurrencyRepositoryException
*/
public function get($code) {
$code = strtoupper($code);
if (is_callable($this->resource)) {
$func = $this->resource;
$attributes = $func($code);
} else
throw new CurrencyRepositoryException;
if (empty($attributes))
throw new CurrencyNotFoundException;
return $attributes;
}
/**
* check if the currency exists
* @param string $code the iso alphanumeric currency code
* @return bool currency exists or not
*/
public function has($code) {
try {
$this->get($code);
return true;
} catch (CurrencyNotFoundException $e) {
return false;
}
}
}
\ No newline at end of file
<?php
/**
* User: thorsten
* Date: 07.07.16
* Time: 21:26
*/
namespace Bnet\Money\Repositories;
use Bnet\Money\Repositories\Exception\CurrencyNotFoundException;
interface CurrencyRepositoryInterface {
/**
* load the attributes for this currency and return it
* @param string $code the iso alphanumeric currency code
* @return array attributes for this currency
* @throws CurrencyNotFoundException
*/
public function get($code);
/**
* check if the currency exists
* @param string $code the iso alphanumeric currency code
* @return bool currency exists or not
*/
public function has($code);
}
\ No newline at end of file
<?php
/**
* User: thorsten
* Date: 07.07.16
* Time: 21:29
*/
namespace Bnet\Money\Repositories\Exception;
class CurrencyNotFoundException extends \Exception{
}
\ No newline at end of file
<?php
/**
* User: thorsten
* Date: 07.07.16
* Time: 22:59
*/
namespace Bnet\Money\Repositories\Exception;
class CurrencyRepositoryException extends \Exception{
}
\ No newline at end of file
<?php
/**
* User: thorsten
* Date: 07.07.16
* Time: 20:55
*/
namespace Tests\Bnet\Money;
class CurrencyTest extends \PHPUnit_Framework_TestCase {
public function testManualAttributes() {
$a = [
'code' => 'EUR',
'iso_numeric' => 123,
'name' => 'Euro',
'symbol_left' => '',
'symbol_right' => '€',
'decimal_place' => 2,
'decimal_mark' => ',',
'thousands_separator' => '.',
'unit_factor' => 100
];
$c = new \Bnet\Money\Currency('EUR', $a);
$this->assertEquals($a['code'], $c->code, 'code');
$this->assertEquals($a['iso'], $c->iso, 'iso');
$this->assertEquals($a['name'], $c->name, 'name');
$this->assertEquals($a['symbol_left'], $c->symbol_left, 'symbol_left');
$this->assertEquals($a['symbol_right'], $c->symbol_right, 'symbol_right');
$this->assertEquals($a['decimal_place'], $c->decimal_place, 'decimal_place');
$this->assertEquals($a['decimal_mark'], $c->decimal_mark, 'decimal_mark');
$this->assertEquals($a['thousands_separator'], $c->thousands_separator, 'thousands_separator');
$this->assertEquals($a['unit_factor'], $c->unit_factor, 'unit_factor');
}
/**
* @expectedException \Bnet\Money\Repositories\Exception\CurrencyRepositoryException
*/
public function testNotFoundRepository() {
new \Bnet\Money\Currency('EUR');
}
}
<?php
/**
* User: thorsten
* Date: 07.07.16
* Time: 20:30
*/
namespace Tests\Bnet\Money;
use Bnet\Money\Money;
use Bnet\Money\MoneyException;
use Bnet\Money\Repositories\ArrayRepository;
class MoneyTest extends \PHPUnit_Framework_TestCase {
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.