Skip to content

Commit

Permalink
Merge pull request happypixels#18 from happypixels/feature/custom-mon…
Browse files Browse the repository at this point in the history
…ey-formatter

Customizable money formatter
  • Loading branch information
mattias-persson authored Mar 4, 2019
2 parents ec3e36a + 490ee61 commit b00811c
Show file tree
Hide file tree
Showing 11 changed files with 378 additions and 20 deletions.
43 changes: 38 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,52 @@
dist: xenial
language: php

php:
- 7.1
- 7.2
- 7.3
env:
global:
- ICU_VERSION=58.2

matrix:
include:
- php: 7.1
- php: 7.2
- php: 7.3

services:
- mysql

before_install:
- sudo apt-get update
- travis_retry composer self-update
- |
if [[ $ICU_VERSION ]]; then
ICU_DIR=$HOME/.build/icu-$ICU_VERSION
ICU_PHP_VERSION=$(php -r "echo PHP_VERSION;")
ICU_PHP_DIR=$HOME/.build/php-$ICU_PHP_VERSION-icu-$ICU_VERSION
export ICU_PHP=$ICU_PHP_DIR/bin/php
if [ ! -f $ICU_PHP ]; then
wget -O icu-src.tgz http://download.icu-project.org/files/icu4c/$ICU_VERSION/icu4c-$(echo $ICU_VERSION | tr '.' '_')-src.tgz
mkdir icu-src && tar xzf icu-src.tgz -C icu-src --strip-components=1
pushd icu-src/source
./configure --prefix=$ICU_DIR
make && make install
popd
wget -O php-src.tgz http://us1.php.net/get/php-$ICU_PHP_VERSION.tar.gz/from/this/mirror
mkdir php-src && tar xzf php-src.tgz -C php-src --strip-components=1
pushd php-src
./configure --prefix=$ICU_PHP_DIR --enable-intl --with-icu-dir=$ICU_DIR
make && make install
popd
fi
$ICU_PHP -r "echo INTL_ICU_VERSION.PHP_EOL;"
$ICU_PHP -r "var_dump((new ReflectionClass('Normalizer'))->getConstants());"
fi
- $ICU_PHP -i

install:
- travis_retry composer update --prefer-dist --no-interaction --prefer-stable --no-suggest

script:
- vendor/bin/phpunit
- $ICU_PHP ./vendor/bin/phpunit

branches:
only:
Expand Down
6 changes: 6 additions & 0 deletions config/shopr.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
*/
'currency' => 'USD',

/*
* The money formatter class. You may provide your own class here, just make sure it extends
* the default Happypixels\Shopr\Money\Formatter class.
*/
'money_formatter' => Happypixels\Shopr\Money\Formatter::class,

/*
* The tax percentage.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Cart/BaseCart.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function summary()
$subTotal = $this->subTotal();
$taxTotal = $this->taxTotal();
$total = $this->total();
$formatter = new Formatter;
$formatter = app(Formatter::class);

return [
'items' => $this->items(),
Expand Down
4 changes: 2 additions & 2 deletions src/CartItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function __construct($shoppableType, $shoppableId, $quantity, $options, $
$this->options = $options;
$this->subItems = $this->addSubItems($subItems);
$this->price = ($price) ?? $this->shoppable->getPrice();
$this->price_formatted = (new Formatter)->format($this->price);
$this->price_formatted = app(Formatter::class)->format($this->price);
$this->total = $this->total();
}

Expand Down Expand Up @@ -86,7 +86,7 @@ public function refreshDiscountValue()
$value = $this->shoppable->getPrice();

$this->price = $value;
$this->price_formatted = (new Formatter)->format($value);
$this->price_formatted = app(Formatter::class)->format($value);
$this->total = $this->total();
}
}
2 changes: 1 addition & 1 deletion src/CartSubItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function __construct($shoppableType, $shoppableId, $quantity, $options =
$this->quantity = $quantity;
$this->options = $options;
$this->price = (is_numeric($price)) ? $price : $this->shoppable->getPrice();
$this->price_formatted = (new Formatter)->format($this->price);
$this->price_formatted = app(Formatter::class)->format($this->price);
$this->total = $this->total();
}

Expand Down
6 changes: 3 additions & 3 deletions src/Models/Order.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ class Order extends Model

public function getTotalFormattedAttribute()
{
return (new Formatter)->format($this->total);
return app(Formatter::class)->format($this->total);
}

public function getSubTotalFormattedAttribute()
{
return (new Formatter)->format($this->sub_total);
return app(Formatter::class)->format($this->sub_total);
}

public function getTaxFormattedAttribute()
{
return (new Formatter)->format($this->tax);
return app(Formatter::class)->format($this->tax);
}

public function items()
Expand Down
4 changes: 2 additions & 2 deletions src/Models/OrderItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ class OrderItem extends Model

public function getPriceFormattedAttribute()
{
return (new Formatter)->format($this->price);
return app(Formatter::class)->format($this->price);
}

public function getTotalFormattedAttribute()
{
return (new Formatter)->format($this->price * $this->quantity);
return app(Formatter::class)->format($this->price * $this->quantity);
}

public function children()
Expand Down
226 changes: 221 additions & 5 deletions src/Money/Formatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,244 @@

use Money\Money;
use Money\Currency;
use NumberFormatter;
use Money\Currencies\ISOCurrencies;
use Money\Formatter\IntlMoneyFormatter;

class Formatter
{
/**
* The decimal separator symbol.
*
* @var string
*/
public $decimalSeparator;

