diff --git a/.gitignore b/.gitignore
index c026a3ea7..574f09392 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
html/
vendor/
+!content/
+
web/*
!web/assets/
web/assets/*
diff --git a/content/.htaccess b/content/.htaccess
new file mode 100644
index 000000000..b0dc5406e
--- /dev/null
+++ b/content/.htaccess
@@ -0,0 +1,24 @@
+# Deny all requests from Apache 2.4+.
+
In February 2012, I saw a tweet from Tim Millwood asking if anyone wanted to maintain or co-maintain a Drupal module called Override Node Options<\/a>.<\/p>\n\n It had more than 9,200 active installations at that time, with versions for Drupal 5, 6 and 7.<\/p>\n\n I said yes and became the module\u2019s maintainer.<\/p>\n\n The module now has versions for Drupal 7, 8 and 9, with (at the latest count, according to Drupal.org) 32,292 active installations - which makes it currently the 197th most installed module.<\/p>\n\n There have been two main things that come to mind with this module, related to automated testing.<\/p>\n\n Before I become the maintainer, a feature request had been created, along with a large patch file, to add some new permissions to the module. There were some large merge conflicts that stopped me from just committing the changes but I was able to fix them manually and, because the tests still passed, ensure that the original functionality still worked. There weren\u2019t tests for the new permissions but I committed the patch and added the tests later.<\/p>\n\n Without the tests to ensure that the original functionality still worked, I probably wouldn\u2019t have committed the patch and would have just closed the issue.<\/p>\n\n More recently, a friend and ex-colleague and I decided to refactor some of the module's code.<\/p>\n\n We wanted to split the Without the tests ensuring that the module still worked after the refactor, we probably wouldn\u2019t have done it as it was used on over 30,000 sites that I didn't want to break.<\/p>\n\n When I was learning about testing, I was working on projects where I was writing the code during the day and the tests in the evening on my own time.<\/p>\n\n I remember once when my manual testing had been fine, but when writing the test, I found that I\u2019d used an incorrect permission name in the code that was causing the test to fail. This was a bug that, rather than waiting for a QA Engineer or the client to discover and report, I was able to fix it locally before I'd even committed the code.<\/p>\n\n I also worked on an event booking and management website, where we had code responsible for calculating the number of available spaces for an event based on orders, determining the correct price based on the customer's status and the time until the event, creating voucher codes for new members and event leaders, and bulk messaging event attendees. All of the custom functionality was covered by automated tests.<\/p>\n\n The great thing about testing is that it gives you confidence that everything still works how you expect - not only when you wrote the code, but also in the future.<\/p>\n\n I've talked about this, and how to get started with automated testing in Drupal, in a presentation called TDD - Test-Driven Drupal<\/a>. If you want to find out more, the slides and a video recording are embedded there.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n In February 2012, I saw a tweet from Tim Millwood asking if anyone wanted to maintain or co-maintain a Drupal module called Override Node Options<\/a>.<\/p>\n\n It had more than 9,200 active installations at that time, with versions for Drupal 5, 6 and 7.<\/p>\n\n I said yes and became the module\u2019s maintainer.<\/p>\n\n The module now has versions for Drupal 7, 8 and 9, with (at the latest count, according to Drupal.org) 32,292 active installations - which makes it currently the 197th most installed module.<\/p>\n\n There have been two main things that come to mind with this module, related to automated testing.<\/p>\n\n Before I become the maintainer, a feature request had been created, along with a large patch file, to add some new permissions to the module. There were some large merge conflicts that stopped me from just committing the changes but I was able to fix them manually and, because the tests still passed, ensure that the original functionality still worked. There weren\u2019t tests for the new permissions but I committed the patch and added the tests later.<\/p>\n\n Without the tests to ensure that the original functionality still worked, I probably wouldn\u2019t have committed the patch and would have just closed the issue.<\/p>\n\n More recently, a friend and ex-colleague and I decided to refactor some of the module's code.<\/p>\n\n We wanted to split the Without the tests ensuring that the module still worked after the refactor, we probably wouldn\u2019t have done it as it was used on over 30,000 sites that I didn't want to break.<\/p>\n\n When I was learning about testing, I was working on projects where I was writing the code during the day and the tests in the evening on my own time.<\/p>\n\n I remember once when my manual testing had been fine, but when writing the test, I found that I\u2019d used an incorrect permission name in the code that was causing the test to fail. This was a bug that, rather than waiting for a QA Engineer or the client to discover and report, I was able to fix it locally before I'd even committed the code.<\/p>\n\n I also worked on an event booking and management website, where we had code responsible for calculating the number of available spaces for an event based on orders, determining the correct price based on the customer's status and the time until the event, creating voucher codes for new members and event leaders, and bulk messaging event attendees. All of the custom functionality was covered by automated tests.<\/p>\n\n The great thing about testing is that it gives you confidence that everything still works how you expect - not only when you wrote the code, but also in the future.<\/p>\n\n I've talked about this, and how to get started with automated testing in Drupal, in a presentation called TDD - Test-Driven Drupal<\/a>. If you want to find out more, the slides and a video recording are embedded there.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:07+00:00",
+ "guid": null,
+ "hash": "55096628413f9bbe1be1adce212ab46c",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.010cb9ae-6877-48ab-aa77-7c26d067c9b8.json b/content/node.010cb9ae-6877-48ab-aa77-7c26d067c9b8.json
new file mode 100644
index 000000000..8df1b0f17
--- /dev/null
+++ b/content/node.010cb9ae-6877-48ab-aa77-7c26d067c9b8.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "010cb9ae-6877-48ab-aa77-7c26d067c9b8"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:12:58+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": "Committing CI artifacts"
+ }
+ ],
+ "created": [
+ {
+ "value": "2024-07-03T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:12:58+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2024\/07\/03\/committing-ci-artifacts",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n One of the main uses for a CI pipeline<\/a> is to build artifacts for your application, such as installing your dependencies using Composer or npm, or using build tools to perform tasks such as building your CSS and JavaScript assets.<\/p>\n\n Performing these tasks in a CI pipeline means the resulting files can be ignored from your code repository and not committed - making your commits smaller and easier to review, and less likely for you to encounter merge conflicts.<\/p>\n\n The alternative approach is to not use a CI pipline and to perform the tasks manually and commit them to your repository.<\/p>\n\n This introduces a separate set of challenges, but people like having the files in their repository and not worrying about failures in their pipeline.<\/p>\n\n Which do you prefer?<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n One of the main uses for a CI pipeline<\/a> is to build artifacts for your application, such as installing your dependencies using Composer or npm, or using build tools to perform tasks such as building your CSS and JavaScript assets.<\/p>\n\n Performing these tasks in a CI pipeline means the resulting files can be ignored from your code repository and not committed - making your commits smaller and easier to review, and less likely for you to encounter merge conflicts.<\/p>\n\n The alternative approach is to not use a CI pipline and to perform the tasks manually and commit them to your repository.<\/p>\n\n This introduces a separate set of challenges, but people like having the files in their repository and not worrying about failures in their pipeline.<\/p>\n\n Which do you prefer?<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:12:58+00:00",
+ "guid": null,
+ "hash": "9b83d5f29fb969f33b4f82824db6eee3",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.0126db35-8bee-48eb-9586-b2084be1c852.json b/content/node.0126db35-8bee-48eb-9586-b2084be1c852.json
new file mode 100644
index 000000000..d6242a7ac
--- /dev/null
+++ b/content/node.0126db35-8bee-48eb-9586-b2084be1c852.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "0126db35-8bee-48eb-9586-b2084be1c852"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:07+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": "I wrote a Neovim plugin"
+ }
+ ],
+ "created": [
+ {
+ "value": "2022-08-13T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:07+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2022\/08\/13\/i-wrote-a-neovim-plugin",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n I enjoy writing and working with open-source software, starting back to when I started working with PHP and Drupal in 2007.<\/p>\n\n Since then, I've written and maintained a number of Drupal modules and themes, PHP libraries, npm packages, Ansible roles and Docker images - all of which are available on my GitHub and Drupal.org pages.<\/p>\n\n Just over a year ago, I switched to using Neovim full-time<\/a> for my development and DevOps work, and last week, I wrote my first Neovim plugin, written in Lua.<\/p>\n\n I've used Lua to configure Neovim but this is the first time that I've written and open-sourced a standalone Neovim plugin.<\/p>\n\n It's called toggle-checkbox.nvim<\/a> and is used toggle checkboxes in Markdown files - something that I use frequently for to-do lists.<\/p>\n\n For example, this a simple list containing both checked and unchecked checkboxes:<\/p>\n\n To toggle a checkbox, the This is done by calling the In my Neovim configuration, I've added a keymap to do this:<\/p>\n\n This means that I can use the same keymap by running As it's my first Neovim plugin, I decided to keep it simple.<\/p>\n\n The main You can view the plugin at https:\/\/github.com\/opdavies\/toggle-checkbox.nvim<\/a>, as well as my Neovim configuration (which is also written in Lua) as part of my Dotfiles repository<\/a>.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n I enjoy writing and working with open-source software, starting back to when I started working with PHP and Drupal in 2007.<\/p>\n\n Since then, I've written and maintained a number of Drupal modules and themes, PHP libraries, npm packages, Ansible roles and Docker images - all of which are available on my GitHub and Drupal.org pages.<\/p>\n\n Just over a year ago, I switched to using Neovim full-time<\/a> for my development and DevOps work, and last week, I wrote my first Neovim plugin, written in Lua.<\/p>\n\n I've used Lua to configure Neovim but this is the first time that I've written and open-sourced a standalone Neovim plugin.<\/p>\n\n It's called toggle-checkbox.nvim<\/a> and is used toggle checkboxes in Markdown files - something that I use frequently for to-do lists.<\/p>\n\n For example, this a simple list containing both checked and unchecked checkboxes:<\/p>\n\n To toggle a checkbox, the This is done by calling the In my Neovim configuration, I've added a keymap to do this:<\/p>\n\n This means that I can use the same keymap by running As it's my first Neovim plugin, I decided to keep it simple.<\/p>\n\n The main You can view the plugin at https:\/\/github.com\/opdavies\/toggle-checkbox.nvim<\/a>, as well as my Neovim configuration (which is also written in Lua) as part of my Dotfiles repository<\/a>.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:07+00:00",
+ "guid": null,
+ "hash": "da50f1968f98fdd321596a74d98b2ec4",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.01c4184d-7c73-479f-b005-18264ea50c2e.json b/content/node.01c4184d-7c73-479f-b005-18264ea50c2e.json
new file mode 100644
index 000000000..7b586259f
--- /dev/null
+++ b/content/node.01c4184d-7c73-479f-b005-18264ea50c2e.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "01c4184d-7c73-479f-b005-18264ea50c2e"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:03+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": "Deployments or releases\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-06-21T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:03+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/06\/21\/deployments-or-releases",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n \"Deployments\" and \"releases\" are often used interchangeably but mean different things.<\/p>\n\n A deployment moves a change from one place to another, such as some updated code from a staging environment to production.<\/p>\n\n A release is when the change made is available to users.<\/p>\n\n They can happen at the same time, or you can use feature flags to separate them, deploying the code in advance, and the change is only released (or unreleased) by toggling the feature flag.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n \"Deployments\" and \"releases\" are often used interchangeably but mean different things.<\/p>\n\n A deployment moves a change from one place to another, such as some updated code from a staging environment to production.<\/p>\n\n A release is when the change made is available to users.<\/p>\n\n They can happen at the same time, or you can use feature flags to separate them, deploying the code in advance, and the change is only released (or unreleased) by toggling the feature flag.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:03+00:00",
+ "guid": null,
+ "hash": "b418347cf60efb3abda4a93b884ed151",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.023d16f6-0ebc-4c81-91e6-90458fbbf9c1.json b/content/node.023d16f6-0ebc-4c81-91e6-90458fbbf9c1.json
new file mode 100644
index 000000000..d45c0b887
--- /dev/null
+++ b/content/node.023d16f6-0ebc-4c81-91e6-90458fbbf9c1.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "023d16f6-0ebc-4c81-91e6-90458fbbf9c1"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:04+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": "Tailwind: why I prefer to extract HTML components\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-02-20T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:04+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/02\/20\/tailwind-why-i-prefer-to-extract-html-components",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n Tailwind offers the @apply directive that you can use to extract components in your CSS by applying the styles the classes would have added.<\/p>\n\n For example:<\/p>\n\n Whilst this works and reduced duplication, I prefer to handle this within my HTML instead.<\/p>\n\n All templating engines I've used have ways to loop over or map through items and including separate files such as separate partials or different components.<\/p>\n\n The main benefit of this is that you get the HTML structure of the component and not just the styling. If you extract a .btn component, it may depend on a span or other element within it to display correctly but as this isn't obvious, it may be missed when writing an implementation of it in HTML - this can't happen when working inside a loop or importing a canonical version.<\/p>\n\n Also, by working just in the HTML markup, we continue to get the lower cognitive load and speed benefits that we're used to when styling with utility classes rather than switching between multiple files.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n Tailwind offers the @apply directive that you can use to extract components in your CSS by applying the styles the classes would have added.<\/p>\n\n For example:<\/p>\n\n Whilst this works and reduced duplication, I prefer to handle this within my HTML instead.<\/p>\n\n All templating engines I've used have ways to loop over or map through items and including separate files such as separate partials or different components.<\/p>\n\n The main benefit of this is that you get the HTML structure of the component and not just the styling. If you extract a .btn component, it may depend on a span or other element within it to display correctly but as this isn't obvious, it may be missed when writing an implementation of it in HTML - this can't happen when working inside a loop or importing a canonical version.<\/p>\n\n Also, by working just in the HTML markup, we continue to get the lower cognitive load and speed benefits that we're used to when styling with utility classes rather than switching between multiple files.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:04+00:00",
+ "guid": null,
+ "hash": "de94dc72634b64860368d368dd1b936a",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.025d8016-8a75-4a90-8442-be30f05baad8.json b/content/node.025d8016-8a75-4a90-8442-be30f05baad8.json
new file mode 100644
index 000000000..1d795b88d
--- /dev/null
+++ b/content/node.025d8016-8a75-4a90-8442-be30f05baad8.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "025d8016-8a75-4a90-8442-be30f05baad8"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:02+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": "PHP types and assertions\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-08-20T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:02+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/08\/20\/php-types-and-assertions",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n Following yesterday's email about input validation, guard clauses and assertion libraries, these can be used to compliment PHP's native types and checking.<\/p>\n\n For example:<\/p>\n\n In this code, each parameter has a type, but there's no validation on the values.<\/p>\n\n If I run this:<\/p>\n\n I would get this output:<\/p>\n\n This is probably not what you want.<\/p>\n\n I expect I can use an assertion library or throw my own Exceptions if the values pass the type checks but aren't what I need.<\/p>\n\n For example:<\/p>\n\n Now, if an empty string or negative duration is passed - in my implementation or test code - an Exception will be thrown.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n Following yesterday's email about input validation, guard clauses and assertion libraries, these can be used to compliment PHP's native types and checking.<\/p>\n\n For example:<\/p>\n\n In this code, each parameter has a type, but there's no validation on the values.<\/p>\n\n If I run this:<\/p>\n\n I would get this output:<\/p>\n\n This is probably not what you want.<\/p>\n\n I expect I can use an assertion library or throw my own Exceptions if the values pass the type checks but aren't what I need.<\/p>\n\n For example:<\/p>\n\n Now, if an empty string or negative duration is passed - in my implementation or test code - an Exception will be thrown.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:02+00:00",
+ "guid": null,
+ "hash": "7a9179502a50a3f6f3443cdebef1a615",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.02be26ba-44c0-4be5-b05d-327c1387edc9.json b/content/node.02be26ba-44c0-4be5-b05d-327c1387edc9.json
new file mode 100644
index 000000000..aeb8aaf2d
--- /dev/null
+++ b/content/node.02be26ba-44c0-4be5-b05d-327c1387edc9.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "02be26ba-44c0-4be5-b05d-327c1387edc9"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:12:57+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": "Using Nix for local application development"
+ }
+ ],
+ "created": [
+ {
+ "value": "2024-11-30T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:12:57+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2024\/11\/30\/using-nix-for-local-application-development",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n Instead of using tools like Docker or nvm to manage dependencies for your projects, you can use Nix instead.<\/p>\n\n Creating a Nix shell or flake for each project with its dependencies will install everything without needing containers and with the benefit of everything being locked to specific versions, making environments reproducible<\/a>.<\/p>\n\n If you need a specific version of PHP or node for a project, it will be available and different versions can be used for other projects.<\/p>\n\n And if you need services like MySQL and you're not using NixOS, you can also use devenv to manage services, tasks and processes for each project.<\/p>\n\n For me, Nix and devenv have replaced Docker and Docker Compose<\/a> on my development projects.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n Instead of using tools like Docker or nvm to manage dependencies for your projects, you can use Nix instead.<\/p>\n\n Creating a Nix shell or flake for each project with its dependencies will install everything without needing containers and with the benefit of everything being locked to specific versions, making environments reproducible<\/a>.<\/p>\n\n If you need a specific version of PHP or node for a project, it will be available and different versions can be used for other projects.<\/p>\n\n And if you need services like MySQL and you're not using NixOS, you can also use devenv to manage services, tasks and processes for each project.<\/p>\n\n For me, Nix and devenv have replaced Docker and Docker Compose<\/a> on my development projects.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:12:57+00:00",
+ "guid": null,
+ "hash": "29f50807932d09cb8473fe0be543a5d1",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.0354447e-bafe-46fe-8064-c458c6619d68.json b/content/node.0354447e-bafe-46fe-8064-c458c6619d68.json
new file mode 100644
index 000000000..dcccc4e74
--- /dev/null
+++ b/content/node.0354447e-bafe-46fe-8064-c458c6619d68.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "0354447e-bafe-46fe-8064-c458c6619d68"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:02+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": "Prove the concept\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-07-26T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:02+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/07\/26\/prove-the-concept",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n When starting a new task, find the simplest way to prove the concept.<\/p>\n\n Investigate upfront and evaluate potential approaches.<\/p>\n\n What's the smallest or quickest thing you could do to validate an idea?<\/p>\n\n It could be a small script that you can run and verify something works before moving it to its correct place within your application or creating a first implementation with hard-coded data that you refactor once you've proven the concept.<\/p>\n\n If you can't, you'll know it won't work without investing a large amount of time and you can move on to the next potential approach.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n When starting a new task, find the simplest way to prove the concept.<\/p>\n\n Investigate upfront and evaluate potential approaches.<\/p>\n\n What's the smallest or quickest thing you could do to validate an idea?<\/p>\n\n It could be a small script that you can run and verify something works before moving it to its correct place within your application or creating a first implementation with hard-coded data that you refactor once you've proven the concept.<\/p>\n\n If you can't, you'll know it won't work without investing a large amount of time and you can move on to the next potential approach.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:02+00:00",
+ "guid": null,
+ "hash": "53531d5906d1f9a7bfd86d494bd38724",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.03a9c0db-a315-44e7-83fd-81527afb0d65.json b/content/node.03a9c0db-a315-44e7-83fd-81527afb0d65.json
new file mode 100644
index 000000000..5be6b4d26
--- /dev/null
+++ b/content/node.03a9c0db-a315-44e7-83fd-81527afb0d65.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "03a9c0db-a315-44e7-83fd-81527afb0d65"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:00+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": "Coding defensively and considering the unhappy path"
+ }
+ ],
+ "created": [
+ {
+ "value": "2024-02-21T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:00+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2024\/02\/21\/coding-defensively-and-considering-the-unhappy-path",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n Last week, whilst speaking about Sculpin at PHP South West, I showed an example from a client project I'd built with Sculpin.<\/p>\n\n The project was for a local gym and fitness centre, and I was demonstrating how I'd created the timetable page and kept the data separate from the HTML and Twig markup so the client could easily administer it (they edit the files on GitHub, which triggers a rebuild of the website).<\/p>\n\n Each class should have a name and start and end times and be on a specified day.<\/p>\n\n But what if the client didn't include a name or start or end time?<\/p>\n\n What if they put a number or boolean instead of a string?<\/p>\n\n What if there are no classes on a particular day?<\/p>\n\n You don't know how people will use your software, so it's best to be defensive - validate and verify things before rendering them and prevent the page or whole application from breaking if something isn't as you expected or assumed.<\/p>\n\n We also can't assume the \"happy path\" will always be correct.<\/p>\n\n What if there aren't any classes? Do we put an empty message on the timetable or not show that day?<\/p>\n\n If you were retrieving or posting data to an API endpoint, would you assume it was successful or verify the response code is what you expected?<\/p>\n\n If you get the data, do you check if it's in the correct format and something you can use?<\/p>\n\n If not, do you verify other actions in your application haven't run and it's not in an invalid state?<\/p>\n\n In PHP, a common approach is to use the Then, write automated tests that don't test just the happy path but also the unhappy paths.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n Last week, whilst speaking about Sculpin at PHP South West, I showed an example from a client project I'd built with Sculpin.<\/p>\n\n The project was for a local gym and fitness centre, and I was demonstrating how I'd created the timetable page and kept the data separate from the HTML and Twig markup so the client could easily administer it (they edit the files on GitHub, which triggers a rebuild of the website).<\/p>\n\n Each class should have a name and start and end times and be on a specified day.<\/p>\n\n But what if the client didn't include a name or start or end time?<\/p>\n\n What if they put a number or boolean instead of a string?<\/p>\n\n What if there are no classes on a particular day?<\/p>\n\n You don't know how people will use your software, so it's best to be defensive - validate and verify things before rendering them and prevent the page or whole application from breaking if something isn't as you expected or assumed.<\/p>\n\n We also can't assume the \"happy path\" will always be correct.<\/p>\n\n What if there aren't any classes? Do we put an empty message on the timetable or not show that day?<\/p>\n\n If you were retrieving or posting data to an API endpoint, would you assume it was successful or verify the response code is what you expected?<\/p>\n\n If you get the data, do you check if it's in the correct format and something you can use?<\/p>\n\n If not, do you verify other actions in your application haven't run and it's not in an invalid state?<\/p>\n\n In PHP, a common approach is to use the Then, write automated tests that don't test just the happy path but also the unhappy paths.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:00+00:00",
+ "guid": null,
+ "hash": "a94b36c1ef2e928071f7d01827c7163b",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.03ab2265-f728-4e06-8aa0-be4f05a41526.json b/content/node.03ab2265-f728-4e06-8aa0-be4f05a41526.json
new file mode 100644
index 000000000..77af1ca74
--- /dev/null
+++ b/content/node.03ab2265-f728-4e06-8aa0-be4f05a41526.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "03ab2265-f728-4e06-8aa0-be4f05a41526"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:03+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": "It's only a bad situation if you fail to learn from it\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-06-22T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:03+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/06\/22\/fail-to-lear",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n I heard this on a non-tech podcast but it applies to tech too:<\/p>\n\n It's only a bad situation if you fail to learn from it<\/p>\n<\/blockquote>\n\n Whether its a missed deadline, a failed deployment, a production outage or pushing a bug to production, there are opportunities to learn and improve.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n I heard this on a non-tech podcast but it applies to tech too:<\/p>\n\n It's only a bad situation if you fail to learn from it<\/p>\n<\/blockquote>\n\n Whether its a missed deadline, a failed deployment, a production outage or pushing a bug to production, there are opportunities to learn and improve.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:03+00:00",
+ "guid": null,
+ "hash": "1d08162ec930721f889a571b47fde853",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.03cc008a-1b6a-48f6-9b60-735cb82830e7.json b/content/node.03cc008a-1b6a-48f6-9b60-735cb82830e7.json
new file mode 100644
index 000000000..21e610092
--- /dev/null
+++ b/content/node.03cc008a-1b6a-48f6-9b60-735cb82830e7.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "03cc008a-1b6a-48f6-9b60-735cb82830e7"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:12:58+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": "Countdown to Drupal 11"
+ }
+ ],
+ "created": [
+ {
+ "value": "2024-06-29T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:12:58+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2024\/06\/29\/countdown-to-drupal-11",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n With Drupal 10.3 released<\/a>, Drupal 11 will be the next major version.<\/p>\n\n There will be a Drupal 10.4, which will be the first minor maintenance version and supported until Drupal 12's release in 2026.<\/p>\n\n According to the current development cycle<\/a>, Drupal 11 will be released in a month's time on the 29th of July, with 11.1 in December alongside 10.4.<\/p>\n\n Drupal 7 will be unsupported as of January 2025.<\/p>\n\n With Drupal 10 websites needing to be upgraded to 10.3 before 11, it gives people the chance to test their applications on the latest Drupal 10 before upgrading to 11 - a refined version of 10 without the deprecated code.<\/p>\n\n It also gives module and theme maintainers the opportunity to make their projects compatible before making any breaking changes, meaning upgrades should be more stable as the main changes can be done beforehand.<\/p>\n\n I'm looking forward to upgrading my applications and projects to Drupal 11, and seeing what's to come in 11.1 and beyond.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n With Drupal 10.3 released<\/a>, Drupal 11 will be the next major version.<\/p>\n\n There will be a Drupal 10.4, which will be the first minor maintenance version and supported until Drupal 12's release in 2026.<\/p>\n\n According to the current development cycle<\/a>, Drupal 11 will be released in a month's time on the 29th of July, with 11.1 in December alongside 10.4.<\/p>\n\n Drupal 7 will be unsupported as of January 2025.<\/p>\n\n With Drupal 10 websites needing to be upgraded to 10.3 before 11, it gives people the chance to test their applications on the latest Drupal 10 before upgrading to 11 - a refined version of 10 without the deprecated code.<\/p>\n\n It also gives module and theme maintainers the opportunity to make their projects compatible before making any breaking changes, meaning upgrades should be more stable as the main changes can be done beforehand.<\/p>\n\n I'm looking forward to upgrading my applications and projects to Drupal 11, and seeing what's to come in 11.1 and beyond.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:12:58+00:00",
+ "guid": null,
+ "hash": "e6345f399d28ab0955c18f1528e29428",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.047d366e-35b4-4569-ac29-2cebe56989fb.json b/content/node.047d366e-35b4-4569-ac29-2cebe56989fb.json
new file mode 100644
index 000000000..3d59ab58b
--- /dev/null
+++ b/content/node.047d366e-35b4-4569-ac29-2cebe56989fb.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "047d366e-35b4-4569-ac29-2cebe56989fb"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:02+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": "More code, more problems\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-07-22T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:02+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/07\/22\/more-code-more-problems",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n The more code you have, the more potential problems you have.<\/p>\n\n More code means more opportunities for bugs in your software.<\/p>\n\n There's more code to maintain and more chance of encountering breaking changes as you update between major software versions of your project's dependencies, such as a CMS or framework.<\/p>\n\n If you can keep your amount of code to a minimum and reduce the maintenance overhead, you are less likely to experience issues.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n The more code you have, the more potential problems you have.<\/p>\n\n More code means more opportunities for bugs in your software.<\/p>\n\n There's more code to maintain and more chance of encountering breaking changes as you update between major software versions of your project's dependencies, such as a CMS or framework.<\/p>\n\n If you can keep your amount of code to a minimum and reduce the maintenance overhead, you are less likely to experience issues.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:02+00:00",
+ "guid": null,
+ "hash": "137f467c7894dcf4d534ab6c7572567a",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.0495b46d-90e9-49a1-9448-51f0737474a8.json b/content/node.0495b46d-90e9-49a1-9448-51f0737474a8.json
new file mode 100644
index 000000000..8d9d12282
--- /dev/null
+++ b/content/node.0495b46d-90e9-49a1-9448-51f0737474a8.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "0495b46d-90e9-49a1-9448-51f0737474a8"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:12:58+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": "Technical debt isn't always bad"
+ }
+ ],
+ "created": [
+ {
+ "value": "2024-10-02T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:12:58+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2024\/10\/02\/technical-debt-isn-t-always-bad",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n Technical debt is usually referred to negatively<\/a>, but technical debt isn't always bad.<\/p>\n\n The key thing is to know when and why you're taking on technical debt and when it will be addressed.<\/p>\n\n If you have a goal or deadline to meet, you may decide to take on technical debt to release a feature sooner or a simpler version is released now and a more complex version will come later.<\/p>\n\n I've done this on multi-site Drupal projects before, where I've hard-coded a background image URL as part of a minimum-viable version and made it changeable only when it needed to be - i.e. when the second website was introduced.<\/p>\n\n For the initial version, that approach was good enough and meant we could move forward.<\/p>\n\n The client and I decided to take on this technical debit in advance so we could release it sooner, and we knew when and how we were going to address it and pay it back.<\/p>\n\n This was a good situation, not a bad one.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n Technical debt is usually referred to negatively<\/a>, but technical debt isn't always bad.<\/p>\n\n The key thing is to know when and why you're taking on technical debt and when it will be addressed.<\/p>\n\n If you have a goal or deadline to meet, you may decide to take on technical debt to release a feature sooner or a simpler version is released now and a more complex version will come later.<\/p>\n\n I've done this on multi-site Drupal projects before, where I've hard-coded a background image URL as part of a minimum-viable version and made it changeable only when it needed to be - i.e. when the second website was introduced.<\/p>\n\n For the initial version, that approach was good enough and meant we could move forward.<\/p>\n\n The client and I decided to take on this technical debit in advance so we could release it sooner, and we knew when and how we were going to address it and pay it back.<\/p>\n\n This was a good situation, not a bad one.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:12:58+00:00",
+ "guid": null,
+ "hash": "1ece063b605c1351451d9f4379ee3bd6",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.04bac412-3008-4d80-88a4-2fb466e77d30.json b/content/node.04bac412-3008-4d80-88a4-2fb466e77d30.json
new file mode 100644
index 000000000..63f39b47c
--- /dev/null
+++ b/content/node.04bac412-3008-4d80-88a4-2fb466e77d30.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "04bac412-3008-4d80-88a4-2fb466e77d30"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:00+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": "Things take as long as they take"
+ }
+ ],
+ "created": [
+ {
+ "value": "2024-01-07T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:00+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2024\/01\/07\/things-take-as-long-as-they-take",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n Today, I saw a post that asked the question:<\/p>\n\n Have you ever spent three days on an issue that should have taken 2 hours?<\/p>\n<\/blockquote>\n\n My thought was, \"Who said it should have taken two hours?\".<\/p>\n\n In my experience, tasks take as long as they take.<\/p>\n\n Even something that seems simple can end up being something complex.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n Today, I saw a post that asked the question:<\/p>\n\n Have you ever spent three days on an issue that should have taken 2 hours?<\/p>\n<\/blockquote>\n\n My thought was, \"Who said it should have taken two hours?\".<\/p>\n\n In my experience, tasks take as long as they take.<\/p>\n\n Even something that seems simple can end up being something complex.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:00+00:00",
+ "guid": null,
+ "hash": "c80c0a0daeda9ac2879e1a8706bfad5e",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.0554df36-c8d8-4383-8afc-da3370b7b8ae.json b/content/node.0554df36-c8d8-4383-8afc-da3370b7b8ae.json
new file mode 100644
index 000000000..e23d193f7
--- /dev/null
+++ b/content/node.0554df36-c8d8-4383-8afc-da3370b7b8ae.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "0554df36-c8d8-4383-8afc-da3370b7b8ae"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:02+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": "Don't inject too many dependencies\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-09-12T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:02+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/09\/12\/dont-inject-too-many-dependencies",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n While dependency injection is a good practice - i.e., passing dependencies into a class, usually via a constructor method - you want to be aware of how many dependencies you inject into each class.<\/p>\n\n There's no hard and fast rule, but I usually notice when I get to three dependencies and rarely inject more than four or five into a class.<\/p>\n\n Having too many dependencies suggests that the class is doing too much and has too many responsibilities and that another class may be needed.<\/p>\n\n Having smaller and simpler classes makes them easier to read, maintain and review. Ideally, each class should only have one responsibility, so it adheres to the Single Responsibility Principle (the \"S\" in SOLID).<\/p>\n\n Creating classes is cheap, so why not split one large and difficult-to-maintain class with many dependencies into multiple smaller and easier-to-work-with ones?<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n While dependency injection is a good practice - i.e., passing dependencies into a class, usually via a constructor method - you want to be aware of how many dependencies you inject into each class.<\/p>\n\n There's no hard and fast rule, but I usually notice when I get to three dependencies and rarely inject more than four or five into a class.<\/p>\n\n Having too many dependencies suggests that the class is doing too much and has too many responsibilities and that another class may be needed.<\/p>\n\n Having smaller and simpler classes makes them easier to read, maintain and review. Ideally, each class should only have one responsibility, so it adheres to the Single Responsibility Principle (the \"S\" in SOLID).<\/p>\n\n Creating classes is cheap, so why not split one large and difficult-to-maintain class with many dependencies into multiple smaller and easier-to-work-with ones?<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:02+00:00",
+ "guid": null,
+ "hash": "b40abd231dda53a740949b3e5d5ac434",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.0564c6eb-6ed8-4390-942d-ba901c038998.json b/content/node.0564c6eb-6ed8-4390-942d-ba901c038998.json
new file mode 100644
index 000000000..50c00a360
--- /dev/null
+++ b/content/node.0564c6eb-6ed8-4390-942d-ba901c038998.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "0564c6eb-6ed8-4390-942d-ba901c038998"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:12:58+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": "Violinist, render arrays and feature flags"
+ }
+ ],
+ "created": [
+ {
+ "value": "2024-09-13T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:12:58+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2024\/09\/13\/violinist-render-arrays-and-feature-flags",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n This week, I spoke with Eirik Morland again on the Beyond Blocks podcast<\/a> about recent improvements to violinist.io, such as team\/multi-user subscriptions.<\/p>\n\n Listen to the episode now<\/a>.<\/p>\n\n I was great to speak to Eirik again and for him to be the first returning guest on the podcast.<\/p>\n\n Listen to the first episode with Eirik<\/a>.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n This week, I spoke with Eirik Morland again on the Beyond Blocks podcast<\/a> about recent improvements to violinist.io, such as team\/multi-user subscriptions.<\/p>\n\n Listen to the episode now<\/a>.<\/p>\n\n I was great to speak to Eirik again and for him to be the first returning guest on the podcast.<\/p>\n\n Listen to the first episode with Eirik<\/a>.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:12:58+00:00",
+ "guid": null,
+ "hash": "fbad09b906f5606d9d39c07e3a6f7cb0",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.0588b7ef-d687-4eea-a55f-c2e0bb556a2c.json b/content/node.0588b7ef-d687-4eea-a55f-c2e0bb556a2c.json
new file mode 100644
index 000000000..687b1cfa2
--- /dev/null
+++ b/content/node.0588b7ef-d687-4eea-a55f-c2e0bb556a2c.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "0588b7ef-d687-4eea-a55f-c2e0bb556a2c"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:12:59+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": "Dead or done"
+ }
+ ],
+ "created": [
+ {
+ "value": "2024-06-14T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:12:59+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2024\/06\/14\/dead-or-done",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n Yesterday, I wrote about some things I look for when evaluating open-source projects<\/a>.<\/p>\n\n One thing I said was \"When was the most recent commit and release?\".<\/p>\n\n If a project hasn't had many recent commits, it could be outdated or no longer supported.<\/p>\n\n Alternatively, it could be considered feature complete and not getting new features, and only getting bug fixes and maintenance updates.<\/p>\n\n I see this a lot with Vim plugins that were written several years ago and are now minimally maintained and updated, but getting no new features.<\/p>\n\n This happens in the Drupal space, too, when people wrote a module for a project which they have since completed, or no longer work with that client or for that company.<\/p>\n\n If there are at least commits for security compatibility, such as new versions of PHP or node, that's a sign the project is in a maintenance phase.<\/p>\n\n If there are no recent commits, the project could be dead and I'd carefully consider if you want to add or use it.<\/p>\n\n Something that could help is if maintainers are explicit about what state their project is in.<\/p>\n\n Add a note to the README.md or CONTRIBUTING.md file saying if the project is feature complete or what the maintenance state is.<\/p>\n\n If the project is no longer maintained, you can also document it and potentially archive the repository too to show that it will no longer be updated and to avoid confusion.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n Yesterday, I wrote about some things I look for when evaluating open-source projects<\/a>.<\/p>\n\n One thing I said was \"When was the most recent commit and release?\".<\/p>\n\n If a project hasn't had many recent commits, it could be outdated or no longer supported.<\/p>\n\n Alternatively, it could be considered feature complete and not getting new features, and only getting bug fixes and maintenance updates.<\/p>\n\n I see this a lot with Vim plugins that were written several years ago and are now minimally maintained and updated, but getting no new features.<\/p>\n\n This happens in the Drupal space, too, when people wrote a module for a project which they have since completed, or no longer work with that client or for that company.<\/p>\n\n If there are at least commits for security compatibility, such as new versions of PHP or node, that's a sign the project is in a maintenance phase.<\/p>\n\n If there are no recent commits, the project could be dead and I'd carefully consider if you want to add or use it.<\/p>\n\n Something that could help is if maintainers are explicit about what state their project is in.<\/p>\n\n Add a note to the README.md or CONTRIBUTING.md file saying if the project is feature complete or what the maintenance state is.<\/p>\n\n If the project is no longer maintained, you can also document it and potentially archive the repository too to show that it will no longer be updated and to avoid confusion.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:12:59+00:00",
+ "guid": null,
+ "hash": "06646d3c892a58bf29e20d2b84896a7c",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.0679084c-8c6d-4212-bd7b-5679bc0ac064.json b/content/node.0679084c-8c6d-4212-bd7b-5679bc0ac064.json
new file mode 100644
index 000000000..2fb25eaa8
--- /dev/null
+++ b/content/node.0679084c-8c6d-4212-bd7b-5679bc0ac064.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "0679084c-8c6d-4212-bd7b-5679bc0ac064"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:02+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": "Test-driven development makes you more productive\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-07-15T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:02+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/07\/15\/test-driven-development-makes-you-more-productive",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n I think that test-driven development (TDD) makes you productive.<\/p>\n\n Firstly, you save time by not needing to switch from your code to a terminal or browser to test it.<\/p>\n\n But, just as importantly, TDD reduces procrastination. It's much clearer to see what the next steps are.<\/p>\n\n You're either thinking and designing your code when writing a failing test or fixing the test failures in the implementation code to get the test to pass. You can focus on each failure and message separately and get them to pass instead of thinking about the whole feature or the rest of the application.<\/p>\n\n Once you have a working test, you can focus on refactoring any code or moving on to writing the next assertion or the next test.<\/p>\n\n I think that achieving small tasks with short feedback loops using test-driven development makes it much easier to remain productive and focussed.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n I think that test-driven development (TDD) makes you productive.<\/p>\n\n Firstly, you save time by not needing to switch from your code to a terminal or browser to test it.<\/p>\n\n But, just as importantly, TDD reduces procrastination. It's much clearer to see what the next steps are.<\/p>\n\n You're either thinking and designing your code when writing a failing test or fixing the test failures in the implementation code to get the test to pass. You can focus on each failure and message separately and get them to pass instead of thinking about the whole feature or the rest of the application.<\/p>\n\n Once you have a working test, you can focus on refactoring any code or moving on to writing the next assertion or the next test.<\/p>\n\n I think that achieving small tasks with short feedback loops using test-driven development makes it much easier to remain productive and focussed.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:02+00:00",
+ "guid": null,
+ "hash": "d8c18f8667771c2497ae722e5849f6fc",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.06873fa4-9ca1-4ac9-acfe-213853600b12.json b/content/node.06873fa4-9ca1-4ac9-acfe-213853600b12.json
new file mode 100644
index 000000000..daf7400aa
--- /dev/null
+++ b/content/node.06873fa4-9ca1-4ac9-acfe-213853600b12.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "06873fa4-9ca1-4ac9-acfe-213853600b12"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:12:56+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": "Tidy then push"
+ }
+ ],
+ "created": [
+ {
+ "value": "2025-02-11T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:12:56+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2025\/02\/11\/tidy",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n As I said in yesterday's email<\/a>, sometimes you change your mind whilst working on something.<\/p>\n\n Maybe you change your approach and have a commit that supersedes an earlier one, fix a typo, or find a bug and need to revert a commit.<\/p>\n\n If you're pushing your changes to a branch for review, I suggest using You can squash the typo fix into the commit that introduced the typo, or remove the original implementation that you later moved away from.<\/p>\n\n Whilst there is an option to squash all the commits when merging, I don't like it and prefer people to tidy their commits before pushing.<\/p>\n\n This means the commits are easier to review and you can keep the original commit history and all the context within the messages instead of a generic As I said in yesterday's email<\/a>, sometimes you change your mind whilst working on something.<\/p>\n\n Maybe you change your approach and have a commit that supersedes an earlier one, fix a typo, or find a bug and need to revert a commit.<\/p>\n\n If you're pushing your changes to a branch for review, I suggest using You can squash the typo fix into the commit that introduced the typo, or remove the original implementation that you later moved away from.<\/p>\n\n Whilst there is an option to squash all the commits when merging, I don't like it and prefer people to tidy their commits before pushing.<\/p>\n\n This means the commits are easier to review and you can keep the original commit history and all the context within the messages instead of a generic Whether it's PHP or JavaScript\/TypeScript, I prefer type declarations in my code.<\/p>\n\n As well as benefits like auto-completion in your IDE or text editor and being able to more effectively statically analyse the code, to me, the code is more readable and easier to understand with the types included.<\/p>\n\n It's more to read, but I can do so easily and immediately know what a function expects as function arguments and what it will return.<\/p>\n\n Here's the code from my previous email on types from a few days ago, with and without the types declared:<\/p>\n\n Without types, I can infer what the function accepts and returns, but that's based on my assumption, which could be incorrect.<\/p>\n\n What if With the type declarations included, I don't need to presume, infer or make best guesses.<\/p>\n\n It's clear from just reading the code.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n Whether it's PHP or JavaScript\/TypeScript, I prefer type declarations in my code.<\/p>\n\n As well as benefits like auto-completion in your IDE or text editor and being able to more effectively statically analyse the code, to me, the code is more readable and easier to understand with the types included.<\/p>\n\n It's more to read, but I can do so easily and immediately know what a function expects as function arguments and what it will return.<\/p>\n\n Here's the code from my previous email on types from a few days ago, with and without the types declared:<\/p>\n\n Without types, I can infer what the function accepts and returns, but that's based on my assumption, which could be incorrect.<\/p>\n\n What if With the type declarations included, I don't need to presume, infer or make best guesses.<\/p>\n\n It's clear from just reading the code.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:02+00:00",
+ "guid": null,
+ "hash": "506ce77f679ad7914294ef3ac3d0201c",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.077780fd-8480-426c-80b9-d54befef06b1.json b/content/node.077780fd-8480-426c-80b9-d54befef06b1.json
new file mode 100644
index 000000000..8a8a53b3e
--- /dev/null
+++ b/content/node.077780fd-8480-426c-80b9-d54befef06b1.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "077780fd-8480-426c-80b9-d54befef06b1"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:01+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": "Avoiding over-mocking\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-11-16T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:01+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/11\/16\/avoiding-over-mocking",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n In unit tests, and sometimes in kernel tests, you need to mock the dependencies you aren't testing, but you can over-mock and only be testing the mocks and not the code you want to test.<\/p>\n\n Here's an example (thanks, ChatGPT, for the code).<\/p>\n\n In this example, the test creates a mock for the The test then calls the The issue with this test is that it's not testing the actual behaviour of It's only testing that the mock is configured correctly.<\/p>\n\n If the real implementation of In unit tests, and sometimes in kernel tests, you need to mock the dependencies you aren't testing, but you can over-mock and only be testing the mocks and not the code you want to test.<\/p>\n\n Here's an example (thanks, ChatGPT, for the code).<\/p>\n\n In this example, the test creates a mock for the The test then calls the The issue with this test is that it's not testing the actual behaviour of It's only testing that the mock is configured correctly.<\/p>\n\n If the real implementation of There's a common saying about not deploying changes on a Friday to prevent outages or issues before the weekend.<\/p>\n\n I've also seen this where people won't deploy after a particular time of the day as it's too close to the evening.<\/p>\n\n The longer it's been since the last deployment, the risker each deployment is.<\/p>\n\n If there are weeks or months of changes, it will be risky regardless of which day it is.<\/p>\n\n If your last deployment was an afternoon, deploying a small change the following morning will be low risk, even on a Thursday and Friday.<\/p>\n\n If you're nervous about deploying on a Friday, I think you need to aim for smaller and more frequent deployments to minimise the risk.<\/p>\n\n The issue isn't when you're deploying. You likely need to do so more often.<\/p>\n\n If there is an issue after a large release, it will take more time to debug or roll back compared to a small release which is easier to find and fix the problem or revert that single change.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n There's a common saying about not deploying changes on a Friday to prevent outages or issues before the weekend.<\/p>\n\n I've also seen this where people won't deploy after a particular time of the day as it's too close to the evening.<\/p>\n\n The longer it's been since the last deployment, the risker each deployment is.<\/p>\n\n If there are weeks or months of changes, it will be risky regardless of which day it is.<\/p>\n\n If your last deployment was an afternoon, deploying a small change the following morning will be low risk, even on a Thursday and Friday.<\/p>\n\n If you're nervous about deploying on a Friday, I think you need to aim for smaller and more frequent deployments to minimise the risk.<\/p>\n\n The issue isn't when you're deploying. You likely need to do so more often.<\/p>\n\n If there is an issue after a large release, it will take more time to debug or roll back compared to a small release which is easier to find and fix the problem or revert that single change.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:03+00:00",
+ "guid": null,
+ "hash": "531214c785675f0543e778a343d5a677",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.07f773b8-2626-470e-b97e-aec79cd9ddeb.json b/content/node.07f773b8-2626-470e-b97e-aec79cd9ddeb.json
new file mode 100644
index 000000000..60e9020db
--- /dev/null
+++ b/content/node.07f773b8-2626-470e-b97e-aec79cd9ddeb.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "07f773b8-2626-470e-b97e-aec79cd9ddeb"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:12:57+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": "Using open source software to build open source software"
+ }
+ ],
+ "created": [
+ {
+ "value": "2024-12-08T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:12:57+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2024\/12\/08\/building-open-source",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n As well as using free and open source software for my homelab<\/a>, I develop software applications for clients using open source software, such as PHP, Drupal, Symfony and Sculpin.<\/p>\n\n I also use open source software to do this.<\/p>\n\n I use NixOS as the operating system on my laptop<\/a>.<\/p>\n\n I use Alacritty as my terminal emulator and tmux as a terminal multiplexer to have different sessions, windows and tabs for each project.<\/p>\n\n I write my code in Neovim and use various additional plugins.<\/p>\n\n I use Git for source control and tools like PHPStan, PHPUnit and Pest to run checks on my code.<\/p>\n\n I use Nix and devenv<\/a> to run the applications locally and host them on Linux servers running Nginx.<\/p>\n\n I also use OBS, Kdenlive and GIMP to record and edit my content, which are all also open source projects.<\/p>\n\n I run an open source business, using open source tools and projects and contribute to and maintain my own open source software projects<\/a>.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n As well as using free and open source software for my homelab<\/a>, I develop software applications for clients using open source software, such as PHP, Drupal, Symfony and Sculpin.<\/p>\n\n I also use open source software to do this.<\/p>\n\n I use NixOS as the operating system on my laptop<\/a>.<\/p>\n\n I use Alacritty as my terminal emulator and tmux as a terminal multiplexer to have different sessions, windows and tabs for each project.<\/p>\n\n I write my code in Neovim and use various additional plugins.<\/p>\n\n I use Git for source control and tools like PHPStan, PHPUnit and Pest to run checks on my code.<\/p>\n\n I use Nix and devenv<\/a> to run the applications locally and host them on Linux servers running Nginx.<\/p>\n\n I also use OBS, Kdenlive and GIMP to record and edit my content, which are all also open source projects.<\/p>\n\n I run an open source business, using open source tools and projects and contribute to and maintain my own open source software projects<\/a>.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:12:57+00:00",
+ "guid": null,
+ "hash": "2408bd8dcaa6ea4bd93b2e65f0fac540",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.08598367-d6b0-45e5-a073-fbf23115417b.json b/content/node.08598367-d6b0-45e5-a073-fbf23115417b.json
new file mode 100644
index 000000000..e7fe62999
--- /dev/null
+++ b/content/node.08598367-d6b0-45e5-a073-fbf23115417b.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "08598367-d6b0-45e5-a073-fbf23115417b"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:02+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": "Too many choices?\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-07-17T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:02+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/07\/17\/too-many-choices",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n I've recently considered moving my infrastructure automation code from Pulumi to Terraform.<\/p>\n\n One of Pulumi's features is that you can write your automation in a programming language instead of a domain-specific language (DSL) with Terraform.<\/p>\n\n As a Developer, this seems appealing, but it poses an important question - which programming language should you use?<\/p>\n\n I've written and re-written Pulumi code in TypeScript and Python and experimented with Go to see which feels best for me.<\/p>\n\n If one of these were my primary language, it would be a no-brainer.<\/p>\n\n When I go into my automation repository, I want to write my code as quickly and simply as possible. I don't want to be thinking about how to write it or what language would be best to write it in.<\/p>\n\n Whilst I'd have to learn another DSL for Terraform, it would simplify my options by removing that choice for me, but also if I write automation code and hand it over to a client.<\/p>\n\n It's like taking my children to a restaurant.<\/p>\n\n They'll get overwhelmed if there are too many options on the menu. If we limit the options or order for them, they won't.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n I've recently considered moving my infrastructure automation code from Pulumi to Terraform.<\/p>\n\n One of Pulumi's features is that you can write your automation in a programming language instead of a domain-specific language (DSL) with Terraform.<\/p>\n\n As a Developer, this seems appealing, but it poses an important question - which programming language should you use?<\/p>\n\n I've written and re-written Pulumi code in TypeScript and Python and experimented with Go to see which feels best for me.<\/p>\n\n If one of these were my primary language, it would be a no-brainer.<\/p>\n\n When I go into my automation repository, I want to write my code as quickly and simply as possible. I don't want to be thinking about how to write it or what language would be best to write it in.<\/p>\n\n Whilst I'd have to learn another DSL for Terraform, it would simplify my options by removing that choice for me, but also if I write automation code and hand it over to a client.<\/p>\n\n It's like taking my children to a restaurant.<\/p>\n\n They'll get overwhelmed if there are too many options on the menu. If we limit the options or order for them, they won't.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:02+00:00",
+ "guid": null,
+ "hash": "960ab3763a91c3a533d2f22da91bd8fd",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.08a03029-d231-45f2-91b3-0a4611156b0c.json b/content/node.08a03029-d231-45f2-91b3-0a4611156b0c.json
new file mode 100644
index 000000000..4f860be70
--- /dev/null
+++ b/content/node.08a03029-d231-45f2-91b3-0a4611156b0c.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "08a03029-d231-45f2-91b3-0a4611156b0c"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:06+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-04-16T14:13:06+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 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 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 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 This is what my code now looks like:<\/p>\n\n b9cea6d (chore: replace Sculpin with Astro):website\/src\/pages\/daily-emails\/2022-10-03.md\n I've added an additional 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 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 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 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 This is what my code now looks like:<\/p>\n\n b9cea6d (chore: replace Sculpin with Astro):website\/src\/pages\/daily-emails\/2022-10-03.md\n I've added an additional 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": "2025-04-16T14:13:06+00:00",
+ "guid": null,
+ "hash": "7ee7cb63765e8fc082bb197e047a1b09",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.08c0fcef-6c95-4e5a-91cd-a196e8027d2a.json b/content/node.08c0fcef-6c95-4e5a-91cd-a196e8027d2a.json
new file mode 100644
index 000000000..d3e75c658
--- /dev/null
+++ b/content/node.08c0fcef-6c95-4e5a-91cd-a196e8027d2a.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "08c0fcef-6c95-4e5a-91cd-a196e8027d2a"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:12:59+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": "Paying it forward"
+ }
+ ],
+ "created": [
+ {
+ "value": "2024-04-09T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:12:59+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2024\/04\/09\/paying-it-forward",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n As well as building applications with PHP and Drupal, I spend a lot of time helping others and \"paying it forward\".<\/p>\n\n I write and contribute to open-source software and offer free online pair programming sessions to work on open-source projects.<\/p>\n\n I speak at conferences and meetups.<\/p>\n\n I create content, including videos and coding live streams.<\/p>\n\n I mentor at in-person events, such as DrupalCon, and for bootcamps, such as School of Code.<\/p>\n\n I've organised events, such as PHP South Wales and DrupalCamp Bristol, and reviewed session submissions for DrupalCon.<\/p>\n\n But I wouldn't have been able to do this without others doing the same when I was learning and getting into software development.<\/p>\n\n In July 2008, when I was evaluating technologies, I posted a question on a forum (I was learning HTML, PHP and MySQL then).<\/p>\n\n Someone replied and recommended using Drupal, which is how I learned about the project.<\/p>\n\n If they hadn't done that, I may not be in the position I am now.<\/p>\n\n I might not have found Drupal, contributed to it, worked for the Drupal Association, or spoken at DrupalCon.<\/p>\n\n Now, it's my turn to pay it forward.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n As well as building applications with PHP and Drupal, I spend a lot of time helping others and \"paying it forward\".<\/p>\n\n I write and contribute to open-source software and offer free online pair programming sessions to work on open-source projects.<\/p>\n\n I speak at conferences and meetups.<\/p>\n\n I create content, including videos and coding live streams.<\/p>\n\n I mentor at in-person events, such as DrupalCon, and for bootcamps, such as School of Code.<\/p>\n\n I've organised events, such as PHP South Wales and DrupalCamp Bristol, and reviewed session submissions for DrupalCon.<\/p>\n\n But I wouldn't have been able to do this without others doing the same when I was learning and getting into software development.<\/p>\n\n In July 2008, when I was evaluating technologies, I posted a question on a forum (I was learning HTML, PHP and MySQL then).<\/p>\n\n Someone replied and recommended using Drupal, which is how I learned about the project.<\/p>\n\n If they hadn't done that, I may not be in the position I am now.<\/p>\n\n I might not have found Drupal, contributed to it, worked for the Drupal Association, or spoken at DrupalCon.<\/p>\n\n Now, it's my turn to pay it forward.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:12:59+00:00",
+ "guid": null,
+ "hash": "43ead52317b471703708839250e11a67",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.09509576-3a6d-47a9-9127-6b01be234771.json b/content/node.09509576-3a6d-47a9-9127-6b01be234771.json
new file mode 100644
index 000000000..1a578d449
--- /dev/null
+++ b/content/node.09509576-3a6d-47a9-9127-6b01be234771.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "09509576-3a6d-47a9-9127-6b01be234771"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:12:56+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": "Drupal and the Open Web"
+ }
+ ],
+ "created": [
+ {
+ "value": "2025-02-05T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:12:56+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2025\/02\/05\/open",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n A few days ago, I said that I still like to use RSS<\/a> to consume a lot of online content.<\/p>\n\n Unfortunately, a lot of websites either don't have RSS or Atom feeds, or they aren't easy to find without viewing the website's source code.<\/p>\n\n The ability for people to publish and easily consume other people's content is a main principle of the open web, instead of relying on other websites and social media.<\/p>\n\n Drupal is one of the projects that make an open web possible<\/a>.<\/p>\n\n It's easy to create an RSS feed of your articles, events or any other content on your Drupal website for others to consume.<\/p>\n\n I also love that Drupal.org itself does this by publishing RSS feeds for project releases and various other content - not just news or blog posts.<\/p>\n\n Because of this, I can subscribe to the feeds I want from any website that creates them and I can consume it how and when I want.<\/p>\n\n Long live the open web!<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n override_node_options.module<\/code> file so that each override was in its own file and its own class. This would make them easier to edit and maintain, and if anyone wanted to add a new one, they\u2019d just need to create a new file for it and add it to the list of overrides.<\/p>\n\n
override_node_options.module<\/code> file so that each override was in its own file and its own class. This would make them easier to edit and maintain, and if anyone wanted to add a new one, they\u2019d just need to create a new file for it and add it to the list of overrides.<\/p>\n\n
- [x] A completed task\n- [ ] An incomplete task\n<\/code><\/pre>\n\n
x<\/code> character needs to be either added or removed, depending on whether we're checking or unchecking it.<\/p>\n\n
toggle()<\/code> function within the plugin.<\/p>\n\n
vim.keymap.set(\n \"n\",\n \"<leader>tt\",\n \"require('toggle-checkbox').toggle()\"\n)\n<\/code><\/pre>\n\n
<leader>tt<\/code> to check or uncheck a checkbox. I could use Vim's replace mode to do this, but I really wanted to have one keymap that I could use for both.<\/p>\n\n
toggle-checkbox.lua<\/code> file is currently only 41 lines of code, and whilst there is an existing Vim plugin that I could have used, I was excited to write my own plugin for Neovim, to start contributing to the Neovim ecosystem, and add a Neovim plugin to my portfolio of open-source projects.<\/p>\n\n
- [x] A completed task\n- [ ] An incomplete task\n<\/code><\/pre>\n\n
x<\/code> character needs to be either added or removed, depending on whether we're checking or unchecking it.<\/p>\n\n
toggle()<\/code> function within the plugin.<\/p>\n\n
vim.keymap.set(\n \"n\",\n \"<leader>tt\",\n \"require('toggle-checkbox').toggle()\"\n)\n<\/code><\/pre>\n\n
<leader>tt<\/code> to check or uncheck a checkbox. I could use Vim's replace mode to do this, but I really wanted to have one keymap that I could use for both.<\/p>\n\n
toggle-checkbox.lua<\/code> file is currently only 41 lines of code, and whilst there is an existing Vim plugin that I could have used, I was excited to write my own plugin for Neovim, to start contributing to the Neovim ecosystem, and add a Neovim plugin to my portfolio of open-source projects.<\/p>\n\n
\/* Input *\/\n\n.btn {\n @apply inline-block rounded-3xl bg-blue-500 px-8 py-3 text-lg text-white hover:bg-blue-800 focus:bg-blue-800;\n}\n\n\/* Output *\/\n\n.btn {\n --tw-bg-opacity: 1;\n --tw-text-opacity: 1;\n background-color: rgb(59 130 246 \/ var(--tw-bg-opacity));\n border-radius: 1.5rem;\n color: rgb(255 255 255 \/ var(--tw-text-opacity));\n display: inline-block;\n font-size: 1.125rem;\n line-height: 1.75rem;\n padding-bottom: 0.75rem;\n padding-left: 2rem;\n padding-right: 2rem;\n padding-top: 0.75rem;\n}\n\n.btn:hover {\n --tw-bg-opacity: 1;\n background-color: rgb(30 64 175 \/ var(--tw-bg-opacity));\n}\n\n.btn:focus {\n --tw-bg-opacity: 1;\n background-color: rgb(30 64 175 \/ var(--tw-bg-opacity));\n}\n<\/code><\/pre>\n\n
\/* Input *\/\n\n.btn {\n @apply inline-block rounded-3xl bg-blue-500 px-8 py-3 text-lg text-white hover:bg-blue-800 focus:bg-blue-800;\n}\n\n\/* Output *\/\n\n.btn {\n --tw-bg-opacity: 1;\n --tw-text-opacity: 1;\n background-color: rgb(59 130 246 \/ var(--tw-bg-opacity));\n border-radius: 1.5rem;\n color: rgb(255 255 255 \/ var(--tw-text-opacity));\n display: inline-block;\n font-size: 1.125rem;\n line-height: 1.75rem;\n padding-bottom: 0.75rem;\n padding-left: 2rem;\n padding-right: 2rem;\n padding-top: 0.75rem;\n}\n\n.btn:hover {\n --tw-bg-opacity: 1;\n background-color: rgb(30 64 175 \/ var(--tw-bg-opacity));\n}\n\n.btn:focus {\n --tw-bg-opacity: 1;\n background-color: rgb(30 64 175 \/ var(--tw-bg-opacity));\n}\n<\/code><\/pre>\n\n
function createJourney(string $from, string $to, int $duration): void {\n var_dump($from, $to, $duration);\n}\n<\/code><\/pre>\n\n
createJourney('', '', -10);\n<\/code><\/pre>\n\n
string(0) \"\"\nstring(0) \"\"\nint(-10)\n<\/code><\/pre>\n\n
$to<\/code> and
$from<\/code> to be not empty and the duration to be greater than zero.<\/p>\n\n
Here's the thing<\/h2>\n\n
function createJourney(string $from, string $to, int $duration): void {\n Assert::stringNotEmpty($from);\n Assert::stringNotEmpty($to);\n Assert::positiveInteger($duration);\n\n var_dump($from, $to, $duration);\n}\n<\/code><\/pre>\n\n
function createJourney(string $from, string $to, int $duration): void {\n var_dump($from, $to, $duration);\n}\n<\/code><\/pre>\n\n
createJourney('', '', -10);\n<\/code><\/pre>\n\n
string(0) \"\"\nstring(0) \"\"\nint(-10)\n<\/code><\/pre>\n\n
$to<\/code> and
$from<\/code> to be not empty and the duration to be greater than zero.<\/p>\n\n
Here's the thing<\/h2>\n\n
function createJourney(string $from, string $to, int $duration): void {\n Assert::stringNotEmpty($from);\n Assert::stringNotEmpty($to);\n Assert::positiveInteger($duration);\n\n var_dump($from, $to, $duration);\n}\n<\/code><\/pre>\n\n
Another scenario<\/h2>\n\n
Here's the thing<\/h2>\n\n
assert()<\/code> function, as we saw in yesterday's email<\/a>.<\/p>\n\n
Another scenario<\/h2>\n\n
Here's the thing<\/h2>\n\n
assert()<\/code> function, as we saw in yesterday's email<\/a>.<\/p>\n\n
\n
\n
\n
\n
Here's the thing<\/h2>\n\n
Here's the thing<\/h2>\n\n
git rebase<\/code> to clean up your commits.<\/p>\n\n
Merge commit..<\/code> message.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n
git rebase<\/code> to clean up your commits.<\/p>\n\n
Merge commit..<\/code> message.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:12:56+00:00",
+ "guid": null,
+ "hash": "7cd7f74b4b05c11ac108a9166e5bf214",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.06f241bc-f1fc-41a4-a385-506d80f150eb.json b/content/node.06f241bc-f1fc-41a4-a385-506d80f150eb.json
new file mode 100644
index 000000000..f6990657a
--- /dev/null
+++ b/content/node.06f241bc-f1fc-41a4-a385-506d80f150eb.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "06f241bc-f1fc-41a4-a385-506d80f150eb"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:02+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": "Why I prefer types\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-09-20T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:02+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/09\/20\/why-i-prefer-types",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n
add(...numbers) {\n \/\/ ...\n}\n\nsubtract(...numbers) {\n \/\/ ...\n}\n\nadd(...numbers: number[]): number {\n \/\/ ...\n}\n\nsubtract(...numbers: number[]): number {\n \/\/ ...\n}\n<\/code><\/pre>\n\n
numbers<\/code> was an array of strings of numbers - e.g.
['one', 'two', 'three']<\/code> - and what if instead of returning the result, it stored it in state to return from a different method like
equals()<\/code> or
calculate()<\/code>?<\/p>\n\n
add(...numbers) {\n \/\/ ...\n}\n\nsubtract(...numbers) {\n \/\/ ...\n}\n\nadd(...numbers: number[]): number {\n \/\/ ...\n}\n\nsubtract(...numbers: number[]): number {\n \/\/ ...\n}\n<\/code><\/pre>\n\n
numbers<\/code> was an array of strings of numbers - e.g.
['one', 'two', 'three']<\/code> - and what if instead of returning the result, it stored it in state to return from a different method like
equals()<\/code> or
calculate()<\/code>?<\/p>\n\n
The Class to be tested (MyClass.php)<\/h2>\n\n
<?php\n\nclass MyClass {\n\n public function __construct(\n private DependencyInterface $dependency\n ) {\n }\n\n public function doSomething() {\n $result = $this->dependency->performAction();\n\n return \"Result: \" . $result;\n }\n}\n<\/code><\/pre>\n\n
Dependency Interface (DependencyInterface.php)<\/h2>\n\n
<?php\n\ninterface DependencyInterface {\n\n public function performAction();\n\n}\n<\/code><\/pre>\n\n
A test class that ends up testing mocks (MyClassTest.php)<\/h2>\n\n
<?php\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass MyClassTest extends TestCase {\n\n public function testDoSomething() {\n \/\/ Creating a mock of the DependencyInterface.\n $dependencyMock = $this->createMock(DependencyInterface::class);\n\n \/\/ Setting up the mock to return a specific value.\n $dependencyMock->expects($this->once())\n ->method('performAction')\n ->willReturn('Mocked result');\n\n \/\/ Creating an instance of MyClass with the mock.\n $myClass = new MyClass($dependencyMock);\n\n \/\/ Calling the method to be tested.\n $result = $myClass->doSomething();\n\n \/\/ Asserting that the result matches the expected value.\n $this->assertEquals('Result: Mocked result', $result);\n }\n\n}\n<\/code><\/pre>\n\n
Here's the thing<\/h2>\n\n
DependencyInterface<\/code> and sets up an expectation that the performAction method will be called once, returning a specific value.<\/p>\n\n
doSomething<\/code> method on
MyClass<\/code> and asserts that the result is as expected.<\/p>\n\n
MyClass<\/code>.<\/p>\n\n
MyClass<\/code> has a bug, this test won't catch it.<\/p>\n\n ",
+ "format": "full_html",
+ "processed": "\n
The Class to be tested (MyClass.php)<\/h2>\n\n
<?php\n\nclass MyClass {\n\n public function __construct(\n private DependencyInterface $dependency\n ) {\n }\n\n public function doSomething() {\n $result = $this->dependency->performAction();\n\n return \"Result: \" . $result;\n }\n}\n<\/code><\/pre>\n\n
Dependency Interface (DependencyInterface.php)<\/h2>\n\n
<?php\n\ninterface DependencyInterface {\n\n public function performAction();\n\n}\n<\/code><\/pre>\n\n
A test class that ends up testing mocks (MyClassTest.php)<\/h2>\n\n
<?php\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass MyClassTest extends TestCase {\n\n public function testDoSomething() {\n \/\/ Creating a mock of the DependencyInterface.\n $dependencyMock = $this->createMock(DependencyInterface::class);\n\n \/\/ Setting up the mock to return a specific value.\n $dependencyMock->expects($this->once())\n ->method('performAction')\n ->willReturn('Mocked result');\n\n \/\/ Creating an instance of MyClass with the mock.\n $myClass = new MyClass($dependencyMock);\n\n \/\/ Calling the method to be tested.\n $result = $myClass->doSomething();\n\n \/\/ Asserting that the result matches the expected value.\n $this->assertEquals('Result: Mocked result', $result);\n }\n\n}\n<\/code><\/pre>\n\n
Here's the thing<\/h2>\n\n
DependencyInterface<\/code> and sets up an expectation that the performAction method will be called once, returning a specific value.<\/p>\n\n
doSomething<\/code> method on
MyClass<\/code> and asserts that the result is as expected.<\/p>\n\n
MyClass<\/code>.<\/p>\n\n
MyClass<\/code> has a bug, this test won't catch it.<\/p>\n\n ",
+ "summary": null
+ }
+ ],
+ "feeds_item": [
+ {
+ "imported": "2025-04-16T14:13:01+00:00",
+ "guid": null,
+ "hash": "db65f35f5c7767e34b717942905448f4",
+ "target_type": "feeds_feed",
+ "target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/node.079c994b-172d-4f47-9217-5edf00858228.json b/content/node.079c994b-172d-4f47-9217-5edf00858228.json
new file mode 100644
index 000000000..5dcba2bbd
--- /dev/null
+++ b/content/node.079c994b-172d-4f47-9217-5edf00858228.json
@@ -0,0 +1,100 @@
+{
+ "uuid": [
+ {
+ "value": "079c994b-172d-4f47-9217-5edf00858228"
+ }
+ ],
+ "langcode": [
+ {
+ "value": "en"
+ }
+ ],
+ "type": [
+ {
+ "target_id": "daily_email",
+ "target_type": "node_type",
+ "target_uuid": "8bde1f2f-eef9-4f2d-ae9c-96921f8193d7"
+ }
+ ],
+ "revision_timestamp": [
+ {
+ "value": "2025-04-16T14:13:03+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": "Should you deploy on a Friday?\n"
+ }
+ ],
+ "created": [
+ {
+ "value": "2023-06-20T00:00:00+00:00"
+ }
+ ],
+ "changed": [
+ {
+ "value": "2025-04-16T14:13:03+00:00"
+ }
+ ],
+ "promote": [
+ {
+ "value": false
+ }
+ ],
+ "sticky": [
+ {
+ "value": false
+ }
+ ],
+ "default_langcode": [
+ {
+ "value": true
+ }
+ ],
+ "revision_translation_affected": [
+ {
+ "value": true
+ }
+ ],
+ "path": [
+ {
+ "alias": "\/daily\/2023\/06\/20\/should-you-deploy-on-a-friday",
+ "langcode": "en"
+ }
+ ],
+ "body": [
+ {
+ "value": "\n
When did you last deploy?<\/h2>\n\n
Conclusion<\/h2>\n\n
When did you last deploy?<\/h2>\n\n
Conclusion<\/h2>\n\n
Here's the thing<\/h2>\n\n
Here's the thing<\/h2>\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
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
Introducing a value object<\/h2>\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
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
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
return Collection::make($stationNodes)\n ->map(fn (NodeInterface $station): string => $station->get('field_station_code')->getString())\n ->values();\n<\/code><\/pre>\n\n
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
Introducing a value object<\/h2>\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
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
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