Skip to content

Commit

Permalink
feat: Segregates immutability into separate library and adjust VO con…
Browse files Browse the repository at this point in the history
…tract.
  • Loading branch information
gustavofreze committed Oct 5, 2024
1 parent 5886b32 commit daa8ef4
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 201 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ With the implementation of the `ValueObject` interface, and the `ValueObjectAdap
namespace Example;

use TinyBlocks\Vo\ValueObject;
use TinyBlocks\Vo\ValueObjectAdapter;
use TinyBlocks\Vo\ValueObjectBehavior;

final class TransactionId implements ValueObject
{
use ValueObjectAdapter;
use ValueObjectBehavior;

public function __construct(private readonly string $value)
{
Expand All @@ -67,7 +67,7 @@ The `equals` method compares the value of two VOs, and checks if they are equal.
$transactionId = new TransactionId(value: 'e6e2442f-3bd8-421f-9ac2-f9e26ac4abd2');
$otherTransactionId = new TransactionId(value: 'e6e2442f-3bd8-421f-9ac2-f9e26ac4abd2');

$transactionId->equals(other: $otherTransactionId); # 1 (true)
$transactionId->equals(other: $otherTransactionId); # true
```

<div id='license'></div>
Expand Down
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
}
},
"require": {
"php": "^8.2"
"php": "^8.2",
"tiny-blocks/immutable-object": "^1"
},
"require-dev": {
"phpmd/phpmd": "^2.15",
Expand Down Expand Up @@ -68,6 +69,9 @@
"tests-no-coverage": [
"@test-no-coverage",
"@test-mutation-no-coverage"
],
"tests-file-no-coverage": [
"@test-no-coverage"
]
}
}
40 changes: 0 additions & 40 deletions src/Immutable.php

This file was deleted.

16 changes: 0 additions & 16 deletions src/Internal/Exceptions/InvalidProperty.php

This file was deleted.

16 changes: 0 additions & 16 deletions src/Internal/Exceptions/PropertyCannotBeChanged.php

This file was deleted.

16 changes: 0 additions & 16 deletions src/Internal/Exceptions/PropertyCannotBeDeactivated.php

This file was deleted.

16 changes: 7 additions & 9 deletions src/ValueObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace TinyBlocks\Vo;

use TinyBlocks\Immutable\Immutable;

/**
* A Value Object is an immutable type that is only distinguishable by the state of its properties, that is,
* unlike an entity, which has a unique identifier and remains distinct even if its properties are
Expand All @@ -14,15 +16,11 @@
interface ValueObject extends Immutable
{
/**
* Returns object values.
* @return array
*/
public function values(): array;

/**
* Compare two ValueObjects, and tell if they are equal.
* @param ValueObject $other
* @return bool
* Compares this ValueObject with another to determine if they are equal.
* Two ValueObjects are considered equal if their properties have the same values.
*
* @param ValueObject $other The ValueObject to compare with.
* @return bool True if the objects are equal, false otherwise.
*/
public function equals(ValueObject $other): bool;
}
39 changes: 0 additions & 39 deletions src/ValueObjectAdapter.php

This file was deleted.

17 changes: 17 additions & 0 deletions src/ValueObjectBehavior.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace TinyBlocks\Vo;

use TinyBlocks\Immutable\Immutability;

trait ValueObjectBehavior
{
use Immutability;

public function equals(ValueObject $other): bool
{
return get_object_vars($this) == get_object_vars($other);
}
}
4 changes: 2 additions & 2 deletions tests/Models/Order.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
namespace TinyBlocks\Vo\Models;

use TinyBlocks\Vo\ValueObject;
use TinyBlocks\Vo\ValueObjectAdapter;
use TinyBlocks\Vo\ValueObjectBehavior;

