Feb 18, 2015

Table of contents:

- The shopping basket analogy so far
- The Reconciler interface
- The DefaultReconciler implementation
- Testing
- Conclusion

Over the past couple of weeks we’ve looked at some of the integral aspects of modelling a shopping basket in code.

We looked at how to model the `Product`

object so that we can encapsulate the data and behaviour that would be expected in the real world (Creating a PHP Shopping Basket Part 3 - Creating the Product object).

Next we looked at creating the `Basket`

object that should be responsible for encapsulating the product list including adding, updating, and removing products (Creating a PHP Shopping Basket Part 4 - The Basket object).

In the real world it is not the responsibility for the product to know how to calculate it’s own totals and liabilities and it’s not the basket’s responsibility to calculate the totals for the entire order.

Instead we need to pass the basket through a reconciliation process.

In today’s tutorial we will be looking at writing a reconciliation process for our PHP shopping basket.

When modelling a real world phenomenon, it’s important to stay close to reality. When your code diverges from reality, it makes it difficult to work with in ways you would expect it should work.

In order to set the scene for today’s tutorial, lets do a quick review of the analogy so far.

The `Basket`

object is responsible for encapsulating the product list including adding, updating, and removing products.

The `Product`

object is responsible for encapsulating the current state and behaviour of the product. This includes the data and associated objects as “potential energy”.

In the real world, when you have finished your shopping you go through a checkout process. This process takes each product and calculates the totals, delivery charges and taxes owed to create an order.

The reconciliation process should accept each `Product`

object from the `Basket`

, calculate and return the appropriate totals.

The reconciliation process should be a stateless service that does not alter the original values of the `Product`

.

Hopefully that all makes sense, but if not, don’t worry, I’m sure everything will fall into place as we start to write the implementation for the reconciliation process.

Something that I discovered when researching into ecommerce development is that the reconciliation process is yet another area of contention.

Yet again, it seems that there is no agreed upon method for calculating totals in ecommerce software.

As we’ve seen in the previous tutorials of this mini-series, you can’t please everyone so you should aim to write your code in a way that allows other developers to extend it without having to make a Pull Request of hack around with internal logic.

This means we should define an interface that states what the class should be capable of and a default implementation to show how it can be used.

But if a developer wants to use their own implementation to satisfy their requirements, they are free to do so as as long as satisfies the interface.

So the first thing to do is to define the interface:

```
<?php namespace PhilipBrown\Basket;
interface Reconciler
{
/**
* Return the value of the Product
*
* @param Product $product
* @return Money
*/
public function value(Product $product);
/**
* Return the discount of the Product
*
* @param Product $product
* @return Money
*/
public function discount(Product $product);
/**
* Return the delivery charge of the Product
*
* @param Product $product
* @return Money
*/
public function delivery(Product $product);
/**
* Return the tax of the Product
*
* @param Product $product
* @return Money
*/
public function tax(Product $product);
/**
* Return the subtotal of the Product
*
* @param Product $product
* @return Money
*/
public function subtotal(Product $product);
/**
* Return the total of the Product
*
* @param Product $product
* @return Money
*/
public function total(Product $product);
}
```

The reconciliation process should be a stateless service. This means it should not hold a reference to any objects, but instead accept an input and return an output.

Each method of the `Reconciler`

interface accepts an instance of `Product`

.

Each method is then responsible for calculating the total in whatever way it deems suitable. The outside world does not need to be concerned with this logic.

In order to show how this package should be used as part of an ecommerce application we should include a default implementation:

```
<?php namespace PhilipBrown\Basket\Reconcilers;
use PhilipBrown\Basket\Reconciler;
class DefaultReconciler implements Reconciler
{
}
```

The first method I will write will be a `private`

method to generate a new `Money`

object of zero value:

```
/**
* Create an initial zero money value
*
* @param Product $product
* @return Money
*/
private function money(Product $product)
{
return new Money(0, $product->price->getCurrency());
}
```

We’re going to need to create a new zero value `Money`

object in a couple of the methods so this is really just for convenience.

The `money()`

method is not part of the `Reconciler`

interface for a couple of reasons.

Firstly, this method is just for our convenience and so it’s a detail of the implementation. The outside world does not need to know it exists.

Secondly, the interface is stating that the object is *“capable”* of performing each of the methods. Generating a zero `Money`

value instance should not be defined as one of the object’s capabilities.

The first method we will look at will be for calculating the value of the products:

```
/**
* Return the value of the Product
*
* @param Product $product
* @return Money
*/
public function value(Product $product)
{
return $product->price->multiply($product->quantity);
}
```

This method calculates the raw value of the products. So if we had a product that was valued at $10 and we had 3 of them, the total “value” of the order would be $30. The value is the raw value of the products before any taxes or discounts have been applied.

