Skip to content

Commit

Permalink
Merge pull request #10 from whitecube/master
Browse files Browse the repository at this point in the history
Added possibility to register custom formatter classes directly
  • Loading branch information
toonvandenbos authored Aug 14, 2021
2 parents 43702aa + 0cc98c6 commit c9bf471
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 15 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,18 @@ $price = Price::EUR(600, 8)->setVat(21);
echo Price::format($price); // 4800
```

The `Price::formatUsing()` method accepts a closure function, a Formatter class name or a Formatter instance. The two last options should both extend `\Whitecube\Price\Formatting\CustomFormatter`:

```php
use Whitecube\Price\Price;

Price::formatUsing(fn($price, $locale = null) => /* Convert $price to a string for $locale */);
// or
Price::formatUsing(\App\Formatters\MyPriceFormatter::class);
// or
Price::formatUsing(new \App\Formatters\MyPriceFormatter($some, $dependencies));
```

For even more flexibility, it is possible to define multiple named formatters and call them using their own dynamic static method:

```php
Expand All @@ -604,7 +616,7 @@ setlocale(LC_ALL, 'en_US');
Price::formatUsing(fn($price, $locale = null) => $price->exclusive()->getMinorAmount()->toInt())
->name('rawExclusiveCents');

Price::formatUsing(fn($price, $locale = null) => Price::formatDefault($price->inclusive()->multipliedBy(-1), $locale))
Price::formatUsing(\App\Formatters\MyInvertedPriceFormatter::class)
->name('inverted');

$price = Price::EUR(600, 8)->setVat(21);
Expand Down
15 changes: 11 additions & 4 deletions src/Concerns/FormatsPrices.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,20 @@ static public function formatDefault($value, $locale = null)
* Formats the given monetary value using the package's default formatter.
* This static method is hardcoded in order to prevent overwriting.
*
* @param string $value
* @param null|string $locale
* @param mixed $formatter
* @return \Whitecube\Price\CustomFormatter
*/
static public function formatUsing(callable $closure) : CustomFormatter
static public function formatUsing($formatter) : CustomFormatter
{
$instance = new CustomFormatter($closure);
if(is_string($formatter) && is_a($formatter, CustomFormatter::class, true)) {
$instance = new $formatter;
} elseif (is_a($formatter, CustomFormatter::class)) {
$instance = $formatter;
} elseif (is_callable($formatter)) {
$instance = new CustomFormatter($formatter);
} else {
throw new \InvalidArgumentException('Price formatter should be callable or extend "\\Whitecube\\Price\\CustomFormatter". "' . gettype($formatter) . '" provided.');
}

static::$formatters[] = $instance;

Expand Down
2 changes: 1 addition & 1 deletion src/Formatting/CustomFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class CustomFormatter extends Formatter
* @param callable $closure
* @return void
*/
public function __construct(callable $closure)
public function __construct(callable $closure = null)
{
$this->closure = $closure;

Expand Down
27 changes: 21 additions & 6 deletions src/Formatting/Formatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,34 @@ public function is($name = null)
*/
public function call(array $arguments) : ?string
{
[$value, $locale] = (count($arguments) > 2)
? array_slice($arguments, 0, 2)
: array_pad($arguments, 2, null);
[$value, $locale] = $this->getMoneyAndLocale($arguments);

if(! is_a($value = $this->toMoney($value), Money::class)) {
if(! is_a($value, Money::class)) {
return null;
}

if(! $locale) {
return $this->format($value, $locale);
}

/**
* Extract the Money and locale arguments from the provided arguments array.
*
* @param array $arguments
* @param int $moneyIndex
* @param int $localeIndex
* @return array
*/
protected function getMoneyAndLocale(array $arguments, int $moneyIndex = 0, int $localeIndex = 1) : array
{
if($money = $arguments[$moneyIndex] ?? null) {
$money = $this->toMoney($money);
}

if(! ($locale = $arguments[$localeIndex] ?? null)) {
$locale = locale_get_default();
}

return $this->format($value, $locale);
return [$money, $locale];
}

/**
Expand Down
27 changes: 27 additions & 0 deletions tests/Fixtures/CustomInvertedFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Tests\Fixtures;

use Brick\Money\Money;
use Whitecube\Price\Price;
use Whitecube\Price\Formatting\CustomFormatter;

class CustomInvertedFormatter extends CustomFormatter
{
/**
* Run the formatter using the provided arguments
*
* @param array $arguments
* @return null|string
*/
public function call(array $arguments) : ?string
{
[$value, $locale] = $this->getMoneyAndLocale($arguments);

if(! is_a($value, Money::class)) {
return null;
}

return Price::formatDefault($value->multipliedBy(-1), $locale);
}
}
40 changes: 37 additions & 3 deletions tests/Unit/FormatsPricesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
expect(Price::format($price->vat(), 'en_GB'))->toBe($fmt->formatCurrency(1101.24, 'EUR'));
});

it('formats monetary values using a previously defined custom formatted closure', function() {
it('formats monetary values using a previously defined custom formatter closure', function() {
setlocale(LC_ALL, 'en_US');

Price::formatUsing(fn($price, $locale = null) => $price->exclusive()->getMinorAmount()->toInt());
Expand All @@ -56,7 +56,41 @@
expect(Price::format($price))->toBe('4800');
});

it('formats monetary values using the default formatter despite of the previously defined custom formatted closure', function() {
it('formats monetary values using a previously defined custom formatter classname', function() {
setlocale(LC_ALL, 'en_US');

Price::formatUsing(\Tests\Fixtures\CustomInvertedFormatter::class);

$price = Price::EUR(600, 8)->setVat(21);

$fmt = new NumberFormatter('en_US', NumberFormatter::CURRENCY);
expect(Price::format($price))->toBe($fmt->formatCurrency(-58.08, 'EUR'));
});

it('formats monetary values using a previously defined custom formatter instance', function() {
setlocale(LC_ALL, 'en_US');

$formatter = new \Tests\Fixtures\CustomInvertedFormatter();

Price::formatUsing($formatter);

$price = Price::EUR(600, 8)->setVat(21);

$fmt = new NumberFormatter('en_US', NumberFormatter::CURRENCY);
expect(Price::format($price))->toBe($fmt->formatCurrency(-58.08, 'EUR'));
});

it('cannot format monetary values using a formatter class that does not extend CustomFormatter', function() {
$formatter = new class() {
public function call($arguments) {
return 'foo';
}
};

Price::formatUsing($formatter);
})->throws(\InvalidArgumentException::class);

it('formats monetary values using the default formatter despite of the previously defined custom formatter closure', function() {
setlocale(LC_ALL, 'en_US');

Price::formatUsing(fn($price, $locale = null) => $price->exclusive()->getMinorAmount()->toInt());
Expand All @@ -73,7 +107,7 @@
Price::formatUsing(fn($price, $locale = null) => $price->exclusive()->getMinorAmount()->toInt())
->name('rawExclusiveCents');

Price::formatUsing(fn($price, $locale = null) => Price::formatDefault($price->inclusive()->multipliedBy(-1), $locale))
Price::formatUsing(\Tests\Fixtures\CustomInvertedFormatter::class)
->name('inverted');

$price = Price::EUR(600, 8)->setVat(21);
Expand Down

0 comments on commit c9bf471

Please sign in to comment.