76 lines
3 KiB
Markdown
76 lines
3 KiB
Markdown
|
---
|
||
|
title: Refactoring to value objects
|
||
|
pubDate: 2022-10-03
|
||
|
permalink: archive/2022/10/03/refactoring-value-objects
|
||
|
tags: [php]
|
||
|
---
|
||
|
|
||
|
|
||
|
Here's a snippet of some Drupal code that I wrote last week. It's responsible for converting an array of nodes into a Collection of one of it's field values.
|
||
|
|
||
|
```php
|
||
|
return Collection::make($stationNodes)
|
||
|
->map(fn (NodeInterface $station): string => $station->get('field_station_code')->getString())
|
||
|
->values();
|
||
|
```
|
||
|
|
||
|
There are two issues with this code.
|
||
|
|
||
|
First, whilst I'm implicitly saying that it accepts a certain type of node, because of the `NodeInterface` typehint this could accept any type of node. If that node doesn't have the required field, the code will error - but I'd like to know sooner if an incorrect type of node is passed and make it explicit that only a certain type of node can be used.
|
||
|
|
||
|
Second, the code for getting the field values is quite verbose and is potentially repeated in other places within the codebase. I'd like to have a simple way to access these field values that I can reuse anywhere else. If the logic for getting these particular field values changes, then I'd only need to change it in one place.
|
||
|
|
||
|
## Introducing a value object
|
||
|
|
||
|
This is the value object that I created.
|
||
|
|
||
|
It accepts the original node but checks to ensure that the node is the correct type. If not, an Exception is thrown.
|
||
|
|
||
|
I've added a helper method to get the field value, encapsulating that logic in a reusable function whilst making the code easier to read and its intent clearer.
|
||
|
|
||
|
```php
|
||
|
namespace Drupal\mymodule\ValueObject;
|
||
|
|
||
|
use Drupal\node\NodeInterface;
|
||
|
|
||
|
final class Station implements StationInterface {
|
||
|
|
||
|
private NodeInterface $node;
|
||
|
|
||
|
private function __construct(NodeInterface $node) {
|
||
|
if ($node->bundle() != 'station') {
|
||
|
throw new \InvalidArgumentException();
|
||
|
}
|
||
|
|
||
|
$this->node = $node;
|
||
|
}
|
||
|
|
||
|
public function getStationCode(): string {
|
||
|
return $this->node->get('field_station_code')->getString();
|
||
|
}
|
||
|
|
||
|
public static function fromNode(NodeInterface $node): self {
|
||
|
return new self($node);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Refactoring to use the value object
|
||
|
|
||
|
This is what my code now looks like:
|
||
|
|
||
|
```php
|
||
|
return Collection::make($stationNodes)
|
||
|
->map(fn (NodeInterface $node): StationInterface => Station::fromNode($node))
|
||
|
->map(fn (StationInterface $station): string => $station->getStationCode())
|
||
|
->values();
|
||
|
```
|
||
|
<<<<<<< HEAD:website/source/_daily_emails/2022-10-03.md
|
||
|
=======
|
||
|
|
||
|
>>>>>>> b9cea6d (chore: replace Sculpin with Astro):website/src/pages/daily-emails/2022-10-03.md
|
||
|
I've added an additional `map` to convert the nodes to the value object, but the second map can now use the new typehint - ensuring better type safety and also giving us auto-completion in IDEs and text editors. If an incorrect node type is passed in, then the Exception will be thrown and a much clearer error message will be shown.
|
||
|
|
||
|
Finally, I can use the helper method to get the field value, encapsulating the logic within the value object and making it intention clearer and easier to read.
|