diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..22aac70
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,14 @@
+/tests export-ignore
+/vendor export-ignore
+
+/LICENSE export-ignore
+/Makefile export-ignore
+/README.md export-ignore
+/phpmd.xml export-ignore
+/phpunit.xml export-ignore
+/phpstan.neon.dist export-ignore
+/infection.json.dist export-ignore
+
+/.github export-ignore
+/.gitignore export-ignore
+/.gitattributes export-ignore
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 32850ac..105bc90 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,6 +16,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
+ - name: Use PHP 8.2
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.2'
+
- name: Install dependencies
run: composer update --no-progress --optimize-autoloader
@@ -33,6 +38,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
+ - name: Use PHP 8.2
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.2'
+
- name: Install dependencies
run: composer update --no-progress --optimize-autoloader
diff --git a/Makefile b/Makefile
index 9bf35fe..1b3026e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,17 @@
DOCKER_RUN = docker run --rm -it --net=host -v ${PWD}:/app -w /app gustavofreze/php:8.2
-.PHONY: configure test test-no-coverage review show-reports clean
+.PHONY: configure test test-file test-no-coverage review show-reports clean
configure:
@${DOCKER_RUN} composer update --optimize-autoloader
-test: review
+test:
@${DOCKER_RUN} composer tests
-test-no-coverage: review
+test-file:
+ @${DOCKER_RUN} composer tests-file-no-coverage ${FILE}
+
+test-no-coverage:
@${DOCKER_RUN} composer tests-no-coverage
review:
@@ -19,4 +22,4 @@ show-reports:
clean:
@sudo chown -R ${USER}:${USER} ${PWD}
- @rm -rf report vendor
+ @rm -rf report vendor .phpunit.cache
diff --git a/composer.json b/composer.json
index 56c4aa9..867bde8 100644
--- a/composer.json
+++ b/composer.json
@@ -9,8 +9,6 @@
"keywords": [
"vo",
"psr",
- "psr-4",
- "psr-12",
"tiny-blocks",
"value-object"
],
@@ -20,6 +18,10 @@
"homepage": "https://github.com/gustavofreze"
}
],
+ "support": {
+ "issues": "https://github.com/tiny-blocks/value-object/issues",
+ "source": "https://github.com/tiny-blocks/value-object"
+ },
"config": {
"sort-packages": true,
"allow-plugins": {
@@ -37,24 +39,27 @@
}
},
"require": {
- "php": "^8.1||^8.2"
+ "php": "^8.2"
},
"require-dev": {
- "infection/infection": "^0.26",
- "phpmd/phpmd": "^2.13",
- "phpunit/phpunit": "^9.6",
- "squizlabs/php_codesniffer": "^3.7"
+ "phpmd/phpmd": "^2.15",
+ "phpunit/phpunit": "^11",
+ "phpstan/phpstan": "^1",
+ "infection/infection": "^0.29",
+ "squizlabs/php_codesniffer": "^3.10"
},
"scripts": {
"phpcs": "phpcs --standard=PSR12 --extensions=php ./src",
"phpmd": "phpmd ./src text phpmd.xml --suffixes php --ignore-violations-on-exit",
+ "phpstan": "phpstan analyse -c phpstan.neon.dist --quiet --no-progress",
"test": "phpunit --log-junit=report/coverage/junit.xml --coverage-xml=report/coverage/coverage-xml --coverage-html=report/coverage/coverage-html tests",
"test-mutation": "infection --only-covered --logger-html=report/coverage/mutation-report.html --coverage=report/coverage --min-msi=100 --min-covered-msi=100 --threads=4",
"test-no-coverage": "phpunit --no-coverage",
"test-mutation-no-coverage": "infection --only-covered --min-msi=100 --threads=4",
"review": [
"@phpcs",
- "@phpmd"
+ "@phpmd",
+ "@phpstan"
],
"tests": [
"@test",
diff --git a/infection.json.dist b/infection.json.dist
index e9bacf3..351e7f9 100644
--- a/infection.json.dist
+++ b/infection.json.dist
@@ -1,23 +1,21 @@
{
"timeout": 10,
"testFramework": "phpunit",
- "tmpDir": "report/",
+ "tmpDir": "report/infection/",
"source": {
"directories": [
"src"
]
},
"logs": {
- "text": "report/logs/infection-text.log",
- "summary": "report/logs/infection-summary.log"
+ "text": "report/infection/logs/infection-text.log",
+ "summary": "report/infection/logs/infection-summary.log"
},
"mutators": {
- "@default": true,
- "PublicVisibility": false,
- "ProtectedVisibility": false
+ "@default": true
},
"phpUnit": {
"configDir": "",
"customPath": "./vendor/bin/phpunit"
}
-}
\ No newline at end of file
+}
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 0000000..b31dbc6
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,8 @@
+parameters:
+ paths:
+ - src
+ level: 9
+ tmpDir: report/phpstan
+ ignoreErrors:
+ - '#return type has no value type specified in iterable type array#'
+ reportUnmatchedIgnoredErrors: false
diff --git a/phpunit.xml b/phpunit.xml
index 3d05fc8..7f080dd 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,25 +1,35 @@
+ bootstrap="vendor/autoload.php"
+ failOnRisky="true"
+ failOnWarning="true"
+ cacheDirectory=".phpunit.cache"
+ beStrictAboutOutputDuringTests="true">
+
+
+
+ src
+
+
+
- tests
+ tests
-
- src
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Immutable.php b/src/Immutable.php
new file mode 100644
index 0000000..c87ad11
--- /dev/null
+++ b/src/Immutable.php
@@ -0,0 +1,40 @@
+equals(other: $other);
-
- self::assertTrue($actual);
- }
-
- public function testWhenEqualIsFalse(): void
- {
- $complex = new ComplexValueMock(
- single: new SingleValueMock(id: 1000),
- multiple: new MultipleValueMock(
- id: 999,
- transactions: [
- new TransactionMock(id: 1, amount: new AmountMock(value: 0.99, currency: 'USD')),
- new TransactionMock(id: 2, amount: new AmountMock(value: 10.55, currency: 'USD'))
- ]
- )
- );
- $other = new ComplexValueMock(
- single: new SingleValueMock(id: 1000),
- multiple: new MultipleValueMock(
- id: 999,
- transactions: [
- new TransactionMock(id: 1, amount: new AmountMock(value: 0.99, currency: 'USD')),
- new TransactionMock(id: 2, amount: new AmountMock(value: 10.56, currency: 'USD'))
- ]
- )
- );
-
- $actual = $complex->equals(other: $other);
-
- self::assertFalse($actual);
- }
-
- public function testInvalidProperty(): void
- {
- $this->expectException(InvalidProperty::class);
- $this->expectExceptionMessage('Invalid property for class .');
-
- $complex = new ComplexValueMock(
- single: new SingleValueMock(id: 1000),
- multiple: new MultipleValueMock(
- id: 999,
- transactions: [
- new TransactionMock(id: 1, amount: new AmountMock(value: 0.99, currency: 'USD')),
- new TransactionMock(id: 2, amount: new AmountMock(value: 10.55, currency: 'USD'))
- ]
- )
- );
- $complex->__get(key: 'other');
- }
-
- public function testPropertyCannotBeChanged(): void
- {
- $this->expectException(PropertyCannotBeChanged::class);
- $this->expectExceptionMessage(
- 'Property cannot be changed in class .'
- );
-
- $complex = new ComplexValueMock(
- single: new SingleValueMock(id: 1000),
- multiple: new MultipleValueMock(
- id: 999,
- transactions: [
- new TransactionMock(id: 1, amount: new AmountMock(value: 0.99, currency: 'USD')),
- new TransactionMock(id: 2, amount: new AmountMock(value: 10.55, currency: 'USD'))
- ]
- )
- );
- $complex->__set(key: 'other', value: new StdClass());
- }
-
- public function testPropertyCannotBeDeactivated(): void
- {
- $this->expectException(PropertyCannotBeDeactivated::class);
- $this->expectExceptionMessage(
- 'Property cannot be deactivated in class .'
- );
-
- $complex = new ComplexValueMock(
- single: new SingleValueMock(id: 1000),
- multiple: new MultipleValueMock(
- id: 999,
- transactions: [
- new TransactionMock(id: 1, amount: new AmountMock(value: 0.99, currency: 'USD')),
- new TransactionMock(id: 2, amount: new AmountMock(value: 10.55, currency: 'USD'))
- ]
- )
- );
- $complex->__unset(key: 'other');
- }
-}
diff --git a/tests/Mock/AmountMock.php b/tests/Mock/AmountMock.php
deleted file mode 100644
index a959b4d..0000000
--- a/tests/Mock/AmountMock.php
+++ /dev/null
@@ -1,10 +0,0 @@
-equals(other: $other);
-
- self::assertTrue($actual);
- }
-
- public function testWhenEqualIsFalse(): void
- {
- $multiple = new MultipleValueMock(
- id: 123,
- transactions: [
- new TransactionMock(id: 100, amount: new AmountMock(value: 10.0, currency: 'BRL')),
- new TransactionMock(id: 200, amount: new AmountMock(value: 11.01, currency: 'BRL'))
- ]
- );
- $other = new MultipleValueMock(
- id: 123,
- transactions: [
- new TransactionMock(id: 100, amount: new AmountMock(value: 10.0, currency: 'USD')),
- new TransactionMock(id: 200, amount: new AmountMock(value: 11.01, currency: 'USD'))
- ]
- );
-
- $actual = $multiple->equals(other: $other);
-
- self::assertFalse($actual);
- }
-
- public function testInvalidProperty(): void
- {
- $this->expectException(InvalidProperty::class);
- $this->expectExceptionMessage('Invalid property for class .');
-
- $multiple = new MultipleValueMock(
- id: 123,
- transactions: [
- new TransactionMock(id: 100, amount: new AmountMock(value: 10.0, currency: 'BRL')),
- new TransactionMock(id: 200, amount: new AmountMock(value: 11.01, currency: 'BRL'))
- ]
- );
- $multiple->__get(key: 'other');
- }
-
- public function testPropertyCannotBeChanged(): void
- {
- $this->expectException(PropertyCannotBeChanged::class);
- $this->expectExceptionMessage(
- 'Property cannot be changed in class .'
- );
-
- $multiple = new MultipleValueMock(
- id: 123,
- transactions: [
- new TransactionMock(id: 100, amount: new AmountMock(value: 10.0, currency: 'BRL')),
- new TransactionMock(id: 200, amount: new AmountMock(value: 11.01, currency: 'BRL'))
- ]
- );
- $multiple->__set(key: 'other', value: new StdClass());
- }
-
- public function testPropertyCannotBeDeactivated(): void
- {
- $this->expectException(PropertyCannotBeDeactivated::class);
- $this->expectExceptionMessage(
- 'Property cannot be deactivated in class .'
- );
-
- $multiple = new MultipleValueMock(
- id: 123,
- transactions: [
- new TransactionMock(id: 100, amount: new AmountMock(value: 10.0, currency: 'BRL')),
- new TransactionMock(id: 200, amount: new AmountMock(value: 11.01, currency: 'BRL'))
- ]
- );
- $multiple->__unset(key: 'other');
- }
-}
diff --git a/tests/SingleValueTest.php b/tests/SingleValueTest.php
deleted file mode 100644
index 17175e7..0000000
--- a/tests/SingleValueTest.php
+++ /dev/null
@@ -1,67 +0,0 @@
-equals(other: $other);
-
- self::assertTrue($actual);
- }
-
- public function testWhenEqualIsFalse(): void
- {
- $single = new SingleValueMock(id: 1);
- $other = new SingleValueMock(id: 2);
-
- $actual = $single->equals(other: $other);
-
- self::assertFalse($actual);
- }
-
- public function testInvalidProperty(): void
- {
- $this->expectException(InvalidProperty::class);
- $this->expectExceptionMessage('Invalid property for class .');
-
- $single = new SingleValueMock(id: 1);
-
- $single->__get(key: 'other');
- }
-
- public function testPropertyCannotBeChanged(): void
- {
- $this->expectException(PropertyCannotBeChanged::class);
- $this->expectExceptionMessage(
- 'Property cannot be changed in class .'
- );
-
- $single = new SingleValueMock(id: 1);
-
- $single->__set(key: 'other', value: new StdClass());
- }
-
- public function testPropertyCannotBeDeactivated(): void
- {
- $this->expectException(PropertyCannotBeDeactivated::class);
- $this->expectExceptionMessage(
- 'Property cannot be deactivated in class .'
- );
-
- $single = new SingleValueMock(id: 1);
-
- $single->__unset(key: 'other');
- }
-}
diff --git a/tests/ValueObjectTest.php b/tests/ValueObjectTest.php
new file mode 100644
index 0000000..c771ee8
--- /dev/null
+++ b/tests/ValueObjectTest.php
@@ -0,0 +1,88 @@
+equals(other: $orderTwo);
+
+ /** @Then the orders should be considered equal as all attributes match */
+ self::assertTrue($actual);
+ }
+
+ public function testValueObjectsAreNotEqual(): void
+ {
+ /** @Given two orders with different products */
+ $productOne = new Product(name: 'Laptop', amount: new Amount(value: 100.0, currency: 'USD'));
+ $productTwo = new Product(name: 'Mouse', amount: new Amount(value: 50.0, currency: 'USD'));
+ $productThree = new Product(name: 'Keyboard', amount: new Amount(value: 75.0, currency: 'USD'));
+
+ $orderOne = new Order(id: 1, products: [$productOne, $productTwo]);
+ $orderTwo = new Order(id: 1, products: [$productOne, $productThree]);
+
+ /** @When checking if both orders, with different products, are not equal */
+ $actual = $orderOne->equals(other: $orderTwo);
+
+ /** @Then the orders should not be considered equal as products differ */
+ self::assertFalse($actual);
+ }
+
+ public function testWhenInvalidProperty(): void
+ {
+ /** @Given an Order object */
+ $order = new Order(id: 1);
+
+ /** @When trying to access a non-existing property */
+ /** @Then it should throw InvalidProperty exception */
+ $template = 'Invalid property <%s> for class <%s>.';
+ $this->expectException(InvalidProperty::class);
+ $this->expectExceptionMessage(sprintf($template, 'nonExistentProperty', Order::class));
+ $order->__get(key: 'nonExistentProperty');
+ }
+
+ public function testWhenPropertyCannotBeChanged(): void
+ {
+ /** @Given an Order object */
+ $order = new Order(id: 1);
+
+ /** @When trying to set a property */
+ /** @Then it should throw PropertyCannotBeChanged exception */
+ $template = 'Property <%s> cannot be changed in class <%s>.';
+ $this->expectException(PropertyCannotBeChanged::class);
+ $this->expectExceptionMessage(sprintf($template, 'id', Order::class));
+ $order->__set(key: 'id', value: 2);
+ }
+
+ public function testWhenPropertyCannotBeDeactivated(): void
+ {
+ /** @Given an Order object */
+ $order = new Order(id: 1);
+
+ /** @When trying to unset a property */
+ /** @Then it should throw PropertyCannotBeDeactivated exception */
+ $template = 'Property <%s> cannot be deactivated in class <%s>.';
+ $this->expectException(PropertyCannotBeDeactivated::class);
+ $this->expectExceptionMessage(sprintf($template, 'id', Order::class));
+ $order->__unset(key: 'id');
+ }
+}