Skip to content

Commit c9bf471

Browse files
Merge pull request #10 from whitecube/master
Added possibility to register custom formatter classes directly
2 parents 43702aa + 0cc98c6 commit c9bf471

File tree

6 files changed

+110
-15
lines changed

6 files changed

+110
-15
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,18 @@ $price = Price::EUR(600, 8)->setVat(21);
594594
echo Price::format($price); // 4800
595595
```
596596

597+
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`:
598+
599+
```php
600+
use Whitecube\Price\Price;
601+
602+
Price::formatUsing(fn($price, $locale = null) => /* Convert $price to a string for $locale */);
603+
// or
604+
Price::formatUsing(\App\Formatters\MyPriceFormatter::class);
605+
// or
606+
Price::formatUsing(new \App\Formatters\MyPriceFormatter($some, $dependencies));
607+
```
608+
597609
For even more flexibility, it is possible to define multiple named formatters and call them using their own dynamic static method:
598610

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

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

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

src/Concerns/FormatsPrices.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,20 @@ static public function formatDefault($value, $locale = null)
5454
* Formats the given monetary value using the package's default formatter.
5555
* This static method is hardcoded in order to prevent overwriting.
5656
*
57-
* @param string $value
58-
* @param null|string $locale
57+
* @param mixed $formatter
5958
* @return \Whitecube\Price\CustomFormatter
6059
*/
61-
static public function formatUsing(callable $closure) : CustomFormatter
60+
static public function formatUsing($formatter) : CustomFormatter
6261
{
63-
$instance = new CustomFormatter($closure);
62+
if(is_string($formatter) && is_a($formatter, CustomFormatter::class, true)) {
63+
$instance = new $formatter;
64+
} elseif (is_a($formatter, CustomFormatter::class)) {
65+
$instance = $formatter;
66+
} elseif (is_callable($formatter)) {
67+
$instance = new CustomFormatter($formatter);
68+
} else {
69+
throw new \InvalidArgumentException('Price formatter should be callable or extend "\\Whitecube\\Price\\CustomFormatter". "' . gettype($formatter) . '" provided.');
70+
}
6471

6572
static::$formatters[] = $instance;
6673

src/Formatting/CustomFormatter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class CustomFormatter extends Formatter
2424
* @param callable $closure
2525
* @return void
2626
*/
27-
public function __construct(callable $closure)
27+
public function __construct(callable $closure = null)
2828
{
2929
$this->closure = $closure;
3030

src/Formatting/Formatter.php

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,34 @@ public function is($name = null)
2828
*/
2929
public function call(array $arguments) : ?string
3030
{
31-
[$value, $locale] = (count($arguments) > 2)
32-
? array_slice($arguments, 0, 2)
33-
: array_pad($arguments, 2, null);
31+
[$value, $locale] = $this->getMoneyAndLocale($arguments);
3432

35-
if(! is_a($value = $this->toMoney($value), Money::class)) {
33+
if(! is_a($value, Money::class)) {
3634
return null;
3735
}
3836

39-
if(! $locale) {
37+
return $this->format($value, $locale);
38+
}
39+
40+
/**
41+
* Extract the Money and locale arguments from the provided arguments array.
42+
*
43+
* @param array $arguments
44+
* @param int $moneyIndex
45+
* @param int $localeIndex
46+
* @return array
47+
*/
48+
protected function getMoneyAndLocale(array $arguments, int $moneyIndex = 0, int $localeIndex = 1) : array
49+
{
50+
if($money = $arguments[$moneyIndex] ?? null) {
51+
$money = $this->toMoney($money);
52+
}
53+
54+
if(! ($locale = $arguments[$localeIndex] ?? null)) {
4055
$locale = locale_get_default();
4156
}
4257

43-
return $this->format($value, $locale);
58+
return [$money, $locale];
4459
}
4560

4661
/**
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Tests\Fixtures;
4+
5+
use Brick\Money\Money;
6+
use Whitecube\Price\Price;
7+
use Whitecube\Price\Formatting\CustomFormatter;
8+
9+
class CustomInvertedFormatter extends CustomFormatter
10+
{
11+
/**
12+
* Run the formatter using the provided arguments
13+
*
14+
* @param array $arguments
15+
* @return null|string
16+
*/
17+
public function call(array $arguments) : ?string
18+
{
19+
[$value, $locale] = $this->getMoneyAndLocale($arguments);
20+
21+
if(! is_a($value, Money::class)) {
22+
return null;
23+
}
24+
25+
return Price::formatDefault($value->multipliedBy(-1), $locale);
26+
}
27+
}

tests/Unit/FormatsPricesTest.php

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
expect(Price::format($price->vat(), 'en_GB'))->toBe($fmt->formatCurrency(1101.24, 'EUR'));
4747
});
4848

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

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

59-
it('formats monetary values using the default formatter despite of the previously defined custom formatted closure', function() {
59+
it('formats monetary values using a previously defined custom formatter classname', function() {
60+
setlocale(LC_ALL, 'en_US');
61+
62+
Price::formatUsing(\Tests\Fixtures\CustomInvertedFormatter::class);
63+
64+
$price = Price::EUR(600, 8)->setVat(21);
65+
66+
$fmt = new NumberFormatter('en_US', NumberFormatter::CURRENCY);
67+
expect(Price::format($price))->toBe($fmt->formatCurrency(-58.08, 'EUR'));
68+
});
69+
70+
it('formats monetary values using a previously defined custom formatter instance', function() {
71+
setlocale(LC_ALL, 'en_US');
72+
73+
$formatter = new \Tests\Fixtures\CustomInvertedFormatter();
74+
75+
Price::formatUsing($formatter);
76+
77+
$price = Price::EUR(600, 8)->setVat(21);
78+
79+
$fmt = new NumberFormatter('en_US', NumberFormatter::CURRENCY);
80+
expect(Price::format($price))->toBe($fmt->formatCurrency(-58.08, 'EUR'));
81+
});
82+
83+
it('cannot format monetary values using a formatter class that does not extend CustomFormatter', function() {
84+
$formatter = new class() {
85+
public function call($arguments) {
86+
return 'foo';
87+
}
88+
};
89+
90+
Price::formatUsing($formatter);
91+
})->throws(\InvalidArgumentException::class);
92+
93+
it('formats monetary values using the default formatter despite of the previously defined custom formatter closure', function() {
6094
setlocale(LC_ALL, 'en_US');
6195

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

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

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

0 commit comments

Comments
 (0)