{ "uuid": [ { "value": "d7f7c11b-8ff6-4543-9945-fd500d9a80f9" } ], "langcode": [ { "value": "en" } ], "type": [ { "target_id": "daily_email", "target_type": "node_type", "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7" } ], "revision_timestamp": [ { "value": "2025-05-11T09:00:53+00:00" } ], "revision_uid": [ { "target_type": "user", "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" } ], "revision_log": [], "status": [ { "value": true } ], "uid": [ { "target_type": "user", "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" } ], "title": [ { "value": "Refactoring to value objects" } ], "created": [ { "value": "2022-10-03T00:00:00+00:00" } ], "changed": [ { "value": "2025-05-11T09:00:53+00:00" } ], "promote": [ { "value": false } ], "sticky": [ { "value": false } ], "default_langcode": [ { "value": true } ], "revision_translation_affected": [ { "value": true } ], "path": [ { "alias": "\/daily\/2022\/10\/03\/refactoring-value-objects", "langcode": "en" } ], "body": [ { "value": "\n

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.<\/p>\n\n

return Collection::make($stationNodes)\n  ->map(fn (NodeInterface $station): string => $station->get('field_station_code')->getString())\n  ->values();\n<\/code><\/pre>\n\n

There are two issues with this code.<\/p>\n\n

First, whilst I'm implicitly saying that it accepts a certain type of node, because of the NodeInterface<\/code> 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.<\/p>\n\n

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.<\/p>\n\n

Introducing a value object<\/h2>\n\n

This is the value object that I created.<\/p>\n\n

It accepts the original node but checks to ensure that the node is the correct type. If not, an Exception is thrown.<\/p>\n\n

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.<\/p>\n\n

namespace Drupal\\mymodule\\ValueObject;\n\nuse Drupal\\node\\NodeInterface;\n\nfinal class Station implements StationInterface {\n\n  private NodeInterface $node;\n\n  private function __construct(NodeInterface $node) {\n    if ($node->bundle() != 'station') {\n      throw new \\InvalidArgumentException();\n    }\n\n    $this->node = $node;\n  }\n\n  public function getStationCode(): string {\n    return $this->node->get('field_station_code')->getString();\n  }\n\n  public static function fromNode(NodeInterface $node): self {\n    return new self($node);\n  }\n\n}\n<\/code><\/pre>\n\n

Refactoring to use the value object<\/h2>\n\n

This is what my code now looks like:<\/p>\n\n

return Collection::make($stationNodes)\n  ->map(fn (NodeInterface $node): StationInterface => Station::fromNode($node))\n  ->map(fn (StationInterface $station): string => $station->getStationCode())\n  ->values();\n<\/code><\/pre>\n\n

<<<<<<< HEAD:website\/source\/_daily_emails\/2022-10-03.md<\/h1>\n\n
\n
\n
\n
\n
\n
\n
\n

b9cea6d (chore: replace Sculpin with Astro):website\/src\/pages\/daily-emails\/2022-10-03.md\n I've added an additional map<\/code> 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.<\/p>\n <\/blockquote>\n <\/blockquote>\n <\/blockquote>\n <\/blockquote>\n <\/blockquote>\n <\/blockquote>\n<\/blockquote>\n\n

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.<\/p>\n\n ", "format": "full_html", "processed": "\n

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.<\/p>\n\n

return Collection::make($stationNodes)\n  ->map(fn (NodeInterface $station): string => $station->get('field_station_code')->getString())\n  ->values();\n<\/code><\/pre>\n\n

There are two issues with this code.<\/p>\n\n

First, whilst I'm implicitly saying that it accepts a certain type of node, because of the NodeInterface<\/code> 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.<\/p>\n\n

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.<\/p>\n\n

Introducing a value object<\/h2>\n\n

This is the value object that I created.<\/p>\n\n

It accepts the original node but checks to ensure that the node is the correct type. If not, an Exception is thrown.<\/p>\n\n

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.<\/p>\n\n

namespace Drupal\\mymodule\\ValueObject;\n\nuse Drupal\\node\\NodeInterface;\n\nfinal class Station implements StationInterface {\n\n  private NodeInterface $node;\n\n  private function __construct(NodeInterface $node) {\n    if ($node->bundle() != 'station') {\n      throw new \\InvalidArgumentException();\n    }\n\n    $this->node = $node;\n  }\n\n  public function getStationCode(): string {\n    return $this->node->get('field_station_code')->getString();\n  }\n\n  public static function fromNode(NodeInterface $node): self {\n    return new self($node);\n  }\n\n}\n<\/code><\/pre>\n\n

Refactoring to use the value object<\/h2>\n\n

This is what my code now looks like:<\/p>\n\n

return Collection::make($stationNodes)\n  ->map(fn (NodeInterface $node): StationInterface => Station::fromNode($node))\n  ->map(fn (StationInterface $station): string => $station->getStationCode())\n  ->values();\n<\/code><\/pre>\n\n

<<<<<<< HEAD:website\/source\/_daily_emails\/2022-10-03.md<\/h1>\n\n
\n
\n
\n
\n
\n
\n
\n

b9cea6d (chore: replace Sculpin with Astro):website\/src\/pages\/daily-emails\/2022-10-03.md\n I've added an additional map<\/code> 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.<\/p>\n <\/blockquote>\n <\/blockquote>\n <\/blockquote>\n <\/blockquote>\n <\/blockquote>\n <\/blockquote>\n<\/blockquote>\n\n

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.<\/p>\n\n ", "summary": null } ], "feeds_item": [ { "imported": "1970-01-01T00:33:45+00:00", "guid": null, "hash": "7ee7cb63765e8fc082bb197e047a1b09", "target_type": "feeds_feed", "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76" } ] }