diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3207304..812aa85 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,11 +7,11 @@ on:
jobs:
testsuite:
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
- php-version: ['7.2', '7.3', '7.4', '8.0']
+ php-version: ['7.4', '8.0', '8.1', '8.2', '8.3']
db-type: [sqlite, mysql, pgsql]
prefer-lowest: ['']
@@ -66,7 +66,7 @@ jobs:
if [[ ${{ matrix.db-type }} == 'mysql' ]]; then export DB_URL='mysql://root:root@127.0.0.1/cakephp'; fi
if [[ ${{ matrix.db-type }} == 'pgsql' ]]; then export DB_URL='postgres://postgres:postgres@127.0.0.1/postgres'; fi
if [[ ${{ matrix.php-version }} == '7.4' ]]; then
- export CODECOVERAGE=1 && vendor/bin/phpunit --verbose --coverage-clover=coverage.xml
+ export CODECOVERAGE=1 && vendor/bin/phpunit --coverage-clover=coverage.xml
else
vendor/bin/phpunit
fi
@@ -77,7 +77,7 @@ jobs:
cs-stan:
name: Coding Standard & Static Analysis
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
diff --git a/.gitignore b/.gitignore
index 244d127..2a55d71 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
/config/Migrations/schema-dump-default.lock
/vendor/
/.idea/
+/.phpunit.cache
\ No newline at end of file
diff --git a/README.md b/README.md
index a08a980..5975d37 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-CakePHP Money Plugin
-===================
+# CakePHP Money Plugin
+
@@ -22,45 +22,45 @@ It covers the following features:
* Store Money objects onto database (as strings)
* Marshal money objects into CakePHP entities.
-Requirements
-------------
+## Requirements
* CakePHP 4.0.0+
* PHP 7.2+
-Versions and branches
----------------------
+## Versions and branches
+
+| CakePHP | CakeDC Money Plugin | Notes |
+| :-------------: | :------------------------: | :---- |
+| ^4.5 | [1.x](https://github.com/cakedc/money/tree/1.next-cake4) | stable |
+| ^4.0 | [0.0.1](https://github.com/cakedc/money/tree/0.0.1) | deprecated |
+
+## Installation
-| CakePHP | CakeDC Money Plugin | Tag | Notes |
-| :-------------: | :------------------------: | :--: | :---- |
-| ^4.0 | [0.1](https://github.com/cakedc/money/tree/1.next-cake4) | 0.0.1 | stable |
+You can install this plugin into your CakePHP application using [composer](http://getcomposer.org).
+The recommended way to install composer packages is:
-Documentation
--------------
+```
+composer require cakedc/cakephp-money
+```
+
+## Documentation
For documentation, as well as tutorials, see the [Docs](Docs/Home.md) directory of this repository.
-Support
--------
+## Support
For bugs and feature requests, please use the [issues](https://github.com/CakeDC/money/issues) section of this repository.
Commercial support is also available, [contact us](https://www.cakedc.com/contact) for more information.
-Contributing
-------------
+## Contributing
This repository follows the [CakeDC Plugin Standard](https://www.cakedc.com/plugin-standard).
If you'd like to contribute new features, enhancements or bug fixes to the plugin, please read our [Contribution Guidelines](https://www.cakedc.com/contribution-guidelines) for detailed instructions.
-License
--------
+## License
Copyright 2021 Cake Development Corporation (CakeDC). All rights reserved.
Licensed under the [MIT](http://www.opensource.org/licenses/mit-license.php) License. Redistributions of the source code included in this repository must retain the copyright notice found in each file.
-
-## To Do
-
-* Add Unit Tests
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index e2b5c3a..5ccb5d2 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,35 +1,22 @@
-
-
-
-
-
-
-
-
-
- tests/TestCase/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- src/
-
-
-
+
+
+
+ src/
+
+
+
+
+
+
+
+
+
+ tests/TestCase/
+
+
+
+
+
+
diff --git a/src/Money.php b/src/Money.php
index b2b8c50..2ebc7cf 100644
--- a/src/Money.php
+++ b/src/Money.php
@@ -49,6 +49,8 @@ public function __construct(MoneyPHP $money)
*/
public function __call($name, $arguments)
{
+ $arguments = self::processArguments($arguments);
+
return call_user_func_array([$this->_money, $name], $arguments);
}
@@ -59,6 +61,8 @@ public function __call($name, $arguments)
*/
public static function __callStatic($name, $arguments)
{
+ $arguments = self::processArguments($arguments);
+
return new self(forward_static_call_array([MoneyPHP::class, $name], $arguments));
}
@@ -69,4 +73,19 @@ public function __toString(): string
{
return MoneyUtil::format($this);
}
+
+ /**
+ * @param array $arguments
+ * @return array
+ */
+ protected static function processArguments($arguments = [])
+ {
+ for ($i=0; $i < count($arguments); $i++) {
+ if ($arguments[$i] instanceof Money) {
+ $arguments[$i] = $arguments[$i]->getMoney();
+ }
+ }
+
+ return $arguments;
+ }
}
diff --git a/src/Utility/MoneyUtil.php b/src/Utility/MoneyUtil.php
index 7f88e63..3813bdd 100644
--- a/src/Utility/MoneyUtil.php
+++ b/src/Utility/MoneyUtil.php
@@ -11,13 +11,10 @@
namespace CakeDC\Money\Utility;
use Cake\Core\Configure;
-use Cake\Error\Debugger;
-use CakeDC\Accounting\Database\Type\MoneyType;
use Money\Currencies\BitcoinCurrencies;
use Money\Currencies\ISOCurrencies;
use Money\Currency;
use Money\Formatter\BitcoinMoneyFormatter;
-use Money\Formatter\DecimalMoneyFormatter;
use Money\Formatter\IntlMoneyFormatter;
use CakeDC\Money\Money;
use Money\MoneyFormatter;
@@ -31,7 +28,9 @@
*/
class MoneyUtil
{
- /** @var MoneyFormatter */
+ /**
+ * @var MoneyFormatter
+ */
protected static $_moneyFormatters = [];
/**
@@ -56,7 +55,7 @@ public static function money($value, $fromDb = false) : ?Money
if (!isset($parts[1])) {
$parts[1] = '00';
}
- $decimalLength = strlen($parts[1] ?? '') ;
+ $decimalLength = strlen($parts[1] ?? '');
if ($decimalLength == 1) {
$parts[1] = $parts[1] . '0';
@@ -64,8 +63,9 @@ public static function money($value, $fromDb = false) : ?Money
$value = ltrim($parts[0] . $parts[1], '0');
}
-
- return Money::USD(!empty($value) ? str_replace(',', '', $value) : 0);
+
+ $currency = Configure::read('Money.currency', 'USD');
+ return Money::{$currency}(!empty($value) ? str_replace(',', '', $value) : 0);
}
/**
@@ -108,7 +108,7 @@ protected static function _loadMoneyFormatter(Currency $currency) : MoneyFormatt
} elseif ($currency->isAvailableWithin($bitcoin)) {
$moneyFormatter = new BitcoinMoneyFormatter(7, $bitcoin);
} else {
- throw new \RuntimeException(sprintf('Cannot format currency \'%s\'. Only ISO currencies and Bitcoin are allowed.'));
+ throw new \RuntimeException(sprintf('Cannot format currency \'%s\'. Only ISO currencies and Bitcoin are allowed.', $currency));
}
static::$_moneyFormatters[$currency->getCode()] = $moneyFormatter;
return static::$_moneyFormatters[$currency->getCode()];
diff --git a/src/View/Helper/MoneyHelper.php b/src/View/Helper/MoneyHelper.php
index 0a1f2c4..7dbd8e7 100644
--- a/src/View/Helper/MoneyHelper.php
+++ b/src/View/Helper/MoneyHelper.php
@@ -43,14 +43,16 @@ public function currency($value): string
{
$class = '';
if ($value instanceof Money) {
- $value = MoneyUtil::format($value);
+ $output = MoneyUtil::format($value);
} else {
- $value = $this->Number->currency($value);
+ $output = $this->Number->currency($value);
}
- if ($value < 0) {
+ if ((is_numeric($value) && $value < 0) ||
+ ($value instanceof Money && MoneyUtil::lessThanZero($value))
+ ) {
$class = 'negative-balance';
}
- return $this->Html->tag('span', $value, ['class' => $class]);
+ return $this->Html->tag('span', $output, ['class' => $class]);
}
}
diff --git a/tests/TestCase/Database/Type/MoneyTypeTest.php b/tests/TestCase/Database/Type/MoneyTypeTest.php
new file mode 100644
index 0000000..43b2053
--- /dev/null
+++ b/tests/TestCase/Database/Type/MoneyTypeTest.php
@@ -0,0 +1,72 @@
+moneyType = new MoneyType();
+ $this->driver = $this->getMockBuilder('Cake\Database\Driver')->getMock();
+ parent::setUp();
+ }
+
+ /**
+ * tearDown method
+ *
+ * @return void
+ */
+ public function tearDown(): void
+ {
+ parent::tearDown();
+ }
+
+ public function testToPhp()
+ {
+ $this->assertNull($this->moneyType->toPHP(null, $this->driver));
+ $this->assertInstanceOf(\CakeDC\Money\Money::class, $this->moneyType->toPHP(100, $this->driver));
+ }
+
+ public function testMarshal()
+ {
+ $this->assertNull($this->moneyType->marshal(null));
+ $this->assertInstanceOf(\CakeDC\Money\Money::class, $this->moneyType->marshal(100));
+ }
+
+ public function testToDatabase()
+ {
+ $this->assertNull($this->moneyType->toDatabase(null, $this->driver));
+ $this->assertEquals('10000', $this->moneyType->toDatabase(MoneyUtil::money(100), $this->driver));
+ }
+
+ public function testToDatabaseInvalidArgument()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->moneyType->toDatabase(100, $this->driver);
+ }
+
+ public function testToStatement()
+ {
+ $this->assertEquals(PDO::PARAM_NULL, $this->moneyType->toStatement(null, $this->driver));
+ $this->assertEquals(PDO::PARAM_INT, $this->moneyType->toStatement(100, $this->driver));
+ }
+
+
+}
\ No newline at end of file
diff --git a/tests/TestCase/Utility/MoneyUtilTest.php b/tests/TestCase/Utility/MoneyUtilTest.php
new file mode 100644
index 0000000..dcf7efb
--- /dev/null
+++ b/tests/TestCase/Utility/MoneyUtilTest.php
@@ -0,0 +1,235 @@
+assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ MoneyUtil::money(100)
+ );
+
+ $this->assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ MoneyUtil::money(100, true)
+ );
+
+ $this->assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ MoneyUtil::money(-100)
+ );
+
+ $this->assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ MoneyUtil::money(100.15)
+ );
+
+ $this->assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ MoneyUtil::money(100.1)
+ );
+ }
+
+ public function testMoneyStringValue(): void
+ {
+ $this->assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ MoneyUtil::money('100')
+ );
+
+ $this->assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ MoneyUtil::money('100', true)
+ );
+
+ $this->assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ MoneyUtil::money('-100')
+ );
+
+ $this->assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ MoneyUtil::money('100.15')
+ );
+ }
+
+ public function testMoneyNullValue(): void
+ {
+ $this->assertNull(
+ MoneyUtil::money('')
+ );
+
+ $this->assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ MoneyUtil::money(0)
+ );
+ }
+
+ public function testMoneyClassValue(): void
+ {
+ $money = MoneyUtil::money(10);
+
+ $this->assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ MoneyUtil::money($money)
+ );
+
+ $this->assertEquals(
+ $money,
+ MoneyUtil::money($money)
+ );
+ }
+
+
+ public function testFloat(): void
+ {
+ $money = MoneyUtil::money(100.15);
+ $this->assertEquals(100.15, MoneyUtil::float($money));
+
+ $money = MoneyUtil::money(200);
+ $this->assertEquals(200.00, MoneyUtil::float($money));
+ }
+
+ public function testFormat()
+ {
+ $money = MoneyUtil::money(100.15);
+ $this->assertEquals('$100.15', MoneyUtil::format($money));
+
+ $money = MoneyUtil::money(200);
+ $this->assertEquals('$200.00', MoneyUtil::format($money));
+ }
+
+ public function testFormatCurrencies()
+ {
+ Configure::write('Money.currency', 'EUR');
+
+ $money = MoneyUtil::money(100.15);
+ $this->assertEquals('€100.15', MoneyUtil::format($money));
+
+ $money = MoneyUtil::money('200');
+ $this->assertEquals('€200.00', MoneyUtil::format($money));
+ }
+
+ public function testFormatBitcoin()
+ {
+ Configure::write('Money.currency', 'XBT');
+ $money = MoneyUtil::money(1);
+
+ $this->assertInstanceOf(\CakeDC\Money\Money::class, $money);
+ $this->assertEquals('Ƀ0.0000010', MoneyUtil::format($money));
+ }
+
+ public function testFormatOther()
+ {
+ try{
+ Configure::write('Money.currency', 'NotCurrency');
+ $money = MoneyUtil::money(100.15);
+ MoneyUtil::format($money);
+ } catch (\Exception $e){
+ $this->assertInstanceOf(RuntimeException::class, $e);
+ }
+ }
+
+ public function testZero()
+ {
+ $money = MoneyUtil::zero();
+
+ $this->assertInstanceOf(
+ \CakeDC\Money\Money::class,
+ $money
+ );
+
+ $this->assertEquals('$0.00', $money->__toString());
+ }
+
+
+ public function testGreaterThanZero()
+ {
+ $money = MoneyUtil::money(100);
+ $this->assertTrue(MoneyUtil::greaterThanZero($money));
+
+ $money = MoneyUtil::money(-100);
+ $this->assertFalse(MoneyUtil::greaterThanZero($money));
+ }
+
+ public function testGreaterThanOrEqualZero()
+ {
+ $money = MoneyUtil::money(100);
+ $this->assertTrue(MoneyUtil::greaterThanOrEqualZero($money));
+
+ $money = MoneyUtil::money(-100);
+ $this->assertFalse(MoneyUtil::greaterThanOrEqualZero($money));
+
+ $this->assertTrue(MoneyUtil::greaterThanOrEqualZero(MoneyUtil::zero()));
+ }
+
+
+ public function testLessThanZero()
+ {
+ $money = MoneyUtil::money(100);
+ $this->assertFalse(MoneyUtil::lessThanZero($money));
+
+ $money = MoneyUtil::money(-100);
+ $this->assertTrue(MoneyUtil::lessThanZero($money));
+ }
+
+ public function testLessThanOrEqualZero()
+ {
+ $money = MoneyUtil::money(100);
+ $this->assertFalse(MoneyUtil::lessThanOrEqualZero($money));
+
+ $money = MoneyUtil::money(-100);
+ $this->assertTrue(MoneyUtil::lessThanOrEqualZero($money));
+
+ $this->assertTrue(MoneyUtil::lessThanOrEqualZero(MoneyUtil::zero()));
+ }
+
+ public function testEqualZero()
+ {
+ $money = MoneyUtil::money(100);
+ $this->assertFalse(MoneyUtil::equalZero($money));
+
+ $this->assertTrue(MoneyUtil::equalZero(MoneyUtil::zero()));
+ }
+
+}
\ No newline at end of file
diff --git a/tests/TestCase/View/Helper/MoneyHelperTest.php b/tests/TestCase/View/Helper/MoneyHelperTest.php
index efd7585..fdcb0d9 100644
--- a/tests/TestCase/View/Helper/MoneyHelperTest.php
+++ b/tests/TestCase/View/Helper/MoneyHelperTest.php
@@ -6,6 +6,7 @@
use CakeDC\Money\View\Helper\MoneyHelper;
use Cake\TestSuite\TestCase;
use Cake\View\View;
+use CakeDC\Money\Utility\MoneyUtil;
/**
* CakeDC\Money\View\Helper\MoneyHelper Test Case
@@ -39,7 +40,6 @@ public function setUp(): void
public function tearDown(): void
{
unset($this->MoneyHelper);
-
parent::tearDown();
}
@@ -50,6 +50,34 @@ public function tearDown(): void
*/
public function testCurrency(): void
{
- $this->markTestIncomplete('Not implemented yet.');
+ $this->assertEquals(
+ '$100.00',
+ $this->MoneyHelper->currency(100)
+ );
+
+ $this->assertEquals(
+ '$100.00',
+ $this->MoneyHelper->currency('100')
+ );
+
+ $this->assertEquals(
+ '-$100.00',
+ $this->MoneyHelper->currency(-100)
+ );
+
+ $this->assertEquals(
+ '-$100.00',
+ $this->MoneyHelper->currency("-100")
+ );
+
+ $this->assertEquals(
+ '$100.00',
+ $this->MoneyHelper->currency(MoneyUtil::money(100))
+ );
+
+ $this->assertEquals(
+ '$100.00',
+ $this->MoneyHelper->currency(MoneyUtil::money('100'))
+ );
}
}
diff --git a/tests/TestCase/View/Widget/MoneyWidgetTest.php b/tests/TestCase/View/Widget/MoneyWidgetTest.php
new file mode 100644
index 0000000..7df1411
--- /dev/null
+++ b/tests/TestCase/View/Widget/MoneyWidgetTest.php
@@ -0,0 +1,118 @@
+ '',
+ ];
+ $this->templates = new StringTemplate($templates);
+ $this->context = $this->getMockBuilder('Cake\View\Form\ContextInterface')->getMock();
+ }
+
+ /**
+ * tearDown method
+ *
+ * @return void
+ */
+ public function tearDown(): void
+ {
+ unset($this->templates);
+ unset($this->context);
+ parent::tearDown();
+ }
+
+ public function testInputMoneyWidget(): void
+ {
+ $money = new MoneyWidget($this->templates);
+ $data = [
+ 'name' => 'amount',
+ 'val' => 10,
+ 'templateVars' => [],
+ ];
+ $this->assertTextContains('type="number"', $money->render($data, $this->context));
+
+ $data = [
+ 'name' => 'amount',
+ 'val' => MoneyUtil::money(10),
+ 'templateVars' => [],
+ ];
+ $this->assertTextContains('type="number"', $money->render($data, $this->context));
+ }
+
+ public function testInputMoneyWidgetMaxMin(): void
+ {
+ $money = new MoneyWidget($this->templates);
+ $data = [
+ 'name' => 'amount',
+ 'val' => 10,
+ 'max' => 15,
+ 'min' => 5,
+ 'templateVars' => [],
+ ];
+
+ $this->assertTextContains('max="15"', $money->render($data, $this->context));
+ $this->assertTextContains('min="5"', $money->render($data, $this->context));
+
+ $money = new MoneyWidget($this->templates);
+ $data = [
+ 'name' => 'amount',
+ 'val' => MoneyUtil::money(10),
+ 'max' => MoneyUtil::money(15),
+ 'min' => MoneyUtil::money(5),
+ 'templateVars' => [],
+ ];
+
+ $this->assertTextContains('max="15"', $money->render($data, $this->context));
+ $this->assertTextContains('min="5"', $money->render($data, $this->context));
+ }
+
+ public function testSecureFields(): void
+ {
+ $money = new MoneyWidget($this->templates);
+ $data = [
+ 'name' => 'amount',
+ 'val' => 10,
+ 'max' => 15,
+ 'min' => 5,
+ 'templateVars' => [],
+ ];
+
+ $this->assertTrue(in_array('amount', $money->secureFields($data)));
+ }
+}
\ No newline at end of file