Copy daily emails from Tome
This commit is contained in:
parent
1b8441608f
commit
b438b27847
70 changed files with 1907 additions and 0 deletions
7
source/_posts/2025-05-10.md
Normal file
7
source/_posts/2025-05-10.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-10
|
||||
title: Write it down
|
||||
permalink: /daily/2025/05/10/write
|
||||
---
|
||||
|
||||
<p>There's nothing more frustrating for me than seeing an error I've seen before and not remembering how I fixed it last time.</p><p>I try to remember and search for the error message, just to find and read the same articles and posts again or watch the same videos.</p><p>I wrote my first blog post on my website in 2010 to document my learning for myself and to share with others.</p><p>I've also been a keen note taker, using tools like Evernote and others to take notes and write documentation for myself to refer to in the future.</p><p>These days, I just <a href="/daily/2024/11/10/write-plain-text-files">write plain text files</a> using <a href="https://github.com/nickjj/notes">Nick Janetakis' notes program</a>, which I've modified slightly <a href="/daily/2025/04/21/patch">by patching it</a> to create daily notes instead of monthly ones. </p><p>They're fast to write, easy to search and available offline if I'm traveling or away from my computer.</p><p>I much prefer being able to search my notes and find what I'm looking for or, if it's a post that I've written publicly, searching online and finding my own answer.</p><p>Whether you're a new or experienced Developer, you're always learning new things, so write them down for yourself and, if you want, write publicly and share your learnings with others.</p>
|
7
source/_posts/2025-05-11.md
Normal file
7
source/_posts/2025-05-11.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-11
|
||||
title: My daily email archive, powered by Drupal and Tome
|
||||
permalink: /daily/2025/05/11/email-archive-tome
|
||||
---
|
||||
|
||||
<p>This email will be the first in my archive to be powered by Drupal and Tome.</p><p>I'm a long-time fan of static site generators and have used Sculpin for several years for my website, but it has slowed down recently - I think because of the number of pages it needs to generate for my email archive pages.</p><p>This email will be #821.</p><p>I've considered moving to Tome - a static site generator for Drupal - since <a href="/podcast/19-sam-mortenson">recording a podcast episode with Sam Mortenson</a> and, after porting across my theme and migrating the emails using the Feeds module, I've updated my Nginx configuration to serve the email archive section to HTML generated by Tome.</p><p>The main branch in <a href="https://code.oliverdavies.uk/opdavies/oliverdavies.uk/src/branch/main">my code repository</a> now includes the Tome commits and I'll be working my way through <a href="https://code.oliverdavies.uk/opdavies/oliverdavies.uk/src/branch/main/todo.txt">my task list</a> as I continue <a href="/daily/2025/04/17/incrementally">migrating incrementally</a> and switching more sections to Tome.</p>
|
7
source/_posts/2025-05-12.md
Normal file
7
source/_posts/2025-05-12.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-12
|
||||
title: Writing tests for Tome
|
||||
permalink: /daily/2025/05/12/writing-tests-tome
|
||||
---
|
||||
|
||||
<p>I have recently <a href="/daily/2025/05/11/email-archive-tome">migrated my daily email archive</a> to Drupal and Tome.</p><p>The result is the same - a static website that I can upload onto my server, but it's created with Drupal 11 locally and exported to a static website.</p><p>Similar to being able to <a href="/daily/2025/05/02/extend">extend Sculpin with PHP</a>, I get the same benefit with Tome.</p><p>I can write the same PHP code and custom Drupal modules as I would with a non-Tome Drupal website.</p><p>To count the number of sent daily emails, I created a <code>opd_daily_emails</code> module with a token that generates the number of emails, and displays it on the page.</p><p>I can also write the same automated tests.</p><p>I have a functional test that confirms the token result is generated and rendered correctly using <code>TokenTestTrait</code> - supported by some kernel tests and a custom <code>DailyEmailTestTrait</code> that makes it easier to create daily email nodes within the tests.</p><p>I have all the power and flexibility of Drupal locally and the benefits of a static site when it's deployed.</p><p>P.S. If you want to learn how to write automated tests in Drupal, subscribe to <a href="/atdc">my free 10-day email course</a>.</p>
|
7
source/_posts/2025-05-13.md
Normal file
7
source/_posts/2025-05-13.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-13
|
||||
title: Writing your own test traits
|
||||
permalink: /daily/2025/05/13/writing-your-own-test-traits
|
||||
---
|
||||
|
||||
<p>In Drupal, there are different types of automated tests we can write.</p><p>The most common I use are Functional, Kernel and Unit - which I explain about in <a href="/presentations/tdd-test-driven-drupal">my Test Driven Drupal talk</a>.</p><p>If I'm writing multiple of the same type, I'll often create my own base test class that extends <code>BrowserTestCase</code>, <code>KernelTestCase</code> or whatever base class I need rather than extending it directly.</p><p>This allows me to write custom helper functions and share behaviour between the tests.</p><p>An example is the <code>createDailyEmailNode</code> method I wrote to simplify creating daily emails in my tests <a href="/daily/2025/05/11/email-archive-tome">since migrating to Tome</a>.</p><p>But, what if you want to do this for different types of test?</p><p>Enter, traits.</p><p>Traits are a way of reusing code without inheritance - meaning without extending a base class.</p><p>I can use a trait in my functional and kernel tests whilst both extend their required base classes.</p><p>I did this with my <code>createDailyEmailNode</code> method so I could re-use it in both types of tests when counting the number of sent daily emails.</p><p>To see this, you can <a href="https://code.oliverdavies.uk/opdavies/oliverdavies.uk/src/commit/88ec3d9e5136c6cbefebba063863ef1a058f4b09/modules/opd_daily_emails/tests/src">look at the code</a> on my Forgejo instance.</p><p>P.S. If you want to learn how to write automated tests in Drupal, subscribe to <a href="http://localhost:8888/atdc">my free 10-day email course</a>.</p>
|
7
source/_posts/2025-05-15.md
Normal file
7
source/_posts/2025-05-15.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-15
|
||||
title: Don't dump. Write a test.
|
||||
permalink: /daily/2025/05/15/dont-dump-write-test
|
||||
---
|
||||
|
||||
<p>How often do you use functions like <code>var_dump</code>, <code>dump</code>, <code>dd</code> or <code>console.log</code> to debug an issue?</p><p>You want to check the type or value of something, so you add the debug code and reload the page.</p><p>If you don't resolve it on the first attempt, you change it or add more debugging lines which, hopefully, you'll remember to clean up before committing your code.</p><p>This is a time-consuming process, especially if the code you're debugging isn't easy to trigger.</p><p>Maybe it only happens when you've submitted a long form, so you need to manually complete the form each time you want to debug it, if certain steps have already happened or the application state is a certain way.</p><p>Instead, consider writing an automated test.</p><p>They can be run when needed without needing to manually fill in forms or complete complex steps, and become a permanent part of the codebase rather than something that's only added temporarily whilst debugging.</p><p>They can be run manually by Developers, automatically in a CI pipeline and serve as documentation and examples.</p><p>Don't dump. Test.</p><p>P.S. If you want to learn how to write automated tests in Drupal, subscribe to <a href="http://localhost:8888/atdc">my free 10-day email course</a>.</p>
|
7
source/_posts/2025-05-18.md
Normal file
7
source/_posts/2025-05-18.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-18
|
||||
title: How quickly can you get back online?
|
||||
permalink: /daily/2025/05/18/how-quickly-can-you-get-back-online
|
||||
---
|
||||
|
||||
<p><a href="https://dora.dev/guides/dora-metrics-four-keys">The DORA metrics</a> are four key metrics used to indicate the velocity and stability of software development.</p><p>They are:</p><ul><li>Deployment frequency - how often you successfully releases to production.</li><li>Lead time for changes - the amount of time it takes for a commit to get into production.</li><li>Change failure rate - the percentage of deployments causing a failure in production.</li><li>Time to restore service - the amount of time it takes to recover from a failure in production.</li></ul><p>If you had an issue after a release to production, how long would it take you to recover?</p><p>If the amount of changes is small and it hasn't been long since the last release, it could be easy to revert the code change and re-deploy.</p><p>If it's been a while since the last release or the release contains large changes, this will be harder.</p><p>If you use feature flags, can you disable a flag and stop the code that's causing the issue?</p><p>What if you need to recreate the whole environment?</p><p>How old is your most recent backup? Have you verified the backup works and can be used to restore a database, the user-uploaded files or the whole environment?</p><p>A backup is only good if it is recent and can be restored. Otherwise, it's useless.</p><p>But, restoring from backups can take time and lose data, so this should be the last option.</p><p>Releasing small changes often and using tools like feature flags will help minimise the downtime from an issue and allow service to be restored as quickly as possible.</p>
|
7
source/_posts/2025-05-19.md
Normal file
7
source/_posts/2025-05-19.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-19
|
||||
title: Learning lessons
|
||||
permalink: /daily/2025/05/19/learning-lessons
|
||||
---
|
||||
|
||||
<p>Once you've <a href="/daily/2025/05/18/how-quickly-can-you-get-back-online">resolved an issue and restored service</a>, what lessons can be learned to ensure the same issue doesn't happen again?</p><p>Could the issue have been caught or identified sooner, before it got to production?</p><p>Ideally, an issue would be identified in a local environment, in a CI pipeline or a staging environment.</p><p>Anything before production, but the sooner, the better.</p><p>Can you introduce more tools or automated checks?</p><p>PHP in particular has a lot of tools for linting, analysing and testing code, such as PHPStan and PHPUnit.</p><p>These can be run locally or automatically within a CI pipeline.</p><p>If the pipeline is passing, the code should be deployable.</p><p>But, if there is an issue, what needs to be added to the pipeline that would have identified it?</p><p>How can you iterate and make your development and deployment processes better and more robust?</p>
|
7
source/_posts/2025-05-20.md
Normal file
7
source/_posts/2025-05-20.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-20
|
||||
title: Why write your own CMS?
|
||||
permalink: /daily/2025/05/20/why-write-your-own-cms
|
||||
---
|
||||
|
||||
<p>I've been surprised to still recently find software development agencies using their own proprietary content management systems and frameworks.</p><p>With so many available open source options, like Drupal and Symfony, why would people write their own?</p><p>Why spend the time and effort to build basic functionality like user registration and login when existing tools already solve that problem?</p><p>Why ignore the thousands of open source modules, themes, plugins, packages and libraries that are available?</p><p>Instead, leverage the tools that already exist and focus on solving domain-specific issues and writing code that's genuinely specific to the project.</p><p>Then you can contribute some of the saved time back to the project and make it better for yourself and others in the future and ready for the next project.</p>
|
7
source/_posts/2025-05-24.md
Normal file
7
source/_posts/2025-05-24.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-24
|
||||
title: Using AI for web coding with Luke McCormick
|
||||
permalink: /daily/2025/05/24/using-ai-web-coding-luke-mccormick
|
||||
---
|
||||
|
||||
<p>This week, I was excited to welcome Luke McCormick back to the Beyond Blocks podcast.</p><p>We discussed his Simplify Drupal project and using AI tools, such as the Cursor editor, for web browsing.</p><p>Luke recently gave a talk about this at the Stanford WebCamp conference, so it was great to discuss this further and learn how he's been using AI in his development workflow.</p><p><a href="/podcast/28-using-ai-tools-web-coding">Listen to the episode here</a>.</p><p>I have a few new guests lined up, so expect more episodes of the Beyond Blocks podcast in the coming weeks.</p><p>If you want to be a guest on a future episode, reply to this email and let me know.</p>
|
7
source/_posts/2025-05-25.md
Normal file
7
source/_posts/2025-05-25.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-25
|
||||
title: Why I prefer a rolling Linux distribution
|
||||
permalink: /daily/2025/05/25/why-i-prefer-rolling-linux-distribution
|
||||
---
|
||||
|
||||
<p>I use NixOS as the Linux distribution on my laptop and home server, and specifically the unstable channel.</p><p>This is the "rolling" version of NixOS that is continuously updated with the newest and latest packages, compared to the stable releases like 24.11 and 25.05 that are released twice a year.</p><p>I don't necessarily do this to get the latest packages.</p><p>I do it to keep on top of configuration changes.</p><p>I run "nix flake update" usually once a week to download the latest updates.</p><p>If there are breaking changes or notices since I last updated, I can address them and do it iteratively rather than waiting to do them every six months.</p><p>Similar to application deployments, <a href="/daily/2023/05/14/releasing-small-changes-often-is-less-risky">I update early and often</a>.</p><p>One of the great things about NixOS is, if there is an application that breaks on the unstable branch, I can pin it to the stable release until it works again.</p><p>This means I get the benefits of both the rolling and stable approaches.</p><p>If you want to see my NixOS configuration, it's <a href="https://code.oliverdavies.uk/opdavies/nix-config">publicly available on my Forgejo instance</a>.</p>
|
7
source/_posts/2025-05-27.md
Normal file
7
source/_posts/2025-05-27.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-27
|
||||
title: Using AI for web coding with Luke McCormick (re-uploaded)
|
||||
permalink: /daily/2025/05/27/using-ai-web-coding-luke-mccormick-re-uploaded
|
||||
---
|
||||
|
||||
<p>A few days ago, I published a new episode of the Beyond Blocks podcast with Luke McCormick, where we spoke about using AI and tools like Cursor for web coding.</p><p>Unfortunately, there was an export issue with the episode that affected the sound quality.</p><p>The recording was fine and the mp3 file was re-created and uploaded again to the episode page and the podcast feed for Spotify, Pocket Casts, etc.</p><p><a href="/podcast/28-using-ai-tools-web-coding">Listen to the episode here</a>.</p><p>Apologies to Luke and anyone who listened to the original release for the poor quality, but it should be fine now and I'll add this to my checklist when editing podcast episodes to ensure it doesn't happen again.</p>
|
50
source/_posts/2025-05-28.md
Normal file
50
source/_posts/2025-05-28.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
date: 2025-05-28
|
||||
title: Drupal-powered podcast pages
|
||||
permalink: /daily/2025/05/28/drupal-powered-podcast-pages
|
||||
---
|
||||
|
||||
<p>Following my daily email archive, I've recently switched the podcast pages on my website from Sculpin to Drupal/Tome.</p><p>The <a href="/podcast/28-using-ai-tools-web-coding">recent episode with Luke McCormick</a> was the first to be created in Drupal and served from static HTML generated by Tome, and I've since re-created the other podcast episodes and <a href="/podcast">the podcast landing page</a>.</p><p>The next steps are to re-add the links on a podcast episode page to other episodes with the same guests, and to rebuild the podcast feed that's used to update players like Spotify and PocketCasts.</p><p>Once I've finished this, I'll move on to <a href="/presentations">my presentations pages</a> as those are the ones that change next frequently.</p><h2>How am I doing this?</h2><p>A lot of the content is still served from HTML generated by Sculpin, which is stored in one directory on my server.</p><p>The newer content, generated by Tome, is stored in another directory.</p><p>In my Nginx configuration, I change the <code>root</code> value based on the URL, so depending on which page you're visiting, you'll get content from Sculpin or Tome.</p><p>Here's part of that configuration:</p><pre><code class="language-plaintext">server {
|
||||
listen localhost:8095:
|
||||
server_name www.oliverdavies.uk:
|
||||
root /var/www/vhosts/website-sculpin;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri.html $uri/index.html =404;
|
||||
}
|
||||
|
||||
location ~ ^/archive {
|
||||
try_files $uri $uri.html $uri/index.html =404;
|
||||
root /var/www/vhosts/website-tome;
|
||||
}
|
||||
|
||||
location ~ ^/core {
|
||||
try_files $uri $uri.html $uri/index.html =404;
|
||||
root /var/www/vhosts/website-tome;
|
||||
}
|
||||
|
||||
location ~ ^/daily/.+ {
|
||||
try_files $uri $uri.html $uri/index.html =404;
|
||||
root /var/www/vhosts/website-tome;
|
||||
}
|
||||
|
||||
location ~ ^/homelab {
|
||||
try_files $uri $uri.html $uri/index.html =404;
|
||||
root /var/www/vhosts/website-tome;
|
||||
}
|
||||
|
||||
location ~ ^/podcast {
|
||||
try_files $uri $uri.html $uri/index.html =404;
|
||||
root /var/www/vhosts/website-tome;
|
||||
}
|
||||
|
||||
location ~ ^/sites/default/files {
|
||||
try_files $uri $uri.html $uri/index.html =404;
|
||||
root /var/www/vhosts/website-tome;
|
||||
}
|
||||
|
||||
location ~ ^/themes/custom/opdavies {
|
||||
try_files $uri $uri.html $uri/index.html =404;
|
||||
root /var/www/vhosts/website-tome;
|
||||
}
|
||||
}</code></pre><p>This is the same approach as <a href="/daily/2025/04/17/incrementally">upgrading incrementally</a> from old versions of software to new versions or different software.</p><p>Neither site knows about the other and they work independently.</p><p>My Nginx configuration is managed within <a href="https://code.oliverdavies.uk/opdavies/nix-config/src/commit/a994777ba631cf95a16e2bb8f71e344a50bc11f3/hosts/nixedo/modules/nginx/www.oliverdavies.uk/default.nix#L11-L39">my NixOS configuration</a>, so you can see the whole configuration for my website and how I've leveraged the Nix language to simplify the process of migrating new paths to Tome.</p>
|
7
source/_posts/2025-05-30.md
Normal file
7
source/_posts/2025-05-30.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-30
|
||||
title: Do you need that module?
|
||||
permalink: /daily/2025/05/30/do-you-need-module
|
||||
---
|
||||
|
||||
<p>You've been asked to add some new functionality to a project.</p><p>There is a module or library available that you could download and use.</p><p>You could write a custom module.</p><p>But do you really need it?</p><p>Is there another way to implement the feature without adding more modules or libraries to your application or writing custom code?</p><p>Is there an alternative implementation that is easier to implement with what you already have?</p><p>How flexible are the requirements?</p><p>Can they be bent to fit within an existing solution?</p><p>Having less code in your application will make it easier to maintain and upgrade in the future, so keep it as lean and minimal as possible.</p>
|
7
source/_posts/2025-05-31.md
Normal file
7
source/_posts/2025-05-31.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-05-31
|
||||
title: Do you still need that module?
|
||||
permalink: /daily/2025/05/31/do-you-still-need-module
|
||||
---
|
||||
|
||||
<p>If a module or library has been added to an application, it was done to serve a purpose.</p><p>It must add some required functionality that was asked for earlier in the application''s lifecycle.</p><p>It was needed then.</p><p>But it is still needed now?</p><p>Are the original requirements still valid?</p><p>Have they changed or are no longer needed?</p><p>If the code is no longer serving a purpose, it should be removed.</p><p>Common culprits in Drupal projects are the Feeds and Migrate modules, which are used to import data from external sources, such as legacy applications when migrating to Drupal.</p><p>Once the data has been imported and the site is live, the modules are often so longer needed.</p><p><a href="/daily/2025/05/30/do-you-need-module">As I said yesterday</a>, having less code in your application will make it easier to maintain and upgrade in the future, so keep it as lean and minimal as possible - but this is an ongoing process as requirements change over time.</p>
|
7
source/_posts/2025-06-01.md
Normal file
7
source/_posts/2025-06-01.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-06-01
|
||||
title: Good commit messages don't always matter
|
||||
permalink: /daily/2025/06/01/good-commit-messages-dont-always-matter
|
||||
---
|
||||
|
||||
<p>Writing <a href=\"/daily/2024/05/17/why-i-dont-commit-with--m\">good commit messages</a> is important.</p><p>A good commit message doesn't just describe what changed, it captures why it was changed.</p><p>It can detail other approaches that were considered or tried before deciding on the end solution.</p><p>It can contain links to relevant issues, documentation pages, blog posts or videos.</p><p>It can document any manual deployment steps, or follow-up actions that will be addressed in future commits.</p><p>This all makes sense for permanent commits.</p><p>But, sometimes you may make a temporary commit that won't always remain in the codebase.</p><p>For example, if you're working in a pair or mob programming session, you want to be able to switch drivers as quickly and seamlessly as possible.</p><p>In that situation, you just need a basic commit message to share the latest changes, so a quick <code>wip</code> commit is fine and it can be amended later.</p>
|
7
source/_posts/2025-06-02.md
Normal file
7
source/_posts/2025-06-02.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-06-02
|
||||
title: Squashing commits can be OK
|
||||
permalink: /daily/2025/06/02/squashing-commits-can-be-ok
|
||||
---
|
||||
|
||||
<p>As well as <a href="/daily/2025/04/04/good">writing good commit messages</a>, I've previously written about <a href="/daily/2023/01/25/to-squash-or-not-to-squash">not squashing commits</a> when merging.</p><p>I think it's beneficial to keep the history of the commits that led to a change, especially if detailed messages have been written for some of the commits.</p><p>Typically, if the commits are squashed as part of a pull or merge request, the history and information is lost or all the messages are merged together - making them hard to read and, arguably, less valuable.</p><p>If you're working in a pair or mob and <a href="/daily/2025/06/01/good-commit-messages-dont-always-matter">creating temporary commits on a short-lived branch</a>, that's a situation when squashing commits is OK - as long as it's done properly.</p><p>I wouldn't have a generic automatically generated message.</p><p>I'd take the time to review the changes on the temporary branch and compare them to the mainline, remove any unrelated changes and write a new commit message that describes all the changes.</p><p>I'd make sure the new message is used and not lost when merged - especially when using online tools.</p><p>Then I can squash any temporary commits and merge the final squashed version.</p>
|
7
source/_posts/2025-06-03.md
Normal file
7
source/_posts/2025-06-03.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-06-03
|
||||
title: Discussing Drupal CMS Recipes with Gareth Alexander
|
||||
permalink: /daily/2025/06/03/discussing-drupal-cms-recipes-gareth-alexander
|
||||
---
|
||||
|
||||
<p>I''m happy to say that Gareth Alexander - Drupal CMS Accessibility Tools Track Lead - is the newest guest on the Beyond Blocks podcast.</p><p>Gareth was a speaker at the recent DrupalCamp England event in Cambridge, where he spoke about Drupal CMS and Recipes - something I was keen to learn more about, so I was delighted to have him on the show.</p><p><a href="/podcast/29-drupal-cms-recipes">Listen to the episode here</a>.</p><p>If you want to be a guest on a future episode or suggest someone who should be, reply to this email and let me know. Any suggestions or recommendations welcomed!</p>
|
7
source/_posts/2025-06-04.md
Normal file
7
source/_posts/2025-06-04.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
date: 2025-06-04
|
||||
title: Picking cherries
|
||||
permalink: /daily/2025/06/04/picking-cherries
|
||||
---
|
||||
|
||||
<p>If you''re working on a feature branch, or a <a href="/daily/2025/06/01/good-commit-messages-dont-always-matter">temporary branch for pair or mob programming</a>, what do you do if you accidentally commit a change to the wrong branch?</p><p>Do you reset your changes, switch to the correct branch and re-create the same changes manually?</p><p>You don''t need to.</p><p>Git has a solution for this.</p><p>Create the commit as you would on the correct branch and copy the commit SHA.</p><p>Use <code>git checkout</code> or <code>git switch</code> to move to the correct branch and use <code>git cherry-pick</code> with the commit SHA.</p><p>It will pluck the commit from the branch and re-apply the changes with the same commit message.</p><p>Then, if you merge or rebase your temporary branch, Git will know the change has already been applied and skip that commit.</p><p>No need to re-do the same changes again manually.</p>
|
30
source/_posts/2025-06-08.md
Normal file
30
source/_posts/2025-06-08.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
date: 2025-06-08
|
||||
title: Switching to NixVim
|
||||
permalink: /daily/2025/06/08/switching-nixvim
|
||||
---
|
||||
|
||||
I've been [a full-time Neovim user][0] since July 2021.
|
||||
|
||||
I've used Nix since September 2022, when I migrated my dotfiles from using Ansible to use Nix and Home Manager.
|
||||
|
||||
My Neovim configuration has gone through several iterations to what it is today.
|
||||
|
||||
I've used Nix for a while to install plugins for Neovim, but the configuration was all written in Lua files which were placed by Home Manager into the correct directory.
|
||||
|
||||
Now, I've switched to using a project called NixVim.
|
||||
|
||||
It enables configuring Neovim using the Nix language, which is converted to Lua code behind the scenes.
|
||||
|
||||
Doing this makes my configuration files [more consistent and easier to maintain][1], and reduced the amount of code I needed for my Neovim configuration.
|
||||
|
||||
Most of the configuration in Nix is equivalent to the options in Lua, but there are some great settings like `plugins.lsp.enable` and `plugins.cmp.enable` that make it easy to configure language servers and completion.
|
||||
|
||||
I've also found a [great episode about this][2] on the Full Time Nix podcast that I'd recommend to find out more.
|
||||
|
||||
If you're interested in Nix and/or Neovim, [give NixVim a look][3].
|
||||
|
||||
[0]: /blog/going-full-vim
|
||||
[1]: /daily/2024/11/21/one-configuration-language-to-rule-them-all
|
||||
[2]: https://fulltimenix.com/episodes/contributing-to-nixpkgs-nixvim-with-gaetan-lepage
|
||||
[3]: https://github.com/nix-community/nixvim
|
38
source/_posts/2025-06-09.md
Normal file
38
source/_posts/2025-06-09.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
date: 2025-06-09
|
||||
title: tmux is my session manager
|
||||
permalink: /daily/2025/06/09/tmux-my-session-manager
|
||||
---
|
||||
|
||||
If you're subscribed to my mailing list, listened to a podcast episode or watched any of my presentations or live streams, you'll probably know that I use a command line-based approach to software development.
|
||||
|
||||
I use NixOS as my operating system, Neovim and tmux for coding, and various other command-line tools as part of my daily workflow.
|
||||
|
||||
tmux is a terminal multiplexer - a tool to run multiple sessions, windows and panes within the same terminal.
|
||||
|
||||
I have one session per project or directory, each with its own windows and panes to run Neovim and other tools to work on that project.
|
||||
|
||||
YouTube and Twitch streamer rwxrob said that tmux was his window manager, based on how he used it to organise his desktop.
|
||||
|
||||
tmux is my session manager.
|
||||
|
||||
As well as being able to easily switch between codebases, each has it's own startup script that bootstraps the project for me.
|
||||
|
||||
This is the script for my website:
|
||||
|
||||
```bash
|
||||
PATH="${PATH}:./vendor/bin"
|
||||
|
||||
tmux new-window -dn scratch
|
||||
tmux new-window -dn server
|
||||
tmux new-window -dn tailwindcss -c "themes/opdavies"
|
||||
|
||||
tmux send-keys -t server "drush runserver" Enter
|
||||
tmux send-keys -t tailwindcss "tailwindcss --input css/tailwind.css --output build/tailwind.css --watch" Enter
|
||||
|
||||
nvim .
|
||||
```
|
||||
|
||||
It creates windows for scratch commands, starts a web server with Drush, starts Tailwind CSS to generate any new styles and opens Neovim.
|
||||
|
||||
Some are simpler and some are more complex, but it reduces the friction between switching projects and makes it quick and simple.
|
17
source/_posts/2025-06-10.md
Normal file
17
source/_posts/2025-06-10.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
date: 2025-06-10
|
||||
title: Easily amend commits with git-instafix
|
||||
permalink: /daily/2025/06/10/easily-amend-commits-git-instafix
|
||||
---
|
||||
|
||||
How often do you make commits to a Git branch to later realise that you need to amend one of your unpushed commits?
|
||||
|
||||
A simple `git commit --amend` will only work on the most recent commit, so that won't work if you've added more commits after the one you want to amend.
|
||||
|
||||
You can make a temporary commit, rebase your branch and squash the temporary commit into the original one.
|
||||
|
||||
Or you can use [git-instafix][0] - a tool for easily fixing and amending old commits.
|
||||
|
||||
Instead of making a temporary commit, running `git-instafix` will show your unpushed commits - allowing you to select one and it will amend it for you in a single step, making it easier to keep your Git commit history cleaner and easier to read and review.
|
||||
|
||||
[0]: https://github.com/quodlibetor/git-instafix
|
17
source/_posts/2025-06-11.md
Normal file
17
source/_posts/2025-06-11.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
date: 2025-06-11
|
||||
title: Nix for PHP Developers
|
||||
permalink: /daily/2025/06/11/nix-php-developers
|
||||
---
|
||||
|
||||
I've been brainstorming ideas for new presentations to give at meetups and conferences.
|
||||
|
||||
The one that appeals to me at the moment is "Nix for PHP Developers".
|
||||
|
||||
I've been using Nix since September 2022 and have replaced Docker in all of my projects with Nix-based development shells to add the required versions of PHP and anything else needed for each project.
|
||||
|
||||
In the talk, I can introduce some Nix and NixOS basics before showing different PHP examples from basic development shells with PHP to full Docker Compose-like setups for Drupal applications.
|
||||
|
||||
Does this sound interesting?
|
||||
|
||||
If you have feedback or suggestions, or would like me to give this or another talk at your meetup, get in touch.
|
21
source/_posts/2025-06-12.md
Normal file
21
source/_posts/2025-06-12.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
date: 2025-06-12
|
||||
title: Refactoring is a rabbit hole
|
||||
permalink: /daily/2025/06/12/refactoring-rabbit-hole
|
||||
---
|
||||
|
||||
I enjoy refactoring code.
|
||||
|
||||
Once I've finished writing some code and all the tests are passing, I like reviewing what I've written and looking for ways to make it more readable or easier to maintain whilst making sure it still works.
|
||||
|
||||
I usually start with one small refactor.
|
||||
|
||||
Then I spot another.
|
||||
|
||||
And another.
|
||||
|
||||
I end up going down a refactoring rabbit hole and I keep seeing changes I could make.
|
||||
|
||||
But, at some point, you need to say enough refactoring and decide if the code is good enough to release, whether you do this yourself or as part of a pair or mob.
|
||||
|
||||
It can always be refactored more in the future, if needed.
|
21
source/_posts/2025-06-13.md
Normal file
21
source/_posts/2025-06-13.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
date: 2025-06-13
|
||||
title: Refactorings should be small
|
||||
permalink: /daily/2025/06/13/refactorings-should-be-small
|
||||
---
|
||||
|
||||
When refactoring code, try and make each change as small as possible.
|
||||
|
||||
Just renaming a variable to something easier to understand is a valid refactor.
|
||||
|
||||
So is extracting a small method or moving logic to another class.
|
||||
|
||||
I recently make [some refactoring commits](https://code.oliverdavies.uk/opdavies/oliverdavies.uk/commits/branch/main/search?q=Refactor) to my website and, rather than squashing them, I pushed them to show how simple refactoring can be.
|
||||
|
||||
It's easier to see and review each refactor separately in its own commit instead of in one large squashed commit.
|
||||
|
||||
It's also easier to keep the code in a working state if the refactors are smaller.
|
||||
|
||||
If you break the code whilst refactoring, get back to a working state as soon as possible - even if it means resetting back to the last working commit.
|
||||
|
||||
Don't keep making changes - it will be harder to get back to a working state.
|
21
source/_posts/2025-06-14.md
Normal file
21
source/_posts/2025-06-14.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
date: 2025-06-14
|
||||
title: Do code reviews prevent refactoring?
|
||||
permalink: /daily/2025/06/14/do-code-reviews-prevent-refactoring
|
||||
---
|
||||
|
||||
Does having a strict code review process prevent or discourage refactoring?
|
||||
|
||||
If I want to make a very small refactor, like renaming a variable, am I likely to do that if I need to make a feature branch, create a merge request, and wait for multiple reviews from team members?
|
||||
|
||||
What if it takes a day or more for someone to review it?
|
||||
|
||||
Is it worth a reviewer's time to review a refactoring that could only be a single line of changed code?
|
||||
|
||||
Alternatively, what if you were doing trunk-based development and/or continuous integration?
|
||||
|
||||
What if you could make the small change, push it and have a CI pipeline automatically review it?
|
||||
|
||||
The whole process could be completed in minutes.
|
||||
|
||||
No long wait for manual code review and once you finish that refactoring, [you may find another](/daily/2025/06/13/refactorings-should-be-small).
|
19
source/_posts/2025-06-15.md
Normal file
19
source/_posts/2025-06-15.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
date: 2025-06-15
|
||||
title: Is refactoring a lost art?
|
||||
permalink: /daily/2025/06/15/refactoring-lost-art
|
||||
---
|
||||
|
||||
How many people or teams take time to refactor their code?
|
||||
|
||||
Once the code is written and working, is it immediately considered done and not looked at again?
|
||||
|
||||
Or it is reviewed to check if it is as clear and readable as it can be?
|
||||
|
||||
Is it changed to be easier to change in the future?
|
||||
|
||||
How many open source projects have issues or merge requests for refactoring, or are they only focused on new features or fixing bugs?
|
||||
|
||||
Like writing automated tests, time should be invested in refactoring code.
|
||||
|
||||
Having cleaner code that's easier to read, understand and change in the future will make the tasks of future Developers easier and make the codebase easier to maintain and provide a solid foundation for future development.
|
31
source/_posts/2025-06-16.md
Normal file
31
source/_posts/2025-06-16.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
date: 2025-06-16
|
||||
title: Refactoring, semantic versioning and backward compatibility
|
||||
permalink: /daily/2025/06/16/refactoring-semantic-versioning-and-backward-compatibility
|
||||
---
|
||||
|
||||
There's an approach to refactoring I've seen in a number of open source projects, including Symfony and Drupal.
|
||||
|
||||
In Drupal 7 and earlier, there were only minor version releases.
|
||||
|
||||
Drupal 7 started at 7.0 and ended at 7.103.
|
||||
|
||||
Code could be refactored, but it still needed to be backward compatible so existing code couldn't be removed.
|
||||
|
||||
Now, many projects have adopted semantic versioning that contain major, minor and patch versions, like Drupal 11.2.0.
|
||||
|
||||
Minor and patch versions are backward compatible, but major versions - such as Drupal 10 and 11 - can break backward compatibility.
|
||||
|
||||
This means old code can be removed and the codebase can be tidied.
|
||||
|
||||
A good example of this was the `drupal_set_message()` function that was replaced by the `Messenger` service.
|
||||
|
||||
The original code was moved, the function was changed to use the new service and the function was marked as deprecated.
|
||||
|
||||
If you used it, you were notified it would be removed in the next major version.
|
||||
|
||||
It still worked so was still backward compatible.
|
||||
|
||||
When the next major version was released, the old function was removed as backward compatibility could be broken.
|
||||
|
||||
I like this approach as it means code can be refactored and improved, users are given time to update their code, new features can be continuously added, and the main codebase is kept clean and avoids a lot of legacy code.
|
52
source/_posts/2025-06-17.md
Normal file
52
source/_posts/2025-06-17.md
Normal file
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
date: 2025-06-17
|
||||
title: Drupal Bundle Classes
|
||||
permalink: /daily/2025/06/17/drupal-bundle-classes
|
||||
---
|
||||
|
||||
As someone who writes a lot of custom Drupal modules, one of my favourite additions to Drupal has been Bundle Classes.
|
||||
|
||||
What do they do?
|
||||
|
||||
When writing Drupal modules, instead of relying on generic classes like `Node` or `Term`, you can create your own class for each entity type (e.g. each content type or taxonomy vocabulary).
|
||||
|
||||
This makes the code more readable and means you can add behaviour to each class by adding its own methods.
|
||||
|
||||
You can see how I've done this on my website:
|
||||
|
||||
```php
|
||||
function opd_presentations_entity_bundle_info_alter(array &$bundles): void {
|
||||
if (isset($bundles['node'])) {
|
||||
$bundles['node'][Presentation::NODE_TYPE]['class'] = Presentation::class;
|
||||
}
|
||||
|
||||
if (isset($bundles['paragraph'])) {
|
||||
$bundles['paragraph'][Event::PARAGRAPH_TYPE]['class'] = Event::class;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Within my `opd_presentations.module` file, I override the classes Drupal uses for Presentation nodes and Event paragraph types.
|
||||
|
||||
My Presentation class looks like this:
|
||||
|
||||
```php
|
||||
final class Presentation extends Node implements NodeInterface {
|
||||
|
||||
public const NODE_TYPE = 'presentation';
|
||||
|
||||
public function getEvents(): Events {
|
||||
return Events::fromEvents($this->get('field_events')->referencedEntities());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
With this, to get the events for any presentation, I can do something like `$presentation->getEvents()` and this code will be used.
|
||||
|
||||
I use the same approach in my podcast module. The [code for my website is public][0] if you want to see other examples of how I'm using this approach.
|
||||
|
||||
P.S. If you have questions about how to use this approach or other ways to improve your Drupal code, [book a one-on-one consulting call with me][1] and I'll help you get started.
|
||||
|
||||
[0]: https://code.oliverdavies.uk/opdavies/oliverdavies.uk/src/commit/8a480121d203ca6f51310f952b15cfa09080b034/modules
|
||||
[1]: /call
|
35
source/_posts/2025-06-18.md
Normal file
35
source/_posts/2025-06-18.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
date: 2025-06-18
|
||||
title: Exploring Drupal Test Traits
|
||||
permalink: /daily/2025/06/18/exploring-drupal-test-traits
|
||||
---
|
||||
|
||||
I've [given talks and workshops][3] on automated testing in Drupal.
|
||||
|
||||
I created [a free 10-day email course][2].
|
||||
|
||||
Something I haven't used until recently is [Drupal Test Traits][4].
|
||||
|
||||
The built-in PHPUnit tests create a new installation of Drupal for each test, setting up everything from scratch and installing the specified modules and configuration for each test.
|
||||
|
||||
The tests don't know about the site they're running on, which is what allows them to run in Drupal CI or on any Developer's computer.
|
||||
|
||||
Each test is responsible for creating its own environment.
|
||||
|
||||
This is part of the "arrange" step of writing an automated test - creating the situation for the test to run, such as installing configuration and creating content, before performing actions and making assertions.
|
||||
|
||||
But, what if you want to test an existing website?
|
||||
|
||||
Drupal Test Traits provides an `ExistingSiteBase` class and a number of traits that allow for testing an existing Drupal website and simplifies the setup process.
|
||||
|
||||
You can see this in [my PresentationTest class][0] - there's no setup like recreating content type and field configuration as it knows about the "real" site I'm working on.
|
||||
|
||||
Some tests like [my PresentationCounterTest][1] I've had to write differently to count for the existing content, but still work.
|
||||
|
||||
I'm going to continue trying out Drupal Test Traits and, if it continues to go well, include it in my future talks and workshops.
|
||||
|
||||
[0]: https://code.oliverdavies.uk/opdavies/oliverdavies.uk/src/commit/b8c3f6d9a761f126616f0be12d3c35dffef41bbe/modules/opd_presentations/tests/src/Functional/PresentationTest.php
|
||||
[1]: https://code.oliverdavies.uk/opdavies/oliverdavies.uk/src/commit/b8c3f6d9a761f126616f0be12d3c35dffef41bbe/modules/opd_presentations/tests/src/Functional/PresentationCounterTest.php
|
||||
[2]: /atdc
|
||||
[3]: /presentations/tdd-test-driven-drupal
|
||||
[4]: https://www.drupal.org/project/dtt
|
23
source/_posts/2025-06-19.md
Normal file
23
source/_posts/2025-06-19.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
date: 2025-06-19
|
||||
title: You have nothing to lose but your bugs
|
||||
permalink: /daily/2025/06/19/you-have-nothing-lose-your-bugs
|
||||
---
|
||||
|
||||
I'm re-reading a book called "Growing Object-Oriented Software, Guided by Tests", written by Steve Freeman and Nat Pryce.
|
||||
|
||||
In it, is says this as one of the highlighted pieces of text:
|
||||
|
||||
> ... you have nothing to lose but your bugs
|
||||
>
|
||||
> We cannot empathize strongly enough how liberating it is to work on test-driven code that has thorough test coverage. We find that we can concentrate on the task in hand, confident that we're doing the right work and that it's actually quite hard to break the system - as long as we follow the practices.
|
||||
|
||||
I definitely agree with this.
|
||||
|
||||
It's great to work on a codebase that has a working and thorough test suite.
|
||||
|
||||
I can focus on adding a feature or fixing a bug without worrying about breaking the existing functionality.
|
||||
|
||||
I can refactor code and know it still works by just running the tests.
|
||||
|
||||
If you haven't already, make adding automated tests to your application a priority.
|
41
source/_posts/2025-06-20.md
Normal file
41
source/_posts/2025-06-20.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
date: 2025-06-20
|
||||
title: My thoughts on the Action pattern
|
||||
permalink: /daily/2025/06/20/my-thoughts-action-pattern
|
||||
---
|
||||
|
||||
The Action pattern is a relatively new design pattern that's become popular in the PHP community, particularly with Laravel Developers.
|
||||
|
||||
The pattern is a simplified version of the Command pattern, with no separate Handler class. The Action class is responsible for the handling and execution logic.
|
||||
|
||||
In most cases, an Action class only has a single public method called `execute()` or `handle()`, or uses PHP's `__invoke()` magic method.
|
||||
|
||||
This is a different approach from a Service class that has multiple methods to perform different tasks.
|
||||
|
||||
Here's a simplified version of the code of an Action from my website:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
readonly final class AddRandomCtaToDailyEmail {
|
||||
|
||||
public function __construct(private EntityTypeManagerInterface $entityTypeManager) {
|
||||
}
|
||||
|
||||
public function __invoke(DailyEmail $email): void {
|
||||
// Checks a call to action isn't already added.
|
||||
// If not, a random one is selected and added.
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Note the `DailyEmail` class is [a bundle class][0] I've created that extends the regular `Node` class.
|
||||
|
||||
Different to a service, the class name describes the action being performed - usually starting with a verb followed by a noun to describe the action being taken and the object it's being taken on.
|
||||
|
||||
It's a simple pattern that doesn't require additional packages or libraries, and it's easy to implement in different frameworks and other languages.
|
||||
|
||||
Whether you call this an Action, Command or something else, I like that it encourages writing more structured code that's easy to read and test.
|
||||
|
||||
[0]: /daily/2025/06/17/drupal-bundle-classes
|
31
source/_posts/2025-06-21.md
Normal file
31
source/_posts/2025-06-21.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
date: 2025-06-21
|
||||
title: Consistency is key
|
||||
permalink: /daily/2025/06/21/consistency-key
|
||||
---
|
||||
|
||||
Yesterday, I wrote about some of my [thoughts about the Action pattern](/daily/2025/06/20/my-thoughts-action-pattern) that's become popular with PHP Developers.
|
||||
|
||||
I showed an example based on the `AddRandomCtaToDailyEmail` action class from my website.
|
||||
|
||||
But, how should these classes be named?
|
||||
|
||||
Should my example by `AddRandomCtaToDailyEmail` or `AddRandomCtaToDailyEmailAction`?
|
||||
|
||||
Should my `Ctas` class - a collection of "Call to Action" nodes - be `Ctas` or `CtaCollection`?
|
||||
|
||||
In these examples, I think the class name is descriptive enough that it doesn't need to be suffixed.
|
||||
|
||||
In other cases, such as Controller classes, Interfaces, and classes that follow other design patterns such as Repositories, Factories and Builders, I will prefix to make it clearer which pattern they implement.
|
||||
|
||||
Some projects have an existing coding standard and guidelines to follow, and some will have contribution documentation or a style guide to explain which patterns to follow and how to name things so changes are consistent with the rest of the project.
|
||||
|
||||
Consider doing the same for your software.
|
||||
|
||||
Document your rules and conventions for your current and future team members.
|
||||
|
||||
The [Spatie Guidelines](https://spatie.be/guidelines) are a great example to follow.
|
||||
|
||||
Then, make sure they are followed when the code is being reviewed, either in a pull/merge request or during a pair or mob programming session.
|
||||
|
||||
Having consistent approaches makes projects more robust and easier to work on.
|
45
source/_posts/2025-06-22.md
Normal file
45
source/_posts/2025-06-22.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
date: 2025-06-22
|
||||
title: Consistency with architectural testing
|
||||
permalink: /daily/2025/06/22/consistency-architectural-testing
|
||||
---
|
||||
|
||||
Yesterday, I asked whether class names should include suffixes or be named a certain way in your application.
|
||||
|
||||
I said that [consistency is key][1].
|
||||
|
||||
But how do you enforce these rules to keep things consistent?
|
||||
|
||||
It could be done manually in a code review or pair programming session, but it would be better if it could be automated.
|
||||
|
||||
Similar to phpcs for code linting and automated tests that ensure functionality works as expected, architectural tests allow you to define and run your own architectural rules.
|
||||
|
||||
For example, I could write this to ensure Action classes don't have the `Action` suffix:
|
||||
|
||||
```php
|
||||
function test_action_classes_are_not_suffixed(): Rule
|
||||
{
|
||||
return PHPat::rule()
|
||||
->classes(Selector::inNamespace('App\Action'))
|
||||
->shouldBeNamed(
|
||||
fqcn: '/^(?!.*Action$).*$/',
|
||||
regex: TRUE,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
This is using PHPat - an extension for PHPStan - that finds all classes in an `App\Action` namespace and checks the class name matches the given format.
|
||||
|
||||
If the class name is incorrect, PHPStan will return an error:
|
||||
|
||||
> App\Action\DoesSomethingAction should be named matching the regex /^(?!.*Action$).*$/
|
||||
|
||||
I [created more examples][0] to ensure the classes are final, read-only and don't extend or implement anything.
|
||||
|
||||
PHPat has [an examples page][2] of potential use cases for architectural tests, and how to implement layered architectures or Model-View-Controller.
|
||||
|
||||
Pest PHP also has an architectural testing library so, if you use PHP, try both and see which works best for you.
|
||||
|
||||
[0]: https://code.oliverdavies.uk/opdavies/phpat-example/src/commit/cf07cec6d20bd4f6108736efdfbd2af549df2748/tests/Architecture/ArchitectureTest.php
|
||||
[1]: /daily/2025/06/21/consistency-key
|
||||
[2]: https://www.phpat.dev/examples
|
59
source/_posts/2025-06-23.md
Normal file
59
source/_posts/2025-06-23.md
Normal file
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
date: 2025-06-23
|
||||
title: Giving things descriptive names
|
||||
permalink: /daily/2025/06/23/giving-things-descriptive-names
|
||||
---
|
||||
|
||||
An approach I like to use when writing code is value objects, and it was great to see a recent talk by Dan Leech (past guest on the [Beyond Blocks podcast][podcast]) about them at a recent PHP meetup.
|
||||
|
||||
To quote from Dan's talk - "value objects are objects that represent a value".
|
||||
|
||||
They are simple classes that give a meaningful name to a value.
|
||||
|
||||
For example, I could write this value object to represent a railway station code:
|
||||
|
||||
```php
|
||||
readonly final class StationCode {
|
||||
|
||||
public function __construct(public string $value) {
|
||||
assert(strlen($value) === 3);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now, instead of referencing a generic `string` type, I can reference a `StationCode` object.
|
||||
|
||||
This makes the code more readable and easier to understand.
|
||||
|
||||
In this case, I can also validate the string is in the correct format so I know that anywhere a `StationCode` object is used, its values are the correct format and this is done in a single place.
|
||||
|
||||
I can also take this a step further and introduce a `Journey` value object that represents a journey between two stations:
|
||||
|
||||
```
|
||||
readonly final class Journey {
|
||||
|
||||
public function __construct(
|
||||
public StationCode $origin,
|
||||
public StationCode $destination,
|
||||
) {
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
In this case, a journey always has two stations - an origin and a destination.
|
||||
|
||||
Creating an object to represent this gives it a name, but also prevents data clumping - where groups of variables are passed around together.
|
||||
|
||||
These are two examples from my recent code, but I could find many others.
|
||||
|
||||
And that's one reason why I like value objects - they are so easy and quick to use.
|
||||
|
||||
If you haven't before, try introducing value objects into your code.
|
||||
|
||||
If you already do, reply and tell me about some of the use cases you've found for them.
|
||||
|
||||
You can see the slides from Dan's presentation at <https://www.dantleech.com/slides/2025/dpc-php-value-objects-and-you/presentation.html>.
|
||||
|
||||
[podcast]: /podcast/6-dan-leech-php-tui
|
25
source/_posts/2025-06-24.md
Normal file
25
source/_posts/2025-06-24.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
date: 2025-06-24
|
||||
title: PHP and Nix shells
|
||||
permalink: /daily/2025/06/24/php-and-nix-shells
|
||||
---
|
||||
|
||||
What if you needed to run or test multiple versions of PHP?
|
||||
|
||||
Would you use options like virtual machines or containers, which install full operating systems, just to get access to different versions of the same package?
|
||||
|
||||
Would you need to create a different VM or container for each version of PHP?
|
||||
|
||||
Another approach is to use Nix or, more specifically, nixpkgs - a package manager with more than 120,000 packages that can be installed on macOS or any Linux distribution.
|
||||
|
||||
The [current stable release][0] (25.05) has 8.4.8, 8.3.22, 8.2.28 and 8.1.32 available, as well as other packages like PHPUnit and Phpactor.
|
||||
|
||||
You can add any of these versions to a Nix configuration file, or create temporary shells with the version you need by running simple commands like `nix shell nixpkgs#php83`.
|
||||
|
||||
In that shell, you have access to PHP 8.3.
|
||||
|
||||
Once you leave it, you can re-run the command with a different version of PHP.
|
||||
|
||||
And it works with other languages such as nodejs, by running similar commands like `nix shell nixpkgs#nodejs_22`, making it even more powerful.
|
||||
|
||||
[0]: https://search.nixos.org/packages?channel=25.05&query=php
|
46
source/_posts/2025-06-25.md
Normal file
46
source/_posts/2025-06-25.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
date: 2025-06-25
|
||||
title: Nix and older versions of PHP
|
||||
permalink: /daily/2025/06/25/nix-and-older-versions-php
|
||||
---
|
||||
|
||||
Yesterday, I wrote about [using Nix to create shells for different versions of PHP][1].
|
||||
|
||||
The current stable version of Nix has PHP 8.4.8, 8.3.22, 8.2.28 and 8.1.32, but what if you need an older version?
|
||||
|
||||
If it has been in nixpkgs previously, it can still be accessed.
|
||||
|
||||
Nix allows you to create different channels to install different versions of the nixpkgs repository and access different sets of packages.
|
||||
|
||||
With flakes, different versions of nixpkgs can be used as inputs to add multiple releases, such as [stable and unstable][0], or older releases or even a specific Git commit.
|
||||
|
||||
For example, if I needed to use PHP 7.4 - which is not available in the current release - I could use this flake.nix file:
|
||||
|
||||
```nix
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs-php74.url = "github:nixos/nixpkgs/81b77fd3847a";
|
||||
};
|
||||
|
||||
outputs = { nixpkgs-php74, ... }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = import nixpkgs-php74 { inherit system; };
|
||||
in {
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
packages = [
|
||||
pkgs.php74
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Pinning nixpkgs to that commit hash gives me access to `php74`, which I can access using `nix develop`.
|
||||
|
||||
This approach works with any package in nixpkgs - not just PHP.
|
||||
|
||||
Or, if it wasn't available in an older version of nixpkgs, I can write a custom derivation and add it myself.
|
||||
|
||||
[0]: /daily/2025/05/25/why-i-prefer-rolling-linux-distribution
|
||||
[1]: /daily/2025/06/24/php-and-nix-shells
|
25
source/_posts/2025-06-26.md
Normal file
25
source/_posts/2025-06-26.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
date: 2025-06-26
|
||||
title: Project-specific dependencies with Nix Flakes
|
||||
permalink: /daily/2025/06/26/project-specific-dependencies-nix-flakes
|
||||
---
|
||||
|
||||
Earlier this week, I wrote about [using Nix to manage different versions of PHP][1].
|
||||
|
||||
PHP 8.4, 8.3, 8.2 and 8.1 are all available in the current stable version of nixpkgs.
|
||||
|
||||
You can only have one version installed globally, but you can have specific versions enabled for different projects.
|
||||
|
||||
You can create a `shell.nix` file or a Nix Flake like [yesterday's example][0] in each project directory and add or override the version of PHP or any other packages as needed.
|
||||
|
||||
Each directory can have its own set of installed packages and configuration, and this works for everything in nixpkgs.
|
||||
|
||||
Because flakes can have multiple inputs, including multiple versions of nixpkgs, you can mix and match between stable, unstable or pinned versions in the same flake.nix and decide which to use for each project.
|
||||
|
||||
This provides a lot of flexibility and offers a lot of customisation so you can configure each project exactly how you need.
|
||||
|
||||
For some examples of flake.nix files I've used, see [my public repositories][2] on my Forgejo instance.
|
||||
|
||||
[0]: /daily/2025/06/25/nix-and-older-versions-php
|
||||
[1]: /daily/2025/06/24/php-and-nix-shells
|
||||
[2]: https://code.oliverdavies.uk/opdavies
|
33
source/_posts/2025-06-27.md
Normal file
33
source/_posts/2025-06-27.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
date: 2025-06-27
|
||||
title: Ready to go devshells
|
||||
permalink: /daily/2025/06/27/ready-go-devshells
|
||||
---
|
||||
|
||||
When I'm quickly evaluating or contributing to an open source project, sometimes I don't need to create a complicated local environment.
|
||||
|
||||
Sometimes I just need PHP, nodejs or Tailwind CSS.
|
||||
|
||||
So, I've made some ready to go development shells that I can use.
|
||||
|
||||
Each devshell has extra packages installed, such as Composer and Phpactor for PHP, and the the Tailwind CSS language server and watchman for Tailwind CSS.
|
||||
|
||||
The code is in a [public Git repository][0], so I can run a simple one-line command to start the devshell:
|
||||
|
||||
```plain
|
||||
nix develop git+https://code.oliverdavies.uk/opdavies/dev-shells#php82
|
||||
```
|
||||
|
||||
If I need PHP and Tailwind CSS, I can run the same command again to create a sub-shell with both sets of packages.
|
||||
|
||||
I can also use direnv to achieve the same result by adding these lines to an .envrc file:
|
||||
|
||||
```bash
|
||||
use flake git+https://code.oliverdavies.uk/opdavies/dev-shells#php74
|
||||
use flake git+https://code.oliverdavies.uk/opdavies/dev-shells#tailwindcss
|
||||
```
|
||||
|
||||
For long-term projects, I would create and commit a [specific flake.nix file][1] to the code repository so the environment is reproducible and the packages are locked, but this works when I just need something quickly.
|
||||
|
||||
[0]: https://code.oliverdavies.uk/opdavies/dev-shells
|
||||
[1]: /daily/2025/06/26/project-specific-dependencies-nix-flakes
|
17
source/_posts/2025-06-28.md
Normal file
17
source/_posts/2025-06-28.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
date: 2025-06-28
|
||||
title: Discussing the Modeler API with Jürgen Haas
|
||||
permalink: /daily/2025/06/28/discussing-modeler-api-jurgen-haas
|
||||
---
|
||||
|
||||
I recently got to speak again with Jürgen Haas who was my guest on [episode 23][0] of the Beyond Blocks podcast where we discussed the Events, Conditions, Actions (ECA) module.
|
||||
|
||||
This time, we discussed some of the updates to ECA since we last spoke, but mostly about the new [Modeler API module][1].
|
||||
|
||||
[Listen to the episode here.][2]
|
||||
|
||||
It was another very interesting conversation and thanks to Jürgen for being on the show again.
|
||||
|
||||
[0]: /podcast/23-jurgen-haas-eca
|
||||
[1]: https://www.drupal.org/project/modeler_api
|
||||
[2]: /podcast/31-modeler-api
|
88
source/_posts/2025-06-29.md
Normal file
88
source/_posts/2025-06-29.md
Normal file
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
date: 2025-06-29
|
||||
title: An example of generics in PHP
|
||||
permalink: /daily/2025/06/29/example-generics-php
|
||||
---
|
||||
|
||||
Unfortunately, unlike other languages like Go and TypeScript, generics aren't supported in PHP, but they are possible with static analysis tools like PHPStan and Psalm.
|
||||
|
||||
I use generics as they allow me to make code more reusable whilst still being robust.
|
||||
|
||||
For example, if I created a Collection class to represent the guests for a podcast episode, it could look like this:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @implements \IteratorAggregate<mixed>
|
||||
*/
|
||||
class Guests implements \IteratorAggregate {
|
||||
|
||||
/**
|
||||
* @param Guest[] $guests
|
||||
*/
|
||||
public function __construct(private array $guests) {
|
||||
}
|
||||
|
||||
public function count(): int {
|
||||
return count($this->items);
|
||||
}
|
||||
|
||||
public function first(): Guest {
|
||||
return array_values($this->items)[0];
|
||||
}
|
||||
|
||||
public function getIterator(): \Traversable {
|
||||
return new \ArrayIterator($this->items);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Note: the idea of using `IteratorAggregate` came from [from Dan Leech][1] and his talk at a local PHP meetup.
|
||||
|
||||
Each contains an array of `Guest` objects that I can run methods like `count()` and `first()` on.
|
||||
|
||||
Everything is typed so PHPStan can parse the code and provide as much assistance as possible.
|
||||
|
||||
But, what if I wanted to make a generic Collection that could accept any type of item?
|
||||
|
||||
That's where generics are useful.
|
||||
|
||||
First, I need to define a template:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @implements \IteratorAggregate<mixed>
|
||||
* @template T
|
||||
*/
|
||||
```
|
||||
|
||||
Now `T` is defined, I can use it in place of the original `Guest` types:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @param T[] $items
|
||||
*/
|
||||
public function __construct(private array $items) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return T|null
|
||||
*/
|
||||
public function first(): mixed {
|
||||
return array_values($this->items)[0];
|
||||
}
|
||||
```
|
||||
|
||||
This says that the Collection accepts an array of `T` items and returns either a `T` or null.
|
||||
|
||||
`T` could be a `Guest`, a `NodeInterface` or anything else.
|
||||
|
||||
But, whatever it is, either a null value or the same type is returned.
|
||||
|
||||
Generics means that PHPStan can continue to give the most detailed analysis, my language server can give the best completion and diagnostics, and the code is flexible and easy to reuse.
|
||||
|
||||
Hopefully, generics will be a core PHP feature but, until then, the same benefits can be found from static analysis tools.
|
||||
|
||||
[1]: /daily/2025/03/29/collections
|
45
source/_posts/2025-06-30.md
Normal file
45
source/_posts/2025-06-30.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: Managing services without NixOS
|
||||
date: 2025-06-30
|
||||
permalink: /daily/2025/06/30/managing-services-without-nixos
|
||||
---
|
||||
|
||||
If you're working on a simple PHP application, [a simple development shell][0] with PHP and Composer may be enough.
|
||||
|
||||
But what if you're building a more complex application, like a Drupal website?
|
||||
|
||||
As well as PHP, it needs services like a database server.
|
||||
|
||||
Installing `mysql` or `mariadb` isn't enough - it needs to be running so your application can connect to it.
|
||||
|
||||
If you use NixOS - the operating system based on the Nix package manager - configuring a database server is as simple as `services.mysql.enable = true;`.
|
||||
|
||||
It will start automatically when the computer starts and you can add more Nix code to create databases and manage permissions.
|
||||
|
||||
But what if you're not using NixOS?
|
||||
|
||||
The Nix package manager can't manage services.
|
||||
|
||||
But, there is a solution - [services-flake][1].
|
||||
|
||||
It can be added to a flake.nix file and adds a tool called Process Compose to manage processes and services.
|
||||
|
||||
Then, I can add code like this to start a database server and create the database with `nix run`:
|
||||
|
||||
```nix
|
||||
services.mysql."mysql1" = {
|
||||
enable = true;
|
||||
|
||||
initialDatabases = [
|
||||
{ name = "drupal_nix_flake_example"; }
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
If you have other processes, such as running Tailwind CSS to build your CSS files, it can do that too.
|
||||
|
||||
To see a full Drupal example using services-flake, see [my drupal-nix-flake-example][2] repository.
|
||||
|
||||
[0]: /daily/2025/06/27/ready-go-devshells
|
||||
[1]: https://github.com/juspay/services-flake
|
||||
[2]: https://code.oliverdavies.uk/opdavies/drupal-nix-flake-example
|
21
source/_posts/2025-07-01.md
Normal file
21
source/_posts/2025-07-01.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
date: 2025-07-01
|
||||
title: PHP, Value Objects and You
|
||||
permalink: /daily/2025/07/01/php-value-objects-and-you
|
||||
---
|
||||
|
||||
Last week, I wrote about [giving things descriptive names][0] with value objects in PHP (and other languages).
|
||||
|
||||
I gave examples of value objects I've recently written to show how they make code descriptive and easier to understand.
|
||||
|
||||
Previous [Beyond Blocks podcast guest][1], Dan Leech, gave a great talk about value objects at a local PHP user group.
|
||||
|
||||
That talk is not online yet, but the video of the version from the [Dutch PHP Conference][2] has just been released.
|
||||
|
||||
The slides are also online on Dan's website.
|
||||
|
||||
I recommend watching the video and/or reviewing the slides, and start trying to use value objects in your code.
|
||||
|
||||
[0]: /daily/2025/06/23/giving-things-descriptive-names
|
||||
[1]: /podcast/6-dan-leech-php-tui
|
||||
[2]: https://www.youtube.com/watch?v=FIKpNzEGQJY
|
37
source/_posts/2025-07-02.md
Normal file
37
source/_posts/2025-07-02.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
date: 2025-07-02
|
||||
title: What is "Infrastructure as code"?
|
||||
permalink: /daily/2025/07/02/what-infrastructure-code
|
||||
---
|
||||
|
||||
What are the usual steps you need to follow to create and provision a server to host a website?
|
||||
|
||||
First, if you don't have hardware, you need to purchase a VPS through a site like DigitalOcean, Linode or Amazon AWS.
|
||||
|
||||
You need to select an operating system to install and provide your SSH keys so you can log in.
|
||||
|
||||
Once installed, you should apply any software updates and upgrades, and perform any initial configuration steps, so things are up to date and secure.
|
||||
|
||||
Then, you can install a web server like Apache, Nginx or Caddy, as well as any other software you need, such as a database server.
|
||||
|
||||
These all need to be configured, too, such as creating a virtual host for each website and declaring which services should start automatically when the server boots.
|
||||
|
||||
Because this is done manually, you need to document or remember everything, in case you need to repeat the same process in the future.
|
||||
|
||||
But, what if it didn't need to be manual?
|
||||
|
||||
## Introducing "Infrastructure as Code"
|
||||
|
||||
Infrastructure as Code (IaC) tools, such as Ansible, Terraform and NixOS, allow you to define your configuration in code and apply it to your server or other infrastructure.
|
||||
|
||||
The code acts as living documentation that you can apply over and over again.
|
||||
|
||||
It speeds up the process and makes it more robust than re-typing or copying and pasting commands.
|
||||
|
||||
But, you aren't limited to web servers.
|
||||
|
||||
I use IaC to manage the DNS for my domains rather than using UIs.
|
||||
|
||||
You can even use it to provision the hardware, if you need to configure a new server.
|
||||
|
||||
If you have an API key for a provider, you can define in code how many servers you want, what specifications they have and what roles they perform.
|
38
source/_posts/2025-07-03.md
Normal file
38
source/_posts/2025-07-03.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
date: 2025-07-03
|
||||
title: Imperative or declarative
|
||||
permalink: /daily/2025/07/03/imperative-or-declarative
|
||||
---
|
||||
|
||||
Yesterday, I wrote about [infrastructure as code][0] and mentioned some of the IaC tools that are available.
|
||||
|
||||
I use Terraform to manage the DNS records for my websites, and use NixOS to configure my laptops and home server.
|
||||
|
||||
Before NixOS, I used Ansible to provision servers and [deploy application code][1].
|
||||
|
||||
Ansible is imperative, which means you need to declare each step that needs to be run.
|
||||
|
||||
You need to say to install a package, like a web server.
|
||||
|
||||
You need to say to create any required users or groups.
|
||||
|
||||
You need to configure the service so it starts when the server boots.
|
||||
|
||||
You need to create the configuration files and symlink or copy them to the correct path.
|
||||
|
||||
You need to add as many steps as needed to perform the task, that will be executed in the given order.
|
||||
|
||||
## Declarative configuration
|
||||
|
||||
This makes sense for application deployments, but for server configuration, you can also use declarative tools like Terraform and NixOS.
|
||||
|
||||
Instead of writing each step, you declare what the final desired state is.
|
||||
|
||||
You say Nginx should be enabled by writing `services.nginx.enable = true;` or declaring what the configuration file contents should be - not how to create or symlink the file.
|
||||
|
||||
This also makes the underlying code easier to update or refactor.
|
||||
|
||||
If how you install or configure a program changes, you don't need to update your infrastructure code as the final state will be the same - regardless of how it's done.
|
||||
|
||||
[0]: /daily/2025/07/02/what-infrastructure-code
|
||||
[1]: /presentations/deploying-php-ansible-ansistrano
|
97
source/_posts/2025-07-04.md
Normal file
97
source/_posts/2025-07-04.md
Normal file
|
@ -0,0 +1,97 @@
|
|||
---
|
||||
date: 2025-07-04
|
||||
title: Avoiding indentation
|
||||
permalink: /daily/2025/07/04/avoiding-indentation
|
||||
---
|
||||
|
||||
A guide that I use when writing or reviewing code is to avoid or minimise indentation.
|
||||
|
||||
Usually, the nearer to the left of the file the text is, the better.
|
||||
|
||||
If you had this code:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
function doSomething() {
|
||||
if ($a === TRUE) {
|
||||
if ($b === 'banana') {
|
||||
if ($c > 3) {
|
||||
// Do something...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
What if you refactored it to use early returns and remove some indentation?
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
function doSomething() {
|
||||
if ($a !== TRUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($b !== 'banana') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($c <= 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do something...
|
||||
}
|
||||
```
|
||||
|
||||
This is easier for me to read and review.
|
||||
|
||||
I can keep track of there I am in the function as it is more linear. Especially if the code within each condition block is more complex.
|
||||
|
||||
## Indentation in CSS
|
||||
|
||||
CSS now supports nesting, similar to Sass and other preprocessors, so you can write CSS like this:
|
||||
|
||||
```css
|
||||
.menu {
|
||||
ul {
|
||||
li {
|
||||
a {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
I usually find this harder to search, particularly if a pre-processor like Sass is being used, so I avoid it as much as possible.
|
||||
|
||||
I only really use it to style pseudo-selectors like `:hover` and `:focus` to element.
|
||||
|
||||
## Indentation in Nix
|
||||
|
||||
In Nix, these two examples achieve the same thing:
|
||||
|
||||
```nix
|
||||
services.gammastep = {
|
||||
enable = true;
|
||||
provider = "geoclue2";
|
||||
};
|
||||
|
||||
services.gammastep.enable = true;
|
||||
services.gammastep.provider = "geoclue2";
|
||||
```
|
||||
|
||||
In long files, it can be hard to keep track of what service you're configuring once it has been indented.
|
||||
|
||||
Whilst it adds some duplication, I've recently favoured the second approach in my code.
|
||||
|
||||
## Here's the thing
|
||||
|
||||
There needs to be some indentation in code to allow for functions, conditions, loops, etc.
|
||||
|
||||
But, whichever language I'm writing, I usually aim for no more than two levels of indentation in a file.
|
||||
|
||||
If code is indented further, I'll try or suggest an alternative approach to reduce the indentation - which usually results in cleaner code overall.
|
30
source/_posts/2025-07-05.md
Normal file
30
source/_posts/2025-07-05.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
date: 2025-07-05
|
||||
title: Dealing with icky code
|
||||
permalink: /daily/2025/07/05/dealing-icky-code
|
||||
---
|
||||
|
||||
Most, if not all, software projects have some code that is difficult to work on.
|
||||
|
||||
Maybe it was written a long time ago or it performs some difficult or complex logic.
|
||||
|
||||
It may not follow modern best practices and, most likely, isn't covered by automated tests.
|
||||
|
||||
No-one wants to work on it.
|
||||
|
||||
But, what happens when it needs to change?
|
||||
|
||||
How can you do it and be confident the change doesn't break the existing functionality?
|
||||
|
||||
Before you make any changes, identify the use cases of the current code and cover them by [writing automated tests][1].
|
||||
|
||||
Functional, web or browser tests allow you to make HTTP requests to an endpoint and make assertions on a response, and ensure it returns the correct status code and content, rather than focussing on the implementation details.
|
||||
|
||||
If you need to test an existing Drupal website, [use Drupal Test Traits][0].
|
||||
|
||||
Once you have tests in place, you can make the required changes and ensure the original functionality still works by running them and checking they still pass.
|
||||
|
||||
Hopefully, you'll have written tests for the new functionality, so the test suite will start to grow.
|
||||
|
||||
[0]: /daily/2025/06/18/exploring-drupal-test-traits
|
||||
[1]: /presentations/tdd-test-driven-drupal
|
35
source/_posts/2025-07-06.md
Normal file
35
source/_posts/2025-07-06.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
date: 2025-07-06
|
||||
title: What type of change are you making?
|
||||
permalink: /daily/2025/07/06/what-type-change-are-you-making
|
||||
---
|
||||
|
||||
Whilst I don't use the [conventional commits][0] approach to writing commit messages any more, I still think it's important to think about the type of change when a commit is made to a code repository.
|
||||
|
||||
Are you adding a new feature?
|
||||
|
||||
Are you fixing a bug?
|
||||
|
||||
Are you refactoring some code?
|
||||
|
||||
Conventional commits has you add keywords like `feat`, `fix`, `chore` and `refactor` to the commit message to identify the type of change being committed.
|
||||
|
||||
I don't add it to the commit message, but I do ask myself the same question.
|
||||
|
||||
What type of change is this?
|
||||
|
||||
If it's more than one, it probably needs to be split into separate commits.
|
||||
|
||||
This makes the intent clearer and the change easier to review.
|
||||
|
||||
If you need to refactor some code before adding a feature, they should be two separate commits.
|
||||
|
||||
If you're fixing a bug, commit a failing test first so it can be easily seen and then commit the fix that makes the test pass.
|
||||
|
||||
## Here's the thing
|
||||
|
||||
Having one change per commit makes it easier to write good commit messages as the change is simpler to explain.
|
||||
|
||||
If a commit includes multiple changes, it is more difficult and causes commit messages like `Updates` or `wip` - which I try to avoid, especially on client and open source projects.
|
||||
|
||||
[0]: /daily/2022/09/01/conventional-commits-changelogs
|
30
source/_posts/2025-07-07.md
Normal file
30
source/_posts/2025-07-07.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
date: 2025-07-07
|
||||
title: Asking the right question
|
||||
permalink: /daily/2025/07/07/asking-right-question
|
||||
---
|
||||
|
||||
I recently watched a YouTube video about [not turning Neovim into VS Code][0] by Jannik Buhr.
|
||||
|
||||
In the video, he compares asking two questions:
|
||||
|
||||
- How do I get VS Code tabs in Neovim?
|
||||
- How do you work with multiple files in Neovim?
|
||||
|
||||
Depending on which question you ask, you'll get quite different answers.
|
||||
|
||||
The same is true when working on software projects.
|
||||
|
||||
A client or Product Owner may request some new functionality or a change to the existing code, but ideally, you want to understand what caused the request.
|
||||
|
||||
You want to understand the problem to suggest the appropriate solution and not be locked into a pre-selected solution.
|
||||
|
||||
If you understand the problem, you may be able to solve it in a different way that could be easier or faster to do, or easier to maintain.
|
||||
|
||||
You may be able to suggest a short-term solution that means you can meet a deadline or work around a blocker, and implement a different solution in the future.
|
||||
|
||||
If you don't understand the problem or what you're trying to achieve, you can't make the best suggestions.
|
||||
|
||||
Ask the right questions.
|
||||
|
||||
[0]: https://www.youtube.com/watch?v=B7i5l0dTAAc
|
73
source/_posts/2025-07-08.md
Normal file
73
source/_posts/2025-07-08.md
Normal file
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
date: 2025-07-08
|
||||
title: Writing robust bash scripts with Nix
|
||||
permalink: /daily/2025/07/08/writing-robust-bash-scripts-nix
|
||||
---
|
||||
|
||||
I have a lot of custom Bash scripts I use - some to perform simple commands like `git log` with some additional arguments, to more complex ones that mount and unmount USB devices plugged into my laptop.
|
||||
|
||||
A lot of people will post their scripts online along with their dotfiles for others to read and take inspiration from.
|
||||
|
||||
Mine are in my [nix config][0] directory and each script is added as a custom package I can install.
|
||||
|
||||
Here's an example of a script written as a Nix package:
|
||||
|
||||
```nix
|
||||
{ pkgs }:
|
||||
|
||||
pkgs.writeShellApplication {
|
||||
name = "get-tags";
|
||||
|
||||
runtimeInputs = with pkgs; [ git ];
|
||||
|
||||
text = ''
|
||||
if [[ "$#" -gt 0 ]]; then
|
||||
git tag | grep "$*"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git tag
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
It gets a filtered list of Git tags within a repository using the `git tag` and `grep` commands.
|
||||
|
||||
Nix automatically adds the shebang line and sets some default shell options, so those aren't included within the script text.
|
||||
|
||||
It also automatically runs `shellcheck` to ensure the script is correct.
|
||||
|
||||
## Injecting Dependencies
|
||||
|
||||
This script depends on `git`. Without it, it would not run successfully.
|
||||
|
||||
Instead of assuming it is installed, `runtimeInputs` ensures any dependencies are present and the script can be executed, even if the dependencies aren't enabled globally.
|
||||
|
||||
Scripts can also rely on other scripts.
|
||||
|
||||
`get-tags` has a corresponding `count-tags` script that counts the returned tags:
|
||||
|
||||
```nix
|
||||
pkgs.writeShellApplication {
|
||||
name = "count-tags";
|
||||
|
||||
runtimeInputs = with pkgs; [
|
||||
coreutils
|
||||
get-tags
|
||||
];
|
||||
|
||||
text = ''
|
||||
get-tags "''${1:-}" | wc -l
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
`get-tags` is declared as a dependency, as well as `coreutils` to add the `wc` command that counts the lines.
|
||||
|
||||
## Here's the thing
|
||||
|
||||
Using this approach gives me more robust scripts that are checked and verified, and will always have the required dependencies.
|
||||
|
||||
And, because they are added as custom Nix packages in my configuration, I am able to decide which scripts to install on which computer, giving me the most flexibility.
|
||||
|
||||
[0]: https://code.oliverdavies.uk/opdavies/nix-config
|
17
source/_posts/2025-07-09.md
Normal file
17
source/_posts/2025-07-09.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
date: 2025-07-09
|
||||
title: Discussing web accessibility with Mike Gifford
|
||||
permalink: /daily/2025/07/09/discussing-web-accessibility-mike-gifford
|
||||
---
|
||||
|
||||
In the latest episode of the Beyond Blocks podcast, I got to speak about web accessibility with Mike Gifford.
|
||||
|
||||
Mike is a Senior Strategist at CivicActions, a W3C Invited Expert and Drupal Core Accessibility Maintainer.
|
||||
|
||||
In this conversation, we discuss what web accessibility is, how you can tell if your website is accessible, and how accessibility is handled in Drupal and Drupal CMS.
|
||||
|
||||
[Listen to the episode here.][0]
|
||||
|
||||
It was another very interesting conversation and thanks to Mike for being on the show.
|
||||
|
||||
[0]: /podcast/32-web-accessibility
|
24
source/_posts/2025-07-10.md
Normal file
24
source/_posts/2025-07-10.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
date: 2025-07-10
|
||||
title: Breaking down tasks
|
||||
permalink: /daily/2025/07/10/breaking-down-tasks
|
||||
---
|
||||
|
||||
What do you do with large development tasks?
|
||||
|
||||
Whatever the size, I try to break down tasks into their smallest parts - into a number of smaller sub-tasks.
|
||||
|
||||
This means that, instead of focussing on delivering the feature as a whole, I can focus on delivering the feature incrementally.
|
||||
|
||||
This makes it easier to track progress and quicker to deliver changes.
|
||||
|
||||
Each sub-task should be releasable, so we know when it is done, and small and frequent releases are [less risky than larger infrequent ones][0].
|
||||
|
||||
If needed, features can be hidden with feature toggles if we don't want them to be visible - but they are still committed and deployed, [even if they are not released][1].
|
||||
|
||||
Each sub-task should have its own automated tests, but smaller tasks are also easier to test manually.
|
||||
|
||||
And, if one sub-task becomes blocked, it may be possible to move onto another and continue to make progress rather than the whole task being blocked.
|
||||
|
||||
[0]: /daily/2023/11/22/frequency-reduces-difficulty
|
||||
[1]: /daily/2023/06/21/deployments-or-releases
|
60
source/_posts/2025-07-11.md
Normal file
60
source/_posts/2025-07-11.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
date: 2025-07-11
|
||||
title: Easier dependency injection with autowiring
|
||||
permalink: /daily/2025/07/11/easier-dependency-injection-autowiring
|
||||
---
|
||||
|
||||
Dependency injection is, as the name suggests, the approach of injecting dependencies into a class - usually within a `__construct` method.
|
||||
|
||||
This makes code less coupled and easier to test.
|
||||
|
||||
In a Drupal application, that usually means not using the static methods on `\Drupal` when fetching dependencies.
|
||||
|
||||
This is an example from my website:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
readonly final class PodcastNodeRepository {
|
||||
|
||||
private NodeStorageInterface $nodeStorage;
|
||||
|
||||
public function __construct(EntityTypeManagerInterface $entityTypeManager) {
|
||||
$this->nodeStorage = $entityTypeManager->getStorage('node');
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The `EntityTypeManagerInterface` dependency is injected and used to get the node storage class, which is used in another method to find the podcast episode nodes.
|
||||
|
||||
For this to work with the dependency injection container, I need to add the class as a service, including its arguments so they can be injected:
|
||||
|
||||
```yaml
|
||||
# opd_podcast.services.yml
|
||||
|
||||
services:
|
||||
Drupal\opd_podcast\Action\GetNextPodcastEpisodeNumber:
|
||||
arguments:
|
||||
- '@entity_type.manager'
|
||||
```
|
||||
|
||||
But, this means you need to update this file if any dependencies change.
|
||||
|
||||
Wouldn't it be better to do this automatically?
|
||||
|
||||
Usually, it can with autowiring.
|
||||
|
||||
Instead of `arguments`, add `autowire: true` and Drupal will try to automatically inject the dependencies for you:
|
||||
|
||||
```yaml
|
||||
# opd_podcast.services.yml
|
||||
|
||||
services:
|
||||
Drupal\opd_podcast\Action\GetNextPodcastEpisodeNumber:
|
||||
autowire: true
|
||||
Drupal\opd_podcast\Repository\PodcastNodeRepository:
|
||||
autowire: true
|
||||
```
|
||||
|
||||
Now, if dependencies change, you don't need to update the services file - making it easier and quicker.
|
28
source/_posts/2025-07-12.md
Normal file
28
source/_posts/2025-07-12.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
date: 2025-07-12
|
||||
title: My dotfiles repository turns 10
|
||||
permalink: /daily/2025/07/12/my-dotfiles-repository-turns-10
|
||||
---
|
||||
|
||||
Earlier this month, my dotfiles repository turned 10 years old.
|
||||
|
||||
[The first commit][0] was a small one - committing my Git configuration file (`.gitconfig`).
|
||||
|
||||
Next, I added an initial `.vimrc`, although I wasn't [using Vim full-time][1] yet.
|
||||
|
||||
It was when I started to use Linux more, though.
|
||||
|
||||
My dotfiles repository has gone through several iterations since the first commits, from using simple bash scripts to tools like Ansible and GNU Stow to apply the configuration.
|
||||
|
||||
Three years ago, I started to use Nix and NixOS, and started to use Home Manager to create and manage my user configuration.
|
||||
|
||||
Now called my "nix-config" repository, it contains much more than my Git configuration - it manages several laptops and my home server, and includes a number of [custom packages and scripts][2].
|
||||
|
||||
I also use Nix to build other things, like my [presentation slides as PDF files][3].
|
||||
|
||||
I don't plan to move from my existing tools like NixOS, tmux and Neovim, but I do wonder what my dotfiles repository will look like in ten years from now.
|
||||
|
||||
[0]: https://code.oliverdavies.uk/opdavies/nix-config/commit/da6754da407b01c3966747c49ed84228c30eb701
|
||||
[1]: /blog/going-full-vim
|
||||
[2]: /daily/2025/07/08/writing-robust-bash-scripts-nix
|
||||
[3]: /daily/2025/04/07/nix-rst2pdf
|
30
source/_posts/2025-07-13.md
Normal file
30
source/_posts/2025-07-13.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
date: 2025-07-13
|
||||
title: Drupal Test Traits is not a replacement for traditional tests
|
||||
permalink: /daily/2025/07/13/drupal-test-traits-not-replacement-traditional-tests
|
||||
---
|
||||
|
||||
I've recently been looking into [the Drupal Test Traits project][0].
|
||||
|
||||
In my previous talks and workshops, and my email course, I haven't covered Drupal Test Traits and stuck to using the existing Drupal test types - `BrowserTestBase`, `KernelTestBase`, etc.
|
||||
|
||||
I maybe assumed that it was a different approach to testing, like introducing a different testing tool.
|
||||
|
||||
I wanted to make sure I understood Drupal's core test types before looking into additional things like Drupal Test Traits.
|
||||
|
||||
Now I've looked into it, I know it's not a replacement for Drupal core tests - it's an extension of them.
|
||||
|
||||
It uses the same PHPUnit framework, but extends it with specific traits and classes for testing an existing Drupal site rather than creating a temporary website for each test.
|
||||
|
||||
This makes the setup much simpler, and easier for people to write their first working test.
|
||||
|
||||
And you can mix and match - not every test needs to implement the `ExistingSiteBase` class that Drupal Test Traits provides.
|
||||
|
||||
If a test is better written as a standard Functional test, you can still use the core classes as you need to.
|
||||
|
||||
You can even include the specific traits you need, giving you full control and flexibility.
|
||||
|
||||
I'll continue to teach the Drupal testing fundamentals, but in future versions, I'll include Drupal Test Traits too, and I'll be using it for any Drupal projects for clients or [my own website][1].
|
||||
|
||||
[0]: /daily/2025/06/18/exploring-drupal-test-traits
|
||||
[1]: https://code.oliverdavies.uk/opdavies/oliverdavies.uk
|
57
source/_posts/2025-07-14.md
Normal file
57
source/_posts/2025-07-14.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
date: 2025-07-14
|
||||
title: The downside to testing existing sites
|
||||
permalink: /daily/2025/07/14/downside-testing-existing-sites
|
||||
---
|
||||
|
||||
Whilst [experimenting with Drupal Test Traits][1], I've needed to re-think how I've written some tests.
|
||||
|
||||
When writing a test for a Repository class that finds published podcast episode nodes, I can't create three published and two unpublished nodes, get the result and assert exactly three were returned.
|
||||
|
||||
Because I'm testing an existing site, all my existing content is available in each test.
|
||||
|
||||
I already have published podcast episode nodes, so the count is always going to be greater than what I created in the test.
|
||||
|
||||
In a traditional test, everything is re-installed from scratch for every test, so this wouldn't be an issue.
|
||||
|
||||
## The issue testing existing sites
|
||||
|
||||
If I'm testing an existing site and already have a page with the path of `/available`, this test would pass:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
/** @test */
|
||||
public function the_available_page_loads(): void {
|
||||
$this->drupalGet('/available');
|
||||
|
||||
$assert = $this->assertSession();
|
||||
$assert->statusCodeEquals(Response::HTTP_OK);
|
||||
$assert->responseContains('Available for Drupal consulting');
|
||||
}
|
||||
```
|
||||
|
||||
But, there's nothing in the test to show the page was created.
|
||||
|
||||
The test assumes the page already exists, but how do we know that?
|
||||
|
||||
Can we assume the page will always exist?
|
||||
|
||||
Will it exist on every development environment or in a CI pipeline?
|
||||
|
||||
## Here's the thing
|
||||
|
||||
Maybe it's against the spirit of testing an existing site, but my advice is to only assert on what you've created in that test.
|
||||
|
||||
It makes the test more readable and easier to understand.
|
||||
|
||||
People don't need to go elsewhere to find how something was created.
|
||||
|
||||
If I'm testing published podcast nodes, I can count how many there are at the start of the test and assert the number has increased by a given amount and get the same result.
|
||||
|
||||
The test just needs to be written slightly differently.
|
||||
|
||||
Or, you can mix and match, and [use traditional test types where appropriate][0].
|
||||
|
||||
[0]: /daily/2025/07/13/drupal-test-traits-not-replacement-traditional-tests
|
||||
[1]: /daily/2025/06/18/exploring-drupal-test-traits
|
28
source/_posts/2025-07-15.md
Normal file
28
source/_posts/2025-07-15.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
date: 2025-07-15
|
||||
title: PHP compatibility checking with phpcs
|
||||
permalink: /daily/2025/07/15/php-compatibility-checking-phpcs
|
||||
---
|
||||
|
||||
PHP 8.5 will be the next version of PHP, scheduled for release this November.
|
||||
|
||||
There are new features being added, including the `|>` pipe operator, that I'll look forward to using.
|
||||
|
||||
Once it's released, how can you tell if your application code is compatible?
|
||||
|
||||
Or, if you're stuck on an older version of PHP, how do you know if you can upgrade?
|
||||
|
||||
Running your automated tests is a great first step.
|
||||
|
||||
Another check is to use PHP CodeSniffer, aka (`phpcs`).
|
||||
|
||||
As well as coding standards, it can also run PHP compatibility checks.
|
||||
|
||||
With the [PHP Compatibility Coding Standard][0] installed, running `phpcs -p . --standard=PHPCompatibility` will perform the compatibility check and show any errors.
|
||||
|
||||
To specify a version of PHP to test against, add `--runtime-set testVersion 8.5` using the desired version.
|
||||
|
||||
If you need to make changes, I recommend [using Rector][1] and have it make the changes automatically.
|
||||
|
||||
[0]: https://github.com/PHPCompatibility/PHPCompatibility
|
||||
[1]: /daily/2025/01/31/rector
|
37
source/_posts/2025-07-16.md
Normal file
37
source/_posts/2025-07-16.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
date: 2025-07-16
|
||||
title: Drupal and Nix similarities
|
||||
permalink: /daily/2025/07/16/drupal-and-nix-similarities
|
||||
---
|
||||
|
||||
I've been a Drupal Developer since 2008 and have worked on countless Drupal projects as a Freelancer and Consultant, and agency and in-house staff.
|
||||
|
||||
No two Drupal websites are the same.
|
||||
|
||||
Every one has its own content types, fields, user roles and other configuration.
|
||||
|
||||
Each uses a different combination of modules and themes.
|
||||
|
||||
Some use Layout Builder and some use Paragraphs, Layout Paragraphs or the core Block system.
|
||||
|
||||
Almost every Drupal website has its own custom theme and a number of custom modules.
|
||||
|
||||
I've been using Nix and NixOS for three years, and they are similar in this way.
|
||||
|
||||
A lot of people post their configuration files online for others to read and take inspiration from ([including mine](https://code.oliverdavies.uk/opdavies/nix-config)).
|
||||
|
||||
From reading a number of these, I think no two Nix configurations are the same either.
|
||||
|
||||
They range from simple to very complex configurations, some managing multiple devices including personal computers, servers and homelabs - and even mobile phones!
|
||||
|
||||
Most use flakes, but some use wrappers like flake-utils or flake-parts.
|
||||
|
||||
## Here's the thing
|
||||
|
||||
Whilst it can be confusing initially, when joining a Drupal project or reading a Nix configuration, I like to learn how different people solve problems.
|
||||
|
||||
Because people can solve problems in different ways shows how flexible these tools are.
|
||||
|
||||
They provide building blocks that people can use to achieve a specific result and build what they need.
|
||||
|
||||
With tools like this, the possibilities are endless.
|
31
source/_posts/2025-07-17.md
Normal file
31
source/_posts/2025-07-17.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
date: 2025-07-17
|
||||
title: Nix and the Dendritic pattern
|
||||
permalink: /daily/2025/07/17/nix-and-dendritic-pattern
|
||||
---
|
||||
|
||||
As I wrote yesterday, [no two Nix configurations are the same][0].
|
||||
|
||||
As well as having different packages and configuration options, Nix configurations can all be structured differently - similar to Drupal websites.
|
||||
|
||||
Some are simple and others are very complex and manage many systems.
|
||||
|
||||
A lot of people will publish their dotfiles/Nix/NixOS configurations online for others to read and take inspiration from.
|
||||
|
||||
I was reading someone's code yesterday and found a pattern they wrote about called [the Dendritic pattern][2].
|
||||
|
||||
Using the flake-parts module system, every file when using this pattern is a flake-parts module.
|
||||
|
||||
This is different to most configurations, where only some files are modules and others are imported as needed within other files.
|
||||
|
||||
From another perspective, [having consistency][1] and following a strict convention makes things like auto-importing modules possible.
|
||||
|
||||
I like it solves a problem, making configurations easier to maintain as each file is responsible for adding one feature and are self-contained so things can easily be re-organised.
|
||||
|
||||
If I was writing my configuration from scratch, this is the approach I'd take.
|
||||
|
||||
Or, I may start looking to refactor my current configuration soon to be based on this pattern.
|
||||
|
||||
[0]: /daily/2025/07/16/drupal-and-nix-similarities
|
||||
[1]: /daily/2023/04/18/consistency-is-key
|
||||
[2]: https://github.com/mightyiam/dendritic
|
32
source/_posts/2025-07-18.md
Normal file
32
source/_posts/2025-07-18.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
date: 2025-07-18
|
||||
title: The John Carmack plan
|
||||
permalink: /daily/2025/07/18/john-carmack-plan
|
||||
---
|
||||
|
||||
Earlier this year, I wrote about [text files being the simplest project management tool][0].
|
||||
|
||||
Whist watching a video by Nick Janetakis about his [notes application][1], I learned about a repository of [John Carmack's plain text notes][2].
|
||||
|
||||
These are a collection of plain text files that he wrote to plan and track his tasks.
|
||||
|
||||
He uses a simple format to represent different types of tasks.
|
||||
|
||||
- Lines without a prefix are to be done or in progress.
|
||||
- Lines prefixed with `*` were completed that day.
|
||||
- Lines prefixed with `+` were completed on a later day.
|
||||
- Lines prefixed with `-` were decided against on a later day.
|
||||
|
||||
I love the simplicity of this system, and how easy it is to add new entries.
|
||||
|
||||
Plain text files are also easy to search with tools like `grep`.
|
||||
|
||||
To find all open tasks, you can run a command like `grep '^[^*+-]'`, and run similar commands to show different types of entries.
|
||||
|
||||
The simpler a system is to use, the more likely you are to stick with it.
|
||||
|
||||
This explains why there are 394 daily plan notes in that repository.
|
||||
|
||||
[0]: /daily/2025/01/03/todotxt
|
||||
[1]: https://github.com/nickjj/notes
|
||||
[2]: https://github.com/ESWAT/john-carmack-plan-archive
|
21
source/_posts/2025-07-19.md
Normal file
21
source/_posts/2025-07-19.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
date: 2025-07-19
|
||||
title: Exploring the Dendritic pattern
|
||||
permalink: /daily/2025/07/19/exploring-dendritic-pattern
|
||||
---
|
||||
|
||||
I've been looking more into [the dendritic pattern for Nix code][0].
|
||||
|
||||
I understood the concept, but wasn't sure how to implement it and refactor my existing configuration.
|
||||
|
||||
I've recently bought a pre-owned computer that I'm going to be adding to my homelab, and this seemed like an opportunity to experiment with the pattern in a new environment.
|
||||
|
||||
What I have is [in this repository][1].
|
||||
|
||||
Each file is its own Nix module, including the NixOS configurations and packages such as Vim and tmux.
|
||||
|
||||
This was a great experiment, and I'm going to continue looking into how to implement this in [my existing Nix configuration][2].
|
||||
|
||||
[0]: https://www.oliverdavies.uk/daily/2025/07/17/nix-and-dendritic-pattern
|
||||
[1]: https://code.oliverdavies.uk/opdavies/dendritic
|
||||
[2]: https://code.oliverdavies.uk/opdavies/nix-config
|
22
source/_posts/2025-07-20.md
Normal file
22
source/_posts/2025-07-20.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
date: 2025-07-20
|
||||
title: I don't like large pull requests
|
||||
permalink: /daily/2025/07/20/i-dont-large-pull-requests
|
||||
---
|
||||
|
||||
I don't like large pull or merge requests, or large changes.
|
||||
|
||||
Since exploring [the dendritic pattern for Nix code][0], I created [an example configuration][1] and wanted to start moving my main configuration to use that approach.
|
||||
|
||||
I could have rewritten everything from scratch, but that would take time and a loss of productivity.
|
||||
|
||||
As soon as I was comfortable and I felt I understood the approach and how to implement it, I went back to my main configuration and looked for the easiest step to start implementing the pattern.
|
||||
|
||||
I was tempted to start with something big, like my Neovim or tmux configuration, but I started with my bluetooth configuration.
|
||||
|
||||
It's a small module, but it gets the ball rolling.
|
||||
|
||||
I've taken the first steps to implementing the dendritic pattern in my Nix configuration, haven't lost any of my existing settings and can continue to iterate and refactor as time allows.
|
||||
|
||||
[0]: /daily/2025/07/17/nix-and-dendritic-pattern
|
||||
[1]: /daily/2025/07/19/exploring-dendritic-pattern
|
21
source/_posts/2025-07-21.md
Normal file
21
source/_posts/2025-07-21.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
date: 2025-07-21
|
||||
title: Exploring Drupal recipes
|
||||
permalink: /daily/2025/07/21/exploring-drupal-recipes
|
||||
---
|
||||
|
||||
Since [Gareth Alexander's talk at DrupalCamp England][1], I've started to explore Drupal's new recipe system.
|
||||
|
||||
I've used the Drupal CMS Admin UI and Drupal CMS SEO Basic recipes, but there are [a lot more available][0].
|
||||
|
||||
Recipes are meta-packages that install other Drupal packages (including other recipes), add configuration and can apply patches.
|
||||
|
||||
The SEO recipe adds and configures Easy Breadcrumb, Pathauto, Redirect and Token modules, so they work straight away with a default configuration.
|
||||
|
||||
It's great that Drupal CMS recipes can be installed with a simple `composer require` command, so you can use them with any Drupal website - even if you're not using Drupal CMS.
|
||||
|
||||
If you want to learn more about Drupal recipes, [listen to episode 39 of the Beyond Blocks podcast][2] with Gareth.
|
||||
|
||||
[0]: https://new.drupal.org/browse/recipes
|
||||
[1]: https://www.youtube.com/watch?v=b7MDA-kKCCk
|
||||
[2]: /podcast/29-drupal-cms-recipes
|
28
source/_posts/2025-07-22.md
Normal file
28
source/_posts/2025-07-22.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
date: 2025-07-22
|
||||
title: Implementing the dendritic pattern
|
||||
permalink: /daily/2025/07/22/implementing-dendritic-pattern
|
||||
---
|
||||
|
||||
I recently started migrating my NixOS configuration repository to follow [the dendritic pattern][0].
|
||||
|
||||
This is a pattern where each Nix file is its own separate standalone module.
|
||||
|
||||
This makes it easier to refactor and re-organise files, and allows modules to be broken into multiple files.
|
||||
|
||||
Rather than rewrite everything at once, I decided to [take an iterative approach][1].
|
||||
|
||||
This meant refactoring modules in-place within my existing configuration, and keeping everything in a working state.
|
||||
|
||||
I've migrated [a number of modules][2], and will continue working through the remaining ones as time allows.
|
||||
|
||||
Once I've refactored everything, I'll go through them again and see what else I can do, like splitting modules into separate smaller files.
|
||||
|
||||
In general, the modules I've migrated have been smaller than what I had before, partly because there's less boilerplate code needed.
|
||||
|
||||
And, whilst there are more files, they are smaller than before, have a single responsibility and [have less indentation][3] than before - making them easier to read and understand.
|
||||
|
||||
[0]: /daily/2025/07/19/exploring-dendritic-pattern
|
||||
[1]: /daily/2025/07/20/i-dont-large-pull-requests
|
||||
[2]: https://code.oliverdavies.uk/opdavies/nix-config/src/branch/main/modules2
|
||||
[3]: /daily/2025/07/04/avoiding-indentation
|
19
source/_posts/2025-07-23.md
Normal file
19
source/_posts/2025-07-23.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
date: 2025-07-23
|
||||
title: It's hard to take things away
|
||||
permalink: /daily/2025/07/23/its-hard-take-things-away
|
||||
---
|
||||
|
||||
A common issue I see on Drupal projects is how the user roles and permissions have been configured.
|
||||
|
||||
They are usually set up correctly to begin with, with users given the correct roles with only the permissions they need to perform their required tasks.
|
||||
|
||||
But, at some point, something doesn't work or a user needs access to something new.
|
||||
|
||||
Rather than assign them a new role, the user is commonly given a role that gives them access to too much - usually an Administrator role that lets them access anything in the Drupal admin UI.
|
||||
|
||||
As well as introducing security risks, when a user has access to all the settings, they will use them and it is very difficult to review and change user roles and permissions once they have been assigned without causing disruption.
|
||||
|
||||
It may be tempting, particularly if there is pressure or deadlines, but avoid giving roles and permissions they don't need.
|
||||
|
||||
Once they are given, they are hard to take away.
|
25
source/_posts/2025-07-24.md
Normal file
25
source/_posts/2025-07-24.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
date: 2025-07-24
|
||||
title: The permissions issue that took down a website
|
||||
permalink: /daily/2025/07/24/permissions-issue-took-down-website
|
||||
---
|
||||
|
||||
Yesterday, I wrote about [the difficulty of removing permissions][0] from users once they have them, and why users should only have access to features they need.
|
||||
|
||||
If a user only needs to perform a few tasks, they do not need an Administrator role that gives them access to everything.
|
||||
|
||||
This, unfortunately, was the case on a project I was consulting on a few years ago.
|
||||
|
||||
I had a message from the client saying the website was offline and if I'd done anything to cause it.
|
||||
|
||||
It turned out that an employee of the client was working on the website, and they had an Administrator role.
|
||||
|
||||
Because of this, they saw a message that an unsupported module was installed and that it needed to be updated.
|
||||
|
||||
They clicked it and the module updated.
|
||||
|
||||
This was a new version of the module with a breaking change, and updating it broke the website and took it offline.
|
||||
|
||||
The previous version was restored and the website was brought back online, but if the user hadn't had the Administrator role, they wouldn't have been able to update the module and take down the website.
|
||||
|
||||
[0]: /daily/2025/07/23/its-hard-take-things-away
|
18
source/_posts/2025-07-25.md
Normal file
18
source/_posts/2025-07-25.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
date: 2025-07-25
|
||||
title: Drupal roles are layerable
|
||||
permalink: /daily/2025/07/25/drupal-roles-are-layerable
|
||||
---
|
||||
|
||||
A common issue I see on Drupal websites is that [users have too many permissions][1].
|
||||
|
||||
They are often given a role like an Administrator that gives them too many options - sometimes introducing a security risk or the possibility of [taking a website down accidentally][0].
|
||||
|
||||
A thing about Drupal roles is that they are layerable.
|
||||
|
||||
A user can have multiple roles and get the combined permissions from each role.
|
||||
|
||||
So why not have a number of small specific roles and assign them to users as needed, rather than a small number of larger roles that give too much?
|
||||
|
||||
[0]: /daily/2025/07/24/permissions-issue-took-down-website
|
||||
[1]: /daily/2025/07/23/its-hard-take-things-away
|
21
source/_posts/2025-07-26.md
Normal file
21
source/_posts/2025-07-26.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
date: 2025-07-26
|
||||
title: Don't share user accounts
|
||||
permalink: /daily/2025/07/26/dont-share-user-accounts
|
||||
---
|
||||
|
||||
Another common Drupal issue I see is shared user accounts that are used by more than one person.
|
||||
|
||||
These commonly have generic names like "Administrator" or "Editor" instead of the name of an individual.
|
||||
|
||||
But, with Drupal's flexible user system, why would you need to do this?
|
||||
|
||||
If you have a shared account, you don't know who made a change when looking at a list of revisions and you can't easily block or delete an account when someone leaves the team without affecting everyone else.
|
||||
|
||||
With no need for any additional modules or services, you can create as many user accounts as you need, with functionality for registering new accounts, logging in and resetting passwords available by default.
|
||||
|
||||
Any user can have multiple roles - each with specific permissions to allow the user to perform specific tasks.
|
||||
|
||||
You can make the roles as fine-grained as you need - with a role only having one additional permission, if that's what is needed.
|
||||
|
||||
Have an individual user account for each person who needs one, and as many roles as you need to give each person exactly the permissions they need.
|
30
source/_posts/2025-07-27.md
Normal file
30
source/_posts/2025-07-27.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
date: 2025-07-27
|
||||
title: Do you keep a changelog?
|
||||
permalink: /daily/2025/07/27/do-you-keep-changelog
|
||||
---
|
||||
|
||||
A changelog is a simple file that file that documents notable changes in your application.
|
||||
|
||||
Drupal has previously used [a CHANGELOG.txt file][0] and many open source projects use a CHANGELOG.md file.
|
||||
|
||||
If you want people to use your application, you should document any new additions, changes or removals so people can see the project is maintained and prepare for any breaking changes in upcoming versions.
|
||||
|
||||
This is also true for internal projects, in case any stakeholders want to know when a certain feature was released, or what was released in a given timeframe.
|
||||
|
||||
The format I like is from <https://keepachangelog.com>.
|
||||
|
||||
It's a Markdown file that shows additions, changes and removals grouped by release numbers, like 1.0.0.
|
||||
|
||||
It uses anchor links to make the headings clickable, and links them to tags or diffs within the Git repository for people to see the commits and code changes between each release.
|
||||
|
||||
As it's a Markdown file, it's easy to update for each release.
|
||||
|
||||
I also like that the example Changelog on the website is the changelog for the format itself.
|
||||
|
||||
I recently started to port one of my bash scripts [to a Go application][1], and decided to use this format in that repository - for me and for potential future users.
|
||||
|
||||
If you don't keep a changelog in your application, I'd suggest trying this format.
|
||||
|
||||
[0]: https://api.drupal.org/api/drupal/CHANGELOG.txt/7.x
|
||||
[1]: https://code.oliverdavies.uk/opdavies/git-repo-updater
|
21
source/_posts/2025-07-28.md
Normal file
21
source/_posts/2025-07-28.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
date: 2025-07-28
|
||||
title: A CHANGLOG isn't just a list of Git commits
|
||||
permalink: /daily/2025/07/28/changlog-isnt-just-list-git-commits
|
||||
---
|
||||
|
||||
Yesterday, I wrote about [keeping a CHANGELOG][0] for software projects, and about the <https://keepachangelog.com> project.
|
||||
|
||||
I like this slogan on their website:
|
||||
|
||||
> Don’t let your friends dump git logs into changelogs.
|
||||
|
||||
A CHANGELOG shouldn't be a list of Git commits in a file.
|
||||
|
||||
It should be in a human-readable format that's easy for people to read and understand, whether they are technical or not.
|
||||
|
||||
This probably isn't the case for the output from `git log`, which may also contain commits like `Refactor` and `wip` that don't mean anything to someone who focused on the higher-level deliverables.
|
||||
|
||||
Git logs are great for Developers, but CHANGELOGs are for everyone.
|
||||
|
||||
[0]: /daily/2025/07/27/do-you-keep-changelog
|
27
source/_posts/2025-07-29.md
Normal file
27
source/_posts/2025-07-29.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
title: Changelogs with Continuous Delivery
|
||||
date: 2025-07-29
|
||||
permalink: /daily/2025/07/29/changelogs-continuous-delivery
|
||||
---
|
||||
|
||||
Over the past few days, I've written why I think it's a good idea to keep a Changelog for software projects.
|
||||
|
||||
I've linked to the [Keep a Changelog][0] project, which gives a format for a Changelog file.
|
||||
|
||||
In it, unreleased changes are grouped at the top, and released changes grouped by version number.
|
||||
|
||||
But, how would this work if you're doing continuous delivery?
|
||||
|
||||
If you're deploying once a day, it could make sense to use release versions.
|
||||
|
||||
But, what if you're releasing multiple times a day, or separately releasing each commit?
|
||||
|
||||
Is it beneficial to tag every commit with its own release number?
|
||||
|
||||
In that situation, I've altered the format to group commits by date, and removed the Unreleased section as it's no longer needed.
|
||||
|
||||
In this case, you still get the benefits of a human-readable Changelog with a simplified structure.
|
||||
|
||||
I like standardisation, but use the format that works for you and your project.
|
||||
|
||||
[0]: https://keepachangelog.com
|
Loading…
Add table
Add a link
Reference in a new issue