tome export

This commit is contained in:
Oliver Davies 2025-04-16 23:34:31 +01:00
parent 60f1805ebf
commit 76a63e781d
1635 changed files with 109466 additions and 0 deletions

2
.gitignore vendored
View file

@ -1,6 +1,8 @@
html/
vendor/
!content/
web/*
!web/assets/
web/assets/*

24
content/.htaccess Normal file
View file

@ -0,0 +1,24 @@
# Deny all requests from Apache 2.4+.
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
# Deny all requests from Apache 2.0-2.2.
<IfModule !mod_authz_core.c>
Deny from all
</IfModule>
# Turn off all options we don't need.
Options -Indexes -ExecCGI -Includes -MultiViews
# Set the catch-all handler to prevent scripts from being executed.
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
<Files *>
# Override the handler again if we're run later in the evaluation list.
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
</Files>
# If we know how to do it safely, disable the PHP engine entirely.
<IfModule mod_php.c>
php_flag engine off
</IfModule>

View file

@ -0,0 +1,73 @@
{
"uuid": [
{
"value": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
],
"type": [
{
"target_id": "daily_emails",
"target_type": "feeds_feed_type",
"target_uuid": "bbef79e9-3f62-4d02-87f7-42aa5dc29f04"
}
],
"title": [
{
"value": "Daily emails"
}
],
"uid": [
{
"target_type": "user",
"target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849"
}
],
"status": [
{
"value": true
}
],
"created": [
{
"value": "2025-04-16T14:12:30+00:00"
}
],
"changed": [
{
"value": "2025-04-16T14:13:07+00:00"
}
],
"imported": [
{
"value": "2025-04-16T14:13:07+00:00"
}
],
"next": [
{
"value": "1969-12-31T23:59:59+00:00"
}
],
"queued": [
{
"value": "1970-01-01T00:00:00+00:00"
}
],
"source": [
{
"value": "public:\/\/feeds\/daily2_1.xml"
}
],
"config": [
{
"fetcher": {
"fid": "1",
"usage_id": "728512ba-6198-4260-9eff-4e30b02d8ec6"
}
}
],
"item_count": [
{
"value": 808
}
]
}

View file

@ -0,0 +1,54 @@
{
"uuid": [
{
"value": "dc87f139-f918-4e29-9991-80f66de686da"
}
],
"langcode": [
{
"value": "en"
}
],
"uid": [
{
"target_type": "user",
"target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849"
}
],
"filename": [
{
"value": "daily2_1.xml"
}
],
"uri": [
{
"value": "public:\/\/feeds\/daily2_1.xml",
"url": "\/sites\/default\/files\/feeds\/daily2_1.xml"
}
],
"filemime": [
{
"value": "application\/xml"
}
],
"filesize": [
{
"value": 1447732
}
],
"status": [
{
"value": true
}
],
"created": [
{
"value": "2025-04-16T14:12:41+00:00"
}
],
"changed": [
{
"value": "2025-04-16T14:12:44+00:00"
}
]
}

View file

@ -0,0 +1,83 @@
{
"uuid": [
{
"value": "0aba67d1-f37a-4b10-80ec-b18f02eafc66"
}
],
"langcode": [
{
"value": "en"
}
],
"bundle": [
{
"value": "menu_link_content"
}
],
"revision_created": [
{
"value": "2025-04-16T18:23:42+00:00"
}
],
"revision_user": [],
"revision_log_message": [],
"enabled": [
{
"value": true
}
],
"title": [
{
"value": "Sponsor me"
}
],
"description": [],
"menu_name": [
{
"value": "main"
}
],
"link": [
{
"uri": "internal:\/sponsor",
"title": "",
"options": []
}
],
"external": [
{
"value": false
}
],
"rediscover": [
{
"value": true
}
],
"weight": [
{
"value": -44
}
],
"expanded": [
{
"value": false
}
],
"parent": [],
"changed": [
{
"value": "2025-04-16T18:24:30+00:00"
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
]
}

View file

@ -0,0 +1,83 @@
{
"uuid": [
{
"value": "5334a2f4-3e6a-4575-b29a-cc2eb9cf3af3"
}
],
"langcode": [
{
"value": "en"
}
],
"bundle": [
{
"value": "menu_link_content"
}
],
"revision_created": [
{
"value": "2025-04-16T18:23:09+00:00"
}
],
"revision_user": [],
"revision_log_message": [],
"enabled": [
{
"value": true
}
],
"title": [
{
"value": "Podcast"
}
],
"description": [],
"menu_name": [
{
"value": "main"
}
],
"link": [
{
"uri": "internal:\/podcast",
"title": "",
"options": []
}
],
"external": [
{
"value": false
}
],
"rediscover": [
{
"value": true
}
],
"weight": [
{
"value": -46
}
],
"expanded": [
{
"value": false
}
],
"parent": [],
"changed": [
{
"value": "2025-04-16T18:24:30+00:00"
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
]
}

View file

@ -0,0 +1,83 @@
{
"uuid": [
{
"value": "6ca73647-c2e0-45c6-95aa-c96ff3b17c44"
}
],
"langcode": [
{
"value": "en"
}
],
"bundle": [
{
"value": "menu_link_content"
}
],
"revision_created": [
{
"value": "2025-04-16T13:51:59+00:00"
}
],
"revision_user": [],
"revision_log_message": [],
"enabled": [
{
"value": true
}
],
"title": [
{
"value": "Sitemap"
}
],
"description": [],
"menu_name": [
{
"value": "footer"
}
],
"link": [
{
"uri": "internal:\/sitemap",
"title": "",
"options": []
}
],
"external": [
{
"value": false
}
],
"rediscover": [
{
"value": true
}
],
"weight": [
{
"value": 0
}
],
"expanded": [
{
"value": false
}
],
"parent": [],
"changed": [
{
"value": "2025-04-16T13:51:59+00:00"
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
]
}

View file

@ -0,0 +1,83 @@
{
"uuid": [
{
"value": "b541a408-2a31-451a-9ef4-88d30a6bc243"
}
],
"langcode": [
{
"value": "en"
}
],
"bundle": [
{
"value": "menu_link_content"
}
],
"revision_created": [
{
"value": "2025-04-16T18:23:19+00:00"
}
],
"revision_user": [],
"revision_log_message": [],
"enabled": [
{
"value": true
}
],
"title": [
{
"value": "Daily list"
}
],
"description": [],
"menu_name": [
{
"value": "main"
}
],
"link": [
{
"uri": "internal:\/daily",
"title": "",
"options": []
}
],
"external": [
{
"value": false
}
],
"rediscover": [
{
"value": true
}
],
"weight": [
{
"value": -45
}
],
"expanded": [
{
"value": false
}
],
"parent": [],
"changed": [
{
"value": "2025-04-16T18:24:30+00:00"
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
]
}

View file

@ -0,0 +1,83 @@
{
"uuid": [
{
"value": "b71a03e9-5723-45e1-81ef-a4b2fbbc2eba"
}
],
"langcode": [
{
"value": "en"
}
],
"bundle": [
{
"value": "menu_link_content"
}
],
"revision_created": [
{
"value": "2025-04-16T18:22:34+00:00"
}
],
"revision_user": [],
"revision_log_message": [],
"enabled": [
{
"value": true
}
],
"title": [
{
"value": "Services"
}
],
"description": [],
"menu_name": [
{
"value": "main"
}
],
"link": [
{
"uri": "internal:\/pricing",
"title": "",
"options": []
}
],
"external": [
{
"value": false
}
],
"rediscover": [
{
"value": true
}
],
"weight": [
{
"value": -48
}
],
"expanded": [
{
"value": false
}
],
"parent": [],
"changed": [
{
"value": "2025-04-16T18:24:30+00:00"
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
]
}

View file

@ -0,0 +1,83 @@
{
"uuid": [
{
"value": "b8a5229a-dc0f-48be-9052-25b7077ecb2b"
}
],
"langcode": [
{
"value": "en"
}
],
"bundle": [
{
"value": "menu_link_content"
}
],
"revision_created": [
{
"value": "2025-04-16T18:22:58+00:00"
}
],
"revision_user": [],
"revision_log_message": [],
"enabled": [
{
"value": true
}
],
"title": [
{
"value": "Presentations"
}
],
"description": [],
"menu_name": [
{
"value": "main"
}
],
"link": [
{
"uri": "internal:\/presentations",
"title": "",
"options": []
}
],
"external": [
{
"value": false
}
],
"rediscover": [
{
"value": true
}
],
"weight": [
{
"value": -47
}
],
"expanded": [
{
"value": false
}
],
"parent": [],
"changed": [
{
"value": "2025-04-16T18:24:30+00:00"
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
]
}

View file

@ -0,0 +1,83 @@
{
"uuid": [
{
"value": "daf937a0-f3cf-485d-aba7-b31832450637"
}
],
"langcode": [
{
"value": "en"
}
],
"bundle": [
{
"value": "menu_link_content"
}
],
"revision_created": [
{
"value": "2025-04-16T18:24:07+00:00"
}
],
"revision_user": [],
"revision_log_message": [],
"enabled": [
{
"value": true
}
],
"title": [
{
"value": "Contact"
}
],
"description": [],
"menu_name": [
{
"value": "main"
}
],
"link": [
{
"uri": "mailto:oliver+website@oliverdavies.uk",
"title": "",
"options": []
}
],
"external": [
{
"value": false
}
],
"rediscover": [
{
"value": false
}
],
"weight": [
{
"value": -43
}
],
"expanded": [
{
"value": false
}
],
"parent": [],
"changed": [
{
"value": "2025-04-16T18:24:30+00:00"
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
]
}

View file

@ -0,0 +1,83 @@
{
"uuid": [
{
"value": "e89b24d4-9d98-4b22-a603-23aaabd19f0b"
}
],
"langcode": [
{
"value": "en"
}
],
"bundle": [
{
"value": "menu_link_content"
}
],
"revision_created": [
{
"value": "2025-04-16T18:22:19+00:00"
}
],
"revision_user": [],
"revision_log_message": [],
"enabled": [
{
"value": true
}
],
"title": [
{
"value": "Press Info"
}
],
"description": [],
"menu_name": [
{
"value": "main"
}
],
"link": [
{
"uri": "internal:\/press",
"title": "",
"options": []
}
],
"external": [
{
"value": false
}
],
"rediscover": [
{
"value": true
}
],
"weight": [
{
"value": -49
}
],
"expanded": [
{
"value": false
}
],
"parent": [],
"changed": [
{
"value": "2025-04-16T18:24:30+00:00"
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
]
}

5682
content/meta/index.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0077d24d-3555-4798-8463-0b47e8aa3a67"
}
],
"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": "Why I write automated tests"
}
],
"created": [
{
"value": "2022-08-14T00: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\/14\/why-i-write-tests",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>In February 2012, I saw a tweet from Tim Millwood asking if anyone wanted to maintain or co-maintain a Drupal module called <a href=\"https:\/\/www.drupal.org\/project\/override_node_options\">Override Node Options<\/a>.<\/p>\n\n<p>It had more than 9,200 active installations at that time, with versions for Drupal 5, 6 and 7.<\/p>\n\n<p>I said yes and became the module\u2019s maintainer.<\/p>\n\n<p>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<p>There have been two main things that come to mind with this module, related to automated testing.<\/p>\n\n<p>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<p>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<p>More recently, a friend and ex-colleague and I decided to refactor some of the module's code.<\/p>\n\n<p>We wanted to split the <code>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<p>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<p>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<p>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<p>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<p>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<p>I've talked about this, and how to get started with automated testing in Drupal, in a presentation called <a href=\"http:\/\/localhost:8000\/presentations\/tdd-test-driven-drupal\">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 <p>In February 2012, I saw a tweet from Tim Millwood asking if anyone wanted to maintain or co-maintain a Drupal module called <a href=\"https:\/\/www.drupal.org\/project\/override_node_options\">Override Node Options<\/a>.<\/p>\n\n<p>It had more than 9,200 active installations at that time, with versions for Drupal 5, 6 and 7.<\/p>\n\n<p>I said yes and became the module\u2019s maintainer.<\/p>\n\n<p>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<p>There have been two main things that come to mind with this module, related to automated testing.<\/p>\n\n<p>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<p>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<p>More recently, a friend and ex-colleague and I decided to refactor some of the module's code.<\/p>\n\n<p>We wanted to split the <code>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<p>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<p>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<p>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<p>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<p>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<p>I've talked about this, and how to get started with automated testing in Drupal, in a presentation called <a href=\"http:\/\/localhost:8000\/presentations\/tdd-test-driven-drupal\">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"
}
]
}

View file

@ -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 <p>One of the main uses for <a href=\"http:\/\/localhost:8000\/daily\/2024\/07\/02\/ci-not-ci-pipeline\">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<p>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<p>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<p>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<p>Which do you prefer?<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>One of the main uses for <a href=\"http:\/\/localhost:8000\/daily\/2024\/07\/02\/ci-not-ci-pipeline\">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<p>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<p>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<p>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<p>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"
}
]
}

View file

@ -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 <p>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<p>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<p>Just over a year ago, <a href=\"\/blog\/going-full-vim\">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<p>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<p>It's called <a href=\"https:\/\/github.com\/opdavies\/toggle-checkbox.nvim\">toggle-checkbox.nvim<\/a> and is used toggle checkboxes in Markdown files - something that I use frequently for to-do lists.<\/p>\n\n<p>For example, this a simple list containing both checked and unchecked checkboxes:<\/p>\n\n<pre><code class=\"markdown\">- [x] A completed task\n- [ ] An incomplete task\n<\/code><\/pre>\n\n<p>To toggle a checkbox, the <code>x<\/code> character needs to be either added or removed, depending on whether we're checking or unchecking it.<\/p>\n\n<p>This is done by calling the <code>toggle()<\/code> function within the plugin.<\/p>\n\n<p>In my Neovim configuration, I've added a keymap to do this:<\/p>\n\n<pre><code class=\"lua\">vim.keymap.set(\n \"n\",\n \"&lt;leader&gt;tt\",\n \"require('toggle-checkbox').toggle()\"\n)\n<\/code><\/pre>\n\n<p>This means that I can use the same keymap by running <code>&lt;leader&gt;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<p>As it's my first Neovim plugin, I decided to keep it simple.<\/p>\n\n<p>The main <code>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<p>You can view the plugin at <a href=\"https:\/\/github.com\/opdavies\/toggle-checkbox.nvim\">https:\/\/github.com\/opdavies\/toggle-checkbox.nvim<\/a>, as well as my Neovim configuration (which is also written in Lua) as part of <a href=\"https:\/\/github.com\/opdavies\/dotfiles\/tree\/main\/roles\/neovim\/files\">my Dotfiles repository<\/a>.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>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<p>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<p>Just over a year ago, <a href=\"\/blog\/going-full-vim\">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<p>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<p>It's called <a href=\"https:\/\/github.com\/opdavies\/toggle-checkbox.nvim\">toggle-checkbox.nvim<\/a> and is used toggle checkboxes in Markdown files - something that I use frequently for to-do lists.<\/p>\n\n<p>For example, this a simple list containing both checked and unchecked checkboxes:<\/p>\n\n<pre><code class=\"markdown\">- [x] A completed task\n- [ ] An incomplete task\n<\/code><\/pre>\n\n<p>To toggle a checkbox, the <code>x<\/code> character needs to be either added or removed, depending on whether we're checking or unchecking it.<\/p>\n\n<p>This is done by calling the <code>toggle()<\/code> function within the plugin.<\/p>\n\n<p>In my Neovim configuration, I've added a keymap to do this:<\/p>\n\n<pre><code class=\"lua\">vim.keymap.set(\n \"n\",\n \"&lt;leader&gt;tt\",\n \"require('toggle-checkbox').toggle()\"\n)\n<\/code><\/pre>\n\n<p>This means that I can use the same keymap by running <code>&lt;leader&gt;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<p>As it's my first Neovim plugin, I decided to keep it simple.<\/p>\n\n<p>The main <code>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<p>You can view the plugin at <a href=\"https:\/\/github.com\/opdavies\/toggle-checkbox.nvim\">https:\/\/github.com\/opdavies\/toggle-checkbox.nvim<\/a>, as well as my Neovim configuration (which is also written in Lua) as part of <a href=\"https:\/\/github.com\/opdavies\/dotfiles\/tree\/main\/roles\/neovim\/files\">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"
}
]
}

View file

@ -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 <p>\"Deployments\" and \"releases\" are often used interchangeably but mean different things.<\/p>\n\n<p>A deployment moves a change from one place to another, such as some updated code from a staging environment to production.<\/p>\n\n<p>A release is when the change made is available to users.<\/p>\n\n<p>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 <p>\"Deployments\" and \"releases\" are often used interchangeably but mean different things.<\/p>\n\n<p>A deployment moves a change from one place to another, such as some updated code from a staging environment to production.<\/p>\n\n<p>A release is when the change made is available to users.<\/p>\n\n<p>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"
}
]
}

View file

@ -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 <p>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<p>For example:<\/p>\n\n<pre><code class=\"css\">\/* 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<p>Whilst this works and reduced duplication, I prefer to handle this within my HTML instead.<\/p>\n\n<p>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<p>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<p>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 <p>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<p>For example:<\/p>\n\n<pre><code class=\"css\">\/* 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<p>Whilst this works and reduced duplication, I prefer to handle this within my HTML instead.<\/p>\n\n<p>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<p>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<p>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"
}
]
}

View file

@ -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 <p>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<p>For example:<\/p>\n\n<pre><code class=\"language-php\">function createJourney(string $from, string $to, int $duration): void {\n var_dump($from, $to, $duration);\n}\n<\/code><\/pre>\n\n<p>In this code, each parameter has a type, but there's no validation on the values.<\/p>\n\n<p>If I run this:<\/p>\n\n<pre><code class=\"language-plain\">createJourney('', '', -10);\n<\/code><\/pre>\n\n<p>I would get this output:<\/p>\n\n<pre><code class=\"language-plain\">string(0) \"\"\nstring(0) \"\"\nint(-10)\n<\/code><\/pre>\n\n<p>This is probably not what you want.<\/p>\n\n<p>I expect <code>$to<\/code> and <code>$from<\/code> to be not empty and the duration to be greater than zero.<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>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<p>For example:<\/p>\n\n<pre><code class=\"language-php\">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<p>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 <p>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<p>For example:<\/p>\n\n<pre><code class=\"language-php\">function createJourney(string $from, string $to, int $duration): void {\n var_dump($from, $to, $duration);\n}\n<\/code><\/pre>\n\n<p>In this code, each parameter has a type, but there's no validation on the values.<\/p>\n\n<p>If I run this:<\/p>\n\n<pre><code class=\"language-plain\">createJourney('', '', -10);\n<\/code><\/pre>\n\n<p>I would get this output:<\/p>\n\n<pre><code class=\"language-plain\">string(0) \"\"\nstring(0) \"\"\nint(-10)\n<\/code><\/pre>\n\n<p>This is probably not what you want.<\/p>\n\n<p>I expect <code>$to<\/code> and <code>$from<\/code> to be not empty and the duration to be greater than zero.<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>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<p>For example:<\/p>\n\n<pre><code class=\"language-php\">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<p>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"
}
]
}

View file

@ -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 <p>Instead of using tools like Docker or nvm to manage dependencies for your projects, you can use Nix instead.<\/p>\n\n<p>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, <a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/12\/why-consistency-and-reproducibility-are-important\">making environments reproducible<\/a>.<\/p>\n\n<p>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<p>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<p>For me, Nix and devenv have <a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/11\/could-nix-and-devenv-replace-docker-compose\">replaced Docker and Docker Compose<\/a> on my development projects.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Instead of using tools like Docker or nvm to manage dependencies for your projects, you can use Nix instead.<\/p>\n\n<p>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, <a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/12\/why-consistency-and-reproducibility-are-important\">making environments reproducible<\/a>.<\/p>\n\n<p>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<p>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<p>For me, Nix and devenv have <a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/11\/could-nix-and-devenv-replace-docker-compose\">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"
}
]
}

View file

@ -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 <p>When starting a new task, find the simplest way to prove the concept.<\/p>\n\n<p>Investigate upfront and evaluate potential approaches.<\/p>\n\n<p>What's the smallest or quickest thing you could do to validate an idea?<\/p>\n\n<p>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<p>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 <p>When starting a new task, find the simplest way to prove the concept.<\/p>\n\n<p>Investigate upfront and evaluate potential approaches.<\/p>\n\n<p>What's the smallest or quickest thing you could do to validate an idea?<\/p>\n\n<p>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<p>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"
}
]
}

View file

@ -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 <p>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<p>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<p>Each class should have a name and start and end times and be on a specified day.<\/p>\n\n<p>But what if the client didn't include a name or start or end time?<\/p>\n\n<p>What if they put a number or boolean instead of a string?<\/p>\n\n<p>What if there are no classes on a particular day?<\/p>\n\n<p>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<p>We also can't assume the \"happy path\" will always be correct.<\/p>\n\n<p>What if there aren't any classes? Do we put an empty message on the timetable or not show that day?<\/p>\n\n<h2 id=\"another-scenario\">Another scenario<\/h2>\n\n<p>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<p>If you get the data, do you check if it's in the correct format and something you can use?<\/p>\n\n<p>If not, do you verify other actions in your application haven't run and it's not in an invalid state?<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>In PHP, a common approach is to use the <code>assert()<\/code> function, as we saw in <a href=\"http:\/\/localhost:8000\/daily\/2024\/02\/20\/which-level-is-right-for-you\">yesterday's email<\/a>.<\/p>\n\n<p>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 <p>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<p>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<p>Each class should have a name and start and end times and be on a specified day.<\/p>\n\n<p>But what if the client didn't include a name or start or end time?<\/p>\n\n<p>What if they put a number or boolean instead of a string?<\/p>\n\n<p>What if there are no classes on a particular day?<\/p>\n\n<p>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<p>We also can't assume the \"happy path\" will always be correct.<\/p>\n\n<p>What if there aren't any classes? Do we put an empty message on the timetable or not show that day?<\/p>\n\n<h2 id=\"another-scenario\">Another scenario<\/h2>\n\n<p>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<p>If you get the data, do you check if it's in the correct format and something you can use?<\/p>\n\n<p>If not, do you verify other actions in your application haven't run and it's not in an invalid state?<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>In PHP, a common approach is to use the <code>assert()<\/code> function, as we saw in <a href=\"http:\/\/localhost:8000\/daily\/2024\/02\/20\/which-level-is-right-for-you\">yesterday's email<\/a>.<\/p>\n\n<p>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"
}
]
}

View file

@ -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 <p>I heard this on a non-tech podcast but it applies to tech too:<\/p>\n\n<blockquote>\n <p>It's only a bad situation if you fail to learn from it<\/p>\n<\/blockquote>\n\n<p>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 <p>I heard this on a non-tech podcast but it applies to tech too:<\/p>\n\n<blockquote>\n <p>It's only a bad situation if you fail to learn from it<\/p>\n<\/blockquote>\n\n<p>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"
}
]
}

View file

@ -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 <p><a href=\"http:\/\/localhost:8000\/daily\/2024\/06\/23\/drupal-10-3-released\">With Drupal 10.3 released<\/a>, Drupal 11 will be the next major version.<\/p>\n\n<p>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<p>According to <a href=\"https:\/\/www.drupal.org\/about\/core\/policies\/core-release-cycles\/schedule#current\">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<p>Drupal 7 will be unsupported as of January 2025.<\/p>\n\n<p>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<p>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<p>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 <p><a href=\"http:\/\/localhost:8000\/daily\/2024\/06\/23\/drupal-10-3-released\">With Drupal 10.3 released<\/a>, Drupal 11 will be the next major version.<\/p>\n\n<p>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<p>According to <a href=\"https:\/\/www.drupal.org\/about\/core\/policies\/core-release-cycles\/schedule#current\">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<p>Drupal 7 will be unsupported as of January 2025.<\/p>\n\n<p>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<p>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<p>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"
}
]
}

View file

@ -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 <p>The more code you have, the more potential problems you have.<\/p>\n\n<p>More code means more opportunities for bugs in your software.<\/p>\n\n<p>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<p>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 <p>The more code you have, the more potential problems you have.<\/p>\n\n<p>More code means more opportunities for bugs in your software.<\/p>\n\n<p>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<p>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"
}
]
}

View file

@ -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 <p><a href=\"http:\/\/localhost:8000\/daily\/2024\/10\/01\/not-all-legacy-code-is-technical-debt\">Technical debt is usually referred to negatively<\/a>, but technical debt isn't always bad.<\/p>\n\n<p>The key thing is to know when and why you're taking on technical debt and when it will be addressed.<\/p>\n\n<p>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<p>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<p>For the initial version, that approach was good enough and meant we could move forward.<\/p>\n\n<p>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<p>This was a good situation, not a bad one.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p><a href=\"http:\/\/localhost:8000\/daily\/2024\/10\/01\/not-all-legacy-code-is-technical-debt\">Technical debt is usually referred to negatively<\/a>, but technical debt isn't always bad.<\/p>\n\n<p>The key thing is to know when and why you're taking on technical debt and when it will be addressed.<\/p>\n\n<p>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<p>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<p>For the initial version, that approach was good enough and meant we could move forward.<\/p>\n\n<p>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<p>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"
}
]
}

View file

@ -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 <p>Today, I saw a post that asked the question:<\/p>\n\n<blockquote>\n <p>Have you ever spent three days on an issue that should have taken 2 hours?<\/p>\n<\/blockquote>\n\n<p>My thought was, \"Who said it should have taken two hours?\".<\/p>\n\n<p>In my experience, tasks take as long as they take.<\/p>\n\n<p>Even something that seems simple can end up being something complex.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Today, I saw a post that asked the question:<\/p>\n\n<blockquote>\n <p>Have you ever spent three days on an issue that should have taken 2 hours?<\/p>\n<\/blockquote>\n\n<p>My thought was, \"Who said it should have taken two hours?\".<\/p>\n\n<p>In my experience, tasks take as long as they take.<\/p>\n\n<p>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"
}
]
}

View file

@ -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 <p>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<p>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<p>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<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>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<p>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 <p>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<p>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<p>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<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>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<p>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"
}
]
}

View file

@ -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 <p>This week, I spoke with Eirik Morland again on the <a href=\"http:\/\/localhost:8000\/podcast\">Beyond Blocks podcast<\/a> about recent improvements to violinist.io, such as team\/multi-user subscriptions.<\/p>\n\n<p><a href=\"http:\/\/localhost:8000\/podcast\/21-eirik-morland-violinist-2\">Listen to the episode now<\/a>.<\/p>\n\n<p>I was great to speak to Eirik again and for him to be the first returning guest on the podcast.<\/p>\n\n<p><a href=\"http:\/\/localhost:8000\/podcast\/8-eirik-morland-violinist\">Listen to the first episode with Eirik<\/a>.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>This week, I spoke with Eirik Morland again on the <a href=\"http:\/\/localhost:8000\/podcast\">Beyond Blocks podcast<\/a> about recent improvements to violinist.io, such as team\/multi-user subscriptions.<\/p>\n\n<p><a href=\"http:\/\/localhost:8000\/podcast\/21-eirik-morland-violinist-2\">Listen to the episode now<\/a>.<\/p>\n\n<p>I was great to speak to Eirik again and for him to be the first returning guest on the podcast.<\/p>\n\n<p><a href=\"http:\/\/localhost:8000\/podcast\/8-eirik-morland-violinist\">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"
}
]
}

View file

@ -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 <p>Yesterday, I wrote about <a href=\"http:\/\/localhost:8000\/daily\/2024\/06\/13\/vetting-third-party-open-source-software\">some things I look for when evaluating open-source projects<\/a>.<\/p>\n\n<p>One thing I said was \"When was the most recent commit and release?\".<\/p>\n\n<p>If a project hasn't had many recent commits, it could be outdated or no longer supported.<\/p>\n\n<p>Alternatively, it could be considered feature complete and not getting new features, and only getting bug fixes and maintenance updates.<\/p>\n\n<p>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<p>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<p>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<p>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<p>Something that could help is if maintainers are explicit about what state their project is in.<\/p>\n\n<p>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<p>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 <p>Yesterday, I wrote about <a href=\"http:\/\/localhost:8000\/daily\/2024\/06\/13\/vetting-third-party-open-source-software\">some things I look for when evaluating open-source projects<\/a>.<\/p>\n\n<p>One thing I said was \"When was the most recent commit and release?\".<\/p>\n\n<p>If a project hasn't had many recent commits, it could be outdated or no longer supported.<\/p>\n\n<p>Alternatively, it could be considered feature complete and not getting new features, and only getting bug fixes and maintenance updates.<\/p>\n\n<p>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<p>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<p>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<p>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<p>Something that could help is if maintainers are explicit about what state their project is in.<\/p>\n\n<p>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<p>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"
}
]
}

View file

@ -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 <p>I think that test-driven development (TDD) makes you productive.<\/p>\n\n<p>Firstly, you save time by not needing to switch from your code to a terminal or browser to test it.<\/p>\n\n<p>But, just as importantly, TDD reduces procrastination. It's much clearer to see what the next steps are.<\/p>\n\n<p>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<p>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<p>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 <p>I think that test-driven development (TDD) makes you productive.<\/p>\n\n<p>Firstly, you save time by not needing to switch from your code to a terminal or browser to test it.<\/p>\n\n<p>But, just as importantly, TDD reduces procrastination. It's much clearer to see what the next steps are.<\/p>\n\n<p>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<p>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<p>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"
}
]
}

View file

@ -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 <p>As I said <a href=\"http:\/\/localhost:8000\/daily\/2025\/02\/10\/refactoring\">in yesterday's email<\/a>, sometimes you change your mind whilst working on something.<\/p>\n\n<p>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<p>If you're pushing your changes to a branch for review, I suggest using <code>git rebase<\/code> to clean up your commits.<\/p>\n\n<p>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<p>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<p>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 <code>Merge commit..<\/code> message.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>As I said <a href=\"http:\/\/localhost:8000\/daily\/2025\/02\/10\/refactoring\">in yesterday's email<\/a>, sometimes you change your mind whilst working on something.<\/p>\n\n<p>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<p>If you're pushing your changes to a branch for review, I suggest using <code>git rebase<\/code> to clean up your commits.<\/p>\n\n<p>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<p>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<p>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 <code>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"
}
]
}

View file

@ -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 <p>Whether it's PHP or JavaScript\/TypeScript, I prefer type declarations in my code.<\/p>\n\n<p>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<p>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<p>Here's the code from my previous email on types from a few days ago, with and without the types declared:<\/p>\n\n<pre><code class=\"js\">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<p>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<p>What if <code>numbers<\/code> was an array of strings of numbers - e.g. <code>['one', 'two', 'three']<\/code> - and what if instead of returning the result, it stored it in state to return from a different method like <code>equals()<\/code> or <code>calculate()<\/code>?<\/p>\n\n<p>With the type declarations included, I don't need to presume, infer or make best guesses.<\/p>\n\n<p>It's clear from just reading the code.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Whether it's PHP or JavaScript\/TypeScript, I prefer type declarations in my code.<\/p>\n\n<p>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<p>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<p>Here's the code from my previous email on types from a few days ago, with and without the types declared:<\/p>\n\n<pre><code class=\"js\">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<p>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<p>What if <code>numbers<\/code> was an array of strings of numbers - e.g. <code>['one', 'two', 'three']<\/code> - and what if instead of returning the result, it stored it in state to return from a different method like <code>equals()<\/code> or <code>calculate()<\/code>?<\/p>\n\n<p>With the type declarations included, I don't need to presume, infer or make best guesses.<\/p>\n\n<p>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"
}
]
}

View file

@ -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 <p>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<p>Here's an example (thanks, ChatGPT, for the code).<\/p>\n\n<h2 id=\"the-class-to-be-tested-myclass.php\">The Class to be tested (MyClass.php)<\/h2>\n\n<pre><code class=\"language-php\">&lt;?php\n\nclass MyClass {\n\n public function __construct(\n private DependencyInterface $dependency\n ) {\n }\n\n public function doSomething() {\n $result = $this-&gt;dependency-&gt;performAction();\n\n return \"Result: \" . $result;\n }\n}\n<\/code><\/pre>\n\n<h2 id=\"dependency-interface-dependencyinterface.php\">Dependency Interface (DependencyInterface.php)<\/h2>\n\n<pre><code class=\"language-php\">&lt;?php\n\ninterface DependencyInterface {\n\n public function performAction();\n\n}\n<\/code><\/pre>\n\n<h2 id=\"a-test-class-that-ends-up-testing-mocks-myclasstest.php\">A test class that ends up testing mocks (MyClassTest.php)<\/h2>\n\n<pre><code class=\"language-php\">&lt;?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-&gt;createMock(DependencyInterface::class);\n\n \/\/ Setting up the mock to return a specific value.\n $dependencyMock-&gt;expects($this-&gt;once())\n -&gt;method('performAction')\n -&gt;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-&gt;doSomething();\n\n \/\/ Asserting that the result matches the expected value.\n $this-&gt;assertEquals('Result: Mocked result', $result);\n }\n\n}\n<\/code><\/pre>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>In this example, the test creates a mock for the <code>DependencyInterface<\/code> and sets up an expectation that the performAction method will be called once, returning a specific value.<\/p>\n\n<p>The test then calls the <code>doSomething<\/code> method on <code>MyClass<\/code> and asserts that the result is as expected.<\/p>\n\n<p>The issue with this test is that it's not testing the actual behaviour of <code>MyClass<\/code>.<\/p>\n\n<p>It's only testing that the mock is configured correctly.<\/p>\n\n<p>If the real implementation of <code>MyClass<\/code> has a bug, this test won't catch it.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>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<p>Here's an example (thanks, ChatGPT, for the code).<\/p>\n\n<h2 id=\"the-class-to-be-tested-myclass.php\">The Class to be tested (MyClass.php)<\/h2>\n\n<pre><code class=\"language-php\">&lt;?php\n\nclass MyClass {\n\n public function __construct(\n private DependencyInterface $dependency\n ) {\n }\n\n public function doSomething() {\n $result = $this-&gt;dependency-&gt;performAction();\n\n return \"Result: \" . $result;\n }\n}\n<\/code><\/pre>\n\n<h2 id=\"dependency-interface-dependencyinterface.php\">Dependency Interface (DependencyInterface.php)<\/h2>\n\n<pre><code class=\"language-php\">&lt;?php\n\ninterface DependencyInterface {\n\n public function performAction();\n\n}\n<\/code><\/pre>\n\n<h2 id=\"a-test-class-that-ends-up-testing-mocks-myclasstest.php\">A test class that ends up testing mocks (MyClassTest.php)<\/h2>\n\n<pre><code class=\"language-php\">&lt;?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-&gt;createMock(DependencyInterface::class);\n\n \/\/ Setting up the mock to return a specific value.\n $dependencyMock-&gt;expects($this-&gt;once())\n -&gt;method('performAction')\n -&gt;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-&gt;doSomething();\n\n \/\/ Asserting that the result matches the expected value.\n $this-&gt;assertEquals('Result: Mocked result', $result);\n }\n\n}\n<\/code><\/pre>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>In this example, the test creates a mock for the <code>DependencyInterface<\/code> and sets up an expectation that the performAction method will be called once, returning a specific value.<\/p>\n\n<p>The test then calls the <code>doSomething<\/code> method on <code>MyClass<\/code> and asserts that the result is as expected.<\/p>\n\n<p>The issue with this test is that it's not testing the actual behaviour of <code>MyClass<\/code>.<\/p>\n\n<p>It's only testing that the mock is configured correctly.<\/p>\n\n<p>If the real implementation of <code>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"
}
]
}

View file

@ -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 <p>There's a common saying about not deploying changes on a Friday to prevent outages or issues before the weekend.<\/p>\n\n<p>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<h2 id=\"when-did-you-last-deploy%3F\">When did you last deploy?<\/h2>\n\n<p>The longer it's been since the last deployment, the risker each deployment is.<\/p>\n\n<p>If there are weeks or months of changes, it will be risky regardless of which day it is.<\/p>\n\n<p>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<h2 id=\"conclusion\">Conclusion<\/h2>\n\n<p>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<p>The issue isn't when you're deploying. You likely need to do so more often.<\/p>\n\n<p>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 <p>There's a common saying about not deploying changes on a Friday to prevent outages or issues before the weekend.<\/p>\n\n<p>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<h2 id=\"when-did-you-last-deploy%3F\">When did you last deploy?<\/h2>\n\n<p>The longer it's been since the last deployment, the risker each deployment is.<\/p>\n\n<p>If there are weeks or months of changes, it will be risky regardless of which day it is.<\/p>\n\n<p>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<h2 id=\"conclusion\">Conclusion<\/h2>\n\n<p>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<p>The issue isn't when you're deploying. You likely need to do so more often.<\/p>\n\n<p>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"
}
]
}

View file

@ -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 <p>As well as using <a href=\"http:\/\/localhost:8000\/daily\/2024\/12\/07\/open-source\">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<p>I also use open source software to do this.<\/p>\n\n<p>I use <a href=\"http:\/\/localhost:8000\/daily\/2024\/09\/08\/my-laptop-died\">NixOS as the operating system on my laptop<\/a>.<\/p>\n\n<p>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<p>I write my code in Neovim and use various additional plugins.<\/p>\n\n<p>I use Git for source control and tools like PHPStan, PHPUnit and Pest to run checks on my code.<\/p>\n\n<p><a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/30\/using-nix-for-local-application-development\">I use Nix and devenv<\/a> to run the applications locally and host them on Linux servers running Nginx.<\/p>\n\n<p>I also use OBS, Kdenlive and GIMP to record and edit my content, which are all also open source projects.<\/p>\n\n<p>I run an open source business, using open source tools and projects and contribute to and <a href=\"http:\/\/localhost:8000\/daily\/2024\/03\/09\/override-node-options-40624-drupal-websites\">maintain my own open source software projects<\/a>.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>As well as using <a href=\"http:\/\/localhost:8000\/daily\/2024\/12\/07\/open-source\">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<p>I also use open source software to do this.<\/p>\n\n<p>I use <a href=\"http:\/\/localhost:8000\/daily\/2024\/09\/08\/my-laptop-died\">NixOS as the operating system on my laptop<\/a>.<\/p>\n\n<p>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<p>I write my code in Neovim and use various additional plugins.<\/p>\n\n<p>I use Git for source control and tools like PHPStan, PHPUnit and Pest to run checks on my code.<\/p>\n\n<p><a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/30\/using-nix-for-local-application-development\">I use Nix and devenv<\/a> to run the applications locally and host them on Linux servers running Nginx.<\/p>\n\n<p>I also use OBS, Kdenlive and GIMP to record and edit my content, which are all also open source projects.<\/p>\n\n<p>I run an open source business, using open source tools and projects and contribute to and <a href=\"http:\/\/localhost:8000\/daily\/2024\/03\/09\/override-node-options-40624-drupal-websites\">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"
}
]
}

View file

@ -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 <p>I've recently considered moving my infrastructure automation code from Pulumi to Terraform.<\/p>\n\n<p>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<p>As a Developer, this seems appealing, but it poses an important question - which programming language should you use?<\/p>\n\n<p>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<p>If one of these were my primary language, it would be a no-brainer.<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>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<p>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<p>It's like taking my children to a restaurant.<\/p>\n\n<p>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 <p>I've recently considered moving my infrastructure automation code from Pulumi to Terraform.<\/p>\n\n<p>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<p>As a Developer, this seems appealing, but it poses an important question - which programming language should you use?<\/p>\n\n<p>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<p>If one of these were my primary language, it would be a no-brainer.<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>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<p>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<p>It's like taking my children to a restaurant.<\/p>\n\n<p>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"
}
]
}

View file

@ -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 <p>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<pre><code class=\"language-php\">return Collection::make($stationNodes)\n -&gt;map(fn (NodeInterface $station): string =&gt; $station-&gt;get('field_station_code')-&gt;getString())\n -&gt;values();\n<\/code><\/pre>\n\n<p>There are two issues with this code.<\/p>\n\n<p>First, whilst I'm implicitly saying that it accepts a certain type of node, because of the <code>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<p>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<h2 id=\"introducing-a-value-object\">Introducing a value object<\/h2>\n\n<p>This is the value object that I created.<\/p>\n\n<p>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<p>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<pre><code class=\"language-php\">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-&gt;bundle() != 'station') {\n throw new \\InvalidArgumentException();\n }\n\n $this-&gt;node = $node;\n }\n\n public function getStationCode(): string {\n return $this-&gt;node-&gt;get('field_station_code')-&gt;getString();\n }\n\n public static function fromNode(NodeInterface $node): self {\n return new self($node);\n }\n\n}\n<\/code><\/pre>\n\n<h2 id=\"refactoring-to-use-the-value-object\">Refactoring to use the value object<\/h2>\n\n<p>This is what my code now looks like:<\/p>\n\n<pre><code class=\"language-php\">return Collection::make($stationNodes)\n -&gt;map(fn (NodeInterface $node): StationInterface =&gt; Station::fromNode($node))\n -&gt;map(fn (StationInterface $station): string =&gt; $station-&gt;getStationCode())\n -&gt;values();\n<\/code><\/pre>\n\n<h1>&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD:website\/source\/_daily_emails\/2022-10-03.md<\/h1>\n\n<blockquote>\n <blockquote>\n <blockquote>\n <blockquote>\n <blockquote>\n <blockquote>\n <blockquote>\n <p>b9cea6d (chore: replace Sculpin with Astro):website\/src\/pages\/daily-emails\/2022-10-03.md\n I've added an additional <code>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<p>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 <p>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<pre><code class=\"language-php\">return Collection::make($stationNodes)\n -&gt;map(fn (NodeInterface $station): string =&gt; $station-&gt;get('field_station_code')-&gt;getString())\n -&gt;values();\n<\/code><\/pre>\n\n<p>There are two issues with this code.<\/p>\n\n<p>First, whilst I'm implicitly saying that it accepts a certain type of node, because of the <code>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<p>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<h2 id=\"introducing-a-value-object\">Introducing a value object<\/h2>\n\n<p>This is the value object that I created.<\/p>\n\n<p>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<p>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<pre><code class=\"language-php\">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-&gt;bundle() != 'station') {\n throw new \\InvalidArgumentException();\n }\n\n $this-&gt;node = $node;\n }\n\n public function getStationCode(): string {\n return $this-&gt;node-&gt;get('field_station_code')-&gt;getString();\n }\n\n public static function fromNode(NodeInterface $node): self {\n return new self($node);\n }\n\n}\n<\/code><\/pre>\n\n<h2 id=\"refactoring-to-use-the-value-object\">Refactoring to use the value object<\/h2>\n\n<p>This is what my code now looks like:<\/p>\n\n<pre><code class=\"language-php\">return Collection::make($stationNodes)\n -&gt;map(fn (NodeInterface $node): StationInterface =&gt; Station::fromNode($node))\n -&gt;map(fn (StationInterface $station): string =&gt; $station-&gt;getStationCode())\n -&gt;values();\n<\/code><\/pre>\n\n<h1>&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD:website\/source\/_daily_emails\/2022-10-03.md<\/h1>\n\n<blockquote>\n <blockquote>\n <blockquote>\n <blockquote>\n <blockquote>\n <blockquote>\n <blockquote>\n <p>b9cea6d (chore: replace Sculpin with Astro):website\/src\/pages\/daily-emails\/2022-10-03.md\n I've added an additional <code>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<p>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"
}
]
}

View file

@ -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 <p>As well as building applications with PHP and Drupal, I spend a lot of time helping others and \"paying it forward\".<\/p>\n\n<p>I write and contribute to open-source software and offer free online pair programming sessions to work on open-source projects.<\/p>\n\n<p>I speak at conferences and meetups.<\/p>\n\n<p>I create content, including videos and coding live streams.<\/p>\n\n<p>I mentor at in-person events, such as DrupalCon, and for bootcamps, such as School of Code.<\/p>\n\n<p>I've organised events, such as PHP South Wales and DrupalCamp Bristol, and reviewed session submissions for DrupalCon.<\/p>\n\n<p>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<p>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<p>Someone replied and recommended using Drupal, which is how I learned about the project.<\/p>\n\n<p>If they hadn't done that, I may not be in the position I am now.<\/p>\n\n<p>I might not have found Drupal, contributed to it, worked for the Drupal Association, or spoken at DrupalCon.<\/p>\n\n<p>Now, it's my turn to pay it forward.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>As well as building applications with PHP and Drupal, I spend a lot of time helping others and \"paying it forward\".<\/p>\n\n<p>I write and contribute to open-source software and offer free online pair programming sessions to work on open-source projects.<\/p>\n\n<p>I speak at conferences and meetups.<\/p>\n\n<p>I create content, including videos and coding live streams.<\/p>\n\n<p>I mentor at in-person events, such as DrupalCon, and for bootcamps, such as School of Code.<\/p>\n\n<p>I've organised events, such as PHP South Wales and DrupalCamp Bristol, and reviewed session submissions for DrupalCon.<\/p>\n\n<p>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<p>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<p>Someone replied and recommended using Drupal, which is how I learned about the project.<\/p>\n\n<p>If they hadn't done that, I may not be in the position I am now.<\/p>\n\n<p>I might not have found Drupal, contributed to it, worked for the Drupal Association, or spoken at DrupalCon.<\/p>\n\n<p>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"
}
]
}

View file

@ -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 <p>A few days ago, I said that <a href=\"http:\/\/localhost:8000\/daily\/2025\/02\/02\/simple\">I still like to use RSS<\/a> to consume a lot of online content.<\/p>\n\n<p>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<p>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<p>Drupal is one of the projects <a href=\"https:\/\/www.drupal.org\/association\/open-web-manifesto\">that make an open web possible<\/a>.<\/p>\n\n<p>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<p>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<p>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<p>Long live the open web!<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>A few days ago, I said that <a href=\"http:\/\/localhost:8000\/daily\/2025\/02\/02\/simple\">I still like to use RSS<\/a> to consume a lot of online content.<\/p>\n\n<p>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<p>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<p>Drupal is one of the projects <a href=\"https:\/\/www.drupal.org\/association\/open-web-manifesto\">that make an open web possible<\/a>.<\/p>\n\n<p>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<p>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<p>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<p>Long live the open web!<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:56+00:00",
"guid": null,
"hash": "96e23c320d92785e0b18c25bec3e0d3c",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "09c1755f-a3b9-44a5-a17f-5daacfe71967"
}
],
"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": "What are Git hooks and why are they useful?"
}
],
"created": [
{
"value": "2022-08-16T00: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\/16\/what-are-git-hooks-why-are-they-useful",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>In yesterday's email, I mentioned Git hooks but didn't go into any detail. So, what are they?<\/p>\n\n<p>Git hooks are Bash scripts that you add to your repository that are executed when certain events happen, such as before a commit is made or before a push to a remote.<\/p>\n\n<p>By default, the script files need to be within the <code>.git\/hooks<\/code> directory, have executable permissions, and be named to exactly match the name of the hook - e.g. <code>pre-push<\/code> - with no file extension.<\/p>\n\n<p>If it returns an error exit code then the process is stopped and the action doesn't complete.<\/p>\n\n<p>This is useful if, for example, you or your team use a specified format for commit messages and you want to prevent the commit if the message doesn't match the requirements.<\/p>\n\n<p>But, the main benefit that I get from Git hooks if from the <code>pre-push<\/code> hook.<\/p>\n\n<p>I use it to run a subset of the checks that are run within project's CI pipeline to limit failures in the CI tool and fix simple errors before I push the code.<\/p>\n\n<p>Typically, these are the quicker tasks such as ensuring the Docker image builds, running linting and static analysis, validating lock files, and some of the automated tests if they don't take too long to run.<\/p>\n\n<p>If a build is going to fail because of something simply like a linting error, then I'd rather find that out and fix it locally rather than waiting for a CI tool to fail.<\/p>\n\n<p>Also, if you're utilising trunk-based development and continuous integration where team members are pushing changes regularly, then you want to keep the pipeline in a passing, deployable state as much as possible and prevent disruption.<\/p>\n\n<p>But what have Git hooks got to do with the \"run\" file?<\/p>\n\n<p>Firstly, I like to keep the scripts as minimal as possible and move the majority of the code into functions within the <code>run<\/code> file. This means that the scripts are only responsible for running functions like <code>.\/run test:commit<\/code> and returning the appropriate exit code, but also means that it's easy to iterate and test them locally without making fake commits or trying to push them to your actual remote repository (and hoping that they don't get pushed).<\/p>\n\n<p>Secondly, I like to simplify the setup of Git hooks with their own functions.<\/p>\n\n<p>For security reasons, the <code>.git\/hooks<\/code> directory cannot be committed and pushed to your remote so they need to be enabled per user within their own clone of the repository.<\/p>\n\n<p>A common workaround is to put the scripts in a directory like <code>.githooks<\/code> and either symlink them to where Git expects them to be, or to use the <code>core.hooksPath<\/code> configuration option and change where Git is going to look.<\/p>\n\n<p>I like to lower the barrier for any team members by creating <code>git-hooks:on<\/code> and <code>git-hooks:off<\/code> functions which either set or unset the <code>core.hooksPath<\/code>. If someone wants to enable the Git hooks then they only need to run one of those commands rather than having to remember the name of the configuration option or manually creating or removing symlinks.<\/p>\n\n<p>There are other Git hooks that can be used but just using <code>pre-commit<\/code> and <code>pre-push<\/code> has saved me and teams that I've worked on both Developer time and build minutes, provides quicker feedback and fewer disruptions in our build pipelines, and I like how simple it can be by creating custom functions in a <code>run<\/code> file.<\/p>\n\n<p>Lastly, I've created <a href=\"https:\/\/github.com\/opdavies\/git-hooks-scratch\">https:\/\/github.com\/opdavies\/git-hooks-scratch<\/a> as an example with a minimal <code>run<\/code> file and some example hooks.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>In yesterday's email, I mentioned Git hooks but didn't go into any detail. So, what are they?<\/p>\n\n<p>Git hooks are Bash scripts that you add to your repository that are executed when certain events happen, such as before a commit is made or before a push to a remote.<\/p>\n\n<p>By default, the script files need to be within the <code>.git\/hooks<\/code> directory, have executable permissions, and be named to exactly match the name of the hook - e.g. <code>pre-push<\/code> - with no file extension.<\/p>\n\n<p>If it returns an error exit code then the process is stopped and the action doesn't complete.<\/p>\n\n<p>This is useful if, for example, you or your team use a specified format for commit messages and you want to prevent the commit if the message doesn't match the requirements.<\/p>\n\n<p>But, the main benefit that I get from Git hooks if from the <code>pre-push<\/code> hook.<\/p>\n\n<p>I use it to run a subset of the checks that are run within project's CI pipeline to limit failures in the CI tool and fix simple errors before I push the code.<\/p>\n\n<p>Typically, these are the quicker tasks such as ensuring the Docker image builds, running linting and static analysis, validating lock files, and some of the automated tests if they don't take too long to run.<\/p>\n\n<p>If a build is going to fail because of something simply like a linting error, then I'd rather find that out and fix it locally rather than waiting for a CI tool to fail.<\/p>\n\n<p>Also, if you're utilising trunk-based development and continuous integration where team members are pushing changes regularly, then you want to keep the pipeline in a passing, deployable state as much as possible and prevent disruption.<\/p>\n\n<p>But what have Git hooks got to do with the \"run\" file?<\/p>\n\n<p>Firstly, I like to keep the scripts as minimal as possible and move the majority of the code into functions within the <code>run<\/code> file. This means that the scripts are only responsible for running functions like <code>.\/run test:commit<\/code> and returning the appropriate exit code, but also means that it's easy to iterate and test them locally without making fake commits or trying to push them to your actual remote repository (and hoping that they don't get pushed).<\/p>\n\n<p>Secondly, I like to simplify the setup of Git hooks with their own functions.<\/p>\n\n<p>For security reasons, the <code>.git\/hooks<\/code> directory cannot be committed and pushed to your remote so they need to be enabled per user within their own clone of the repository.<\/p>\n\n<p>A common workaround is to put the scripts in a directory like <code>.githooks<\/code> and either symlink them to where Git expects them to be, or to use the <code>core.hooksPath<\/code> configuration option and change where Git is going to look.<\/p>\n\n<p>I like to lower the barrier for any team members by creating <code>git-hooks:on<\/code> and <code>git-hooks:off<\/code> functions which either set or unset the <code>core.hooksPath<\/code>. If someone wants to enable the Git hooks then they only need to run one of those commands rather than having to remember the name of the configuration option or manually creating or removing symlinks.<\/p>\n\n<p>There are other Git hooks that can be used but just using <code>pre-commit<\/code> and <code>pre-push<\/code> has saved me and teams that I've worked on both Developer time and build minutes, provides quicker feedback and fewer disruptions in our build pipelines, and I like how simple it can be by creating custom functions in a <code>run<\/code> file.<\/p>\n\n<p>Lastly, I've created <a href=\"https:\/\/github.com\/opdavies\/git-hooks-scratch\">https:\/\/github.com\/opdavies\/git-hooks-scratch<\/a> as an example with a minimal <code>run<\/code> file and some example hooks.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:07+00:00",
"guid": null,
"hash": "2dc52bfbe73534ab29285ff16486431f",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0a5fa15a-2d8a-4a91-ac71-45c758897f96"
}
],
"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": "Static websites are easy to build"
}
],
"created": [
{
"value": "2025-03-12T00: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\/03\/12\/easy",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Static websites are the easiest way to build websites.<\/p>\n\n<p>You create an index.html file, type some words, open the file in a browser and you'll see the words you entered.<\/p>\n\n<p>You built a website!<\/p>\n\n<p>Then you can create any more pages you need and style it with CSS.<\/p>\n\n<p>This how I built my first website, for a Tae Kwon-Do school I used to train at.<\/p>\n\n<p>This worked great, but at some point, becomes hard to scale.<\/p>\n\n<p>What if you want to add a new link to your navigation menu? You'd need to update each HTML page separately.<\/p>\n\n<p>At this point, I started to learn about PHP and MySQL, and then Drupal.<\/p>\n\n<p>Static site generators like Sculpin, Jekyll and Hugo also fix this problem.<\/p>\n\n<p>They allow you to write HTML files with a template language like Twig and use includes, loops and conditions to make your files easier to create and maintain with a language and tools you're familiar with.<\/p>\n\n<p>It still generates a static website with HTML files, but in a more maintainable way.<\/p>\n\n<p>As a PHP Developer, I like Sculpin but also like Tome to export a Drupal website to static HTML.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Static websites are the easiest way to build websites.<\/p>\n\n<p>You create an index.html file, type some words, open the file in a browser and you'll see the words you entered.<\/p>\n\n<p>You built a website!<\/p>\n\n<p>Then you can create any more pages you need and style it with CSS.<\/p>\n\n<p>This how I built my first website, for a Tae Kwon-Do school I used to train at.<\/p>\n\n<p>This worked great, but at some point, becomes hard to scale.<\/p>\n\n<p>What if you want to add a new link to your navigation menu? You'd need to update each HTML page separately.<\/p>\n\n<p>At this point, I started to learn about PHP and MySQL, and then Drupal.<\/p>\n\n<p>Static site generators like Sculpin, Jekyll and Hugo also fix this problem.<\/p>\n\n<p>They allow you to write HTML files with a template language like Twig and use includes, loops and conditions to make your files easier to create and maintain with a language and tools you're familiar with.<\/p>\n\n<p>It still generates a static website with HTML files, but in a more maintainable way.<\/p>\n\n<p>As a PHP Developer, I like Sculpin but also like Tome to export a Drupal website to static HTML.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:56+00:00",
"guid": null,
"hash": "898a4ca4ca6f3d15e2c342efb0420bee",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0aa0a89f-2980-4351-9f9c-345e85c019eb"
}
],
"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": "Write once, manage forever"
}
],
"created": [
{
"value": "2024-01-26T00: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\/26\/write-once-manage-forever",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>I built my <a href=\"http:\/\/localhost:8000\/build-config\">Build Configs<\/a> tool because I only wanted to write a file once and re-use it instead of writing it over again or copying and pasting between projects.<\/p>\n\n<p>Having standardised templates for different languages and project types, I can easily set up new projects and get them running in a few minutes.<\/p>\n\n<p>If I need to add a feature or fix a bug, I can do it once in the Build Configs tool and easily regenerate the configuration files for all projects, and they'll all get the updated files.<\/p>\n\n<p>There's no \"I need to copy this feature from project A or this bug fix from project B.\" when starting on project C.<\/p>\n\n<p>As I only work on fixed-price engagements, it's in my interest to be able to create and maintain projects in a fast and efficient manner.<\/p>\n\n<p>The Build Configs tool enables me to do that.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>I built my <a href=\"http:\/\/localhost:8000\/build-config\">Build Configs<\/a> tool because I only wanted to write a file once and re-use it instead of writing it over again or copying and pasting between projects.<\/p>\n\n<p>Having standardised templates for different languages and project types, I can easily set up new projects and get them running in a few minutes.<\/p>\n\n<p>If I need to add a feature or fix a bug, I can do it once in the Build Configs tool and easily regenerate the configuration files for all projects, and they'll all get the updated files.<\/p>\n\n<p>There's no \"I need to copy this feature from project A or this bug fix from project B.\" when starting on project C.<\/p>\n\n<p>As I only work on fixed-price engagements, it's in my interest to be able to create and maintain projects in a fast and efficient manner.<\/p>\n\n<p>The Build Configs tool enables me to do that.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:00+00:00",
"guid": null,
"hash": "43e93c9e3c74111834d02bcc44647f25",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0ab1e627-0821-4382-b31c-d46b9419e169"
}
],
"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": "When should you run your tests?\n"
}
],
"created": [
{
"value": "2023-10-23T00: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\/10\/23\/when-should-run-your-tests",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>After my talk at DrupalCon, I was asked when you should run your tests.<\/p>\n\n<p>Of course, if you're doing test-driven development, you have to run the tests as you work on them and have the red, green, refactor cycle as you work on the feature or bug fix.<\/p>\n\n<p>If you're not doing TDD, I'd recommend running the whole test suite before you push your code to know it works before a peer review or to an environment for quality assurance or user-acceptance testing.<\/p>\n\n<h2 id=\"what-about-ci-pipelines%3F\">What about CI pipelines?<\/h2>\n\n<p>As well as running tests manually, I'd add them to a CI pipeline, such as GitHub Actions, GitLab CI or Bitbucket Pipelines.<\/p>\n\n<p>There, tasks can be run automatically each time a commit is pushed, so you don't need to rely on them being run manually.<\/p>\n\n<p>If you're doing trunk-based development, you want the CI pipeline to run on every push to prevent regressions and ensure the tests continue to pass.<\/p>\n\n<p>If you're working with feature branches and doing code review, run the tests as part of the merge or pull request so you know everything works as expected before the code is reviewed.<\/p>\n\n<p>This answers the main \"Does it work?\" question, and allows the reviewer to focus on reviewing the code and suggesting improvements.<\/p>\n\n<p>If the CI pipeline in the merge or pull request fails, it needs to be fixed before submitting it for review as there's no need to review the code before it changes to fix the pipeline.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>After my talk at DrupalCon, I was asked when you should run your tests.<\/p>\n\n<p>Of course, if you're doing test-driven development, you have to run the tests as you work on them and have the red, green, refactor cycle as you work on the feature or bug fix.<\/p>\n\n<p>If you're not doing TDD, I'd recommend running the whole test suite before you push your code to know it works before a peer review or to an environment for quality assurance or user-acceptance testing.<\/p>\n\n<h2 id=\"what-about-ci-pipelines%3F\">What about CI pipelines?<\/h2>\n\n<p>As well as running tests manually, I'd add them to a CI pipeline, such as GitHub Actions, GitLab CI or Bitbucket Pipelines.<\/p>\n\n<p>There, tasks can be run automatically each time a commit is pushed, so you don't need to rely on them being run manually.<\/p>\n\n<p>If you're doing trunk-based development, you want the CI pipeline to run on every push to prevent regressions and ensure the tests continue to pass.<\/p>\n\n<p>If you're working with feature branches and doing code review, run the tests as part of the merge or pull request so you know everything works as expected before the code is reviewed.<\/p>\n\n<p>This answers the main \"Does it work?\" question, and allows the reviewer to focus on reviewing the code and suggesting improvements.<\/p>\n\n<p>If the CI pipeline in the merge or pull request fails, it needs to be fixed before submitting it for review as there's no need to review the code before it changes to fix the pipeline.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:01+00:00",
"guid": null,
"hash": "1f2cab06d1412951184ea5c3b5d74edb",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0acd55fb-39a2-400b-bc1b-2acf5896b63d"
}
],
"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": "Reproducible or repeatable"
}
],
"created": [
{
"value": "2025-01-20T00: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\/01\/20\/reproducible",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p><a href=\"http:\/\/localhost:8000\/daily\/2025\/01\/19\/minimum-viable-development-environment\">In yesterday's email<\/a>, I showed how I've been using Nix and flake files to build reproducible and shareable development environments for Drupal applications.<\/p>\n\n<p>The reason it's reproducible is the <code>flake.lock<\/code> file.<\/p>\n\n<p>Similar to <code>composer.lock<\/code> or <code>package-lock.json<\/code>, it captures the exact versions of the packages installed from the nixpkgs repository.<\/p>\n\n<p>This file, along with <code>flake.nix<\/code>, can be committed alongside the application code and anyone with Nix installed can run <code>nix develop<\/code> to get a shell with the same packages and dependencies.<\/p>\n\n<p>This isn't the same as other solutions, where you add something like <code>FROM php:8.2<\/code> but, because there's no lockfile, there's no guarantee the same package versions will be installed so there could be mismatches that cause errors.<\/p>\n\n<p>With <code>flake.lock<\/code>, the environment isn't just repeatable - it's completely reproducible.<\/p>\n\n<p>Locally, in a CI pipeline or in production.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p><a href=\"http:\/\/localhost:8000\/daily\/2025\/01\/19\/minimum-viable-development-environment\">In yesterday's email<\/a>, I showed how I've been using Nix and flake files to build reproducible and shareable development environments for Drupal applications.<\/p>\n\n<p>The reason it's reproducible is the <code>flake.lock<\/code> file.<\/p>\n\n<p>Similar to <code>composer.lock<\/code> or <code>package-lock.json<\/code>, it captures the exact versions of the packages installed from the nixpkgs repository.<\/p>\n\n<p>This file, along with <code>flake.nix<\/code>, can be committed alongside the application code and anyone with Nix installed can run <code>nix develop<\/code> to get a shell with the same packages and dependencies.<\/p>\n\n<p>This isn't the same as other solutions, where you add something like <code>FROM php:8.2<\/code> but, because there's no lockfile, there's no guarantee the same package versions will be installed so there could be mismatches that cause errors.<\/p>\n\n<p>With <code>flake.lock<\/code>, the environment isn't just repeatable - it's completely reproducible.<\/p>\n\n<p>Locally, in a CI pipeline or in production.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:56+00:00",
"guid": null,
"hash": "180481b0b90c165143857e50b30bbb77",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0af916e1-e99f-43fa-b9b0-6472eabf31f1"
}
],
"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": "What if there was no open-source software\n"
}
],
"created": [
{
"value": "2023-06-14T00: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\/14\/what-if-there-was-no-open-source-software",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>I was listening to a podcast today, and the question was mentioned - \"What if there was no open-source software?\".<\/p>\n\n<p>As a self-taught Developer who has worked with open-source technologies and become an expert in Drupal - an open-source content management system - this would have had a big effect.<\/p>\n\n<p>If there were no open-source frameworks or CMSes like Drupal, Symfony, Laravel or Vue.js, Developers would need to write everything from scratch, and companies would need to pay for the extra time.<\/p>\n\n<p>There would be no reusable knowledge as Developers move to different companies as everything would be written in-house.<\/p>\n\n<p>There would be no communities and events like conferences and meetups for open-source technologies.<\/p>\n\n<p>As well as frameworks, what about languages like PHP that are open-sourced? Would companies also need to write and maintain their own programming languages?<\/p>\n\n<p>What about Linux, which I use every day for my desktop environment and servers, and other tools like Neovim, PHPStan, PHPUnit and Pest that are all open-source?<\/p>\n\n<p>Even as someone who contributes to and sponsors open-source projects and their Developers, it's still a shock to think how things would work without open-source software.<\/p>\n\n<p>If you use open-source software, please consider sponsoring, supporting or contributing to the projects you use and depend on.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>I was listening to a podcast today, and the question was mentioned - \"What if there was no open-source software?\".<\/p>\n\n<p>As a self-taught Developer who has worked with open-source technologies and become an expert in Drupal - an open-source content management system - this would have had a big effect.<\/p>\n\n<p>If there were no open-source frameworks or CMSes like Drupal, Symfony, Laravel or Vue.js, Developers would need to write everything from scratch, and companies would need to pay for the extra time.<\/p>\n\n<p>There would be no reusable knowledge as Developers move to different companies as everything would be written in-house.<\/p>\n\n<p>There would be no communities and events like conferences and meetups for open-source technologies.<\/p>\n\n<p>As well as frameworks, what about languages like PHP that are open-sourced? Would companies also need to write and maintain their own programming languages?<\/p>\n\n<p>What about Linux, which I use every day for my desktop environment and servers, and other tools like Neovim, PHPStan, PHPUnit and Pest that are all open-source?<\/p>\n\n<p>Even as someone who contributes to and sponsors open-source projects and their Developers, it's still a shock to think how things would work without open-source software.<\/p>\n\n<p>If you use open-source software, please consider sponsoring, supporting or contributing to the projects you use and depend on.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:03+00:00",
"guid": null,
"hash": "c70e6f1af7689e9704999c27d288916d",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0b3d3cba-7f34-428f-9ccd-414eb68f14df"
}
],
"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": "How much would it cost to build Drupal?"
}
],
"created": [
{
"value": "2025-03-03T00: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\/03\/03\/cost",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Drupal comes with a lot of features available out of the box.<\/p>\n\n<p>It has a flexible system to create complex content models with different content types and fields, rich media management and Views - a visual query builder - to create lists of content.<\/p>\n\n<p>It has the JSON:API module to expose your content via an API for use by other systems, such as mobile apps.<\/p>\n\n<p>It has user management, authentication, password resets, roles and permissions.<\/p>\n\n<p>It has configuration management to easily manage settings and configuration between environments and to provide traceability.<\/p>\n\n<p>It has the Layout Builder to create page layouts with a drag and drop interface and a built-in WYSIWYG editor for entering rich content.<\/p>\n\n<p>These are just some of the features I could mention.<\/p>\n\n<p>Running cloc on Drupal's <code>core<\/code> directory shows 18,289 files and 1,095,970 lines of code.<\/p>\n\n<p>If you were to get someone to build a CMS from scratch with the same features, how long would that take?<\/p>\n\n<p>How much would it cost?<\/p>\n\n<p>This is what you get for free by using free and open source software like Drupal.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Drupal comes with a lot of features available out of the box.<\/p>\n\n<p>It has a flexible system to create complex content models with different content types and fields, rich media management and Views - a visual query builder - to create lists of content.<\/p>\n\n<p>It has the JSON:API module to expose your content via an API for use by other systems, such as mobile apps.<\/p>\n\n<p>It has user management, authentication, password resets, roles and permissions.<\/p>\n\n<p>It has configuration management to easily manage settings and configuration between environments and to provide traceability.<\/p>\n\n<p>It has the Layout Builder to create page layouts with a drag and drop interface and a built-in WYSIWYG editor for entering rich content.<\/p>\n\n<p>These are just some of the features I could mention.<\/p>\n\n<p>Running cloc on Drupal's <code>core<\/code> directory shows 18,289 files and 1,095,970 lines of code.<\/p>\n\n<p>If you were to get someone to build a CMS from scratch with the same features, how long would that take?<\/p>\n\n<p>How much would it cost?<\/p>\n\n<p>This is what you get for free by using free and open source software like Drupal.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:56+00:00",
"guid": null,
"hash": "3b2f48d28cdda255b884d631a4a170ad",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0b453792-d771-4d1b-a852-5837c850ef8a"
}
],
"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:05+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": "Are sprints incompatible with Continuous Deployment?\n"
}
],
"created": [
{
"value": "2022-11-08T00:00:00+00:00"
}
],
"changed": [
{
"value": "2025-04-16T14:13:05+00:00"
}
],
"promote": [
{
"value": false
}
],
"sticky": [
{
"value": false
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
],
"path": [
{
"alias": "\/daily\/2022\/11\/08\/are-sprints-incompatible-with-continuous-deployment",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>It's been common for me whilst working on software projects to have work organised into sprints or cycles - a period, usually between 1 and 3 weeks, where the team is working on stories and tasks for that project.<\/p>\n\n<p>In my experience, those changes are usually released at the end of that cycle. But it seems that's not always the case; see <a href=\"https:\/\/scrumdictionary.com\/term\/release-sprint\">release sprints<\/a>:<\/p>\n\n<blockquote>\n <p>A specialised sprint whose purpose is to release deliverable results; it contains stories specific to release activities and finishing undone work. A release sprint usually contains no additional development.<\/p>\n<\/blockquote>\n\n<p>If we worked in two-week cycles and released at the end of each one, it would be at least two weeks before a change could be deployed to production. But what if we wanted to follow continuous deployment and release more frequently? Maybe daily or hourly?<\/p>\n\n<p>Instead of waiting for a release sprint, if we released multiple times within a single sprint, how would this fit into or affect the process?<\/p>\n\n<p>Does the release cycle need to be tightly coupled to the sprint cycle or can they be separate and independent of each other?<\/p>\n\n<p>I've worked on projects - including a current one - where I've done multiple releases in a sprint, so of course, it can be done from a technical perspective, but how do we get the best from both processes - whether they work together or separately?<\/p>\n\n<p>This is something that I'm going to continue to experiment with, iterate on, and learn more about going forward.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>It's been common for me whilst working on software projects to have work organised into sprints or cycles - a period, usually between 1 and 3 weeks, where the team is working on stories and tasks for that project.<\/p>\n\n<p>In my experience, those changes are usually released at the end of that cycle. But it seems that's not always the case; see <a href=\"https:\/\/scrumdictionary.com\/term\/release-sprint\">release sprints<\/a>:<\/p>\n\n<blockquote>\n <p>A specialised sprint whose purpose is to release deliverable results; it contains stories specific to release activities and finishing undone work. A release sprint usually contains no additional development.<\/p>\n<\/blockquote>\n\n<p>If we worked in two-week cycles and released at the end of each one, it would be at least two weeks before a change could be deployed to production. But what if we wanted to follow continuous deployment and release more frequently? Maybe daily or hourly?<\/p>\n\n<p>Instead of waiting for a release sprint, if we released multiple times within a single sprint, how would this fit into or affect the process?<\/p>\n\n<p>Does the release cycle need to be tightly coupled to the sprint cycle or can they be separate and independent of each other?<\/p>\n\n<p>I've worked on projects - including a current one - where I've done multiple releases in a sprint, so of course, it can be done from a technical perspective, but how do we get the best from both processes - whether they work together or separately?<\/p>\n\n<p>This is something that I'm going to continue to experiment with, iterate on, and learn more about going forward.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:05+00:00",
"guid": null,
"hash": "efd0ec8ddb3fb7929febf9e8017a8f84",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0b69e069-315d-42af-a4c0-b0714764ab83"
}
],
"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": "nix is like nvm, but for everything"
}
],
"created": [
{
"value": "2025-04-15T00: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\/04\/15\/nix-nvm",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>I was recently explaining and demonstrating Nix and direnv to a colleague and showing how, when I moved into a directory, new packages or different versions of packages became available.<\/p>\n\n<p>If I left the directory, I was reverted back to my global packages and versions.<\/p>\n\n<p>In this demonstration, I was showing how I can have different versions of PHP and node for a particular project - replacing a lot of what I'd previously used tools like Vagrant and Docker for.<\/p>\n\n<p>I came up with a comparison between Nix and nvm - the node version manager - a tool that allows you to install multiple versions of nodejs and switch between them.<\/p>\n\n<p>Using Nix and direnv is more seamless, but it works for everything.<\/p>\n\n<p>I'm able to switch versions of PHP, MySQL, MariaDB, PostgreSQL or anything else I need with Nix.<\/p>\n\n<p>Not just node, and without needing containers.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>I was recently explaining and demonstrating Nix and direnv to a colleague and showing how, when I moved into a directory, new packages or different versions of packages became available.<\/p>\n\n<p>If I left the directory, I was reverted back to my global packages and versions.<\/p>\n\n<p>In this demonstration, I was showing how I can have different versions of PHP and node for a particular project - replacing a lot of what I'd previously used tools like Vagrant and Docker for.<\/p>\n\n<p>I came up with a comparison between Nix and nvm - the node version manager - a tool that allows you to install multiple versions of nodejs and switch between them.<\/p>\n\n<p>Using Nix and direnv is more seamless, but it works for everything.<\/p>\n\n<p>I'm able to switch versions of PHP, MySQL, MariaDB, PostgreSQL or anything else I need with Nix.<\/p>\n\n<p>Not just node, and without needing containers.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:56+00:00",
"guid": null,
"hash": "1f1938498491ff40c88d4e5b99fc617b",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0bb328d6-219a-4e8e-8823-8331674eda84"
}
],
"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": "Debugging with git bisect\n"
}
],
"created": [
{
"value": "2023-01-23T00: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\/01\/23\/debugging-with-git-bisect",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Last week, I had to debug a regression in a codebase.<\/p>\n\n<p>Something was working at the last release but is now broken.<\/p>\n\n<p>There have been around 20 commits to the mainline branch since the last release, and the first step to fixing the issue is to determine which commit caused the regression.<\/p>\n\n<p>Git has a great tool for this - <code>git bisect<\/code>.<\/p>\n\n<p>You tell Git what the last known working commit was, such as the tag of the last release, and it will start to split the commits and prompt you to tell it whether the commit is good or bad.<\/p>\n\n<p>If there are 20 commits, it may pick commit number 10, and based on whether the commit is good or bad, it may pick commit 5 or 15.<\/p>\n\n<p>Based on your answers, Git will then tell you which the first bad commit is.<\/p>\n\n<p>Even better, if it's something that you can script or is covered with an automated test, <code>git bisect<\/code> can run a command for you and find the failure automatically rather than a human needing to check manually.<\/p>\n\n<p>Once you've found the commit that breaks, you can view it and find and fix the bug.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Last week, I had to debug a regression in a codebase.<\/p>\n\n<p>Something was working at the last release but is now broken.<\/p>\n\n<p>There have been around 20 commits to the mainline branch since the last release, and the first step to fixing the issue is to determine which commit caused the regression.<\/p>\n\n<p>Git has a great tool for this - <code>git bisect<\/code>.<\/p>\n\n<p>You tell Git what the last known working commit was, such as the tag of the last release, and it will start to split the commits and prompt you to tell it whether the commit is good or bad.<\/p>\n\n<p>If there are 20 commits, it may pick commit number 10, and based on whether the commit is good or bad, it may pick commit 5 or 15.<\/p>\n\n<p>Based on your answers, Git will then tell you which the first bad commit is.<\/p>\n\n<p>Even better, if it's something that you can script or is covered with an automated test, <code>git bisect<\/code> can run a command for you and find the failure automatically rather than a human needing to check manually.<\/p>\n\n<p>Once you've found the commit that breaks, you can view it and find and fix the bug.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:04+00:00",
"guid": null,
"hash": "6fbd09cf55994352dfe3e9a6fae9be71",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0bd8d075-e41f-4ba5-9412-c142576418f3"
}
],
"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": "Testing is all about confidence\n"
}
],
"created": [
{
"value": "2023-07-24T00: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\/24\/testing-is-all-about-confidence",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Testing - manual or automated - is about building confidence.<\/p>\n\n<p>If we deploy this change or release this feature, are we confident it will work as expected and not cause regressions elsewhere?<\/p>\n\n<p>What if someone asked you on a scale between one and ten?<\/p>\n\n<p>From an automated perspective, have you written enough tests for the feature to be confident it works?<\/p>\n\n<p>If you're fixing a bug, do you have a test that reproduces the bug that was originally failing but now passing since you've added the fix?<\/p>\n\n<p>Do the tests have enough assertions, and have you covered enough use cases and scenarios?<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>You can utilise code coverage metrics, but no hard rule says that the feature will work once x percentage is covered. Something with 100% coverage can still contain bugs.<\/p>\n\n<p>For me, it's about the answer to the question:<\/p>\n\n<p>If we deploy this change, how confident are you that it will work as expected?<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Testing - manual or automated - is about building confidence.<\/p>\n\n<p>If we deploy this change or release this feature, are we confident it will work as expected and not cause regressions elsewhere?<\/p>\n\n<p>What if someone asked you on a scale between one and ten?<\/p>\n\n<p>From an automated perspective, have you written enough tests for the feature to be confident it works?<\/p>\n\n<p>If you're fixing a bug, do you have a test that reproduces the bug that was originally failing but now passing since you've added the fix?<\/p>\n\n<p>Do the tests have enough assertions, and have you covered enough use cases and scenarios?<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>You can utilise code coverage metrics, but no hard rule says that the feature will work once x percentage is covered. Something with 100% coverage can still contain bugs.<\/p>\n\n<p>For me, it's about the answer to the question:<\/p>\n\n<p>If we deploy this change, how confident are you that it will work as expected?<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:02+00:00",
"guid": null,
"hash": "60c550711c51c2b739051e4640eba954",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0bde3c8b-1a55-4f36-9a2e-ec5834771482"
}
],
"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:05+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": "Creating a small proof-of-concept application in an afternoon\n"
}
],
"created": [
{
"value": "2022-11-11T00:00:00+00:00"
}
],
"changed": [
{
"value": "2025-04-16T14:13:05+00:00"
}
],
"promote": [
{
"value": false
}
],
"sticky": [
{
"value": false
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
],
"path": [
{
"alias": "\/daily\/2022\/11\/12\/creating-small-proof-of-concept-application-afternoon",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>This morning, I was asked a \u201cCould you build\u2026\u201d question.<\/p>\n\n<p>It was an idea mentioned a short while ago and involves a simple, interactive form on the front end that sends requests to a public API, filters the results from the response and displays them to the user.<\/p>\n\n<p>I\u2019d probably want to hide the API request behind a service responsible for interacting with the API and filtering the results - ensuring that the API could be switched with something else later if needed.<\/p>\n\n<p>This afternoon, I built a small proof-of-concept application with Vue.js and TypeScript.<\/p>\n\n<p>There\u2019s no API, or service retrieving real-time results. All of the data is hard-coded within the App component, as well as the code that filters, sorts and returns the results.<\/p>\n\n<p>The results are shown by adding a <code>&lt;pre&gt;&lt;\/pre&gt;<\/code> to the page, with a <code>&lt;pre&gt;&lt;\/pre&gt;<\/code> to show the input data.<\/p>\n\n<p>There isn\u2019t even any styling, with just some basic horizontal rules to split the page - similar to <a href=\"https:\/\/twitter.com\/taylorotwell\/status\/1203356860818087944\">these screenshots from Taylor Otwell<\/a> of some work-in-progress versions of Vapor and Nova.<\/p>\n\n<p>A working proof of concept, or a \"spike\", answers the initial \"Can we build...\" question. It can be shown to a client or other stakeholders, act as a starting point for discussions and requirements gathering and then be turned into user stories. It also allows the Developers to validate their initial thoughts and experiment with different approaches.<\/p>\n\n<p>If the spike is successful, the idea can then be moved forward and implemented in a full way, otherwise, it can be stopped with a minimal amount of effort and time.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>This morning, I was asked a \u201cCould you build\u2026\u201d question.<\/p>\n\n<p>It was an idea mentioned a short while ago and involves a simple, interactive form on the front end that sends requests to a public API, filters the results from the response and displays them to the user.<\/p>\n\n<p>I\u2019d probably want to hide the API request behind a service responsible for interacting with the API and filtering the results - ensuring that the API could be switched with something else later if needed.<\/p>\n\n<p>This afternoon, I built a small proof-of-concept application with Vue.js and TypeScript.<\/p>\n\n<p>There\u2019s no API, or service retrieving real-time results. All of the data is hard-coded within the App component, as well as the code that filters, sorts and returns the results.<\/p>\n\n<p>The results are shown by adding a <code>&lt;pre&gt;&lt;\/pre&gt;<\/code> to the page, with a <code>&lt;pre&gt;&lt;\/pre&gt;<\/code> to show the input data.<\/p>\n\n<p>There isn\u2019t even any styling, with just some basic horizontal rules to split the page - similar to <a href=\"https:\/\/twitter.com\/taylorotwell\/status\/1203356860818087944\">these screenshots from Taylor Otwell<\/a> of some work-in-progress versions of Vapor and Nova.<\/p>\n\n<p>A working proof of concept, or a \"spike\", answers the initial \"Can we build...\" question. It can be shown to a client or other stakeholders, act as a starting point for discussions and requirements gathering and then be turned into user stories. It also allows the Developers to validate their initial thoughts and experiment with different approaches.<\/p>\n\n<p>If the spike is successful, the idea can then be moved forward and implemented in a full way, otherwise, it can be stopped with a minimal amount of effort and time.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:05+00:00",
"guid": null,
"hash": "f378ea534b4141dc8290191f8941df2c",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0c92ae14-fa57-4daf-a5f9-20b1354346fa"
}
],
"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": "Should I wait to upgrade from Drupal 7?\n"
}
],
"created": [
{
"value": "2023-07-29T00: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\/29\/should-i-wait-to-upgrade-from-drupal-7",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>It was announced at DrupalCon that Drupal 7 support was being extended one final time until January 2025.<\/p>\n\n<p>But if you have a Drupal 7 website, does that mean you should wait to start upgrading it?<\/p>\n\n<p>I recommend starting the process as soon as possible.<\/p>\n\n<p>Even though Drupal core support is extended, I've looked at projects that use modules marked as unsupported by their maintainers for some time as they focus on versions for Drupal 8, 9 or 10.<\/p>\n\n<p>In that case, those modules will have no new features, bug fixes or security updates, although Drupal core support has been extended.<\/p>\n\n<p>You may have a lot of custom code that needs to be ported to Drupal 10 or a complex data structure that needs to be migrated, These things will take time, so it's best not to leave it until the last minute.<\/p>\n\n<p>If you're stuck on Drupal 7, book an <a href=\"http:\/\/localhost:8000\/call\">upgrade consultation call<\/a> with me or purchase an <a href=\"http:\/\/localhost:8000\/drupal7\">upgrade roadmap for your project<\/a> and I'll get you unstuck.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>It was announced at DrupalCon that Drupal 7 support was being extended one final time until January 2025.<\/p>\n\n<p>But if you have a Drupal 7 website, does that mean you should wait to start upgrading it?<\/p>\n\n<p>I recommend starting the process as soon as possible.<\/p>\n\n<p>Even though Drupal core support is extended, I've looked at projects that use modules marked as unsupported by their maintainers for some time as they focus on versions for Drupal 8, 9 or 10.<\/p>\n\n<p>In that case, those modules will have no new features, bug fixes or security updates, although Drupal core support has been extended.<\/p>\n\n<p>You may have a lot of custom code that needs to be ported to Drupal 10 or a complex data structure that needs to be migrated, These things will take time, so it's best not to leave it until the last minute.<\/p>\n\n<p>If you're stuck on Drupal 7, book an <a href=\"http:\/\/localhost:8000\/call\">upgrade consultation call<\/a> with me or purchase an <a href=\"http:\/\/localhost:8000\/drupal7\">upgrade roadmap for your project<\/a> and I'll get you unstuck.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:02+00:00",
"guid": null,
"hash": "591efe5fb3861d0c68d3b83fffa1da68",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0c9750c0-7050-4f7e-ac67-b896554df0f1"
}
],
"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": "People read more code than they write"
}
],
"created": [
{
"value": "2024-08-07T00: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\/08\/07\/people-read-more-code-than-they-write",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Which do you do more?<\/p>\n\n<p>Read code or write code?<\/p>\n\n<p>If you include colleagues' code, logs and CI pipeline output, open-source software, books, courses, tutorials, examples, videos, live streams, meetup and conference talks and blog posts (just to think of some), you definitely read more code than you write.<\/p>\n\n<p>Which is why it's important to <a href=\"http:\/\/localhost:8000\/daily\/2024\/08\/06\/computers-dont-care\">optimise for readability<\/a>.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Which do you do more?<\/p>\n\n<p>Read code or write code?<\/p>\n\n<p>If you include colleagues' code, logs and CI pipeline output, open-source software, books, courses, tutorials, examples, videos, live streams, meetup and conference talks and blog posts (just to think of some), you definitely read more code than you write.<\/p>\n\n<p>Which is why it's important to <a href=\"http:\/\/localhost:8000\/daily\/2024\/08\/06\/computers-dont-care\">optimise for readability<\/a>.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:58+00:00",
"guid": null,
"hash": "b71f49a1681732644d719090945fd2d3",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0ce24277-65f4-49a0-8325-c7fce30d93b5"
}
],
"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": "Drupal Commerce: not just for selling t-shirts and hats"
}
],
"created": [
{
"value": "2024-03-19T00: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\/03\/19\/drupal-commerce-not-just-for-selling-t-shirts-and-hats",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>I recently had Ryan Szrama as a guest on the <a href=\"http:\/\/localhost:8000\/podcast\/13-ryan-szrama-centarro\">Beyond Blocks podcast<\/a>.<\/p>\n\n<p>Ryan is the CEO of Centarro - the company behind Drupal Commerce, the eCommerce platform built on the Drupal CMS.<\/p>\n\n<p>I've used Drupal Commerce for a number of projects since it was released in 2011, as well as Ubercart before that.<\/p>\n\n<p>One of the major things I like about it is its flexibility.<\/p>\n\n<p>The Commerce Kickstart distribution is a great way to create a demo Drupal Commerce project that shows a typical eCommerce store selling everything from books to hats, furniture and inflatable flamingos.<\/p>\n\n<p>I've used Drupal Commerce for these typical scenarios but also for some non-typical ones.<\/p>\n\n<p>I created a multi-site Drupal Commerce store for a gadget insurance company, dealing with many products and product variations. I built a custom Vue.js form that created an order with the required items before passing customers to a Drupal Commerce checkout flow.<\/p>\n\n<p>I created a yearly photography competition website that photographers can enter by purchasing a product and uploading their photographs to the order. I built custom judging functionality, which allows jurors to score each entry and the site owner to see the totals and which submission won the competition.<\/p>\n\n<p>I created an events management and booking website where each event was a product with different variations based on the different prices - early bird, regular and last minute. Each event had a maximum number of places and, potentially, a waitlist.<\/p>\n\n<p>This website also included a loyalty scheme for event organisers and attendees, who received coupons after organising or attending a certain number of events.<\/p>\n\n<p>Drupal Commerce can do a lot and isn't just selling t-shirts, hats, books or furniture.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>I recently had Ryan Szrama as a guest on the <a href=\"http:\/\/localhost:8000\/podcast\/13-ryan-szrama-centarro\">Beyond Blocks podcast<\/a>.<\/p>\n\n<p>Ryan is the CEO of Centarro - the company behind Drupal Commerce, the eCommerce platform built on the Drupal CMS.<\/p>\n\n<p>I've used Drupal Commerce for a number of projects since it was released in 2011, as well as Ubercart before that.<\/p>\n\n<p>One of the major things I like about it is its flexibility.<\/p>\n\n<p>The Commerce Kickstart distribution is a great way to create a demo Drupal Commerce project that shows a typical eCommerce store selling everything from books to hats, furniture and inflatable flamingos.<\/p>\n\n<p>I've used Drupal Commerce for these typical scenarios but also for some non-typical ones.<\/p>\n\n<p>I created a multi-site Drupal Commerce store for a gadget insurance company, dealing with many products and product variations. I built a custom Vue.js form that created an order with the required items before passing customers to a Drupal Commerce checkout flow.<\/p>\n\n<p>I created a yearly photography competition website that photographers can enter by purchasing a product and uploading their photographs to the order. I built custom judging functionality, which allows jurors to score each entry and the site owner to see the totals and which submission won the competition.<\/p>\n\n<p>I created an events management and booking website where each event was a product with different variations based on the different prices - early bird, regular and last minute. Each event had a maximum number of places and, potentially, a waitlist.<\/p>\n\n<p>This website also included a loyalty scheme for event organisers and attendees, who received coupons after organising or attending a certain number of events.<\/p>\n\n<p>Drupal Commerce can do a lot and isn't just selling t-shirts, hats, books or furniture.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:00+00:00",
"guid": null,
"hash": "8974a98575ca24f1c5bc317715f49365",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0cee44e0-f233-406c-ab11-250a71c03c48"
}
],
"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": "You can do utility-first CSS with Sass"
}
],
"created": [
{
"value": "2024-07-09T00: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\/09\/you-can-do-utility-first-css-with-sass",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Yesterday, I said that <a href=\"http:\/\/localhost:8000\/daily\/2024\/07\/08\/back-to-sass-and-traditional-css\">I'm working on a Sass project with no utility or atomic styles<\/a>.<\/p>\n\n<p>But, the two aren't mutually exclusive.<\/p>\n\n<p>You can do both.<\/p>\n\n<p>You can write your own utility classes, like <code>flex<\/code>, <code>font-bold<\/code> or <code>text-red<\/code> in Sass or plain CSS.<\/p>\n\n<p>You can use a framework like Tailwind CSS, but you don't need to.<\/p>\n\n<p>In some projects, with existing stylesheets and usually other frameworks, you can't add anothe full framework without having unintended consequences.<\/p>\n\n<p>Usually, if I want to introduce utility classes to an existing project, I start by writing my own that are inspired by a framework such as Tailwind CSS and maybe refactor to the framework later once the concept has been introduced and the codebase is able to work with it.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Yesterday, I said that <a href=\"http:\/\/localhost:8000\/daily\/2024\/07\/08\/back-to-sass-and-traditional-css\">I'm working on a Sass project with no utility or atomic styles<\/a>.<\/p>\n\n<p>But, the two aren't mutually exclusive.<\/p>\n\n<p>You can do both.<\/p>\n\n<p>You can write your own utility classes, like <code>flex<\/code>, <code>font-bold<\/code> or <code>text-red<\/code> in Sass or plain CSS.<\/p>\n\n<p>You can use a framework like Tailwind CSS, but you don't need to.<\/p>\n\n<p>In some projects, with existing stylesheets and usually other frameworks, you can't add anothe full framework without having unintended consequences.<\/p>\n\n<p>Usually, if I want to introduce utility classes to an existing project, I start by writing my own that are inspired by a framework such as Tailwind CSS and maybe refactor to the framework later once the concept has been introduced and the codebase is able to work with it.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:58+00:00",
"guid": null,
"hash": "b32818061edabf585a8d041c5fc9739b",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0d033a94-9e45-4d39-a15a-825678128782"
}
],
"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": "An interesting thing I spotted about the Override Node Options module"
}
],
"created": [
{
"value": "2024-11-16T00: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\/16\/an-interesting-thing-i-spotted-about-the-override-node-options-module",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Before <a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/13\/speaking-at-the-drupal-london-meetup\">my remote talk for the Drupal London meetup<\/a>, I'm updating the usage statistics for <a href=\"https:\/\/www.drupal.org\/project\/override_node_options\">the Override Node Options module<\/a> - one of the modules I maintain on Drupal.org.<\/p>\n\n<p>In my slides for DrupalCamp Belgium, I showed the usage figures from October 2023, which showed 38,096 installations and it being the 173rd most installed module.<\/p>\n\n<p>This week, the number of installations has slightly increased to 38,223.<\/p>\n\n<p>What's interesting is that whilst the number of installations has been consistent, there are a lot less Drupal 7 websites using the module and a lot more Drupal 8+ sites using it.<\/p>\n\n<h2 id=\"october-2023\">October 2023<\/h2>\n\n<ul>\n<li>5.x-1.x: 1<\/li>\n<li>6.x-1.x: 297<\/li>\n<li>7.x-1.x: 13,717<\/li>\n<li>8.x-2.x: 24,081<\/li>\n<li>Total: 38,096<\/li>\n<\/ul>\n\n<h2 id=\"november-2024\">November 2024<\/h2>\n\n<ul>\n<li>5.x-1.x: 4<\/li>\n<li>6.x-1.x: 202<\/li>\n<li>7.x-1.x: 10,429<\/li>\n<li>8.x-2.x: 27,588<\/li>\n<li>Total: 38,223<\/li>\n<\/ul>\n\n<p>Assuming these numbers are correct, this makes me feel very positive and happy about the adoption of newer versions of Drupal and that people are upgrading their D7 websites to Drupal 10 or 11.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Before <a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/13\/speaking-at-the-drupal-london-meetup\">my remote talk for the Drupal London meetup<\/a>, I'm updating the usage statistics for <a href=\"https:\/\/www.drupal.org\/project\/override_node_options\">the Override Node Options module<\/a> - one of the modules I maintain on Drupal.org.<\/p>\n\n<p>In my slides for DrupalCamp Belgium, I showed the usage figures from October 2023, which showed 38,096 installations and it being the 173rd most installed module.<\/p>\n\n<p>This week, the number of installations has slightly increased to 38,223.<\/p>\n\n<p>What's interesting is that whilst the number of installations has been consistent, there are a lot less Drupal 7 websites using the module and a lot more Drupal 8+ sites using it.<\/p>\n\n<h2 id=\"october-2023\">October 2023<\/h2>\n\n<ul>\n<li>5.x-1.x: 1<\/li>\n<li>6.x-1.x: 297<\/li>\n<li>7.x-1.x: 13,717<\/li>\n<li>8.x-2.x: 24,081<\/li>\n<li>Total: 38,096<\/li>\n<\/ul>\n\n<h2 id=\"november-2024\">November 2024<\/h2>\n\n<ul>\n<li>5.x-1.x: 4<\/li>\n<li>6.x-1.x: 202<\/li>\n<li>7.x-1.x: 10,429<\/li>\n<li>8.x-2.x: 27,588<\/li>\n<li>Total: 38,223<\/li>\n<\/ul>\n\n<p>Assuming these numbers are correct, this makes me feel very positive and happy about the adoption of newer versions of Drupal and that people are upgrading their D7 websites to Drupal 10 or 11.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:57+00:00",
"guid": null,
"hash": "f0490b5fa42d0058d6f97d5177d6648d",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0d710a10-9959-4e55-b1c6-c5c41c20d492"
}
],
"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": "It depends\n"
}
],
"created": [
{
"value": "2023-11-07T00: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\/07\/it-depends",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Usually, in software development, there isn't always a definitive black-and-white answer to a question or situation.<\/p>\n\n<p>Most of the time, the answer is \"it depends\".<\/p>\n\n<p>How you approach a problem depends on context.<\/p>\n\n<p>How long do you have?<\/p>\n\n<p>Are you working on the final version or a prototype or minimum-viable product?<\/p>\n\n<p>Should you use a contributed module or write one yourself?<\/p>\n\n<p>What if an existing module hasn't been updated for some time or doesn't have tests or other quality checks included?<\/p>\n\n<p>Do you write custom CSS or use a framework like Tailwind CSS or Bootstrap?<\/p>\n\n<p>Should this project be written in this framework or CMS, or would a different one be better suited?<\/p>\n\n<h2 id=\"here%27s-the-thing...\">Here's the thing...<\/h2>\n\n<p>There are usually multiple approaches to achieve the same result.<\/p>\n\n<p>Decisions will depend on a combination of various factors. In a different situation, the answer could be different.<\/p>\n\n<p>This doesn't make any solution outright wrong.<\/p>\n\n<p>It was right given the situation.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Usually, in software development, there isn't always a definitive black-and-white answer to a question or situation.<\/p>\n\n<p>Most of the time, the answer is \"it depends\".<\/p>\n\n<p>How you approach a problem depends on context.<\/p>\n\n<p>How long do you have?<\/p>\n\n<p>Are you working on the final version or a prototype or minimum-viable product?<\/p>\n\n<p>Should you use a contributed module or write one yourself?<\/p>\n\n<p>What if an existing module hasn't been updated for some time or doesn't have tests or other quality checks included?<\/p>\n\n<p>Do you write custom CSS or use a framework like Tailwind CSS or Bootstrap?<\/p>\n\n<p>Should this project be written in this framework or CMS, or would a different one be better suited?<\/p>\n\n<h2 id=\"here%27s-the-thing...\">Here's the thing...<\/h2>\n\n<p>There are usually multiple approaches to achieve the same result.<\/p>\n\n<p>Decisions will depend on a combination of various factors. In a different situation, the answer could be different.<\/p>\n\n<p>This doesn't make any solution outright wrong.<\/p>\n\n<p>It was right given the situation.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:01+00:00",
"guid": null,
"hash": "f40826212e5c798e718edd8ad155d3c0",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0dafdc61-c714-4581-8485-e6b9e8888629"
}
],
"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": "Bootcamps, communities and first Developer jobs"
}
],
"created": [
{
"value": "2024-06-30T00: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\/30\/bootcamps--communities-and-first-developer-jobs",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Tomorrow is the first day of cohort 17 of School of Code - a free 16-week software development bootcamp - and I'm happy to be mentoring a student again for my third time.<\/p>\n\n<p>I also mentored the winning team at a hack day event in January and <a href=\"http:\/\/localhost:8000\/presentations\/communities-contribution\">spoke at the TechConnect meetup<\/a> last year.<\/p>\n\n<p>Last week, I spoke with George Gordon - a recent School of Code graduate who I met at the hack day and later at another meetup I was speaking at.<\/p>\n\n<p>We discussed his experience with coding bootcamps, getting into the software industry and involved with community events and how he found his first Developer job!<\/p>\n\n<p>It will be released soon on <a href=\"http:\/\/localhost:8000\/podcast\">the Beyond Blocks podcast page<\/a>.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Tomorrow is the first day of cohort 17 of School of Code - a free 16-week software development bootcamp - and I'm happy to be mentoring a student again for my third time.<\/p>\n\n<p>I also mentored the winning team at a hack day event in January and <a href=\"http:\/\/localhost:8000\/presentations\/communities-contribution\">spoke at the TechConnect meetup<\/a> last year.<\/p>\n\n<p>Last week, I spoke with George Gordon - a recent School of Code graduate who I met at the hack day and later at another meetup I was speaking at.<\/p>\n\n<p>We discussed his experience with coding bootcamps, getting into the software industry and involved with community events and how he found his first Developer job!<\/p>\n\n<p>It will be released soon on <a href=\"http:\/\/localhost:8000\/podcast\">the Beyond Blocks podcast page<\/a>.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:58+00:00",
"guid": null,
"hash": "e106e4056daf94f25e5ee0b03bcef5a9",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0e278570-ecee-419f-83c5-f26596d2c521"
}
],
"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": "Just use curl"
}
],
"created": [
{
"value": "2025-01-10T00: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\/01\/10\/curl",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>I don't use complicated or bloated applications to test HTTP requests and API endpoints.<\/p>\n\n<p>I just use <code>curl<\/code> on my command line.<\/p>\n\n<p>For example, if I want to query the Drupal.org API for my user information, I can run <code>curl https:\/\/www.drupal.org\/api-d7\/user.json?uid=381388<\/code> and see the response.<\/p>\n\n<p>To see the request and response headers, status code, SSL certificate information and more, I can run <code>curl -v<\/code> to run it in verbose mode.<\/p>\n\n<p>If the response returns JSON, I can use <code>jq<\/code> to format the results.<\/p>\n\n<p>If I need to make a POST request, I can use <code>-X POST<\/code>, I can use <code>--data<\/code> or <code>--json<\/code> to send data and <code>--header<\/code> to send any required headers.<\/p>\n\n<p>curl is great program with so many options and no AI bloat, complicated UIs or paid plans.<\/p>\n\n<p>Want to see what else it can do?<\/p>\n\n<p>Just open your terminal and type <code>man curl<\/code>.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>I don't use complicated or bloated applications to test HTTP requests and API endpoints.<\/p>\n\n<p>I just use <code>curl<\/code> on my command line.<\/p>\n\n<p>For example, if I want to query the Drupal.org API for my user information, I can run <code>curl https:\/\/www.drupal.org\/api-d7\/user.json?uid=381388<\/code> and see the response.<\/p>\n\n<p>To see the request and response headers, status code, SSL certificate information and more, I can run <code>curl -v<\/code> to run it in verbose mode.<\/p>\n\n<p>If the response returns JSON, I can use <code>jq<\/code> to format the results.<\/p>\n\n<p>If I need to make a POST request, I can use <code>-X POST<\/code>, I can use <code>--data<\/code> or <code>--json<\/code> to send data and <code>--header<\/code> to send any required headers.<\/p>\n\n<p>curl is great program with so many options and no AI bloat, complicated UIs or paid plans.<\/p>\n\n<p>Want to see what else it can do?<\/p>\n\n<p>Just open your terminal and type <code>man curl<\/code>.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:56+00:00",
"guid": null,
"hash": "071392af7e184fa125a44531711fdfde",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0e9497b7-171a-4509-910b-40d18d62e134"
}
],
"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": "Drupalisms and de-jargoning Drupal"
}
],
"created": [
{
"value": "2025-02-14T00: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\/14\/drupalisms",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>After the topic of Drupalisms and Drupal jargon came up in previous podcast episodes, I was happy to speak with Emma Horrell and Luke McCormick.<\/p>\n\n<p>We discussed the work happening in Drupal and Drupal CMS to de-jargon Drupal, the Drupalisms working group.<\/p>\n\n<p><a href=\"http:\/\/localhost:8000\/podcast\/27-drupalisms\">Listen to the episode here<\/a>.<\/p>\n\n<p>If you want to be a guest on the <a href=\"http:\/\/localhost:8000\/podcast\">Beyond Blocks podcast<\/a>, reply and let me know.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>After the topic of Drupalisms and Drupal jargon came up in previous podcast episodes, I was happy to speak with Emma Horrell and Luke McCormick.<\/p>\n\n<p>We discussed the work happening in Drupal and Drupal CMS to de-jargon Drupal, the Drupalisms working group.<\/p>\n\n<p><a href=\"http:\/\/localhost:8000\/podcast\/27-drupalisms\">Listen to the episode here<\/a>.<\/p>\n\n<p>If you want to be a guest on the <a href=\"http:\/\/localhost:8000\/podcast\">Beyond Blocks podcast<\/a>, reply and let me know.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:56+00:00",
"guid": null,
"hash": "bffebd32dd75bb3cc31ae5a5a69bff5d",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0ea40f58-e645-4187-996b-3e45c1550d6a"
}
],
"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": "One more \"run\" command, for Git worktrees"
}
],
"created": [
{
"value": "2022-08-17T00: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\/17\/one-more-run-command-git-worktrees",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Here's another <code>run<\/code> file example, this time relating to Git worktrees...<\/p>\n\n<p>One project that I work on is a multilingual Drupal application that needs to work in both English and Welsh. As I'm cloning a fresh version today, I'm doing it as a bare repository so I can use worktrees.<\/p>\n\n<p>To work on it locally, just like in production, I need to use a different URL for each language so that Drupal can identify it and load the correct content and configuration.<\/p>\n\n<p>For fixed environments like production or staging, the URLs are set in configuration files, but for ad-hoc environments such as local worktrees, I thought that the best approach was to override them as needed per worktree using Drush (a Drupal CLI tool).<\/p>\n\n<p>I could do this manually each time or I could automate it in a <code>run<\/code> command. :)<\/p>\n\n<p>Here's the function that I came up with:<\/p>\n\n<pre><code class=\"bash\">function drupal:set-urls-for-worktree {\n # Set the site URLs based on the current Git worktree name.\n local worktree_name=\"$(basename $PWD)\"\n\n local cy_url=\"cy-projectname-${worktree_name}.docker.localhost\"\n local en_url=\"projectname-${worktree_name}.docker.localhost\"\n\n # Update the URLs.\n drush config:set language.negotiation url.domains.cy -y $cy_url\n drush config:set language.negotiation url.domains.en -y $en_url\n\n # Display the domains configuration to ensure that they were set correctly.\n drush config:get language.negotiation url.domains\n}\n<\/code><\/pre>\n\n<p>It builds the worktree URL for each language based on the directory name, executes the configuration change, and finally displays the updated configuration so I can confirm that it's been set correctly.<\/p>\n\n<p>This is a good example of why I like using <code>run<\/code> files and how I use them to automate and simplify parts of my workflow.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Here's another <code>run<\/code> file example, this time relating to Git worktrees...<\/p>\n\n<p>One project that I work on is a multilingual Drupal application that needs to work in both English and Welsh. As I'm cloning a fresh version today, I'm doing it as a bare repository so I can use worktrees.<\/p>\n\n<p>To work on it locally, just like in production, I need to use a different URL for each language so that Drupal can identify it and load the correct content and configuration.<\/p>\n\n<p>For fixed environments like production or staging, the URLs are set in configuration files, but for ad-hoc environments such as local worktrees, I thought that the best approach was to override them as needed per worktree using Drush (a Drupal CLI tool).<\/p>\n\n<p>I could do this manually each time or I could automate it in a <code>run<\/code> command. :)<\/p>\n\n<p>Here's the function that I came up with:<\/p>\n\n<pre><code class=\"bash\">function drupal:set-urls-for-worktree {\n # Set the site URLs based on the current Git worktree name.\n local worktree_name=\"$(basename $PWD)\"\n\n local cy_url=\"cy-projectname-${worktree_name}.docker.localhost\"\n local en_url=\"projectname-${worktree_name}.docker.localhost\"\n\n # Update the URLs.\n drush config:set language.negotiation url.domains.cy -y $cy_url\n drush config:set language.negotiation url.domains.en -y $en_url\n\n # Display the domains configuration to ensure that they were set correctly.\n drush config:get language.negotiation url.domains\n}\n<\/code><\/pre>\n\n<p>It builds the worktree URL for each language based on the directory name, executes the configuration change, and finally displays the updated configuration so I can confirm that it's been set correctly.<\/p>\n\n<p>This is a good example of why I like using <code>run<\/code> files and how I use them to automate and simplify parts of my workflow.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:07+00:00",
"guid": null,
"hash": "1d0346a7d6dd5cac3b65b148563e58b2",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0ec2ce4a-6ab7-4edb-b23a-48d91780da7c"
}
],
"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": "Don't put business logic in templates"
}
],
"created": [
{
"value": "2024-01-10T00: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\/10\/dont-put-business-logic-in-templates",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Here is some code from my website:<\/p>\n\n<p><img src=\"http:\/\/localhost:8000\/assets\/images\/talk-count-code.png\" alt=\"A screenshot of the code that calculates the number of talks I've given.\" \/><\/p>\n\n<p>If you want, you can also <a href=\"https:\/\/raw.githubusercontent.com\/opdavies\/oliverdavies.uk\/main\/source\/_pages\/presentations.md\">view it on GitHub<\/a>.<\/p>\n\n<p>It is business logic responsible for counting the number of talks I've given at different events so I can display it on my Talks page.<\/p>\n\n<p>It starts at zero, loops over each talk, and increments the talk count if the event is the current day or a past date.<\/p>\n\n<p>It's only used in a single place, so the same logic isn't duplicated elsewhere.<\/p>\n\n<p>But it's in the page's Twig template.<\/p>\n\n<p>It has no test coverage.<\/p>\n\n<p>If I need to change or refactor it, I'd need to test it again manually.<\/p>\n\n<p>Don't do this.<\/p>\n\n<h2 id=\"so%2C-what-should-i-do%3F\">So, what should I do?<\/h2>\n\n<p>It's OK to put simple presentational logic, such as looping over a list or whether to show or hide a value within a template, but not complex business logic.<\/p>\n\n<p>Business logic should be separated and executed elsewhere. The values should be passed to the template to be rendered.<\/p>\n\n<p>This makes the business logic easier to test as you can test the logic itself and determine the value passed to the template is correct without being concerned about the templating engine.<\/p>\n\n<p>In an application, you may need to output a value to a template and a terminal. You'd have one source of truth, such as a Service, Action or Command class that calculates the value before passing it to the appropriate output.<\/p>\n\n<p>Once the logic is separated, you only need to test it once.<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>In a previous version of my website, I did this by creating a custom Twig function.<\/p>\n\n<p>It was as simple as adding <code><\/code> to the template.<\/p>\n\n<p>All the logic was moved from the template to my custom extension.<\/p>\n\n<p>The logic was separated.<\/p>\n\n<p>It had tests.<\/p>\n\n<p>This is the approach I'd take to achieve the same result for a client application.<\/p>\n\n<p>For a client, I want to run a test suite and be confident my logic works as expected - now and in the future.<\/p>\n\n<p>For myself, and for calculating something simple, this is fine.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Here is some code from my website:<\/p>\n\n<p><img src=\"http:\/\/localhost:8000\/assets\/images\/talk-count-code.png\" alt=\"A screenshot of the code that calculates the number of talks I've given.\"><\/p>\n\n<p>If you want, you can also <a href=\"https:\/\/raw.githubusercontent.com\/opdavies\/oliverdavies.uk\/main\/source\/_pages\/presentations.md\">view it on GitHub<\/a>.<\/p>\n\n<p>It is business logic responsible for counting the number of talks I've given at different events so I can display it on my Talks page.<\/p>\n\n<p>It starts at zero, loops over each talk, and increments the talk count if the event is the current day or a past date.<\/p>\n\n<p>It's only used in a single place, so the same logic isn't duplicated elsewhere.<\/p>\n\n<p>But it's in the page's Twig template.<\/p>\n\n<p>It has no test coverage.<\/p>\n\n<p>If I need to change or refactor it, I'd need to test it again manually.<\/p>\n\n<p>Don't do this.<\/p>\n\n<h2 id=\"so%2C-what-should-i-do%3F\">So, what should I do?<\/h2>\n\n<p>It's OK to put simple presentational logic, such as looping over a list or whether to show or hide a value within a template, but not complex business logic.<\/p>\n\n<p>Business logic should be separated and executed elsewhere. The values should be passed to the template to be rendered.<\/p>\n\n<p>This makes the business logic easier to test as you can test the logic itself and determine the value passed to the template is correct without being concerned about the templating engine.<\/p>\n\n<p>In an application, you may need to output a value to a template and a terminal. You'd have one source of truth, such as a Service, Action or Command class that calculates the value before passing it to the appropriate output.<\/p>\n\n<p>Once the logic is separated, you only need to test it once.<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>In a previous version of my website, I did this by creating a custom Twig function.<\/p>\n\n<p>It was as simple as adding <code><\/code> to the template.<\/p>\n\n<p>All the logic was moved from the template to my custom extension.<\/p>\n\n<p>The logic was separated.<\/p>\n\n<p>It had tests.<\/p>\n\n<p>This is the approach I'd take to achieve the same result for a client application.<\/p>\n\n<p>For a client, I want to run a test suite and be confident my logic works as expected - now and in the future.<\/p>\n\n<p>For myself, and for calculating something simple, this is fine.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:00+00:00",
"guid": null,
"hash": "6661315587de91080c79443d9409cf7c",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "0f50c6a3-84b8-4199-a2ba-f891b13fca5f"
}
],
"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": "Enforcing consistency with automation"
}
],
"created": [
{
"value": "2024-09-23T00: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\/23\/enforce-consistency-with-automation",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>If you're trying to <a href=\"http:\/\/localhost:8000\/daily\/2024\/09\/20\/be-consistent\">keep your code consistent<\/a>, such as following the same coding style or following conventions such as <a href=\"http:\/\/localhost:8000\/daily\/2024\/09\/05\/find-vs-get\">find vs get<\/a> or design systems such as repositories or builder classes, instead of relying on manual code review and taking the time of a colleague, you can leverage automation to run checks for you.<\/p>\n\n<p>You can run tools such as phpcs or eslint to enforce a coding style and use Git hooks or a CI pipeline to run them automatically or integrate them into your text editor or IDE so you can see and resolve issues as the code is being written.<\/p>\n\n<p>You can use static analysis tools such as PHPStan to find potential bugs but also enforce conventions by <a href=\"http:\/\/localhost:8000\/daily\/2024\/09\/22\/writing-custom-phpstan-rules-for-drupal-projects\">writing custom rules for your project<\/a> or using architectural testing tools such as PHPat.<\/p>\n\n<p>By automating checks, you'll have a consistent result every time and don't need to wait for someone else to find small issues you could have fixed quickly.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>If you're trying to <a href=\"http:\/\/localhost:8000\/daily\/2024\/09\/20\/be-consistent\">keep your code consistent<\/a>, such as following the same coding style or following conventions such as <a href=\"http:\/\/localhost:8000\/daily\/2024\/09\/05\/find-vs-get\">find vs get<\/a> or design systems such as repositories or builder classes, instead of relying on manual code review and taking the time of a colleague, you can leverage automation to run checks for you.<\/p>\n\n<p>You can run tools such as phpcs or eslint to enforce a coding style and use Git hooks or a CI pipeline to run them automatically or integrate them into your text editor or IDE so you can see and resolve issues as the code is being written.<\/p>\n\n<p>You can use static analysis tools such as PHPStan to find potential bugs but also enforce conventions by <a href=\"http:\/\/localhost:8000\/daily\/2024\/09\/22\/writing-custom-phpstan-rules-for-drupal-projects\">writing custom rules for your project<\/a> or using architectural testing tools such as PHPat.<\/p>\n\n<p>By automating checks, you'll have a consistent result every time and don't need to wait for someone else to find small issues you could have fixed quickly.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:58+00:00",
"guid": null,
"hash": "a867a7abfe6d96c4373347012cc61fe0",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "10021257-0fbb-4447-be14-036e81674cb9"
}
],
"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:05+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": "One test a day keeps bugs away\n"
}
],
"created": [
{
"value": "2022-11-18T00:00:00+00:00"
}
],
"changed": [
{
"value": "2025-04-16T14:13:05+00:00"
}
],
"promote": [
{
"value": false
}
],
"sticky": [
{
"value": false
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
],
"path": [
{
"alias": "\/daily\/2022\/11\/18\/one-test-a-day-keeps-bugs-away",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>This is a quote from a presentation by Diego Aguiar at SymfonyCon that I saw from <a href=\"https:\/\/twitter.com\/SymfonyCasts\/status\/1593551105471938560?t=A8wnRUa0tLbb2q5qLhcQnA\">a tweet from SymfonyCasts<\/a>.<\/p>\n\n<p>I haven't seen the rest of the presentation, but I liked this quote and the idea of continuously improving a codebase using automated tests.<\/p>\n\n<p>The talk was titled \"Advanced Test Driven Development\" so I assume that it was focused on ensuring that new functionality also has accompanying tests but it could also apply to existing code.<\/p>\n\n<p>A lot of existing code that I've worked on wasn't covered by tests, so going back and writing tests for that code would be beneficial too - even if it's only one test a day. It would help to prevent and uncover existing bugs, enable the code to be refactored and changed without introducing regressions, and make the codebase more maintainable.<\/p>\n\n<p>Small changes over time add up.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>This is a quote from a presentation by Diego Aguiar at SymfonyCon that I saw from <a href=\"https:\/\/twitter.com\/SymfonyCasts\/status\/1593551105471938560?t=A8wnRUa0tLbb2q5qLhcQnA\">a tweet from SymfonyCasts<\/a>.<\/p>\n\n<p>I haven't seen the rest of the presentation, but I liked this quote and the idea of continuously improving a codebase using automated tests.<\/p>\n\n<p>The talk was titled \"Advanced Test Driven Development\" so I assume that it was focused on ensuring that new functionality also has accompanying tests but it could also apply to existing code.<\/p>\n\n<p>A lot of existing code that I've worked on wasn't covered by tests, so going back and writing tests for that code would be beneficial too - even if it's only one test a day. It would help to prevent and uncover existing bugs, enable the code to be refactored and changed without introducing regressions, and make the codebase more maintainable.<\/p>\n\n<p>Small changes over time add up.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:05+00:00",
"guid": null,
"hash": "53ec3852bd16e9152d04d6bd43553d18",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "103c1386-9cde-4e56-9779-7c0d0859b696"
}
],
"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": "Using Ansible for local environment configuration"
}
],
"created": [
{
"value": "2022-09-05T00: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\/09\/05\/using-ansible-for-local-configuration",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>As well as <a href=\"http:\/\/localhost:8000\/daily\/2022\/09\/04\/using-ansible-for-server-configuration\">configuring servers<\/a>, you can use Ansible to configure your own local machine and development environment.<\/p>\n\n<p>The change that you need to make is within the <code>hosts.ini<\/code> file:<\/p>\n\n<pre><code>127.0.0.1 ansible_connection=local\n<\/code><\/pre>\n\n<p>Instead of the server's IP address or hostname, use the localhost IP address and set <code>ansible_connection<\/code> to <code>local<\/code> to tell Ansible to run locally instead of using an SSH connection.<\/p>\n\n<p>Another way to do this is to set <code>hosts: 127.0.0.1<\/code> and <code>connection: true<\/code> in your playbook.<\/p>\n\n<p>Once this is done, you can run tasks, roles, and collections to automate tasks such as installing software, adding your SSH keys, configuring your project directories, and anything else that you need to do.<\/p>\n\n<p>For an example of this, you can see <a href=\"https:\/\/github.com\/opdavies\/dotfiles\">my dotfiles repository on GitHub<\/a>.<\/p>\n\n<hr \/>\n\n<p>Want to learn more about how I use Ansible? <a href=\"http:\/\/localhost:8000\/ansible-course\">Register for my upcoming free email course<\/a>.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>As well as <a href=\"http:\/\/localhost:8000\/daily\/2022\/09\/04\/using-ansible-for-server-configuration\">configuring servers<\/a>, you can use Ansible to configure your own local machine and development environment.<\/p>\n\n<p>The change that you need to make is within the <code>hosts.ini<\/code> file:<\/p>\n\n<pre><code>127.0.0.1 ansible_connection=local\n<\/code><\/pre>\n\n<p>Instead of the server's IP address or hostname, use the localhost IP address and set <code>ansible_connection<\/code> to <code>local<\/code> to tell Ansible to run locally instead of using an SSH connection.<\/p>\n\n<p>Another way to do this is to set <code>hosts: 127.0.0.1<\/code> and <code>connection: true<\/code> in your playbook.<\/p>\n\n<p>Once this is done, you can run tasks, roles, and collections to automate tasks such as installing software, adding your SSH keys, configuring your project directories, and anything else that you need to do.<\/p>\n\n<p>For an example of this, you can see <a href=\"https:\/\/github.com\/opdavies\/dotfiles\">my dotfiles repository on GitHub<\/a>.<\/p>\n\n<hr>\n\n<p>Want to learn more about how I use Ansible? <a href=\"http:\/\/localhost:8000\/ansible-course\">Register for my upcoming free email course<\/a>.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:06+00:00",
"guid": null,
"hash": "b5a41270135fb96aaed1d4455d154c36",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "10798a5f-35e3-475a-b9ec-fb43d88d99a8"
}
],
"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": "Writing test and implementation code are the same task\n"
}
],
"created": [
{
"value": "2023-08-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\/08\/15\/writing-test-and-implementation-code-are-the-same-task",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>In Sunday's email, I said not to estimate separately for testing and implementation.<\/p>\n\n<p>But you can't do this anyway if you're doing test-driven development.<\/p>\n\n<p>With TDD, you aren't writing all of your tests and all of the implementation code or vice versa.<\/p>\n\n<p>You're continuously switching back and forth, starting by writing a failing test and then enough implementation code for it to pass.<\/p>\n\n<p>Then you write more test code, whether expanding the same test or writing a new one until you have a new failure.<\/p>\n\n<p>You get it to pass, refactor, and repeat the process until the task is complete.<\/p>\n\n<p>It's all part of the same task and the same estimate.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>In Sunday's email, I said not to estimate separately for testing and implementation.<\/p>\n\n<p>But you can't do this anyway if you're doing test-driven development.<\/p>\n\n<p>With TDD, you aren't writing all of your tests and all of the implementation code or vice versa.<\/p>\n\n<p>You're continuously switching back and forth, starting by writing a failing test and then enough implementation code for it to pass.<\/p>\n\n<p>Then you write more test code, whether expanding the same test or writing a new one until you have a new failure.<\/p>\n\n<p>You get it to pass, refactor, and repeat the process until the task is complete.<\/p>\n\n<p>It's all part of the same task and the same estimate.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:02+00:00",
"guid": null,
"hash": "07d86990b8ff8b2156a15058bc92d8e4",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "10813c17-b6a2-4d01-a95c-e944f0339c31"
}
],
"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": "Learning by reading"
}
],
"created": [
{
"value": "2025-01-16T00: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\/01\/16\/learning",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>When I'm working as part of a Development team, working on open source projects, watching live streams or reading code that people have released online - such as their website or dotfiles - I read a lot of other people's code.<\/p>\n\n<p>It's an opportunity to learn from others and how they approach things.<\/p>\n\n<p>How they achieved a desired result or fixed a bug, which I can learn from.<\/p>\n\n<p>I can refer to it if I need to do something similar in the future.<\/p>\n\n<p>Or I may find something random that's I didn't know I needed, such as a module, library or configuration setting I wasn't aware of.<\/p>\n\n<p>I recently learned about .mailmap files from Greg Hurrell (wincent)'s dotfiles on GitHub.<\/p>\n\n<p>.mailmap is a file that is used by Git when displaying history, such as running <code>git log<\/code>, and allows you to define canonical names and email addresses for committers and contributors.<\/p>\n\n<p>I've accidentally used the wrong email address or typed my name incorrectly before in my Git configuration, which was there for all to see, but this file allows me to consolidate my identities within a repository so my commits are grouped together and attributed to me, regardless of which email address I used or how I wrote my name.<\/p>\n\n<p>You can see <a href=\"https:\/\/code.oliverdavies.uk\/opdavies\/oliverdavies.uk\/src\/commit\/633e11abedfa4cc8d85d37695c1ca014874fd4c1\/.mailmap\">the one I added to my website directory<\/a>.<\/p>\n\n<p>If I hadn't looked at that repository, I wouldn't have learned about it.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>When I'm working as part of a Development team, working on open source projects, watching live streams or reading code that people have released online - such as their website or dotfiles - I read a lot of other people's code.<\/p>\n\n<p>It's an opportunity to learn from others and how they approach things.<\/p>\n\n<p>How they achieved a desired result or fixed a bug, which I can learn from.<\/p>\n\n<p>I can refer to it if I need to do something similar in the future.<\/p>\n\n<p>Or I may find something random that's I didn't know I needed, such as a module, library or configuration setting I wasn't aware of.<\/p>\n\n<p>I recently learned about .mailmap files from Greg Hurrell (wincent)'s dotfiles on GitHub.<\/p>\n\n<p>.mailmap is a file that is used by Git when displaying history, such as running <code>git log<\/code>, and allows you to define canonical names and email addresses for committers and contributors.<\/p>\n\n<p>I've accidentally used the wrong email address or typed my name incorrectly before in my Git configuration, which was there for all to see, but this file allows me to consolidate my identities within a repository so my commits are grouped together and attributed to me, regardless of which email address I used or how I wrote my name.<\/p>\n\n<p>You can see <a href=\"https:\/\/code.oliverdavies.uk\/opdavies\/oliverdavies.uk\/src\/commit\/633e11abedfa4cc8d85d37695c1ca014874fd4c1\/.mailmap\">the one I added to my website directory<\/a>.<\/p>\n\n<p>If I hadn't looked at that repository, I wouldn't have learned about it.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:56+00:00",
"guid": null,
"hash": "87636a91b190a3f76a189dfb1cfa6e92",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "109ba717-65c7-4cf6-bb26-f1c71afd6cdc"
}
],
"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": "Should you strictly enforce the 50\/72 rule?"
}
],
"created": [
{
"value": "2024-05-18T00: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\/05\/18\/should-you-strictly-enforce-the-5072-rule",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p><a href=\"http:\/\/localhost:8000\/daily\/2024\/05\/17\/why-i-dont-commit-with--m\">Yesterday<\/a>, I mentioned the 50\/72 rule when writing Git commit messages.<\/p>\n\n<p>The first line in the commit message is the subject line and should be no longer than 50 characters.<\/p>\n\n<p>Any additional lines are the message body and should be wrapped at 72 characters.<\/p>\n\n<p>As I said, I have Neovim configured to format my commit messages based on these rules, although they're more like guidelines.<\/p>\n\n<p>There's no hard limit on the number of characters in the subject line or the number of characters in the body.<\/p>\n\n<p>The commit will work and not be rejected when pushing to your remote repository.<\/p>\n\n<p>There are likely post-commit <a href=\"http:\/\/localhost:8000\/daily\/2022\/08\/16\/what-are-git-hooks-why-are-they-useful\">Git hooks<\/a> to do this, but by default, things will work.<\/p>\n\n<p>A commit message to Drupal core today was 178 characters long, including the issue ID and contributors.<\/p>\n\n<p>When working on project teams, ideally, everyone would follow the 50\/72 rule, but if they don't consistently, I don't think it's an issue.<\/p>\n\n<p>I'd rather they focused on writing a good and descriptive commit message and if it's formatted correctly, that's a bonus.<\/p>\n\n<p>Whilst I could automate checks for this, I don't think it's the best use of everyone's time and, especially for Junior Developers who already have enough to learn already, not where their focus should be.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p><a href=\"http:\/\/localhost:8000\/daily\/2024\/05\/17\/why-i-dont-commit-with--m\">Yesterday<\/a>, I mentioned the 50\/72 rule when writing Git commit messages.<\/p>\n\n<p>The first line in the commit message is the subject line and should be no longer than 50 characters.<\/p>\n\n<p>Any additional lines are the message body and should be wrapped at 72 characters.<\/p>\n\n<p>As I said, I have Neovim configured to format my commit messages based on these rules, although they're more like guidelines.<\/p>\n\n<p>There's no hard limit on the number of characters in the subject line or the number of characters in the body.<\/p>\n\n<p>The commit will work and not be rejected when pushing to your remote repository.<\/p>\n\n<p>There are likely post-commit <a href=\"http:\/\/localhost:8000\/daily\/2022\/08\/16\/what-are-git-hooks-why-are-they-useful\">Git hooks<\/a> to do this, but by default, things will work.<\/p>\n\n<p>A commit message to Drupal core today was 178 characters long, including the issue ID and contributors.<\/p>\n\n<p>When working on project teams, ideally, everyone would follow the 50\/72 rule, but if they don't consistently, I don't think it's an issue.<\/p>\n\n<p>I'd rather they focused on writing a good and descriptive commit message and if it's formatted correctly, that's a bonus.<\/p>\n\n<p>Whilst I could automate checks for this, I don't think it's the best use of everyone's time and, especially for Junior Developers who already have enough to learn already, not where their focus should be.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:59+00:00",
"guid": null,
"hash": "8fef3d909b3ef76bcd25bb9610301eb8",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "10b7ad4b-bd5a-49d6-a041-7bd26bf6337d"
}
],
"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:05+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": "How and why I started using PostCSS\n"
}
],
"created": [
{
"value": "2022-12-09T00:00:00+00:00"
}
],
"changed": [
{
"value": "2025-04-16T14:13:05+00:00"
}
],
"promote": [
{
"value": false
}
],
"sticky": [
{
"value": false
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
],
"path": [
{
"alias": "\/daily\/2022\/12\/09\/how-and-why-i-started-using-postcss",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>I assume that, like many other Developers, when I started learning front-end development, I wrote normal, plain CSS and later discovered and adopted pre-processors like Less and Sass that added features such as variables and nesting to my stylesheets.<\/p>\n\n<p>This was the case when I first saw what became Tailwind CSS, which were some stylesheets written in Less and ported manually between projects.<\/p>\n\n<p>I remember watching one of those streams, and a fellow viewer suggested PostCSS, which Tailwind CSS would later be written in.<\/p>\n\n<p>PostCSS, a CSS post-processor rather than a pre-processor, has become my preferred way of writing CSS because of Tailwind.<\/p>\n\n<p>When I started using Tailwind in my projects, I was layering it on top of another CSS framework or styles that were written using Less or Sass, so I needed to pre-process them into CSS first and then run PostCSS - essentially running two build steps and adding to the build time.<\/p>\n\n<p>I moved to use PostCSS by default - removing one of the build steps.<\/p>\n\n<p>What I liked about it, as well as the quicker build times, was that I could start with plain CSS and add the extra features I needed. I didn't use all of Sass and Less' features, and now, if I needed nesting or real-time imports, I could add it via a PostCSS plugin or write my own.<\/p>\n\n<p>It's also quick and easy to use, using the PostCSS CLI tool and without more complex tools like Webpack.<\/p>\n\n<p>If you haven't tried PostCSS, I recommend taking a look.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>I assume that, like many other Developers, when I started learning front-end development, I wrote normal, plain CSS and later discovered and adopted pre-processors like Less and Sass that added features such as variables and nesting to my stylesheets.<\/p>\n\n<p>This was the case when I first saw what became Tailwind CSS, which were some stylesheets written in Less and ported manually between projects.<\/p>\n\n<p>I remember watching one of those streams, and a fellow viewer suggested PostCSS, which Tailwind CSS would later be written in.<\/p>\n\n<p>PostCSS, a CSS post-processor rather than a pre-processor, has become my preferred way of writing CSS because of Tailwind.<\/p>\n\n<p>When I started using Tailwind in my projects, I was layering it on top of another CSS framework or styles that were written using Less or Sass, so I needed to pre-process them into CSS first and then run PostCSS - essentially running two build steps and adding to the build time.<\/p>\n\n<p>I moved to use PostCSS by default - removing one of the build steps.<\/p>\n\n<p>What I liked about it, as well as the quicker build times, was that I could start with plain CSS and add the extra features I needed. I didn't use all of Sass and Less' features, and now, if I needed nesting or real-time imports, I could add it via a PostCSS plugin or write my own.<\/p>\n\n<p>It's also quick and easy to use, using the PostCSS CLI tool and without more complex tools like Webpack.<\/p>\n\n<p>If you haven't tried PostCSS, I recommend taking a look.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:05+00:00",
"guid": null,
"hash": "df6cac0e06b78b0e457c5d6f45c51e64",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "110760fe-ad47-4549-ac15-21e2e7191ed3"
}
],
"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": "Why write software for this?"
}
],
"created": [
{
"value": "2024-03-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\/03\/07\/why-write-software-for-this",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Recently, when speaking with prospective clients, instead of focusing on the features they want, I like to ask questions like:<\/p>\n\n<p>What's wrong with what you have now?<\/p>\n\n<p>Why not fix what you have instead of rebuilding?<\/p>\n\n<p>What problem are you trying to solve?<\/p>\n\n<p>Why write software for this at all?<\/p>\n\n<p>Why not do it manually?<\/p>\n\n<p>Why do you need to do this now?<\/p>\n\n<p>What results would you expect to see after this project?<\/p>\n\n<p>If they can answer these types of questions and explain the business outcomes they're looking to achieve and what value I can add, those are the projects I want to work on.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Recently, when speaking with prospective clients, instead of focusing on the features they want, I like to ask questions like:<\/p>\n\n<p>What's wrong with what you have now?<\/p>\n\n<p>Why not fix what you have instead of rebuilding?<\/p>\n\n<p>What problem are you trying to solve?<\/p>\n\n<p>Why write software for this at all?<\/p>\n\n<p>Why not do it manually?<\/p>\n\n<p>Why do you need to do this now?<\/p>\n\n<p>What results would you expect to see after this project?<\/p>\n\n<p>If they can answer these types of questions and explain the business outcomes they're looking to achieve and what value I can add, those are the projects I want to work on.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:00+00:00",
"guid": null,
"hash": "e52d296563e9560ea0b8d1060cc77506",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "11b6c399-2466-4ad4-818e-e70e826e1c2d"
}
],
"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": "Override Node Options and Drupal 11"
}
],
"created": [
{
"value": "2024-12-02T00: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\/02\/override-node-options-and-drupal-11",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Last week, I released a new version of the <a href=\"https:\/\/www.drupal.org\/project\/override_node_options\">Override Node Options module<\/a> - version 8.x-2.9.<\/p>\n\n<p>This version makes the module compatible with Drupal 11 and, as there are no breaking changes, it's still compatible with Drupal 9 and 10.<\/p>\n\n<p>It's great to see the module used on many Drupal 8+ websites and distributions such as <a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/17\/override-node-options-used-by-localgov-drupal\">LocalGov Drupal<\/a>.<\/p>\n\n<p>Whilst <a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/16\/an-interesting-thing-i-spotted-about-the-override-node-options-module\">the overall number of installations has been consistent<\/a>, the number of Drupal 7 installations has decreased whilst the Drupal 8+ version installations have increased.<\/p>\n\n<p>With a Drupal 11-compatible version now available, I hope it continues to increase.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Last week, I released a new version of the <a href=\"https:\/\/www.drupal.org\/project\/override_node_options\">Override Node Options module<\/a> - version 8.x-2.9.<\/p>\n\n<p>This version makes the module compatible with Drupal 11 and, as there are no breaking changes, it's still compatible with Drupal 9 and 10.<\/p>\n\n<p>It's great to see the module used on many Drupal 8+ websites and distributions such as <a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/17\/override-node-options-used-by-localgov-drupal\">LocalGov Drupal<\/a>.<\/p>\n\n<p>Whilst <a href=\"http:\/\/localhost:8000\/daily\/2024\/11\/16\/an-interesting-thing-i-spotted-about-the-override-node-options-module\">the overall number of installations has been consistent<\/a>, the number of Drupal 7 installations has decreased whilst the Drupal 8+ version installations have increased.<\/p>\n\n<p>With a Drupal 11-compatible version now available, I hope it continues to increase.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:57+00:00",
"guid": null,
"hash": "c073c1355c5aca882b7ed13c49447c41",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "1293fb0c-837c-4870-82f9-d5c46745bb35"
}
],
"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": "Cleaner PHP code with promoted constructor properties\n"
}
],
"created": [
{
"value": "2023-04-12T00: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\/04\/12\/cleaner-php-code-with-promoted-constructor-properties",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>One of my favorite features that was introducted in PHP 8 was promoted constructor properties.<\/p>\n\n<p>If I'm passing arguments into a constructor, I can declare a visibility and it will be promoted to a property on the class.<\/p>\n\n<p>Here's an example of a value of a data transfer object that accepts a sort code and account number as strings:<\/p>\n\n<pre><code class=\"language-php\">class AccountDetails {\n\n public function __construct(\n public string $accountNumber,\n public string $sortCode,\n ) {}\n\n}\n<\/code><\/pre>\n\n<p>Without promoted constructor properties, I'd need to create the properties and assign them manually, and I'd have this:<\/p>\n\n<pre><code class=\"language-php\">class AccountDetails {\n\n public string $accountNumber;\n\n public string $sortCode;\n\n public function __construct(\n string $accountNumber,\n string $sortCode,\n ) {\n $this-&gt;accountNumber = $accountNumber;\n $this-&gt;sortCode = $sortCode;\n }\n\n}\n<\/code><\/pre>\n\n<p>Whilst text editors and IDEs can create the properties automatically, I prefer this as it's less code, more readable and easier to understand.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>One of my favorite features that was introducted in PHP 8 was promoted constructor properties.<\/p>\n\n<p>If I'm passing arguments into a constructor, I can declare a visibility and it will be promoted to a property on the class.<\/p>\n\n<p>Here's an example of a value of a data transfer object that accepts a sort code and account number as strings:<\/p>\n\n<pre><code class=\"language-php\">class AccountDetails {\n\n public function __construct(\n public string $accountNumber,\n public string $sortCode,\n ) {}\n\n}\n<\/code><\/pre>\n\n<p>Without promoted constructor properties, I'd need to create the properties and assign them manually, and I'd have this:<\/p>\n\n<pre><code class=\"language-php\">class AccountDetails {\n\n public string $accountNumber;\n\n public string $sortCode;\n\n public function __construct(\n string $accountNumber,\n string $sortCode,\n ) {\n $this-&gt;accountNumber = $accountNumber;\n $this-&gt;sortCode = $sortCode;\n }\n\n}\n<\/code><\/pre>\n\n<p>Whilst text editors and IDEs can create the properties automatically, I prefer this as it's less code, more readable and easier to understand.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:04+00:00",
"guid": null,
"hash": "6e7b86e1b92a964e048c10b4a1bb65da",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "12960a25-eada-4232-9f35-00f5dd91910f"
}
],
"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": "Self hosting the Beyond Blocks podcast"
}
],
"created": [
{
"value": "2024-12-18T00: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\/18\/self-hosting-podcast",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>As part of my recent digital simplification, I've started hosting the episodes of the <a href=\"http:\/\/localhost:8000\/podcast\">Beyond Blocks podcast<\/a> myself.<\/p>\n\n<p>I've always had the podcast pages on my website and used them as the primary pages to redirect people for information about the episodes.<\/p>\n\n<p>I embedded an audio player from a third party service that were hosting the files, but have replaced it with the native HTML audio element.<\/p>\n\n<p>It's simpler, but I like it.<\/p>\n\n<p>My website is built with Sculpin, so I was able to do this easily with Twig by adding the path to the MP3 file to each episode:<\/p>\n\n<pre><code class=\"twig\">&lt;audio controls&gt;\n &lt;source src=\"\/files\/bb\/episodes\/{{ page.episode_filename }}\" type=\"audio\/mp3\"&gt;\n&lt;\/audio&gt;\n<\/code><\/pre>\n\n<p>This is already live. You can see it on <a href=\"http:\/\/localhost:8000\/podcast\/25-jess-archer-drush-laravel-prompts\">any of the podcast episode pages<\/a>.<\/p>\n\n<p>The other thing I'm using is the feed that publishes episodes to Spotify, iTunes, etc.<\/p>\n\n<p>But I have all the information and ability to create this myself.<\/p>\n\n<p>This part is still in development and I'll need to test it before switching to it, but it will mean the feed URL will change and people may need to resubscribe.<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>Using a hosted service was a great way to get the podcast up and running quickly, but I'm looking forward to having more control over it, even if it involves a little upfront development work.<\/p>\n\n<p>But, it will make it easier to post future episodes as I'll be able to do it all in one place.<\/p>\n\n<p>I have some new guests lined up for 2025, which I'm looking forward to.<\/p>\n\n<p>If you want to be a guest on the podcast or want to make a suggestion for someone I should have on, reply to this email and let me know.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>As part of my recent digital simplification, I've started hosting the episodes of the <a href=\"http:\/\/localhost:8000\/podcast\">Beyond Blocks podcast<\/a> myself.<\/p>\n\n<p>I've always had the podcast pages on my website and used them as the primary pages to redirect people for information about the episodes.<\/p>\n\n<p>I embedded an audio player from a third party service that were hosting the files, but have replaced it with the native HTML audio element.<\/p>\n\n<p>It's simpler, but I like it.<\/p>\n\n<p>My website is built with Sculpin, so I was able to do this easily with Twig by adding the path to the MP3 file to each episode:<\/p>\n\n<pre><code class=\"twig\">&lt;audio controls&gt;\n &lt;source src=\"\/files\/bb\/episodes\/{{ page.episode_filename }}\" type=\"audio\/mp3\"&gt;\n&lt;\/audio&gt;\n<\/code><\/pre>\n\n<p>This is already live. You can see it on <a href=\"http:\/\/localhost:8000\/podcast\/25-jess-archer-drush-laravel-prompts\">any of the podcast episode pages<\/a>.<\/p>\n\n<p>The other thing I'm using is the feed that publishes episodes to Spotify, iTunes, etc.<\/p>\n\n<p>But I have all the information and ability to create this myself.<\/p>\n\n<p>This part is still in development and I'll need to test it before switching to it, but it will mean the feed URL will change and people may need to resubscribe.<\/p>\n\n<h2 id=\"here%27s-the-thing\">Here's the thing<\/h2>\n\n<p>Using a hosted service was a great way to get the podcast up and running quickly, but I'm looking forward to having more control over it, even if it involves a little upfront development work.<\/p>\n\n<p>But, it will make it easier to post future episodes as I'll be able to do it all in one place.<\/p>\n\n<p>I have some new guests lined up for 2025, which I'm looking forward to.<\/p>\n\n<p>If you want to be a guest on the podcast or want to make a suggestion for someone I should have on, reply to this email and let me know.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:57+00:00",
"guid": null,
"hash": "0dd05a62a76fc7a1d8279b63d5566798",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "12b2ecc6-70e9-497e-ad49-68980fa0e935"
}
],
"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": "What's the simplest test to begin with?\n"
}
],
"created": [
{
"value": "2023-09-07T00: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\/07\/what-s-the-simplest-test-to-begin-with",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>When giving talks and workshops or coaching on automated testing and test-driven development, some people may not have written tests before and aren't familiar with the structure or know where to begin.<\/p>\n\n<p>In the workshops I ran for DrupalCamp London and DrupalCamp NYC, I wanted to cover this first before writing any implementation code.<\/p>\n\n<p>Where do you put a test class, and what does it contain?<\/p>\n\n<p>How do you run the tests, and how can you make it pass or fail?<\/p>\n\n<h2 id=\"what-we-did\">What we did<\/h2>\n\n<p>To start, we wrote a test for existing functionality within Drupal core - anonymous users can visit the front page.<\/p>\n\n<p>This is the whole test:<\/p>\n\n<pre><code class=\"language-php\">&lt;?php\n\nnamespace Drupal\\Tests\\my_module\\Functional;\n\nuse Drupal\\Tests\\BrowserTestBase;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass MyModuleTest extends BrowserTestBase {\n\n\u00a0 protected $defaultTheme = 'stark';\n\n\u00a0 \/** @test *\/\n\u00a0 public function the_front_page_loads_for_anonymous_users() {\n\u00a0 \u00a0 $this-&gt;drupalGet('&lt;front&gt;');\n\n\u00a0 \u00a0 $this-&gt;assertResponse(Response::HTTP_OK);\n\u00a0 }\n\n}\n<\/code><\/pre>\n\n<p>This is a test someone can write, run and see the test pass.<\/p>\n\n<p>They can then experiment by changing the values to make the test fail in different ways.<\/p>\n\n<h2 id=\"what-next%3F\">What next?<\/h2>\n\n<p>Then, we tested anonymous users cannot access the administration pages, which is also already the case in Drupal core, and then authenticated users with the correct permissions could access them.<\/p>\n\n<p>People were getting the idea by now, and we moved on to writing and testing some of our own code.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>When giving talks and workshops or coaching on automated testing and test-driven development, some people may not have written tests before and aren't familiar with the structure or know where to begin.<\/p>\n\n<p>In the workshops I ran for DrupalCamp London and DrupalCamp NYC, I wanted to cover this first before writing any implementation code.<\/p>\n\n<p>Where do you put a test class, and what does it contain?<\/p>\n\n<p>How do you run the tests, and how can you make it pass or fail?<\/p>\n\n<h2 id=\"what-we-did\">What we did<\/h2>\n\n<p>To start, we wrote a test for existing functionality within Drupal core - anonymous users can visit the front page.<\/p>\n\n<p>This is the whole test:<\/p>\n\n<pre><code class=\"language-php\">&lt;?php\n\nnamespace Drupal\\Tests\\my_module\\Functional;\n\nuse Drupal\\Tests\\BrowserTestBase;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass MyModuleTest extends BrowserTestBase {\n\n&nbsp; protected $defaultTheme = 'stark';\n\n&nbsp; \/** @test *\/\n&nbsp; public function the_front_page_loads_for_anonymous_users() {\n&nbsp; &nbsp; $this-&gt;drupalGet('&lt;front&gt;');\n\n&nbsp; &nbsp; $this-&gt;assertResponse(Response::HTTP_OK);\n&nbsp; }\n\n}\n<\/code><\/pre>\n\n<p>This is a test someone can write, run and see the test pass.<\/p>\n\n<p>They can then experiment by changing the values to make the test fail in different ways.<\/p>\n\n<h2 id=\"what-next%3F\">What next?<\/h2>\n\n<p>Then, we tested anonymous users cannot access the administration pages, which is also already the case in Drupal core, and then authenticated users with the correct permissions could access them.<\/p>\n\n<p>People were getting the idea by now, and we moved on to writing and testing some of our own code.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:02+00:00",
"guid": null,
"hash": "af41a18ab3e2e252ab86e7243333ad9f",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "1301b49a-e997-4ed4-825d-01e5e06443b8"
}
],
"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": "Back to live streaming"
}
],
"created": [
{
"value": "2024-03-01T00: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\/03\/01\/back-to-live-streaming",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Today, after saying I wanted to get back into it, I did my first live coding stream in over two years.<\/p>\n\n<p>I spent around an hour and a half working on <a href=\"http:\/\/localhost:8000\/build-configs\">Build Configs<\/a> - a Symfony command line application I wrote to generate build configuration files.<\/p>\n\n<p>To learn more about it, I <a href=\"http:\/\/localhost:8000\/presentations\/building-build-configs\">gave a meetup talk<\/a> about it recently.<\/p>\n\n<p>On stream, I explained what Build Configs is, added a <code>--dry-run<\/code> option that prevents any files from being created and started to look again at Behat to see how I might use it to test the app's functionality.<\/p>\n\n<p>I want to do at least one stream a week going forward. Most likely, it will be on Friday afternoons.\nYou can <a href=\"https:\/\/www.youtube.com\/watch?v=Wlkcf1PLWN8\">watch the full stream<\/a> on YouTube now. I'll split it into sections this week and upload some lightly edited versions to the same channel.<\/p>\n\n<p>I won't post a link to every video here, so please subscribe to my YouTube channel to be notified when I go live next.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Today, after saying I wanted to get back into it, I did my first live coding stream in over two years.<\/p>\n\n<p>I spent around an hour and a half working on <a href=\"http:\/\/localhost:8000\/build-configs\">Build Configs<\/a> - a Symfony command line application I wrote to generate build configuration files.<\/p>\n\n<p>To learn more about it, I <a href=\"http:\/\/localhost:8000\/presentations\/building-build-configs\">gave a meetup talk<\/a> about it recently.<\/p>\n\n<p>On stream, I explained what Build Configs is, added a <code>--dry-run<\/code> option that prevents any files from being created and started to look again at Behat to see how I might use it to test the app's functionality.<\/p>\n\n<p>I want to do at least one stream a week going forward. Most likely, it will be on Friday afternoons.\nYou can <a href=\"https:\/\/www.youtube.com\/watch?v=Wlkcf1PLWN8\">watch the full stream<\/a> on YouTube now. I'll split it into sections this week and upload some lightly edited versions to the same channel.<\/p>\n\n<p>I won't post a link to every video here, so please subscribe to my YouTube channel to be notified when I go live next.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:00+00:00",
"guid": null,
"hash": "84e3303878d45f3d98d9f6fb684f34d0",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "13e4fea4-532c-4d15-890e-3deac97e7a72"
}
],
"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": "Credited on 200 fixed issues on Drupal.org\n"
}
],
"created": [
{
"value": "2023-06-24T00: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\/24\/credited-on-200-fixed-issues",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>While writing yesterday's email, I saw this on my Drupal.org profile (https:\/\/www.drupal.org\/u\/opdavies):<\/p>\n\n<blockquote>\n <p>Credited on 200 fixed issues<\/p>\n<\/blockquote>\n\n<p>This is not the number of commits I've made to projects and doesn't include issues on other websites like GitHub, but that I've been tagged as a contributor in 200 fixed issues on Drupal.org - aka \"contribution credits\".<\/p>\n\n<p>It includes issues for projects I maintain, like Override Node Options and the Tailwind CSS starter kit, as well as contributions for events I've spoken at, like DrupalCon Europe and BADCamp, and modules for Drupal.org itself that I worked on whilst at the Drupal Association.<\/p>\n\n<p>It also includes contrib projects others maintain, such as the Feature Toggle module and Drupal Commerce, and distributions like Commerce Kickstart and Open Atrium.<\/p>\n\n<p>In particular, I'm proud of the 17 issues for Drupal core - some for patches I've contributed, some I've reviewed, and some whilst I've mentored at events like DrupalCons and DrupalCamps.<\/p>\n\n<p>I've been allocating more open-source time recently, so expect the number of contributions on Drupal.org and GitHub to continue.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>While writing yesterday's email, I saw this on my Drupal.org profile (https:\/\/www.drupal.org\/u\/opdavies):<\/p>\n\n<blockquote>\n <p>Credited on 200 fixed issues<\/p>\n<\/blockquote>\n\n<p>This is not the number of commits I've made to projects and doesn't include issues on other websites like GitHub, but that I've been tagged as a contributor in 200 fixed issues on Drupal.org - aka \"contribution credits\".<\/p>\n\n<p>It includes issues for projects I maintain, like Override Node Options and the Tailwind CSS starter kit, as well as contributions for events I've spoken at, like DrupalCon Europe and BADCamp, and modules for Drupal.org itself that I worked on whilst at the Drupal Association.<\/p>\n\n<p>It also includes contrib projects others maintain, such as the Feature Toggle module and Drupal Commerce, and distributions like Commerce Kickstart and Open Atrium.<\/p>\n\n<p>In particular, I'm proud of the 17 issues for Drupal core - some for patches I've contributed, some I've reviewed, and some whilst I've mentored at events like DrupalCons and DrupalCamps.<\/p>\n\n<p>I've been allocating more open-source time recently, so expect the number of contributions on Drupal.org and GitHub to continue.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:03+00:00",
"guid": null,
"hash": "40f6813369f442150f84b87eab9b30ed",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "1420eeef-8e96-4932-8ab5-b27573d07989"
}
],
"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": "Done is better than perfect\n"
}
],
"created": [
{
"value": "2023-06-15T00: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\/15\/done-is-better-than-perfect",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Whether I'm doing test-driven development or not, I focus on making the feature work or fixing the bug in the simplest way possible.<\/p>\n\n<p>Then, once this is done, I can pass through the code again and refactor it as needed.<\/p>\n\n<p>I can split code into separate files, move logic from the Controller into separate services, ensure that patterns like dependency injection are followed, and checks like coding standards and static analysis are passing.<\/p>\n\n<p>I might deploy a change in its passing state and leave TODO comments or create follow-up issues to describe potential refactors or changes to be addressed later.<\/p>\n\n<p>The main goal is to get the application working and providing value for its users.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Whether I'm doing test-driven development or not, I focus on making the feature work or fixing the bug in the simplest way possible.<\/p>\n\n<p>Then, once this is done, I can pass through the code again and refactor it as needed.<\/p>\n\n<p>I can split code into separate files, move logic from the Controller into separate services, ensure that patterns like dependency injection are followed, and checks like coding standards and static analysis are passing.<\/p>\n\n<p>I might deploy a change in its passing state and leave TODO comments or create follow-up issues to describe potential refactors or changes to be addressed later.<\/p>\n\n<p>The main goal is to get the application working and providing value for its users.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:03+00:00",
"guid": null,
"hash": "4830962fbdad357d03b6e3606bb1df38",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "144642ab-380d-4f8a-8aa2-364f430fb413"
}
],
"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": "Using Tailwind CSS is a great way to learn CSS"
}
],
"created": [
{
"value": "2024-01-09T00: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\/09\/using-tailwind-css-is-a-great-way-to-learn-css",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>I was in a pair programming session today, working on some Twig components with Tailwind CSS.<\/p>\n\n<p>We knew what we needed to implement and did so based on an example from a Tailwind component library and some additional styles.<\/p>\n\n<p>After implementing the feature, we could review the classes we added and review what each did.<\/p>\n\n<p>We could easily move or remove a class and see what effect it had.<\/p>\n\n<p>Something nice is that the Tailwind classes usually relate to what CSS they're applying, such as <code>block<\/code> and <code>flex<\/code> for <code>display<\/code> and <code>relative<\/code> and <code>absolute<\/code> for positioning.<\/p>\n\n<p>This makes Tailwind a great way to learn CSS compared to other frameworks that give you prebuilt HTML and expect you to add a generic class like <code>card<\/code>.<\/p>\n\n<p>In that case, the knowledge is hidden within a stylesheet the Developer doesn't see, which makes it harder to read and learn from.<\/p>\n\n<p>Other utility-class frameworks have shorter class names that are less readable.<\/p>\n\n<p>Tailwind strikes the perfect balance, in my opinion.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>I was in a pair programming session today, working on some Twig components with Tailwind CSS.<\/p>\n\n<p>We knew what we needed to implement and did so based on an example from a Tailwind component library and some additional styles.<\/p>\n\n<p>After implementing the feature, we could review the classes we added and review what each did.<\/p>\n\n<p>We could easily move or remove a class and see what effect it had.<\/p>\n\n<p>Something nice is that the Tailwind classes usually relate to what CSS they're applying, such as <code>block<\/code> and <code>flex<\/code> for <code>display<\/code> and <code>relative<\/code> and <code>absolute<\/code> for positioning.<\/p>\n\n<p>This makes Tailwind a great way to learn CSS compared to other frameworks that give you prebuilt HTML and expect you to add a generic class like <code>card<\/code>.<\/p>\n\n<p>In that case, the knowledge is hidden within a stylesheet the Developer doesn't see, which makes it harder to read and learn from.<\/p>\n\n<p>Other utility-class frameworks have shorter class names that are less readable.<\/p>\n\n<p>Tailwind strikes the perfect balance, in my opinion.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:00+00:00",
"guid": null,
"hash": "7b51105f14f48cc301c0e7b3db261bab",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "1457df52-83fe-473d-81d7-040b585104b5"
}
],
"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": "Only write enough code to get a failing test\n"
}
],
"created": [
{
"value": "2023-05-08T00: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\/05\/08\/only-write-enough-code-to-get-a-failing-test",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Instead of writing a whole test and then attempting to make it pass, only write enough code to get the test to fail.<\/p>\n\n<p>This could be by starting with a failing assertion that is fixed with a hard-coded value and then iterating on the test to introduce the next failure before repeating the process.<\/p>\n\n<p>This allows you to keep the feedback loop small and not write more code than is needed, to focus on the objective of the test, and not code yourself into a corner.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Instead of writing a whole test and then attempting to make it pass, only write enough code to get the test to fail.<\/p>\n\n<p>This could be by starting with a failing assertion that is fixed with a hard-coded value and then iterating on the test to introduce the next failure before repeating the process.<\/p>\n\n<p>This allows you to keep the feedback loop small and not write more code than is needed, to focus on the objective of the test, and not code yourself into a corner.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:03+00:00",
"guid": null,
"hash": "8b2ab9118da9393b9e5398111f66ff1f",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "14b488aa-fd30-4b51-976a-8e5eb2089e88"
}
],
"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": "Just say Drupal"
}
],
"created": [
{
"value": "2024-03-14T00: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\/03\/14\/just-say-drupal",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Today, I've been following a conversation on Twitter and reading a blog post about Drupal naming and removing the version number from promotional materials.<\/p>\n\n<p>In the post, the author recommends stopping using the version number and \"just say Drupal\" and referring to Drupal 7 and older as \"Legacy Drupal\".<\/p>\n\n<p>Here's a quote from the article that stood out to me:<\/p>\n\n<blockquote>\n <p>This is no longer true! We have successfully eliminated the need for organizations to leave Drupal because \u201cit\u2019s cheaper to just rebuild it in Wordpress than upgrade from 8 to 9 or from 9 to 10.\u201d We\u2019ve made it easier for our clients to spend less on major upgrades, we should be proud!<\/p>\n<\/blockquote>\n\n<p>Another suggestion was to also use the term \"Modern Drupal\" for anything since Drupal 8.<\/p>\n\n<p>I've been doing this or calling it \"Drupal 8+\" on streams and podcast episodes, but I like this approach and I'm going to standardise on this, help change the perception of Drupal and that large rebuilds are no longer needed to upgrade as they were between the older legacy versions.<\/p>\n\n<p><a href=\"https:\/\/ten7.com\/blog\/post\/just-say-drupal\">Read the article<\/a> for more information about this.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Today, I've been following a conversation on Twitter and reading a blog post about Drupal naming and removing the version number from promotional materials.<\/p>\n\n<p>In the post, the author recommends stopping using the version number and \"just say Drupal\" and referring to Drupal 7 and older as \"Legacy Drupal\".<\/p>\n\n<p>Here's a quote from the article that stood out to me:<\/p>\n\n<blockquote>\n <p>This is no longer true! We have successfully eliminated the need for organizations to leave Drupal because \u201cit\u2019s cheaper to just rebuild it in Wordpress than upgrade from 8 to 9 or from 9 to 10.\u201d We\u2019ve made it easier for our clients to spend less on major upgrades, we should be proud!<\/p>\n<\/blockquote>\n\n<p>Another suggestion was to also use the term \"Modern Drupal\" for anything since Drupal 8.<\/p>\n\n<p>I've been doing this or calling it \"Drupal 8+\" on streams and podcast episodes, but I like this approach and I'm going to standardise on this, help change the perception of Drupal and that large rebuilds are no longer needed to upgrade as they were between the older legacy versions.<\/p>\n\n<p><a href=\"https:\/\/ten7.com\/blog\/post\/just-say-drupal\">Read the article<\/a> for more information about this.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:00+00:00",
"guid": null,
"hash": "e9efe0920d1be12d60386b0f70dfbd4b",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "15200e41-4479-49e6-aa2a-f87dd7995a9d"
}
],
"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": "Clients dont care which design pattern you use\n"
}
],
"created": [
{
"value": "2023-02-19T00: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\/19\/clients-dont-care-which-design-pattern-you-use",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>In most cases, clients don't care which CMS, framework, CSS library or design patterns you use. Clients are focused on the business value that those tools can provide, such as increasing traffic or conversion rate to increase revenue or awareness.<\/p>\n\n<p>Improving the load speed of a page is good, but what business value does that offer? Are customers more likely to make a purchase if the page is faster?<\/p>\n\n<p>If you can build the same page with different CMSes or frameworks, and style it in the same way with different CSS libraries, is there a business case for one over the other, such as better maintainability or a quicker time to launch the application?<\/p>\n\n<p>Can you ship a simple MVP sooner, and refactor to use design patterns later?<\/p>\n\n<p>Instead of focusing on technologies, tools and strategies, focus on the benefits and business value they can offer.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>In most cases, clients don't care which CMS, framework, CSS library or design patterns you use. Clients are focused on the business value that those tools can provide, such as increasing traffic or conversion rate to increase revenue or awareness.<\/p>\n\n<p>Improving the load speed of a page is good, but what business value does that offer? Are customers more likely to make a purchase if the page is faster?<\/p>\n\n<p>If you can build the same page with different CMSes or frameworks, and style it in the same way with different CSS libraries, is there a business case for one over the other, such as better maintainability or a quicker time to launch the application?<\/p>\n\n<p>Can you ship a simple MVP sooner, and refactor to use design patterns later?<\/p>\n\n<p>Instead of focusing on technologies, tools and strategies, focus on the benefits and business value they can offer.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:04+00:00",
"guid": null,
"hash": "9d8842c7057445df7a5341831b17c570",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "1530d66c-7dc3-428f-a835-834383959edb"
}
],
"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": "Stepping back into debugging"
}
],
"created": [
{
"value": "2024-04-30T00: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\/30\/stepping-back-into-debugging",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>In PHP, we have functions like <code>var_dump()<\/code>, <code>dump()<\/code> and <code>dd()<\/code> that are used to debug code and print output to the screen.<\/p>\n\n<p>In Drupal, we have functions like <code>dpm()<\/code> and <code>kint()<\/code>, too.<\/p>\n\n<p>These functions are great for simple debugging but sometimes I need more, which is when I reach for a step debugger - namely, Xdebug.<\/p>\n\n<p>This is common when working in complex legacy code, where you need to be able to see a breakpoint and step through the code to see what path it takes and what the state is at each step.<\/p>\n\n<h2 id=\"enter-xdebug\">Enter Xdebug<\/h2>\n\n<p>Xdebug is a tool I use fairly often and something I have configured on an individual project basis.<\/p>\n\n<p>This week, I spent some time adding it to a new project and ensured my notes and documentation still worked.<\/p>\n\n<p>I use Docker and Docker Compose on Linux, so there are slight changes compared to running PHP natively, so I wanted to make sure it still works.<\/p>\n\n<p>I've added my latest setup to my <a href=\"https:\/\/github.com\/opdavies\/docker-example-drupal\">Drupal Docker Example repository<\/a> and plan to add it to my standard <a href=\"http:\/\/localhost:8000\/build-configs\">Build Configs<\/a> setup for Drupal projects.<\/p>\n\n<p>If you use Docker Compose on Linux, it may be useful for you.<\/p>\n\n<p>If you haven't tried Xdebug before, I suggest giving it a try and see if improves your debugging.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>In PHP, we have functions like <code>var_dump()<\/code>, <code>dump()<\/code> and <code>dd()<\/code> that are used to debug code and print output to the screen.<\/p>\n\n<p>In Drupal, we have functions like <code>dpm()<\/code> and <code>kint()<\/code>, too.<\/p>\n\n<p>These functions are great for simple debugging but sometimes I need more, which is when I reach for a step debugger - namely, Xdebug.<\/p>\n\n<p>This is common when working in complex legacy code, where you need to be able to see a breakpoint and step through the code to see what path it takes and what the state is at each step.<\/p>\n\n<h2 id=\"enter-xdebug\">Enter Xdebug<\/h2>\n\n<p>Xdebug is a tool I use fairly often and something I have configured on an individual project basis.<\/p>\n\n<p>This week, I spent some time adding it to a new project and ensured my notes and documentation still worked.<\/p>\n\n<p>I use Docker and Docker Compose on Linux, so there are slight changes compared to running PHP natively, so I wanted to make sure it still works.<\/p>\n\n<p>I've added my latest setup to my <a href=\"https:\/\/github.com\/opdavies\/docker-example-drupal\">Drupal Docker Example repository<\/a> and plan to add it to my standard <a href=\"http:\/\/localhost:8000\/build-configs\">Build Configs<\/a> setup for Drupal projects.<\/p>\n\n<p>If you use Docker Compose on Linux, it may be useful for you.<\/p>\n\n<p>If you haven't tried Xdebug before, I suggest giving it a try and see if improves your debugging.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:59+00:00",
"guid": null,
"hash": "b1951ab6e3a135f7f0a48f49cfb2b876",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "156678a7-81a3-4619-b6c2-0cb9b8659fd3"
}
],
"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": "Today I learned"
}
],
"created": [
{
"value": "2024-06-19T00: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\/19\/today-i-learned",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>As a continuous learner, I always try to learn and incorporate new things into my development workflow.<\/p>\n\n<p>To keep track of them, I recently started to add them to a TIL.txt file.<\/p>\n\n<p>It's a simple plain-text file in my wiki directory that I append to when I learn something new, and that I can reference in the future.<\/p>\n\n<p>Whether it's a useful Drupal module, some API documentation, a Git command or something else, my TIL file is becoming a collection of small but valuable pieces of information.<\/p>\n\n<p>Here's one I added today:<\/p>\n\n<blockquote>\n <p>When adding links in Drupal's menu UI, as well as using <code>&lt;nolink&gt;<\/code> to create an empty link, you can also use <code>&lt;button&gt;<\/code> to create a keyboard-accessible button. #drupal<\/p>\n<\/blockquote>\n\n<p>I may have learned this before and forgotten it, but, now it's in my TIL file, I have somewhere to keep it.<\/p>\n\n<p>Always be learning!<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>As a continuous learner, I always try to learn and incorporate new things into my development workflow.<\/p>\n\n<p>To keep track of them, I recently started to add them to a TIL.txt file.<\/p>\n\n<p>It's a simple plain-text file in my wiki directory that I append to when I learn something new, and that I can reference in the future.<\/p>\n\n<p>Whether it's a useful Drupal module, some API documentation, a Git command or something else, my TIL file is becoming a collection of small but valuable pieces of information.<\/p>\n\n<p>Here's one I added today:<\/p>\n\n<blockquote>\n <p>When adding links in Drupal's menu UI, as well as using <code>&lt;nolink&gt;<\/code> to create an empty link, you can also use <code>&lt;button&gt;<\/code> to create a keyboard-accessible button. #drupal<\/p>\n<\/blockquote>\n\n<p>I may have learned this before and forgotten it, but, now it's in my TIL file, I have somewhere to keep it.<\/p>\n\n<p>Always be learning!<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:59+00:00",
"guid": null,
"hash": "f5036af047c759046d5ac11623982bad",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "15980a5d-a355-4985-ac07-d213cc5297ac"
}
],
"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": "Don't delete my commit messages"
}
],
"created": [
{
"value": "2024-05-11T00: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\/05\/11\/don-t-delete-my-commit-messages",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Another reason I don't like squashing commits within pull\/merge request is losing detail within the commit messages.<\/p>\n\n<p>As someone who usually writes detailed commit messages that explain why the change is being made, any gotchas or alternative approaches that were tried, etc, I want that information to be retained.<\/p>\n\n<p>Previously, when working on a team and doing merge\/pull requests, when merging a feature or release branch, either the tool or reviewer would delete all the messages from the squashed commits.<\/p>\n\n<p>The time I spent writing the messages was wasted, and the information was lost.<\/p>\n\n<p>I'd rather <a href=\"http:\/\/localhost:8000\/daily\/2024\/05\/10\/optimise-for-revertability\">keep the original commits intact<\/a> but, if you need to squash commits, please don't remove the previous messages as they could be useful in the future.<\/p>\n\n<p>People can see the changes by viewing the commits, but the information within the commit messages are valuable, too.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Another reason I don't like squashing commits within pull\/merge request is losing detail within the commit messages.<\/p>\n\n<p>As someone who usually writes detailed commit messages that explain why the change is being made, any gotchas or alternative approaches that were tried, etc, I want that information to be retained.<\/p>\n\n<p>Previously, when working on a team and doing merge\/pull requests, when merging a feature or release branch, either the tool or reviewer would delete all the messages from the squashed commits.<\/p>\n\n<p>The time I spent writing the messages was wasted, and the information was lost.<\/p>\n\n<p>I'd rather <a href=\"http:\/\/localhost:8000\/daily\/2024\/05\/10\/optimise-for-revertability\">keep the original commits intact<\/a> but, if you need to squash commits, please don't remove the previous messages as they could be useful in the future.<\/p>\n\n<p>People can see the changes by viewing the commits, but the information within the commit messages are valuable, too.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:59+00:00",
"guid": null,
"hash": "f4d648329e9b5af30de350cb589e8f6f",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "159e0128-4282-4856-8572-0db763e957da"
}
],
"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": "Experimenting with the Nix package manager"
}
],
"created": [
{
"value": "2022-09-26T00: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\/09\/26\/experimenting-with-the-nix-package-manager",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>After seeing it on some recent live streams and YouTube videos, I've recently been trying out the Nix package manager and looking into how I might use it for my local environment setup - potentially replacing some of my current Ansible configuration.<\/p>\n\n<p>Separate from the NixOS operating system, Nix is a cross-platform package manager, so instead of using <code>apt<\/code> on Ubuntu and <code>brew<\/code> on macOS, you could run Nix on both and install from the 80,000 packages listed on https:\/\/search.nixos.org\/packages.<\/p>\n\n<p>There is a community project called Home Manager which can be installed alongside Nix which, similar to Stow or what I'm doing with Ansible, can manage your dotfiles or even create them from your Home Manager configuration, and can manage plugins for other tools such as ZSH and tmux.<\/p>\n\n<p>There's also a Nix feature called \"Flakes\" which allow you to separate configuration for different operating systems. I currently have a flake for Pop!&#95;OS which installs all of my packages and a minimal flake for my WSL2 environment as some of the packages are installed in Windows instead of Linux.<\/p>\n\n<p>I can see Ansible still being used to set up my post-setup tasks such as cloning my initial projects, but the majority of my current Ansible setup where I'm installing and configuring packages I think could be moved to Nix.<\/p>\n\n<p>I have a work-in-progress Nix-based version <a href=\"https:\/\/github.com\/opdavies\/dotfiles\/tree\/7c3436c553f8b81f99031e6bcddf385d47b7e785\">in my dotfiles repository<\/a> where you can also see <a href=\"https:\/\/github.com\/opdavies\/dotfiles\/blob\/7c3436c553f8b81f99031e6bcddf385d47b7e785\/home-manager\/modules\/git.nix\">how I've configured Git with Home Manager<\/a>.<\/p>\n\n<p>I may install NixOS on an old laptop to test that out too.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>After seeing it on some recent live streams and YouTube videos, I've recently been trying out the Nix package manager and looking into how I might use it for my local environment setup - potentially replacing some of my current Ansible configuration.<\/p>\n\n<p>Separate from the NixOS operating system, Nix is a cross-platform package manager, so instead of using <code>apt<\/code> on Ubuntu and <code>brew<\/code> on macOS, you could run Nix on both and install from the 80,000 packages listed on https:\/\/search.nixos.org\/packages.<\/p>\n\n<p>There is a community project called Home Manager which can be installed alongside Nix which, similar to Stow or what I'm doing with Ansible, can manage your dotfiles or even create them from your Home Manager configuration, and can manage plugins for other tools such as ZSH and tmux.<\/p>\n\n<p>There's also a Nix feature called \"Flakes\" which allow you to separate configuration for different operating systems. I currently have a flake for Pop!_OS which installs all of my packages and a minimal flake for my WSL2 environment as some of the packages are installed in Windows instead of Linux.<\/p>\n\n<p>I can see Ansible still being used to set up my post-setup tasks such as cloning my initial projects, but the majority of my current Ansible setup where I'm installing and configuring packages I think could be moved to Nix.<\/p>\n\n<p>I have a work-in-progress Nix-based version <a href=\"https:\/\/github.com\/opdavies\/dotfiles\/tree\/7c3436c553f8b81f99031e6bcddf385d47b7e785\">in my dotfiles repository<\/a> where you can also see <a href=\"https:\/\/github.com\/opdavies\/dotfiles\/blob\/7c3436c553f8b81f99031e6bcddf385d47b7e785\/home-manager\/modules\/git.nix\">how I've configured Git with Home Manager<\/a>.<\/p>\n\n<p>I may install NixOS on an old laptop to test that out too.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:06+00:00",
"guid": null,
"hash": "951d2d9e9835481f997f95a29b18efdf",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "15b5f42b-7642-4020-9018-21c1f69afc5e"
}
],
"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": "Making PHPStan stricter"
}
],
"created": [
{
"value": "2024-05-05T00: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\/05\/05\/making-phpstan-stricter",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Continuing yesterday's thought on <a href=\"http:\/\/localhost:8000\/daily\/2024\/05\/04\/strict-typing-in-php\">strictness in PHP<\/a>, today I want to talk about adding more strictness to PHPStan.<\/p>\n\n<p>Adding the <a href=\"https:\/\/github.com\/phpstan\/phpstan-strict-rules\">PHPStan Strict Rules extension<\/a> makes PHPStan stricter by adding new, more opinionated rules.<\/p>\n\n<p>For example:<\/p>\n\n<ul>\n<li>Require booleans in if, elseif, ternary operator, after !, and on both sides of &amp;&amp; and ||.<\/li>\n<li>Use the <code>$strict<\/code> parameter with <code>in_array<\/code>, <code>array_search<\/code>, <code>array_keys<\/code> and <code>base64_decode<\/code>.<\/li>\n<li>Disallow empty().<\/li>\n<li>Require calling parent constructor.<\/li>\n<\/ul>\n\n<p>You can enable and disable rules as needed but, like setting the PHPStan level, ideally I like to enable them all by default and see how strict I go.<\/p>\n\n<p>It depends on the code being tested and the preference of the team, though I find the stricter the rules, the less bugs there are.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Continuing yesterday's thought on <a href=\"http:\/\/localhost:8000\/daily\/2024\/05\/04\/strict-typing-in-php\">strictness in PHP<\/a>, today I want to talk about adding more strictness to PHPStan.<\/p>\n\n<p>Adding the <a href=\"https:\/\/github.com\/phpstan\/phpstan-strict-rules\">PHPStan Strict Rules extension<\/a> makes PHPStan stricter by adding new, more opinionated rules.<\/p>\n\n<p>For example:<\/p>\n\n<ul>\n<li>Require booleans in if, elseif, ternary operator, after !, and on both sides of &amp;&amp; and ||.<\/li>\n<li>Use the <code>$strict<\/code> parameter with <code>in_array<\/code>, <code>array_search<\/code>, <code>array_keys<\/code> and <code>base64_decode<\/code>.<\/li>\n<li>Disallow empty().<\/li>\n<li>Require calling parent constructor.<\/li>\n<\/ul>\n\n<p>You can enable and disable rules as needed but, like setting the PHPStan level, ideally I like to enable them all by default and see how strict I go.<\/p>\n\n<p>It depends on the code being tested and the preference of the team, though I find the stricter the rules, the less bugs there are.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:59+00:00",
"guid": null,
"hash": "cf75cacb3311266a77a29d4ad842c6a6",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "15d2ccbd-6da6-47f2-a96a-2a3a3e5e8f19"
}
],
"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": "Drupal is a content management framework"
}
],
"created": [
{
"value": "2024-04-06T00: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\/06\/drupal-is-a-content-management-framework",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>There used to be a saying:<\/p>\n\n<p>If you want to build a website, use <enter CMS name here>. If you want to build <CMS name>, use Drupal.<\/p>\n\n<p>I think I've heard it used with all the other content management systems, and it isn't about saying one is better.<\/p>\n\n<p>It highlights that Drupal isn't just a content management system - it's a content management framework.<\/p>\n\n<p>A framework you use to build your content management system to meet your needs.<\/p>\n\n<p>Straight away after installing Drupal, you can create as many content types as you want with as many fields as you want to build as simple or complex a content model as you need.<\/p>\n\n<p>You aren't restricted to the default page and article content types or a fixed set of fields.<\/p>\n\n<p>That, along with Views - a built-in query builder to build pages and blocks of your content, user login and registration, and JSON:API to allow other applications, such as mobile apps, to access your data, to name a few things, makes Drupal a very powerful option.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>There used to be a saying:<\/p>\n\n<p>If you want to build a website, use <enter cms name here>. If you want to build <cms name>, use Drupal.<\/cms><\/enter><\/p>\n\n<p>I think I've heard it used with all the other content management systems, and it isn't about saying one is better.<\/p>\n\n<p>It highlights that Drupal isn't just a content management system - it's a content management framework.<\/p>\n\n<p>A framework you use to build your content management system to meet your needs.<\/p>\n\n<p>Straight away after installing Drupal, you can create as many content types as you want with as many fields as you want to build as simple or complex a content model as you need.<\/p>\n\n<p>You aren't restricted to the default page and article content types or a fixed set of fields.<\/p>\n\n<p>That, along with Views - a built-in query builder to build pages and blocks of your content, user login and registration, and JSON:API to allow other applications, such as mobile apps, to access your data, to name a few things, makes Drupal a very powerful option.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:59+00:00",
"guid": null,
"hash": "09e70a063c30cc59ead229e026e00f13",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "15d7d5bc-30ea-41f3-9f1d-272753352d5b"
}
],
"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": "The open-source-first development workflow\n"
}
],
"created": [
{
"value": "2022-10-29T00: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\/29\/the-open-source-first-development-workflow",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Yesterday's email talked about <a href=\"http:\/\/localhost:8000\/daily\/2022\/10\/28\/why-write-framework-agnostic-packages\">writing reusable, framework-agnostic packages<\/a> but didn't mention where those packages could be located.<\/p>\n\n<p>They could be kept within a private repository and still have the same benefits, such as re-usability for internal projects, but I like to open-source code as often as I can and make it available publicly to see and use.<\/p>\n\n<p>My preference is to follow an open-source-first workflow, identify which parts of a solution can be open-sourced and create them as open-source libraries or modules from the beginning rather than planning to extract them later. They can then be included within the main project using a dependency manager tool like Composer, npm or Yarn.<\/p>\n\n<p>The eBook integration project that I mentioned was an example of this. I identified which pieces could be open-sourced, set up a public repository and put together an MVP based on that project's requirements. Issues were created for nice-to-have additions that could be added later, and the working version was installed with Composer.<\/p>\n\n<p>There was no need to extract the code from the main project, no need to \"clean it up\" or check that it contained no client information, and I had the full Git history for the project - not just a new history from the point when the code was extracted and open-sourced.<\/p>\n\n<p>I've worked on projects that contained a number of potential open-source components that would be released after project completion, but this didn't always happen - I assume due to time pressures to move on to the next project, a focus on adding new features or avoiding the risk of introducing breakages into the code. If the code had been open-sourced from the beginning, these things wouldn't have been an issue.<\/p>\n\n<p>I've also worked on projects where I've followed an open-source-first approach and released a number of PHP libraries and Drupal modules, including <a href=\"https:\/\/www.drupal.org\/project\/private_message_queue\">Private Message Queue<\/a>, <a href=\"https:\/\/www.drupal.org\/project\/system_user\">System User<\/a>, and <a href=\"https:\/\/www.drupal.org\/project\/null_user\">Null User<\/a> modules. I've also been working on some legacy code recently and started to replace it with a library that I've already open-sourced, even though I'm in the early stages of developing it.<\/p>\n\n<p>As someone who enjoys creating and working on open-source software, I would encourage you to open-source your code if you can and to do so sooner rather than later and not wait until the end of your project.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Yesterday's email talked about <a href=\"http:\/\/localhost:8000\/daily\/2022\/10\/28\/why-write-framework-agnostic-packages\">writing reusable, framework-agnostic packages<\/a> but didn't mention where those packages could be located.<\/p>\n\n<p>They could be kept within a private repository and still have the same benefits, such as re-usability for internal projects, but I like to open-source code as often as I can and make it available publicly to see and use.<\/p>\n\n<p>My preference is to follow an open-source-first workflow, identify which parts of a solution can be open-sourced and create them as open-source libraries or modules from the beginning rather than planning to extract them later. They can then be included within the main project using a dependency manager tool like Composer, npm or Yarn.<\/p>\n\n<p>The eBook integration project that I mentioned was an example of this. I identified which pieces could be open-sourced, set up a public repository and put together an MVP based on that project's requirements. Issues were created for nice-to-have additions that could be added later, and the working version was installed with Composer.<\/p>\n\n<p>There was no need to extract the code from the main project, no need to \"clean it up\" or check that it contained no client information, and I had the full Git history for the project - not just a new history from the point when the code was extracted and open-sourced.<\/p>\n\n<p>I've worked on projects that contained a number of potential open-source components that would be released after project completion, but this didn't always happen - I assume due to time pressures to move on to the next project, a focus on adding new features or avoiding the risk of introducing breakages into the code. If the code had been open-sourced from the beginning, these things wouldn't have been an issue.<\/p>\n\n<p>I've also worked on projects where I've followed an open-source-first approach and released a number of PHP libraries and Drupal modules, including <a href=\"https:\/\/www.drupal.org\/project\/private_message_queue\">Private Message Queue<\/a>, <a href=\"https:\/\/www.drupal.org\/project\/system_user\">System User<\/a>, and <a href=\"https:\/\/www.drupal.org\/project\/null_user\">Null User<\/a> modules. I've also been working on some legacy code recently and started to replace it with a library that I've already open-sourced, even though I'm in the early stages of developing it.<\/p>\n\n<p>As someone who enjoys creating and working on open-source software, I would encourage you to open-source your code if you can and to do so sooner rather than later and not wait until the end of your project.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:06+00:00",
"guid": null,
"hash": "4c3b02f20d46eb6183001dbee4ca0680",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "1609c8aa-832f-42b0-9ff2-ddb4fb926d61"
}
],
"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": "How do you know when to remove a feature flag?\n"
}
],
"created": [
{
"value": "2023-06-10T00: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\/10\/how-do-you-know-when-to-remove-a-feature-flag",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>But once a feature has been enabled, how do you know if you can remove its flag?<\/p>\n\n<p>A simple suggestion I've heard is to add a \"Remove when...\" comment above where the flag is used and detail what conditions need to be in place before the flag can be removed.<\/p>\n\n<p>It might be a period of time or after another feature has been shipped, but it will give some clarity when you see the flag in the code to if or when it can be removed.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>But once a feature has been enabled, how do you know if you can remove its flag?<\/p>\n\n<p>A simple suggestion I've heard is to add a \"Remove when...\" comment above where the flag is used and detail what conditions need to be in place before the flag can be removed.<\/p>\n\n<p>It might be a period of time or after another feature has been shipped, but it will give some clarity when you see the flag in the code to if or when it can be removed.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:03+00:00",
"guid": null,
"hash": "41c6635bf0b850f244bdf48de16fe27a",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "169d855f-7530-46ff-8913-a9b93253d56a"
}
],
"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": "Automated tests prevent you from adding regressions"
}
],
"created": [
{
"value": "2024-02-02T00: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\/02\/automated-tests-prevent-you-from-adding-regressions",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Continuing from <a href=\"http:\/\/localhost:8000\/daily\/2024\/01\/30\/tdd-doesnt-mean-you-know-everything-upfront\">my last few emails<\/a>, as well as adding the new use case more easily and quicker, having automated tests also saved me from adding a regression into the code I was changing.<\/p>\n\n<p>I'd written a condition in the query to ensure only results that started with the search term.<\/p>\n\n<p>Initially, I removed it, but then the tests failed.<\/p>\n\n<p>This reminded me why I'd written the condition that way, and I was able to re-add my fix differently.<\/p>\n\n<p>Without the tests, I'd likely have removed it and introduced a regression.<\/p>\n\n<p>Whilst fixing a bug, I'd have introduced a different bug.<\/p>\n\n<p>My tests saved me from doing that and I was able to rectify it quickly before pushing to CI or the staging environment.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Continuing from <a href=\"http:\/\/localhost:8000\/daily\/2024\/01\/30\/tdd-doesnt-mean-you-know-everything-upfront\">my last few emails<\/a>, as well as adding the new use case more easily and quicker, having automated tests also saved me from adding a regression into the code I was changing.<\/p>\n\n<p>I'd written a condition in the query to ensure only results that started with the search term.<\/p>\n\n<p>Initially, I removed it, but then the tests failed.<\/p>\n\n<p>This reminded me why I'd written the condition that way, and I was able to re-add my fix differently.<\/p>\n\n<p>Without the tests, I'd likely have removed it and introduced a regression.<\/p>\n\n<p>Whilst fixing a bug, I'd have introduced a different bug.<\/p>\n\n<p>My tests saved me from doing that and I was able to rectify it quickly before pushing to CI or the staging environment.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:00+00:00",
"guid": null,
"hash": "9aaba1b4d2672b083ea8f3e8f3285597",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "175deb35-b01c-445b-92c0-76f9fb19252a"
}
],
"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": "The single responsibility principle\n"
}
],
"created": [
{
"value": "2023-05-09T00: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\/05\/09\/the-single-responsibility-principle",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Today, I added a new feature to a project that allows a member to search for a node based on either its title or a specified field on that node, select a result from an autocomplete list and then be redirected to their selected node.<\/p>\n\n<p>I've already implemented this for other node types but needed to do the same for this node type.<\/p>\n\n<p>There are some differences, such as the node type to query for; the additional field depends on which node type as does the text shown in the autocomplete list.<\/p>\n\n<p>To do this, I needed to add a custom block and form, update the <code>AutocompleteController<\/code>, create a new instance of a <code>NodeQuery<\/code> class (a custom class within the custom module), register it as a service and update the <code>SearchQueryFactory<\/code> class.<\/p>\n\n<p>A principle that I follow as much as possible is the single responsibility principle, or SRP (the 'S' in SOLID), where each function or class only has one responsibility - such as returning a response for the autocomplete list, determining the correct node query to use based on the search being run or building the query itself - these are separated and split into their own files.<\/p>\n\n<p>Although more files and functions are created when coding in this way, though they are smaller and more straightforward to work with - which makes them easier to read, debug and maintain. It also makes code like the node query classes reusable as they aren't embedded within a larger class and are easier to test.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Today, I added a new feature to a project that allows a member to search for a node based on either its title or a specified field on that node, select a result from an autocomplete list and then be redirected to their selected node.<\/p>\n\n<p>I've already implemented this for other node types but needed to do the same for this node type.<\/p>\n\n<p>There are some differences, such as the node type to query for; the additional field depends on which node type as does the text shown in the autocomplete list.<\/p>\n\n<p>To do this, I needed to add a custom block and form, update the <code>AutocompleteController<\/code>, create a new instance of a <code>NodeQuery<\/code> class (a custom class within the custom module), register it as a service and update the <code>SearchQueryFactory<\/code> class.<\/p>\n\n<p>A principle that I follow as much as possible is the single responsibility principle, or SRP (the 'S' in SOLID), where each function or class only has one responsibility - such as returning a response for the autocomplete list, determining the correct node query to use based on the search being run or building the query itself - these are separated and split into their own files.<\/p>\n\n<p>Although more files and functions are created when coding in this way, though they are smaller and more straightforward to work with - which makes them easier to read, debug and maintain. It also makes code like the node query classes reusable as they aren't embedded within a larger class and are easier to test.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:03+00:00",
"guid": null,
"hash": "74bd5f418ef3374adbd56755b08e95bb",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "1797ee77-5bbc-4cf9-944b-4ce4c951f660"
}
],
"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": "Visual testing and Diffy with Yuri Gerasymov"
}
],
"created": [
{
"value": "2024-03-10T00: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\/03\/10\/visual-testing-and-diffy-with-yuri-gerasymov",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>This week on the Beyond Blocks podcast, I'm joined by Yuri Gerasymov to discuss Diffy visual regression testing.<\/p>\n\n<h2 id=\"talking-points\">Talking Points<\/h2>\n\n<ul>\n<li>What is visual regression testing?<\/li>\n<li>How do you deal with false positives?<\/li>\n<li>Different use cases for visual regression testing.<\/li>\n<li>Automatic updates.<\/li>\n<li>Scheduling content.<\/li>\n<li>Visual regression testing in CI.<\/li>\n<li>Diffy in WordPress.<\/li>\n<li>What's a baseline?<\/li>\n<li>Initial setup and onboarding feedback.<\/li>\n<li>Testing dark mode?<\/li>\n<li>Component testing with Storybook and Fractal?<\/li>\n<li>Testing local environments.<\/li>\n<li>Testing as authenticated users.<\/li>\n<li>The roadmap for Diffy.<\/li>\n<\/ul>\n\n<h2 id=\"quotable-quotes\">Quotable Quotes<\/h2>\n\n<ul>\n<li>We help development teams to have less visual bugs in their website. We take screenshots of the pages and compare them so you can see what changed and how. (YG)<\/li>\n<li>We built tools for you to mock the content. You provide selectors for the elements with the content of the article and we'll replace it with lorem ipsum text so it will be exactly the same across multiple environments. (YG)<\/li>\n<li>I can still write an assertion to check the text is on the page or not, but it won't confirm it's in the correct place. (OD)<\/li>\n<li>Having a tool checking for changes on a regular basis instead of only after a deployment would be very useful. (OD)<\/li>\n<li>So, you could have a tool like Violinst automatically creating pull requests and Diffy checking those PRs, so the two could work together? (OD)<\/li>\n<li>With visual testing, it's very easy to get started. (YG)<\/li>\n<li>Visual testing is great for showing your client your work. (YG)<\/li>\n<\/ul>\n\n<p>I learned a lot during this conversation and have added visual regression testing to my testing toolbox for working on projects.<\/p>\n\n<p><a href=\"http:\/\/localhost:8000\/podcast\/14-yuri-gerasymov-diffy\">Listen to the episode<\/a><\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>This week on the Beyond Blocks podcast, I'm joined by Yuri Gerasymov to discuss Diffy visual regression testing.<\/p>\n\n<h2 id=\"talking-points\">Talking Points<\/h2>\n\n<ul>\n<li>What is visual regression testing?<\/li>\n<li>How do you deal with false positives?<\/li>\n<li>Different use cases for visual regression testing.<\/li>\n<li>Automatic updates.<\/li>\n<li>Scheduling content.<\/li>\n<li>Visual regression testing in CI.<\/li>\n<li>Diffy in WordPress.<\/li>\n<li>What's a baseline?<\/li>\n<li>Initial setup and onboarding feedback.<\/li>\n<li>Testing dark mode?<\/li>\n<li>Component testing with Storybook and Fractal?<\/li>\n<li>Testing local environments.<\/li>\n<li>Testing as authenticated users.<\/li>\n<li>The roadmap for Diffy.<\/li>\n<\/ul>\n\n<h2 id=\"quotable-quotes\">Quotable Quotes<\/h2>\n\n<ul>\n<li>We help development teams to have less visual bugs in their website. We take screenshots of the pages and compare them so you can see what changed and how. (YG)<\/li>\n<li>We built tools for you to mock the content. You provide selectors for the elements with the content of the article and we'll replace it with lorem ipsum text so it will be exactly the same across multiple environments. (YG)<\/li>\n<li>I can still write an assertion to check the text is on the page or not, but it won't confirm it's in the correct place. (OD)<\/li>\n<li>Having a tool checking for changes on a regular basis instead of only after a deployment would be very useful. (OD)<\/li>\n<li>So, you could have a tool like Violinst automatically creating pull requests and Diffy checking those PRs, so the two could work together? (OD)<\/li>\n<li>With visual testing, it's very easy to get started. (YG)<\/li>\n<li>Visual testing is great for showing your client your work. (YG)<\/li>\n<\/ul>\n\n<p>I learned a lot during this conversation and have added visual regression testing to my testing toolbox for working on projects.<\/p>\n\n<p><a href=\"http:\/\/localhost:8000\/podcast\/14-yuri-gerasymov-diffy\">Listen to the episode<\/a><\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:00+00:00",
"guid": null,
"hash": "be8b52f6ba3712bf29af218e689bd6d7",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "17a19a3b-8227-4690-b9ea-6a74c1af0cab"
}
],
"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": "How I've configured Git"
}
],
"created": [
{
"value": "2022-08-24T00: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\/24\/2022-08-24",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>After yesterday's post on why I prefer using Git on the command line rather than using a GUI tool, today I thought that I'd post about how I've configured Git.<\/p>\n\n<p>First, I rarely ever run the <code>git<\/code> command - I usually run a <code>g<\/code> function that I've created within my zsh configuration.<\/p>\n\n<p>Rather than being an simple alias, it's a shell function that will run <code>git status -sb<\/code> to show the current status of the repository if there are no additional arguments. If there are, such as when running <code>g add<\/code>, then this is executed as a normal Git command. (This is something that I first saw from Thoughtbot, if I remember correctly).<\/p>\n\n<h2 id=\"using-.gitconfig\">Using .gitconfig<\/h2>\n\n<p>The main part of my configuration is within Git's <code>~\/.gitconfig<\/code> file, where I can configure Git to work how I want.<\/p>\n\n<p>For example, I like to avoid merge conflicts, so I always want to use fast-forward merges whilst pulling and also to rebase by default. I can do this by adding <code>ff = only<\/code> and <code>rebase = true<\/code> to the <code>[pull]<\/code> section of my <code>~\/.gitconfig<\/code> file.<\/p>\n\n<p>I can do this manually, or running <code>git config --global pull.rebase true<\/code> will set the option but also update the file automatically.<\/p>\n\n<p>Some of the tweaks that I've made are to only allow fast-forward merges by adding <code>merge.ff = only<\/code>, automatically squash commits when rebasing by setting <code>rebase.autosquash = true<\/code>, and automatically pruning branches by adding <code>fetch.prune = true<\/code>.<\/p>\n\n<h3 id=\"simple-aliases\">Simple aliases<\/h3>\n\n<p>Another way that I configure Git is using aliases, which are also within the <code>~\/.gitconfig<\/code> file.<\/p>\n\n<p>For example, if I ran <code>git config --global alias.b \"branch\"<\/code>, then running <code>git b<\/code> would just run <code>git branch<\/code> which shortens the command and saves some time and keystrokes.<\/p>\n\n<p>I have similar one- or two letter \"short\" aliases for pushing and pulling code, and some that also set some additional arguments such as <code>aa<\/code> for <code>add --all<\/code> and <code>worktrees<\/code> for <code>worktree list<\/code>.<\/p>\n\n<h3 id=\"more-complicated-aliases\">More complicated aliases<\/h3>\n\n<p>Aliases can be more complex if needed by prefixing it with a <code>!<\/code>, meaning that it executes it as a shell command.<\/p>\n\n<p>This means that I can have <code>repush = !git pull --rebase &amp;&amp; git push<\/code> to chain two separate Git commands and combine them into one, and <code>ureset = !git reset --hard $(git upstream)<\/code> which executes the full command, including another alias as part of it.<\/p>\n\n<p>I also have <code>issues = !gh issue list --web<\/code> and <code>pulls = !gh pr list --web<\/code> to open the current repository's GitHub issues or pull requests respectively, which can be done as it's not limited to just running <code>git<\/code> commands.<\/p>\n\n<h3 id=\"custom-functions\">Custom functions<\/h3>\n\n<p>Finally, if an alias is getting too long or complex, then it can extracted to it's own file.<\/p>\n\n<p>Any executable file within your <code>$PATH<\/code> that starts with <code>git-<\/code> will automatically become a Git command.<\/p>\n\n<p>One example that I have is <a href=\"https:\/\/github.com\/opdavies\/dotfiles\/blob\/2b20cd1e59ae3b1fa81074077e855cbdfa02f146\/bin\/bin\/git-cm\">git-cm<\/a> which, similar to the <code>g<\/code> function`, is a bash script that checks for any arguments passed to it and runs a slightly different command. It achieves the same thing as if it were an alias, but it does make it easier to write and maintain as it's in a separate file.<\/p>\n\n<p>These are just some examples. If you want to see my entire configuration, then check out <a href=\"https:\/\/github.com\/opdavies\/dotfiles\/tree\/2b20cd1e59ae3b1fa81074077e855cbdfa02f146\/roles\/git\/files\">my dotfiles repository on GitHub<\/a>.<\/p>\n\n<p>How have you configured Git for your workflow? Reply to this email and let me know.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>After yesterday's post on why I prefer using Git on the command line rather than using a GUI tool, today I thought that I'd post about how I've configured Git.<\/p>\n\n<p>First, I rarely ever run the <code>git<\/code> command - I usually run a <code>g<\/code> function that I've created within my zsh configuration.<\/p>\n\n<p>Rather than being an simple alias, it's a shell function that will run <code>git status -sb<\/code> to show the current status of the repository if there are no additional arguments. If there are, such as when running <code>g add<\/code>, then this is executed as a normal Git command. (This is something that I first saw from Thoughtbot, if I remember correctly).<\/p>\n\n<h2 id=\"using-.gitconfig\">Using .gitconfig<\/h2>\n\n<p>The main part of my configuration is within Git's <code>~\/.gitconfig<\/code> file, where I can configure Git to work how I want.<\/p>\n\n<p>For example, I like to avoid merge conflicts, so I always want to use fast-forward merges whilst pulling and also to rebase by default. I can do this by adding <code>ff = only<\/code> and <code>rebase = true<\/code> to the <code>[pull]<\/code> section of my <code>~\/.gitconfig<\/code> file.<\/p>\n\n<p>I can do this manually, or running <code>git config --global pull.rebase true<\/code> will set the option but also update the file automatically.<\/p>\n\n<p>Some of the tweaks that I've made are to only allow fast-forward merges by adding <code>merge.ff = only<\/code>, automatically squash commits when rebasing by setting <code>rebase.autosquash = true<\/code>, and automatically pruning branches by adding <code>fetch.prune = true<\/code>.<\/p>\n\n<h3 id=\"simple-aliases\">Simple aliases<\/h3>\n\n<p>Another way that I configure Git is using aliases, which are also within the <code>~\/.gitconfig<\/code> file.<\/p>\n\n<p>For example, if I ran <code>git config --global alias.b \"branch\"<\/code>, then running <code>git b<\/code> would just run <code>git branch<\/code> which shortens the command and saves some time and keystrokes.<\/p>\n\n<p>I have similar one- or two letter \"short\" aliases for pushing and pulling code, and some that also set some additional arguments such as <code>aa<\/code> for <code>add --all<\/code> and <code>worktrees<\/code> for <code>worktree list<\/code>.<\/p>\n\n<h3 id=\"more-complicated-aliases\">More complicated aliases<\/h3>\n\n<p>Aliases can be more complex if needed by prefixing it with a <code>!<\/code>, meaning that it executes it as a shell command.<\/p>\n\n<p>This means that I can have <code>repush = !git pull --rebase &amp;&amp; git push<\/code> to chain two separate Git commands and combine them into one, and <code>ureset = !git reset --hard $(git upstream)<\/code> which executes the full command, including another alias as part of it.<\/p>\n\n<p>I also have <code>issues = !gh issue list --web<\/code> and <code>pulls = !gh pr list --web<\/code> to open the current repository's GitHub issues or pull requests respectively, which can be done as it's not limited to just running <code>git<\/code> commands.<\/p>\n\n<h3 id=\"custom-functions\">Custom functions<\/h3>\n\n<p>Finally, if an alias is getting too long or complex, then it can extracted to it's own file.<\/p>\n\n<p>Any executable file within your <code>$PATH<\/code> that starts with <code>git-<\/code> will automatically become a Git command.<\/p>\n\n<p>One example that I have is <a href=\"https:\/\/github.com\/opdavies\/dotfiles\/blob\/2b20cd1e59ae3b1fa81074077e855cbdfa02f146\/bin\/bin\/git-cm\">git-cm<\/a> which, similar to the <code>g<\/code> function`, is a bash script that checks for any arguments passed to it and runs a slightly different command. It achieves the same thing as if it were an alias, but it does make it easier to write and maintain as it's in a separate file.<\/p>\n\n<p>These are just some examples. If you want to see my entire configuration, then check out <a href=\"https:\/\/github.com\/opdavies\/dotfiles\/tree\/2b20cd1e59ae3b1fa81074077e855cbdfa02f146\/roles\/git\/files\">my dotfiles repository on GitHub<\/a>.<\/p>\n\n<p>How have you configured Git for your workflow? Reply to this email and let me know.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:07+00:00",
"guid": null,
"hash": "4d3892ad51836bdd33e47e7043615d75",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "17eb9a79-861d-4f0b-8086-ed3e7a166f5f"
}
],
"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": "Another way to create test module configuration"
}
],
"created": [
{
"value": "2024-02-17T00: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\/17\/another-way-to-create-test-module-configuration",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>In one of the lessons in my <a href=\"http:\/\/localhost:8000\/atdc\">free automated testing in Drupal email course<\/a>, I explain how I create configuration that I need within my tests, such as adding a custom field:<\/p>\n\n<blockquote>\n <p>But how do you know what to name the configuration files and what content to put in them?<\/p>\n \n <p>Rather than trying to write them by hand, I create the configuration I need, such as fields, within a Drupal site and then export and edit the files I need.<\/p>\n<\/blockquote>\n\n<p>As well as creating the fields in the Drupal UI, I was also using it to export the configuration files I needed:<\/p>\n\n<blockquote>\n <p>Once Drupal is installed and the configuration has been created, you can go to - \/admin\/config\/development\/configuration\/single\/export and select the configuration type and name.<\/p>\n \n <p>The filename is shown at the bottom of the page, and you can copy the content into files within your module.<\/p>\n<\/blockquote>\n\n<h2 id=\"there%27s-another-way\">There's another way<\/h2>\n\n<p>After reading that lesson, somene replied and reminded me that there's a <code>--destination<\/code> option you can use with the <code>drush config:export<\/code> command.<\/p>\n\n<p>Instead of exporting to the standard configuration directory, I can do it to a temporary directory:<\/p>\n\n<pre><code class=\"language-shell\">run drush cex --destination \/app\/.ignored\/config\n<\/code><\/pre>\n\n<p>Everyhing in a <code>.ignored<\/code> direcotry is automatically ignored by Git, and to get the files I need, I can use Linux's <code>find<\/code> command to find any files that contain the field name and copy them into my test module directory:<\/p>\n\n<pre><code class=\"language-shell\">find .ignored\/config \\\n -type f \\\n -name \\*drupal_project\\* \\\n -exec cp -r {} web\/modules\/custom\/foo\/modules\/foo_test\/config\/install \\;\n<\/code><\/pre>\n\n<p>I still need to edit the files to remove the <code>uuid<\/code> and <code>_core<\/code> values, but this approach means less clicking in the Drupal UI which makes me more productive.<\/p>\n\n<p>I used this approach when <a href=\"http:\/\/localhost:8000\/daily\/2024\/02\/16\/keep-logic-within-tests-for-as-long-as-you-can\">writing my SaaS code yesterday<\/a> and it worked well.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>In one of the lessons in my <a href=\"http:\/\/localhost:8000\/atdc\">free automated testing in Drupal email course<\/a>, I explain how I create configuration that I need within my tests, such as adding a custom field:<\/p>\n\n<blockquote>\n <p>But how do you know what to name the configuration files and what content to put in them?<\/p>\n \n <p>Rather than trying to write them by hand, I create the configuration I need, such as fields, within a Drupal site and then export and edit the files I need.<\/p>\n<\/blockquote>\n\n<p>As well as creating the fields in the Drupal UI, I was also using it to export the configuration files I needed:<\/p>\n\n<blockquote>\n <p>Once Drupal is installed and the configuration has been created, you can go to - \/admin\/config\/development\/configuration\/single\/export and select the configuration type and name.<\/p>\n \n <p>The filename is shown at the bottom of the page, and you can copy the content into files within your module.<\/p>\n<\/blockquote>\n\n<h2 id=\"there%27s-another-way\">There's another way<\/h2>\n\n<p>After reading that lesson, somene replied and reminded me that there's a <code>--destination<\/code> option you can use with the <code>drush config:export<\/code> command.<\/p>\n\n<p>Instead of exporting to the standard configuration directory, I can do it to a temporary directory:<\/p>\n\n<pre><code class=\"language-shell\">run drush cex --destination \/app\/.ignored\/config\n<\/code><\/pre>\n\n<p>Everyhing in a <code>.ignored<\/code> direcotry is automatically ignored by Git, and to get the files I need, I can use Linux's <code>find<\/code> command to find any files that contain the field name and copy them into my test module directory:<\/p>\n\n<pre><code class=\"language-shell\">find .ignored\/config \\\n -type f \\\n -name \\*drupal_project\\* \\\n -exec cp -r {} web\/modules\/custom\/foo\/modules\/foo_test\/config\/install \\;\n<\/code><\/pre>\n\n<p>I still need to edit the files to remove the <code>uuid<\/code> and <code>_core<\/code> values, but this approach means less clicking in the Drupal UI which makes me more productive.<\/p>\n\n<p>I used this approach when <a href=\"http:\/\/localhost:8000\/daily\/2024\/02\/16\/keep-logic-within-tests-for-as-long-as-you-can\">writing my SaaS code yesterday<\/a> and it worked well.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:00+00:00",
"guid": null,
"hash": "26374b4758fb11f97c3f835751afe302",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "17fbca57-b30a-4453-bca6-ef90bb211b88"
}
],
"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:05+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": "Building a minimum viable product and managing technical debt\n"
}
],
"created": [
{
"value": "2022-11-12T00:00:00+00:00"
}
],
"changed": [
{
"value": "2025-04-16T14:13:05+00:00"
}
],
"promote": [
{
"value": false
}
],
"sticky": [
{
"value": false
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
],
"path": [
{
"alias": "\/daily\/2022\/11\/12\/building-a-minimum-viable-product-and-managing-technical-debt",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Yesterday's email was about a proof-of-concept application that I\u2019d quickly built to validate an idea and explore some initial approaches.<\/p>\n\n<p>Today, I\u2019ve been working on a client project that I\u2019ve improved and maintained for a few years.<\/p>\n\n<p>When I started working with this client, they had one website, built with Drupal 7 and Drupal Commerce. Now, there are x websites using the same codebase due to Drupal\u2019s multi-site functionality.<\/p>\n\n<p>My main task for the last few months has been to get one of their sites onto Drupal 9 (which I did, it went live in October).<\/p>\n\n<p>This first site was the \"minimum viable product\" (MVP) - the least amount of functionality required to make it releasable to customers. This is different to a proof of concept which is to validate the idea and start a conversation about requirements and scope - where we define the MVP.<\/p>\n\n<p>For example, there is the ability to create products and product variations from a CSV file. It loads the file from disk and creates the products, but it doesn't update a product variation if a row with an existing SKU is changed, or disable the variation if a row is removed from the file. There is no admin UI for the client to upload a file - the only file that it'll use is the one that's path is hard-coded within the module.<\/p>\n\n<p>There are user stories for this, but we decided that we didn't need it for the initial launch and that we were happy to take on some technical debt, knowing that we can address it later when the original solutions are no longer sufficient.<\/p>\n\n<p>Now the minimum viable solution has been released, we can continue to iterate and enhance it based on customers' feedback, add more functionality, and address the technical debt as needed and as requirements require us to do so.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Yesterday's email was about a proof-of-concept application that I\u2019d quickly built to validate an idea and explore some initial approaches.<\/p>\n\n<p>Today, I\u2019ve been working on a client project that I\u2019ve improved and maintained for a few years.<\/p>\n\n<p>When I started working with this client, they had one website, built with Drupal 7 and Drupal Commerce. Now, there are x websites using the same codebase due to Drupal\u2019s multi-site functionality.<\/p>\n\n<p>My main task for the last few months has been to get one of their sites onto Drupal 9 (which I did, it went live in October).<\/p>\n\n<p>This first site was the \"minimum viable product\" (MVP) - the least amount of functionality required to make it releasable to customers. This is different to a proof of concept which is to validate the idea and start a conversation about requirements and scope - where we define the MVP.<\/p>\n\n<p>For example, there is the ability to create products and product variations from a CSV file. It loads the file from disk and creates the products, but it doesn't update a product variation if a row with an existing SKU is changed, or disable the variation if a row is removed from the file. There is no admin UI for the client to upload a file - the only file that it'll use is the one that's path is hard-coded within the module.<\/p>\n\n<p>There are user stories for this, but we decided that we didn't need it for the initial launch and that we were happy to take on some technical debt, knowing that we can address it later when the original solutions are no longer sufficient.<\/p>\n\n<p>Now the minimum viable solution has been released, we can continue to iterate and enhance it based on customers' feedback, add more functionality, and address the technical debt as needed and as requirements require us to do so.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:05+00:00",
"guid": null,
"hash": "f44966eb1681af53eb81ff390ccadd27",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "188dcc40-aed2-4da5-be6b-8dc52ac0044d"
}
],
"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": "Avoiding nesting"
}
],
"created": [
{
"value": "2024-04-07T00: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\/07\/avoiding-nesting",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>One of my goals when coding is to reduce the amount of nesting in the code I write.<\/p>\n\n<p>I mean both in my PHP code where if conditions and foreach loops can be nested within each other, and CSS and Sass files, which support nesting CSS rules.<\/p>\n\n<p>My aim is to have a maximum of two or three levels of indentation, though sometimes this isn't possible.<\/p>\n\n<p>Doing so where I can, though, makes my code easier to read and understand and encourages other clean code approaches, such as having small and well-named functions.<\/p>\n\n<p>In CSS or Sass, avoiding nesting makes it easier to find a rule I'm looking for instead of having to find how rules have been nested or names have been concatenated - making it hard to search or grep for a string.<\/p>\n\n<p>This approach is part of \"object callisthenics\", which was introduced by Jeff Bay and includes other approaches that I like to follow, such as not using the <code>else<\/code> keyword and other good practices that I like to try and implement when possible.--<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>One of my goals when coding is to reduce the amount of nesting in the code I write.<\/p>\n\n<p>I mean both in my PHP code where if conditions and foreach loops can be nested within each other, and CSS and Sass files, which support nesting CSS rules.<\/p>\n\n<p>My aim is to have a maximum of two or three levels of indentation, though sometimes this isn't possible.<\/p>\n\n<p>Doing so where I can, though, makes my code easier to read and understand and encourages other clean code approaches, such as having small and well-named functions.<\/p>\n\n<p>In CSS or Sass, avoiding nesting makes it easier to find a rule I'm looking for instead of having to find how rules have been nested or names have been concatenated - making it hard to search or grep for a string.<\/p>\n\n<p>This approach is part of \"object callisthenics\", which was introduced by Jeff Bay and includes other approaches that I like to follow, such as not using the <code>else<\/code> keyword and other good practices that I like to try and implement when possible.--<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:12:59+00:00",
"guid": null,
"hash": "6f502f1378e2ffcb9acf63bd11ff5325",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "18a12f53-d669-4b4e-b2bb-287ddd8ae790"
}
],
"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": "Use Drupal to own your content\n"
}
],
"created": [
{
"value": "2023-08-05T00: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\/05\/use-drupal-to-own-your-content",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>I recently saw a social media website taking over users' handles - renaming them so they can have the original.<\/p>\n\n<p>There were similar issues a few years ago when a website put a paywall in front of articles written by its users.<\/p>\n\n<p>I've written articles and tutorials for companies I've worked at that no longer exist as the companies have been acquired.<\/p>\n\n<p>This is why I like to own and control my content. I still have my versions if a website shuts down or changes its settings.<\/p>\n\n<p>There are a lot of options, including Drupal, that can help with this. It's easy to set up a new website and start publishing your own content.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>I recently saw a social media website taking over users' handles - renaming them so they can have the original.<\/p>\n\n<p>There were similar issues a few years ago when a website put a paywall in front of articles written by its users.<\/p>\n\n<p>I've written articles and tutorials for companies I've worked at that no longer exist as the companies have been acquired.<\/p>\n\n<p>This is why I like to own and control my content. I still have my versions if a website shuts down or changes its settings.<\/p>\n\n<p>There are a lot of options, including Drupal, that can help with this. It's easy to set up a new website and start publishing your own content.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:02+00:00",
"guid": null,
"hash": "674a5bd2088bccaaabc0533c88ba62f8",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "18addbd4-d79a-4be6-9d5e-62eea0cdcfee"
}
],
"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": "There's no value in a broken CI pipeline\n"
}
],
"created": [
{
"value": "2023-06-28T00: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\/28\/theres-no-value-in-a-broken-ci-pipeline",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>The value in a CI pipeline is when its commands and checks are running successfully, and the pipeline is passing.<\/p>\n\n<p>And then keeping it passing.<\/p>\n\n<p>If the pipeline fails, it loses all of its value.<\/p>\n\n<p>Passing should be its default state, and effort should be made to ensure it continues to pass.<\/p>\n\n<p>If a pipeline fails, the change is not deployed, and the failure should be investigated and rectified so the pipeline is returned to a passing state and providing value.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>The value in a CI pipeline is when its commands and checks are running successfully, and the pipeline is passing.<\/p>\n\n<p>And then keeping it passing.<\/p>\n\n<p>If the pipeline fails, it loses all of its value.<\/p>\n\n<p>Passing should be its default state, and effort should be made to ensure it continues to pass.<\/p>\n\n<p>If a pipeline fails, the change is not deployed, and the failure should be investigated and rectified so the pipeline is returned to a passing state and providing value.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:03+00:00",
"guid": null,
"hash": "10d1ecac9f555ea67f22615547fb1f6a",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

View file

@ -0,0 +1,100 @@
{
"uuid": [
{
"value": "18ca6a8e-9c35-47ec-8fe3-3721edc47e13"
}
],
"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": "How long should a feature flag live?\n"
}
],
"created": [
{
"value": "2023-06-05T00: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\/05\/how-long-should-a-feature-flag-live",
"langcode": "en"
}
],
"body": [
{
"value": "\n <p>Instead of creating a branch that lives for as long as the code takes to write, if it's behind a feature flag, the code can be merged into the mainline branch without affecting the rest of the codebase.<\/p>\n\n<p>Being able to release changes incrementally lowers the risk compared to releasing a large change all at once.<\/p>\n\n<p>But the same issue can occur with feature flags, and the longer that code is behind a feature flag, the more risk there will be when enabling the feature.<\/p>\n\n<p>So, like feature branches, feature flags should be short-lived and only used for as long as is needed to create the first releasable version of the feature. The feature flag can be removed once the feature is live, and the feature can continue to be iterated on and improved.<\/p>\n\n ",
"format": "full_html",
"processed": "\n <p>Instead of creating a branch that lives for as long as the code takes to write, if it's behind a feature flag, the code can be merged into the mainline branch without affecting the rest of the codebase.<\/p>\n\n<p>Being able to release changes incrementally lowers the risk compared to releasing a large change all at once.<\/p>\n\n<p>But the same issue can occur with feature flags, and the longer that code is behind a feature flag, the more risk there will be when enabling the feature.<\/p>\n\n<p>So, like feature branches, feature flags should be short-lived and only used for as long as is needed to create the first releasable version of the feature. The feature flag can be removed once the feature is live, and the feature can continue to be iterated on and improved.<\/p>\n\n ",
"summary": null
}
],
"feeds_item": [
{
"imported": "2025-04-16T14:13:03+00:00",
"guid": null,
"hash": "d7e1f6d61d5541ac83cd2bfe9bc3e669",
"target_type": "feeds_feed",
"target_uuid": "90c85284-7ca8-4074-9178-97ff8384fe76"
}
]
}

Some files were not shown because too many files have changed in this diff Show more