final readonly class Order implements ValueObject
{
use ValueObjectAdapter;
use ValueObjectBehavior;

public function __construct(public int $id, public iterable $products = [])
{
Expand Down
119 changes: 60 additions & 59 deletions tests/ValueObjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,85 +4,86 @@

namespace TinyBlocks\Vo;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use TinyBlocks\Vo\Internal\Exceptions\InvalidProperty;
use TinyBlocks\Vo\Internal\Exceptions\PropertyCannotBeChanged;
use TinyBlocks\Vo\Internal\Exceptions\PropertyCannotBeDeactivated;
use TinyBlocks\Vo\Models\Amount;
use TinyBlocks\Vo\Models\Order;
use TinyBlocks\Vo\Models\Product;

final class ValueObjectTest extends TestCase
{
public function testValueObjectsAreEqual(): void
#[DataProvider('dataProviderForEqualObjects')]
public function testValueObjectsAreEqual(ValueObject $valueObject, ValueObject $otherValueObject): void
{
/** @Given two identical orders with the same ID and identical 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'));

$orderOne = new Order(id: 1, products: [$productOne, $productTwo]);
$orderTwo = new Order(id: 1, products: [$productOne, $productTwo]);
/** @Given two ValueObjects that should be equal */
/** @When comparing both ValueObjects for equality */
$actual = $valueObject->equals(other: $otherValueObject);

/** @When checking if both orders, with identical attributes, are equal */
$actual = $orderOne->equals(other: $orderTwo);

/** @Then the orders should be considered equal as all attributes match */
/** @Then they should be considered equal */
self::assertTrue($actual);
}

public function testValueObjectsAreNotEqual(): void
#[DataProvider('dataProviderForNotEqualObjects')]
public function testValueObjectsAreNotEqual(ValueObject $valueObject, ValueObject $otherValueObject): 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]);
/** @Given two ValueObjects that should not be equal */
/** @When comparing both ValueObjects for equality */
$actual = $valueObject->equals(other: $otherValueObject);

/** @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 */
/** @Then they should not be considered equal */
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
public static function dataProviderForEqualObjects(): array
{
/** @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);
$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'));
$productDuplicate = new Product(name: 'Laptop', amount: new Amount(value: 100.0, currency: 'USD'));
$emptyOrderOne = new Order(id: 1, products: []);
$emptyOrderTwo = new Order(id: 1, products: []);

return [
'Empty arrays should be equal' => [
'valueObject' => $emptyOrderOne,
'otherValueObject' => $emptyOrderTwo
],
'Identical orders same products' => [
'valueObject' => new Order(id: 1, products: [$productOne, $productTwo]),
'otherValueObject' => new Order(id: 1, products: [$productOne, $productTwo])
],
'Same products different instances' => [
'valueObject' => new Order(id: 1, products: [$productOne]),
'otherValueObject' => new Order(id: 1, products: [$productDuplicate])
]
];
}

public function testWhenPropertyCannotBeDeactivated(): void
public static function dataProviderForNotEqualObjects(): array
{
/** @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');
$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'));
$productDifferentAmount = new Product(name: 'Laptop', amount: new Amount(value: 200.0, currency: 'USD'));
$orderWithNullProduct = new Order(id: 1, products: [null]);
$orderWithProduct = new Order(id: 1, products: [$productOne]);

return [
'Same products, different IDs' => [
'valueObject' => new Order(id: 1, products: [$productOne, $productTwo]),
'otherValueObject' => new Order(id: 2, products: [$productOne, $productTwo])
],
'Orders with different products' => [
'valueObject' => new Order(id: 1, products: [$productOne, $productTwo]),
'otherValueObject' => new Order(id: 1, products: [$productOne, $productThree])
],
'Null product vs actual product' => [
'valueObject' => $orderWithNullProduct,
'otherValueObject' => $orderWithProduct
],
'Same product names but different amounts' => [
'valueObject' => new Order(id: 1, products: [$productOne]),
'otherValueObject' => new Order(id: 1, products: [$productDifferentAmount])
]
];
}
}

0 comments on commit daa8ef4

Please sign in to comment.