/**
* The amount of decimals.
*
* @var int
*/
public $decimalCount;

/**
* The default symbol to be used.
*
* @var string
*/
public $symbol;

/**
* The desired position of the symbol. Can be 'before' or 'after'.
* If unspecified, the symbol will be automatically positioned.
*
* @var string
*/
public $symbolPosition;

/**
* The thousand separator symbol.
*
* @var string
*/
public $thousandSeparator;

/**
* The formatter.
*
* @var NumberFormatter
*/
protected $formatter;

/**
* The amount undergoing formatting.
*
* @var mixed
*/
protected $amount;

/**
* The symbol that gets assigned to the format, either by customization or by PHP magic.
*
* @var string
*/
protected $assignedSymbol;

/**
* Create the default formatter.
*/
public function __construct()
{
$this->formatter = new NumberFormatter($this->getLocale(), NumberFormatter::CURRENCY);
}

/**
* Format the amount into a human readable currency value.
*
* @return string
*/
public function format($amount)
{
$money = new Money(round($amount * 100), new Currency(strtoupper($this->getCurrency())));
$this->amount = $amount;

return $this->applySymbol()
->applyThousandSeparator()
->applyDecimalSeparator()
->applyDecimalCount()
->formatAmount()
->removeUnwantedWhitespace()
->applySymbolPosition()
->getResult();
}

/**
* Apply a custom symbol to the formatting.
*
* @return self
*/
public function applySymbol()
{
if ($this->symbol !== null) {
$this->formatter->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $this->symbol);
}

$this->assignedSymbol = $this->formatter->getSymbol(NumberFormatter::CURRENCY_SYMBOL);

return $this;
}

/**
* Apply a custom thousand separator to the formatting.
*
* @return self
*/
public function applyThousandSeparator()
{
if ($this->thousandSeparator) {
$this->formatter->setSymbol(
NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL,
$this->thousandSeparator
);
}

return $this;
}

/**
* Apply a specific decimal count.
*
* @return self
*/
public function applyDecimalCount()
{
if ($this->decimalCount !== null) {
$this->formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $this->decimalCount);
$this->formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $this->decimalCount);
}

return $this;
}

/**
* Apply a custom decimal separator to the formatting.
*
* @return self
*/
public function applyDecimalSeparator()
{
if ($this->decimalSeparator) {
$this->formatter->setSymbol(
NumberFormatter::MONETARY_SEPARATOR_SYMBOL,
$this->decimalSeparator
);
}

return $this;
}

/**
* Returns the formatted amount.
*
* @return self
*/
protected function getResult()
{
return $this->amount;
}

/**
* Runs the PHP NumberFormatter on the amount.
*
* @return self
*/
protected function formatAmount()
{
$this->amount = (new IntlMoneyFormatter($this->formatter, new ISOCurrencies()))->format(
new Money(round($this->amount * 100), new Currency($this->getCurrency()))
);

return $this;
}

/**
* Puts the symbol in the desired position.
*
* @return self
*/
protected function applySymbolPosition()
{
if ($this->symbolPosition && in_array($this->symbolPosition, ['before', 'after'])) {
// First, remove the assigned symbol from the formatted amount.
$this->amount = trim(str_replace($this->assignedSymbol, '', $this->amount));

// Then, add the symbol in it's correct position.
if ($this->symbolPosition === 'before') {
$this->amount = $this->assignedSymbol.$this->amount;
} else {
$this->amount .= $this->assignedSymbol;
}
}

return $this;
}

/**
* Removes unwanted whitespace in the amount.
*
* @return self
*/
protected function removeUnwantedWhitespace()
{
// Remove unprintable spaces created by NumberFormatter for some reason.
$this->amount = trim(str_replace(' ', ' ', $this->amount));

$numberFormatter = new \NumberFormatter($this->getLocale(), \NumberFormatter::CURRENCY);
$moneyFormatter = new IntlMoneyFormatter($numberFormatter, new ISOCurrencies());
// Remove unwanted whitespace around the symbol.
$this->amount = str_replace(
[$this->assignedSymbol.' ', ' '.$this->assignedSymbol],
$this->assignedSymbol,
$this->amount
);

return $moneyFormatter->format($money);
return $this;
}

/**
* Returns the current locale.
*
* @return string
*/
protected function getLocale()
{
return app()->getLocale() ?? 'en';
}

/**
* Returns the current currency code.
*
* @return string
*/
protected function getCurrency()
{
return config('shopr.currency') ?? 'USD';
return config('shopr.currency') ? strtoupper(config('shopr.currency')) : 'USD';
}
}
Loading

0 comments on commit b00811c

Please sign in to comment.