diff --git a/website/source/_daily_emails/2022-10-03.md b/website/source/_daily_emails/2022-10-03.md new file mode 100644 index 00000000..d65dafa9 --- /dev/null +++ b/website/source/_daily_emails/2022-10-03.md @@ -0,0 +1,71 @@ +--- +title: Refactoring to value objects +date: "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(); +``` +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.