Due to the fact that `Money`

objects are immutable, calling the `multiply()`

method will return a new `Money`

object.

Next we have the `discount()`

method for calculating the total discount of the product:

```
/**
* Return the discount of the Product
*
* @param Product $product
* @return Money
*/
public function discount(Product $product)
{
$discount = $this->money($product);
if ($product->discount) {
$discount = $product->discount->product($product);
$discount = $discount->multiply($product->quantity);
}
return $discount;
}
```

First we create a new zero value `Money`

object using the `money()`

method from earlier.

Next we check to see if the `Product`

object has a `Discount`

. By default this value will be set to `NULL`

.

If there is no `Discount`

set we can simply return the zero value `Money`

object.

If there is a `Discount`

we can calculate the value of it and then multiply it by the `quantity`

of the products.

Some products will have an associated delivery charge and so we can calculate that value using the `delivery()`

method:

```
/**
* Return the delivery charge of the Product
*
* @param Product $product
* @return Money
*/
public function delivery(Product $product)
{
$delivery = $product->delivery->multiply($product->quantity);
return $delivery;
}
```

By default the `delivery`

property of the `Product`

object will already be set to a zero value instance of `Money`

.

This means we can simply multiply the applied `delivery`

value by the `quantity`

of products to get a total.

The `tax()`

method is slightly more complicated than the previous methods because we have to take a couple of things into consideration:

```
/**
* Return the tax of the Product
*
* @param Product $product
* @return Money
*/
public function tax(Product $product)
{
$tax = $this->money($product);
if (! $product->taxable || $product->freebie) {
return $tax;
}
$value = $this->value($product);
$discount = $this->discount($product);
$value = $value->subtract($discount)
$tax = $value->multiply($product->rate->float());
return $tax;
}
```

Firstly we need to generate a new zero value `Money`

object.

Next we can check to see if the product is not taxable or if the product is a freebie. If either of these conditions are true, we can simply return the zero value `Money`

object because no tax should be added.

Next we calculate the `$value`

and the `$discount`

from the `value()`

and `discount()`

methods from earlier.

Finally we can calculate the `$value`

minus the `$discount`

and then calculate the `$tax`

by multipling that value by the tax rate.

Next the `subtotal()`

method is again slightly more complicated:

```
/**
* Return the subtotal of the Product
*
* @param Product $product
* @return Money
*/
public function subtotal(Product $product)
{
$subtotal = $this->money($product);
if (! $product->freebie) {
$value = $this->value($product);
$discount = $this->discount($product);
$subtotal = $subtotal->add($value)->subtract($discount);
}
$delivery = $this->delivery($product);
$subtotal = $subtotal->add($delivery);
return $subtotal;
}
```

First we create a zero value `Money`

instance.

Next we check to make sure the product is not a freebie. If the product is not a freebie we can calculate the subtotal from the value and the discount.

Next we can calculate the delivery charge and finally add that value to the subtotal.

Finally, the `total()`

method is as follows:

```
/**
* Return the total of the Product
*
* @param Product $product
* @return Money
*/
public function total(Product $product)
{
$tax = $this->tax($product);
$subtotal = $this->subtotal($product);
$total = $subtotal->add($tax);
return $total;
}
```

To calculate the total we need to first calculate the tax and the subtotal, and then add those two values together. Fortunately we can just leverage the existing methods on this service class so we don’t have to repeat the logic.

You’re probably thinking, *“where are the tests?!”*.

Whilst creating this package I found that it was quite difficult to test due to the different set ups required to test every possible permutation.

To solve this problem I create a fixtures file that would generate new `Product`

objects.

I then ran each of those fixtures through the reconciliation process in this test file.

Rather than repeating all of that code here, I would urge you to check out the source code to see how I’ve went about testing this functionality.

In the real world, everyone has an opinion on how something should work. You are probably reading this article and thinking you would of calculated the totals in a different way too.

At the end of the day, you will never please everyone. If you try to please everyone, you will end up pleasing no one.

Instead of trying to find the “truth”, simply provide a way for the developer to suit her own needs.

The reconciliation process is a stateless service for calculating totals. when you go through the checkout process in a shop, the checkout doesn’t hold a reference to your order, it’s just input, output.

The `Product`

object holds the current state of the product, but it should not be responsible for calculating it’s own totals. We need a way of calculating the totals without corrupting the state of the product.

Instead of dumping this responsibility on the `Product`

object we can instead define a service to do the heavy lifting for us.

The beauty of this is you can then allow anyone to develop their own set of rules for reconciliation.

If you would like to see the full source code for this package, take a look at GitHub.

©
Yellow Flag Ltd
2024.