diff --git a/config/sync/core.base_field_override.node.daily_email.promote.yml b/config/sync/core.base_field_override.node.daily_email.promote.yml new file mode 100644 index 0000000..00bd54c --- /dev/null +++ b/config/sync/core.base_field_override.node.daily_email.promote.yml @@ -0,0 +1,22 @@ +uuid: 1cec9098-9a97-48a0-8b75-c0129fe2187a +langcode: en +status: true +dependencies: + config: + - node.type.daily_email +id: node.daily_email.promote +field_name: promote +entity_type: node +bundle: daily_email +label: 'Promoted to front page' +description: '' +required: false +translatable: true +default_value: + - + value: 0 +default_value_callback: '' +settings: + on_label: 'On' + off_label: 'Off' +field_type: boolean diff --git a/config/sync/core.entity_form_display.node.daily_email.default.yml b/config/sync/core.entity_form_display.node.daily_email.default.yml new file mode 100644 index 0000000..8595277 --- /dev/null +++ b/config/sync/core.entity_form_display.node.daily_email.default.yml @@ -0,0 +1,82 @@ +uuid: 4de70dfa-13cf-4eef-a1c5-df22a9f227a0 +langcode: en +status: true +dependencies: + config: + - field.field.node.daily_email.body + - node.type.daily_email + module: + - path + - text +id: node.daily_email.default +targetEntityType: node +bundle: daily_email +mode: default +content: + body: + type: text_textarea_with_summary + weight: 121 + region: content + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + show_summary: false + third_party_settings: { } + created: + type: datetime_timestamp + weight: 10 + region: content + settings: { } + third_party_settings: { } + path: + type: path + weight: 30 + region: content + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + weight: 15 + region: content + settings: + display_label: true + third_party_settings: { } + status: + type: boolean_checkbox + weight: 120 + region: content + settings: + display_label: true + third_party_settings: { } + sticky: + type: boolean_checkbox + weight: 16 + region: content + settings: + display_label: true + third_party_settings: { } + title: + type: string_textfield + weight: -5 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + region: content + settings: + match_operator: CONTAINS + match_limit: 10 + size: 60 + placeholder: '' + third_party_settings: { } + url_redirects: + weight: 50 + region: content + settings: { } + third_party_settings: { } +hidden: { } diff --git a/config/sync/core.entity_view_display.node.daily_email.default.yml b/config/sync/core.entity_view_display.node.daily_email.default.yml new file mode 100644 index 0000000..552e72b --- /dev/null +++ b/config/sync/core.entity_view_display.node.daily_email.default.yml @@ -0,0 +1,28 @@ +uuid: 29a419e1-d32b-480f-b2a5-5b53bd304fa0 +langcode: en +status: true +dependencies: + config: + - field.field.node.daily_email.body + - node.type.daily_email + module: + - text + - user +id: node.daily_email.default +targetEntityType: node +bundle: daily_email +mode: default +content: + body: + type: text_default + label: hidden + settings: { } + third_party_settings: { } + weight: 101 + region: content + links: + settings: { } + third_party_settings: { } + weight: 100 + region: content +hidden: { } diff --git a/config/sync/core.entity_view_display.node.daily_email.teaser.yml b/config/sync/core.entity_view_display.node.daily_email.teaser.yml new file mode 100644 index 0000000..8113ef1 --- /dev/null +++ b/config/sync/core.entity_view_display.node.daily_email.teaser.yml @@ -0,0 +1,30 @@ +uuid: 49c930bc-7524-41ba-a8e0-241c657f5f66 +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.teaser + - field.field.node.daily_email.body + - node.type.daily_email + module: + - text + - user +id: node.daily_email.teaser +targetEntityType: node +bundle: daily_email +mode: teaser +content: + body: + type: text_summary_or_trimmed + label: hidden + settings: + trim_length: 600 + third_party_settings: { } + weight: 101 + region: content + links: + settings: { } + third_party_settings: { } + weight: 100 + region: content +hidden: { } diff --git a/config/sync/field.field.node.daily_email.body.yml b/config/sync/field.field.node.daily_email.body.yml new file mode 100644 index 0000000..74ca33e --- /dev/null +++ b/config/sync/field.field.node.daily_email.body.yml @@ -0,0 +1,24 @@ +uuid: c909819c-22ba-462f-8277-9bc514799ad7 +langcode: en +status: true +dependencies: + config: + - field.storage.node.body + - node.type.daily_email + module: + - text +id: node.daily_email.body +field_name: body +entity_type: node +bundle: daily_email +label: Body +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + display_summary: true + required_summary: false + allowed_formats: { } +field_type: text_with_summary diff --git a/config/sync/node.type.daily_email.yml b/config/sync/node.type.daily_email.yml new file mode 100644 index 0000000..79ab975 --- /dev/null +++ b/config/sync/node.type.daily_email.yml @@ -0,0 +1,17 @@ +uuid: c40f9dd1-769b-4ebf-8fbf-2fef1f2b34cb +langcode: en +status: true +dependencies: + module: + - menu_ui +third_party_settings: + menu_ui: + available_menus: { } + parent: '' +name: 'Daily email' +type: daily_email +description: '' +help: '' +new_revision: true +preview_mode: 1 +display_submitted: true diff --git a/config/sync/rabbit_hole.behavior_settings.node_type_daily_email.yml b/config/sync/rabbit_hole.behavior_settings.node_type_daily_email.yml new file mode 100644 index 0000000..8b3b9d8 --- /dev/null +++ b/config/sync/rabbit_hole.behavior_settings.node_type_daily_email.yml @@ -0,0 +1,14 @@ +uuid: 5ecc8902-0c21-4e80-9bf4-a8db1ab93de5 +langcode: en +status: true +dependencies: + config: + - node.type.daily_email +id: node_type_daily_email +entity_type_id: node_type +entity_id: daily_email +action: display_page +allow_override: 0 +redirect: '' +redirect_code: 301 +redirect_fallback_action: access_denied diff --git a/config/sync/views.view.daily_emails.yml b/config/sync/views.view.daily_emails.yml new file mode 100644 index 0000000..acee894 --- /dev/null +++ b/config/sync/views.view.daily_emails.yml @@ -0,0 +1,431 @@ +uuid: 5ea1f419-9ff4-4ef8-8ba5-5faf94279ac8 +langcode: en +status: true +dependencies: + config: + - node.type.daily_email + - system.menu.main + module: + - node + - user +id: daily_emails +label: 'Daily emails' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +display: + default: + id: default + display_title: Default + display_plugin: default + position: 0 + display_options: + title: 'The Daily Drupaler: Email Archive' + fields: + view_node: + id: view_node + table: node + field: view_node + relationship: none + group_type: group + admin_label: '' + entity_type: node + plugin_id: entity_link + label: '' + exclude: true + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + text: view + output_url_as_text: true + absolute: false + created: + id: created + table: node_field_data + field: created + relationship: none + group_type: group + admin_label: '' + entity_type: node + entity_field: created + plugin_id: field + label: '' + exclude: true + alter: + alter_text: true + text: '{{ created }}:' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '0' + element_class: '' + element_label_type: '0' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '0' + element_wrapper_class: '' + element_default_classes: false + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + tooltip: + date_format: long + custom_date_format: '' + time_diff: + enabled: false + future_format: '@interval hence' + past_format: '@interval ago' + granularity: 2 + refresh: 60 + description: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + plugin_id: field + label: '' + exclude: false + alter: + alter_text: true + text: '{{ created }} {{ title }}' + make_link: true + path: '{{ view_node }}' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: + link_to_entity: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + pager: + type: mini + options: + offset: 0 + items_per_page: 31 + total_pages: null + id: 0 + tags: + next: ›› + previous: ‹‹ + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + empty: { } + sorts: + created: + id: created + table: node_field_data + field: created + relationship: none + group_type: group + admin_label: '' + entity_type: node + entity_field: created + plugin_id: date + order: DESC + expose: + label: '' + field_identifier: '' + exposed: false + granularity: second + arguments: { } + filters: + status: + id: status + table: node_field_data + field: status + entity_type: node + entity_field: status + plugin_id: boolean + value: '1' + group: 1 + expose: + operator: '' + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + entity_type: node + entity_field: type + plugin_id: bundle + operator: in + value: + daily_email: daily_email + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + operator_limit_selection: false + operator_list: { } + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + style: + type: html_list + options: + grouping: { } + row_class: '' + default_row_class: true + type: ul + wrapper_class: item-list + class: '' + row: + type: fields + options: + default_field_elements: true + inline: { } + separator: ' - ' + hide_empty: false + query: + type: views_query + options: + query_comment: '' + disable_sql_rewrite: false + distinct: false + replica: false + query_tags: { } + relationships: { } + header: + area: + id: area + table: views + field: area + relationship: none + group_type: group + admin_label: '' + plugin_id: text + empty: false + content: + value: '
This is an archive of the 520+ email messages I have sent to my daily mailing list since the 12th of August, 2022. Enjoy!
' + format: basic_html + tokenize: false + footer: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } + feed_1: + id: feed_1 + display_title: Feed + display_plugin: feed + position: 2 + display_options: + title: "The Daily Drupaler: Oliver Davies' Daily Email List" + pager: + type: some + options: + offset: 0 + items_per_page: 1 + row: + type: node_rss + options: { } + defaults: + title: false + sitename_title: false + display_extenders: { } + path: rss/daily.xml + sitename_title: false + displays: + page_1: page_1 + default: '0' + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - 'user.node_grants:view' + - user.permissions + tags: { } + page_1: + id: page_1 + display_title: Page + display_plugin: page + position: 1 + display_options: + display_extenders: { } + path: archive + menu: + type: normal + title: 'Daily email archive' + menu_name: main + parent: '' + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/tools/scripts/daily.json b/tools/scripts/daily.json new file mode 100644 index 0000000..54137c1 --- /dev/null +++ b/tools/scripts/daily.json @@ -0,0 +1,3686 @@ +{ + "emails": [ + { + "title": "Make it easy", + "date": "1716940800", + "permalink": "/daily/2024/05/29/make-it-easy", + "text": "What can you do to make your current task easier to achieve?<\/p>
Can you refactor the existing code to make it easier to change?<\/p>
Are there requirements that are no longer needed and can be de-scoped to make the code simpler or smaller?<\/p>
Is there a more minimum-viable version that you can release now and iterate on it later?<\/p>
Is there an existing solution, such as a library, module or plugin you can use instead of implementing it yourself?<\/p>
Is there a different or simpler implementation that satisfies most of the requirements that you can use?<\/p>
Taking an easier option means less code to maintain and a more stable application.<\/p>", + "cta": "d7eol", + "tags": ["software-development"] + }, { + "title": "Why is everyone moving to SQLite?", + "date": "1716854400", + "permalink": "/daily/2024/05/28/why-is-everyone-moving-to-sqlite", + "text": "
I've noticed a lot of Developers recently adopting SQLite for their database and I wonder why this is.<\/p>
Laravel changed their default database to SQLite for local development.<\/p>
It simplifies the development environment as there's no need for a separate database like MySQL or MariaDB but, if you'll be using one of those in production, won't that cause more issues when you migrate your local application?<\/p>
Drupal supports using SQLite, but, other than for my automated testing course<\/a>, or when running automated tests, I've always used a MySQL or MariaDB database.<\/p> Maybe this is something to keep an eye on and potentially use more for some scenarios in the future.<\/p>",
+ "cta": "d7eol",
+ "tags": ["software-development"]
+ }, {
+ "title": "Why do you still write Sass?",
+ "date": "1716768000",
+ "permalink": "/daily/2024/05/27/why-do-you-still-write-sass",
+ "text": " Yesterday, I asked if it's time to stop writing Sass<\/a>.<\/p> If you still use Sass and are writing new styles with Sass, I'd like to know why.<\/p> Reply to this email and let me know.<\/p> I'm working on a project and migrating from the existing Sass styles to Tailwind CSS-generated utility classes, but with CSS custom properties (variables) and other Sass-like features now supported by browsers, writing vanilla CSS again is an interesting option.<\/p>",
+ "cta": "",
+ "tags": ["software-development","css"]
+ }, {
+ "title": "Is it time to stop writing Sass?",
+ "date": "1716681600",
+ "permalink": "/daily/2024/05/26/is-it-time-to-stop-writing-sass",
+ "text": " I've seen a lot of recent posts that ask questions like \"Is it time to stop writing Sass?\".<\/p> I haven't written a Less or Sass stylesheet since I adopted utility classes and Tachyons, and then Tailwind CSS, and I moved to PostCSS.<\/p> But, with recent native browser support added for some Sass features, such as CSS nesting<\/a> and CSS custom properties<\/a> (variables), people are considering moving from Sass to regular CSS.<\/p> Using regular CSS also makes it easier to onboard new Developers onto your project, which is particularly helpful in open-source projects, as Mark Conroy and I discussed<\/a> on the Beyond Blocks podcast.<\/p>",
+ "cta": "",
+ "tags": ["software-development","css"]
+ }, {
+ "title": "Testing is a reusable skill",
+ "date": "1716595200",
+ "permalink": "/daily/2024/05/25/testing-is-a-reusable-skill",
+ "text": " I like skills and technologies that I can re-use.<\/p> If there's a solution that's only available in one framework and another that is framework-agnostic, I'll use the agnostic one, if possible.<\/p> I'd rather invest my time learning something I'll be able to re-use and get the most benefit from.<\/p> Automated testing is an example of something re-usable.<\/p> Regardless of the CMS, framework or language, how to write and structure automated tests is the same, and you still get the benefits from writing them and\/or doing test-driven development.<\/p> Whilst the syntax can be different, even in the PHP space comparing PHPUnit, Pest and Behat, the fundamentals and approaches are the same.<\/p>",
+ "cta": "testing_course",
+ "tags": ["software-development","automated-testing","test-driven-development"]
+ }, {
+ "title": "Don't put HTML in your body field",
+ "date": "1716508800",
+ "permalink": "/daily/2024/05/24/dont-put-html-in-your-body-field",
+ "text": " I often see Drupal projects where people have put raw HTML code into their body or other rich-text fields.<\/p> Whilst it can be useful for short-term prototyping, I don't think it should be done for content that will be reused or kept for a period of time.<\/p> If you have structured HTML code in multiple nodes, you can't make changes without editing each instance.<\/p> What if you need to fix a bug and have hundreds or thousands of instances?<\/p> If you have inline styles, they won't be updated or affected by changes to your stylesheets, such as changing colour or font family.<\/p> Instead, create first-class components that use templates that are easier to change and maintain and have a single source of truth that adheres to your design system.<\/p> In Drupal, use structured data in fields with Paragraphs or Layout Builder and build the templates around them.<\/p> This makes it easier to maintain and also for people to add and edit content.<\/p>",
+ "cta": "d7eol",
+ "tags": ["software-development","drupal"]
+ }, {
+ "title": "Why I use long parameter names in scripts",
+ "date": "1716422400",
+ "permalink": "/daily/2024/05/23/why-i-use-long-parameter-names-in-scripts",
+ "text": " The other day, I posted about a script I'd written<\/a> that found the longest commit message in a repository.<\/p> As I couldn't find a native way to do this with Git, the script loops over each commit in the repository, calculates its length and stores the length and commit SHA in a file.<\/p> The lines in the file are sorted so the longest commit is first.<\/p> Whilst I commonly use short parameters, such as For example, in the script, I execute this command to sort the lines:<\/p> This could be re-written as:<\/p> Whilst the original is more verbose and longer to type, I prefer its verbosity which makes it easier for me or others to read and understand in the future.<\/p>",
+ "cta": "",
+ "tags": ["software-development","bash","zsh","linux"]
+ }, {
+ "title": "`git revert` is your friend",
+ "date": "1716336000",
+ "permalink": "/daily/2024/05/22/git-revert-is-your-friend",
+ "text": " Imagine you've made a commit and want to undo it, or a particular commit is causing issues in production and you want to roll it back.<\/p> Instead of having to change it back manually, You specify the commit SHA you want to revert and Git will automatically try and revert that commit.<\/p> It creates its own commit message which includes the original commit message and the reverted commit SHA<\/a>, so you can easily find or navigate to the original commit.<\/p> For example:<\/p> Revert \"Sort talks only by the event date\"<\/p> This reverts commit cbd1417b24a608df8b451a3ab5c9f888de41e758.<\/p><\/blockquote> Next time, instead of manually reverting a commit, give I write and advocate for others to write<\/a> detailed Git commit messages within the subject body to provide context and information about why the commit was needed, what other options were considered, any consequences or knock-on effects there could be, or even code snippets or pseudo code.<\/p> Recently, I was curious to know which commit in a repository has the longest commit message, what code has changed in that commit, and who wrote it.<\/p> I was hoping for a command like It's a bash script that loops over the commits in the repo, calculates the length of each message, sorts them and shows the commit with longest message.<\/p> It was an interesting task and shows examples of using various UNIX commands and Linux coreutils, such as The longest in this website's code base is 546 characters<\/a>, which is fairly small compared to some of my messages in other projects.<\/p> What's the longest commit message in the repository you're working in?<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Referencing other commits in commit messages",
+ "date": "1716163200",
+ "permalink": "/daily/2024/05/20/referencing-other-commits-in-commit-messages",
+ "text": " Last week, I asked whether you should include issue IDs in commit messages<\/a>.<\/p> Another thing I like to reference in a commit message is the commit ID (or SHA) of a related commit.<\/p> For example, when I run The sha for this commit is If I was to make another commit that was related to this one, I can include this commit sha in my new commit message.<\/p> I also don't need to include the entire thing - only enough for it to be unique (usually five or six characters).<\/p> Once pushed, the commit IDs should never change, so this will be a permanent reference to the first commit.<\/p> Helpfully, websites like GitHub, GitLab and Bitbucket will identify it as a commit sha and make it clickable so you can easily navigate to the referenced commit.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Better commit messages means better pull requests",
+ "date": "1716076800",
+ "permalink": "/daily/2024/05/19/better-commit-messages-better-pull-requests",
+ "text": " If you're working on a team or project that uses pull or merge requests, then writing commit messages with detailed descriptions<\/a> using the message body and not just the subject line, will result in better pull requests.<\/p> Whatever text you put in the message body will be used as the description for your pull or merge request, as well as the subject line being used for the title.<\/p> So, instead of writing the message after completing the work, when you may forget important details, write it at the time in the message when writing the commit and re-use it in the PR or MR.<\/p> Pull or merge requests with more information and detail are more likely to be accepted and merged.<\/p> The text will also remain in the pull or merge request, even if someone squashes your commits and deletes the old messages<\/a>.<\/p> The issue then is, what if you move to a different Git hosting service<\/a>?<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Should you strictly enforce the 50/72 rule?",
+ "date": "1715990400",
+ "permalink": "/daily/2024/05/18/should-you-strictly-enforce-the-5072-rule",
+ "text": " Yesterday<\/a>, I mentioned the 50\/72 rule when writing Git commit messages.<\/p> The first line in the commit message is the subject line and should be no longer than 50 characters.<\/p> Any additional lines are the message body and should be wrapped at 72 characters.<\/p> As I said, I have Neovim configured to format my commit messages based on these rules, although they're more like guidelines.<\/p> There's no hard limit on the number of characters in the subject line or the number of characters in the body.<\/p> The commit will work and not be rejected when pushing to your remote repository.<\/p> There are likely post-commit Git hooks<\/a> to do this, but by default, things will work.<\/p> A commit message to Drupal core today was 178 characters long, including the issue ID and contributors.<\/p> When working on project teams, ideally, everyone would follow the 50\/72 rule, but if they don't consistently, I don't think it's an issue.<\/p> I'd rather they focused on writing a good and descriptive commit message and if it's formatted correctly, that's a bonus.<\/p> Whilst I could automate checks for this, I don't think it's the best use of everyone's time and, especially for Junior Developers who already have enough to learn already, not where their focus should be.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Why I don't commit with -m",
+ "date": "1715904000",
+ "permalink": "/daily/2024/05/17/why-i-dont-commit-with--m",
+ "text": " A lot of tutorials show using I don't do that, though.<\/p> Using the Alternatively, people could write a longer commit message in one line and not follow the 50 character limit for the subject line.<\/p> Whilst it's possible to write multi-line commits on the command-line, I use my editor (Neovim) to write my commit messages.<\/p> I can write as much information as I need, I have coloured columns for the 50 character subject limit and the 72 characters for wrapping the body message and, if I write more than that, it will either wrap for me, or I can format the message for it to be compliant.<\/p> These are rules that aren't also enforced in some Git tools, so I prefer knowing my setup is configured correctly.<\/p> Though, whether you use Whether you're writing tests before the implementation code or not, the first test is always the hardest to write.<\/p> What will the arrange and act phases look like?<\/p> What dependencies will you need?<\/p> What do you need to mock or create fakes of?<\/p> In a Drupal test, what other modules and configuration do you need to install?<\/p> What installation profile and default theme do you need to use?<\/p> What other unknown or complicated setup steps are there?<\/p> Do you need to configure your environment to run the tests and get the expected output?<\/p> The hardest part is getting the arrange\/setup phase working and getting to when the test is running your business logic.<\/p> Once you've got the first test running, adding more for similar use cases will be much easier.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php","automated-testing","test-driven-development"]
+ }, {
+ "title": "Should you include issue IDs in your commit messages?",
+ "date": "1715731200",
+ "permalink": "/daily/2024/05/15/should-you-include-issue-ids-in-your-commit-messages",
+ "text": " It's shown in the examples of the conventional commits specification<\/a> as part of the optional footer data.<\/p> But is it useful?<\/p> It can be if your issue tracker is linked to your Git repository and you can click the issue ID in a commit message and see the issue.<\/p> But, how often do teams change issue-tracking software or the project is passed to a different company that uses a different issue tracker?<\/p> That makes the issue IDs that reference the old IDs useless as no one has access to the issues it references.<\/p> I'd recommend putting as much information in the commit message itself and not relying on it being in an external source, like an issue tracker.<\/p> The Git log and commit messages will remain even if a different issue tracker is used, or a different team starts working on the project, and that additional information isn't lost.<\/p> I'm not against putting the issue ID in the commit message but don't do it instead of writing a descriptive commit message.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Free code reviews",
+ "date": "1715644800",
+ "permalink": "/daily/2024/05/14/free-code-reviews",
+ "text": " Do you want to contribute to open-source software, such as Drupal core or one of the thousands of modules or themes?<\/p> Are you worried about receiving negative comments or feedback?<\/p> Instead, think of it as free code reviews.<\/p> Contributing to open-source is a great way to gain experience and learn from other Developers, and something that I still do often, 17 years after starting to develop software.<\/p> I'm also happy to review people's open-source for free. If that would be of interest, reply and let me know.<\/p>",
+ "cta": "",
+ "tags": ["software-development","open-source"]
+ }, {
+ "title": "DrupalCamp Ghent",
+ "date": "1715558400",
+ "permalink": "/daily/2024/05/13/drupalcamp-ghent",
+ "text": " Last Friday and Saturday, I was happy to attend and present two sessions at DrupalCamp Ghent in Belgium.<\/p> I was able to present talks on automated testing and Tailwind CSS, which were my 99th and 100th talks, respectively!<\/p> The sessions were streamed live, or the slides and previous recordings are on my website<\/a>.<\/p> It was a great event and it's always nice to see and meet new people within the Drupal community, to see new places and share knowledge by giving presentations.<\/p> Reply and let me know if you have any feedback on the sessions or if you'd like me to present a talk or workshop at your event.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php","drupalcamp"]
+ }, {
+ "title": "Merging without merge commits",
+ "date": "1715472000",
+ "permalink": "/daily/2024/05/12/merging-without-merge-commits",
+ "text": " Since I posted about optimising for revertability<\/a>, I've received a few questions about how I avoid merge commits when working with Git.<\/p> This is an extract from my This changes the behaviour of when I run Only allowing fast-forward merges avoids merge commits as Git can just move the pointer for the branch to the latest commit.<\/p> If I can't do a fast-forward merge, I need to rebase first to update everything and bring it up to date.<\/p> Sometimes, when working in team, merge commits will still creep in sometimes and there are situations where you can only create a merge commit.<\/p> In this situation, I can do Hint: there's a lot more information on the configuration and arguments if you run and read When working with online tools such as GitHub and GitLab, I avoid any options like Or use trunk-based development<\/a> and don't work on topic branches at all.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Don't delete my commit messages",
+ "date": "1715385600",
+ "permalink": "/daily/2024/05/11/don-t-delete-my-commit-messages",
+ "text": " Another reason I don't like squashing commits within pull\/merge request is losing detail within the commit messages.<\/p> As someone who usually writes detailed commit messages that explain why the change is being made, any gotchas or alternative approaches that were tried, etc, I want that information to be retained.<\/p> Previously, when working on a team and doing merge\/pull requests, when merging a feature or release branch, either the tool or reviewer would delete all the messages from the squashed commits.<\/p> The time I spent writing the messages was wasted, and the information was lost.<\/p> I'd rather keep the original commits intact<\/a> but, if you need to squash commits, please don't remove the previous messages as they could be useful in the future.<\/p> People can see the changes by viewing the commits, but the information within the commit messages are valuable, too.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Optimise for revertability",
+ "date": "1715299200",
+ "permalink": "/daily/2024/05/10/optimise-for-revertability",
+ "text": " There are two things I avoid when merging changes in Git.<\/p> Merge commits and squashing commits.<\/p> Both make it hard to revert changes if needed once they've been merged, such as a major bug in production and you quickly need to roll back.<\/p> Merge commits are difficult to revert and if a commit has been squashed into one larger commit, you can't revert it without also reverting everything else.<\/p> Working with small, unsquashed commits makes it simple to revert a specific one and only that one.<\/p> If I need to revert something, I want to be able to do as simply and specifically as possible.<\/p> Optimise for revertability.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Do you have a deadline?",
+ "date": "1715212800",
+ "permalink": "/daily/2024/05/09/do-you-have-a-deadline",
+ "text": " Does your task or project have a deadline for completion?<\/p> Is there a date that it must be completed by?<\/p> Or, is your \"deadline\" an arbitrary date that can be moved?<\/p> If so, you don't have a deadline.<\/p> You have a goal.<\/p> Understand and be clear whether you have a fixed deadline or a flexible goal.<\/p>",
+ "cta": "",
+ "tags": ["software-development"]
+ }, {
+ "title": "Assertions aren't just for tests",
+ "date": "1715126400",
+ "permalink": "/daily/2024/05/08/assertions-arent-just-for-tests",
+ "text": " If you've written or seen automated tests in PHP, you'll have seen lines like this:<\/p> But, did you know assertions can be used outside of tests.<\/p> PHP has an For example, if I had this code:<\/p> I know I've made my assumptions explicit.<\/p> If I think this is better than assuming or hoping the values are as you expect, and it also makes the intent of the code much easier to see and understand.<\/p> If you haven't tried In April 2023<\/a>, I wondered if we'd see a 7.100 release of Drupal.<\/p> I just noticed that we did.<\/p> It was released on the 6th of March.<\/p> From the release notes:<\/p> Maintenance release of the Drupal 7 series. Includes bug fixes and small API\/feature improvements only (no major, non-backwards-compatible new functionality). Unusually, this release does include a new core module.<\/p><\/blockquote> This is also the first Drupal release to be affected by the Year 2000 problem, a.k.a. the Y2K problem, or the Millennium Bug:<\/p> Note that this is the first Drupal 7 release with 3 digits. If you receive a report that your version of Drupal is over a decade out of date, see https:\/\/en.wikipedia.org\/wiki\/Year_2000_problem.<\/p><\/blockquote> According to the usage figures on Drupal.org, there are at least 316,843 active Drupal 7 websites and, with only 243 days left until D7 is end-of-life, hopefully most of them will be upgrading to Drupal 10 (or 11) soon.<\/p> If you're stuck on Drupal 7, I can help! Reply to this email and let's start a conversation.<\/p>",
+ "cta": "d7eol",
+ "tags": ["software-development","drupal","php"]
+ }, {
+ "title": "Interactive staging",
+ "date": "1714953600",
+ "permalink": "/daily/2024/05/06/interactive-staging",
+ "text": " A major addition to my Git workflow has been the ability to interactively add hunks of code to be committed.<\/p> There's This will ask you to confirm if you want to add each hunk to the commit (a.k.a. the staging area) or not.<\/p> For example, here's the prompt I get whilst working on the post for this email:<\/p> I can add the whole hunk, split it into smaller hunks, add all the hunks in the file or ignore this hunk and later hunks in the file.<\/p> Then the process is repeated for any following hunks.<\/p> This means I can add the relevant hunks to craft the commit I want and I can keep my commits small and meaningful, and easy to revert if there is an issue.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Making PHPStan stricter",
+ "date": "1714867200",
+ "permalink": "/daily/2024/05/05/making-phpstan-stricter",
+ "text": " Continuing yesterday's thought on strictness in PHP<\/a>, today I want to talk about adding more strictness to PHPStan.<\/p> Adding the PHPStan Strict Rules extension<\/a> makes PHPStan stricter by adding new, more opinionated rules.<\/p> For example:<\/p> You can enable and disable rules as needed but, like setting the PHPStan level, ideally I like to enable them all by default and see how strict I go.<\/p> It depends on the code being tested and the preference of the team, though I find the stricter the rules, the less bugs there are.<\/p>",
+ "cta": "",
+ "tags": ["software-development","php","phpstan"]
+ }, {
+ "title": "Strict typing in PHP",
+ "date": "1714780800",
+ "permalink": "/daily/2024/05/04/strict-typing-in-php",
+ "text": " I prefer writing and working with strictly typed code.<\/p> One of the major improvements in PHP has been the option to enable strict types.<\/p> For example, this code will usually not error and give the result:<\/p> However, I'd prefer if it failed as I'm passing the function an integer and a string, but specifying they should both be integers.<\/p> Fixing this is simple, by adding this line to the top of the file:<\/p> I add this to every PHP file by default.<\/p> I want my code to be as strict and predictable as possible, and to error when I want it to and make any bugs more explicit and easier to find and fix.<\/p>",
+ "cta": "",
+ "tags": ["software-development","php"]
+ }, {
+ "title": "Don't add boolean arguments",
+ "date": "1714694400",
+ "permalink": "/daily/2024/05/03/dont-add-boolean-arguments",
+ "text": " A convention I like from the Laravel framework is to avoid adding boolean arguments to methods.<\/p> For example, if I have this function:<\/p> If I wanted to only get published posts, one way would be to add a boolean argument:<\/p> Then, I'd need to use that within the method body to add another condition (this is referred to as control coupling, where one method affects another).<\/p> The non-boolean approach would be to create a separate method with its own distinct name.<\/p> For example, Whilst we have two methods now instead of one, it's much clearer what each does and there aren't any random It's been a while since I used Ansible.<\/p> I used it for automating infrastructure and deploying applications.<\/p> I gave a talk about it<\/a> on several occasions, including remotely for the Ansible London meetup during COVID.<\/p> I haven't had to use it recently but, today, I brought it back<\/a> out of my toolkit.<\/p> I was reviewing how I manage my website's configuration and files and Ansible seemed to be a perfect choice.<\/p> The files need to be uploaded to the server and, to handle redirects, I also have a custom NGINX virtual host configuration that needs to be uploaded if I change it and the NGINX service reloaded.<\/p> Ansible is a great tool for this type of task and, just because it's a tool I haven't used it for a while, that doesn't mean I can't re-evaluate it and see if fits my current requirements.<\/p> It's still in my toolkit, just in case I need it.<\/p>",
+ "cta": "",
+ "tags": ["software-development","devops","ansible"]
+ }, {
+ "title": "Broken pipeline? Fix or revert it.",
+ "date": "1714521600",
+ "permalink": "/daily/2024/05/01/broken-pipeline-fix-or-revert-it",
+ "text": " If you're doing trunk-based development where multiple people are committing and pushing work to the same branch, what do you do if you've pushed a commit that fails the checks and breaks the pipeline?<\/p> This is a bad state and needs to be solved as soon as possible as it's causing a problem for everyone else.<\/p> If the pipeline is failing, someone else could push a change and have it fail for a different reason, but wouldn't know.<\/p> The responsibility is on the person who pushed the failing commit to resolve it and get the pipeline passing again as soon as possible.<\/p> You break it, you bought it.<\/p> Hopefully, the changes in the failing commit are small and it's something you can resolve quickly and push a fixed commit.<\/p> If you can't fix it within a few minutes, revert the failing commit and try again.<\/p> What if someone's pushed a failing commit and hasn't fixed it in a timely manner?<\/p> Revert it for them, fix the pipeline and unblock the rest of the team.<\/p>",
+ "cta": "",
+ "tags": ["software-development","continuous-integration","trunk-based-development"]
+ }, {
+ "title": "Stepping back into debugging",
+ "date": "1714435200",
+ "permalink": "/daily/2024/04/30/stepping-back-into-debugging",
+ "text": " In PHP, we have functions like In Drupal, we have functions like These functions are great for simple debugging but sometimes I need more, which is when I reach for a step debugger - namely, Xdebug.<\/p> This is common when working in complex legacy code, where you need to be able to see a breakpoint and step through the code to see what path it takes and what the state is at each step.<\/p> Xdebug is a tool I use fairly often and something I have configured on an individual project basis.<\/p> This week, I spent some time adding it to a new project and ensured my notes and documentation still worked.<\/p> I use Docker and Docker Compose on Linux, so there are slight changes compared to running PHP natively, so I wanted to make sure it still works.<\/p> I've added my latest setup to my Drupal Docker Example repository<\/a> and plan to add it to my standard Build Configs<\/a> setup for Drupal projects.<\/p> If you use Docker Compose on Linux, it may be useful for you.<\/p> If you haven't tried Xdebug before, I suggest giving it a try and see if improves your debugging.<\/p>",
+ "cta": "",
+ "tags": ["software-development","php"]
+ }, {
+ "title": "Some kind words",
+ "date": "1714348800",
+ "permalink": "/daily/2024/04/29/some-kind-words",
+ "text": " I recently received these kind words from Frank Landry, who recently completed my Drupal automated testing email course<\/a>:<\/p> The course was very informative.<\/p> One of the biggest pain points with Drupal testing was that there was no clear and definitive guide on setting up the php unit XML file to get functional and kernel tests working right away.<\/p> Your guide was fantastic and I will definitely be using it going forward in my module development for work.<\/p><\/blockquote> Thanks, Frank, for your feedback.<\/p> It's great to get feedback on my content and to know it's helping and providing value to people.<\/p>",
+ "cta": "atdc",
+ "tags": ["software-development","drupal","automated-testing","test-driven-development"]
+ }, {
+ "title": "Replicating a bug with a test",
+ "date": "1714262400",
+ "permalink": "/daily/2024/04/28/replicating-a-bug-with-a-test",
+ "text": " When fixing a bug, can you write a new failing test to replicate the bug?<\/p> If so, you verify the bug exists, you know when it's fixed (the test passes), and you know you won't introduce the same bug again - otherwise, the new test will fail.<\/p>",
+ "cta": "",
+ "tags": ["software-development","automated-testing"]
+ }, {
+ "title": "Can you make a test fail?",
+ "date": "1714176000",
+ "permalink": "/daily/2024/04/27/can-you-make-a-test-fail",
+ "text": " Can you make an automated test fail when you want it to?<\/p> If not, how do you know it's testing the correct thing?<\/p>",
+ "cta": "",
+ "tags": ["software-development","automated-testing"]
+ }, {
+ "title": "Don't cherry-pick features from a branch to deploy",
+ "date": "1714089600",
+ "permalink": "/daily/2024/04/26/don-t-cherry-pick-features-from-a-branch-to-deploy",
+ "text": " I previously worked on a project where, after a code change had been reviewed and merged, it was pushed to a UAT environment for the client to test.<\/p> This usually resulted in a group of changes pushed to the UAT environment, waiting for the client to test them.<\/p> They would, and then decide which changes they wanted to be moved to production.<\/p> Maybe changes 1, 2 and 4 would be asked to be deployed, but not 3 or 5.<\/p> Someone would then cherry pick the relevant commits onto the mainline branch and deploy them to production.<\/p> But, if the code isn't the same as on that UAT environment, how do you know it still works?<\/p> Could a commit have been missed or could not including a non-selected commit have caused a regression or unintended side effects?<\/p> If you want to select which changes go live, feature flags are a better option as you don't need to change the commits or code you're pushing.<\/p> You push all the commits from UAT to production and enable the feature flags for the things you want to release.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "If everyone branches, no-one gets the updates",
+ "date": "1714003200",
+ "permalink": "/daily/2024/04/25/if-everyone-branches-no-one-gets-updates",
+ "text": " A common response I get when debating topic branches is \"I regularly pull But if everyone is working on individual topic branches instead of the mainline branch, where do the changes come from?<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Testing topic branches in isolation",
+ "date": "1713916800",
+ "permalink": "/daily/2024/04/26/testing-topic-branches-in-isolation",
+ "text": " I was recently asked about setting up different testing environments based on topic (a.k.a. feature) branches.<\/p> When a feature or bug fix was finished, it would create a new environment for it to be tested.<\/p> If it passed testing, the topic branch was merged and it would be included in the next release.<\/p> But, there's no guarantee it still works once it's been merged.<\/p> It could conflict with changes from a different topic branch and no longer work - even if it worked when it was tested in isolation on its own branch.<\/p> To ensure it still works, it will need to be tested again.<\/p> So, what is the benefit of testing it in isolation if it needs to be tested again once it's merged?<\/p> This is why I prefer continuous integration and delivery (CI\/CD), as I always know all changes work together and not just in isolation.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Why use a static site generator",
+ "date": "1713830400",
+ "permalink": "/daily/2024/04/23/why-use-a-static-site-generator",
+ "text": " In February<\/a>, I spoke at the PHP South West meetup about Sculpin - a static site generator written in PHP.<\/p> Sculpin uses Twig, HTML and Markdown to generate static HTML files that you can upload to any web server.<\/p> You don't need PHP or a database server - making it simpler and cheaper to host compared to a CMS-powered site.<\/p> There's also no database for people to hack into and, as they're just static HTML pages, they are very quick to load.<\/p> The downside is that files need to be created and edited locally or editing Git files on GitHub, etc, as there's no CMS to log into.<\/p> Still, for some projects, static site generators are a great option.<\/p> For Drupal, there's Tome - a module that creates a static website from a Drupal installation, and something I plan to investigate.<\/p>",
+ "cta": "",
+ "tags": ["software-development","php","sculpin"]
+ }, {
+ "title": "Building websites with PHP and Sculpin",
+ "date": "1713744000",
+ "permalink": "/daily/2024/04/22/building-websites-with-php-and-sculpin",
+ "text": " The video of the most recent version of my Building static websites with PHP and Sculpin<\/a> from February's PHP South West meetup has been released<\/a>.<\/p> In the talk, I explain what Sculpin is and how it works before a live demo where I rebuild part of the PHPSW website (which I worked on originally).<\/p> If you have any feedback or questions, reply to this email and let me know.<\/p>",
+ "cta": "",
+ "tags": ["software-development","php","public-speaking"]
+ }, {
+ "title": "Almost at 100 talks and workshops",
+ "date": "1713657600",
+ "permalink": "/daily/2024/04/21/almost-at-100-talks-and-workshops",
+ "text": " My talks page<\/a> on my website says I've presented 98 talks and workshops at different events since September 2012.<\/p> Next month, I'm giving two talks at DrupalCamp Ghent.<\/p> One on automated testing and test-driven development<\/a> and another on Tailwind CSS<\/a>.<\/p> This means that these will be my 99th and 100th presentations!<\/p> I enjoy speaking at events such as meetups and conferences, and I hope people have learned from them as well.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","drupalcamp","php","public-speaking"]
+ }, {
+ "title": "Speaking at DrupalCamp Belgium",
+ "date": "1713571200",
+ "permalink": "/daily/2024/04/20/speaking-at-drupalcamp-belgium",
+ "text": " I'm happy to be speaking at DrupalCamp Belgium next month, on the 10th and 11th of May in Ghent.<\/p> I'll be presenting two sessions on automated testing in Drupal<\/a> (Friday) and Tailwind CSS<\/a> (Saturday).<\/p> I spoke about testing most recently at DrupalCon Lille, and I'm looking forward to updating my Tailwind talk again to include the latest features within the framework.<\/p> Tickets are still available<\/a> and it would be great to see you there.<\/p>",
+ "cta": "",
+ "tags": ["drupal","drupalcamp","public-speaking"]
+ }, {
+ "title": "When should you tag 1.0?",
+ "date": "1713484800",
+ "permalink": "/daily/2024/04/19/when-should-you-tag-1-0",
+ "text": " Something I've seen, both with contributed Drupal modules and other open-source projects, over the past few years is they spend a lot of time in the 0.x versions or releasing alpha and beta versions rather than releasing a 1.0 or stable version.<\/p> I presume it's a concern around backward compatibility and maintaining that once a stable version is released.<\/p> But, if you want people to use your module or upgrade it to the latest version, that's much easier to do once there's a stable version.<\/p> Some organisations prohibit using alpha or unstable versions of projects so, if there isn't a stable version, they wouldn't be able to use it.<\/p> Personally, if I'm using one of my open-source modules, plugins or libraries in production, there should be a stable 1.0 version tagged.<\/p> Once it's in production, I'm already making an implied commitment that it's going to be stable and I won't break everything in the next release, so why not make that explicit and tag a stable release?<\/p> Version numbers are free and nothing is stopping you from deprecating code and releasing a new major version with breaking changes in the future, so go ahead and tag that stable version.<\/p>",
+ "cta": "",
+ "tags": ["software-development","open-source"]
+ }, {
+ "title": "I made my first commit to LocalGov Drupal Microsites",
+ "date": "1713398400",
+ "permalink": "/daily/2024/04/18/first-commit-localgov-drupal-microsites",
+ "text": " This week, I submitted my first change<\/a> to the LocalGov Microsites project.<\/p> It was a small change - correcting a typo that I spotted within the README file.<\/p> It's OK to start small, especially when first working on a new codebase.<\/p> It was a good introduction to the project and the first of what I hope to be numerous contributions to the Microsites distribution and LocalGov Drupal in general.<\/p>",
+ "cta": "subscription",
+ "tags": ["software-development","drupal","localgov-drupal","open-source"]
+ }, {
+ "title": "Regular releases encourage contribution",
+ "date": "1713312000",
+ "permalink": "/daily/2024/04/17/regular-releases-encourage-contribution",
+ "text": " As well as writing and maintaining my open-source software projects, I regularly contribute to other people's - including PHP libraries, Drupal modules and Tailwind CSS plugins.<\/p> I'm more likely to contribute to your project if you release new versions regularly.<\/p> Why would I if my changes may sit on a development branch for a long time or not be released at all?<\/p> If I can see that my changes are likely to be released soon, I'm more likely to spend the time and effort to contribute instead of looking for a different solution.<\/p>",
+ "cta": "d7eol",
+ "tags": ["software-development","open-source"]
+ }, {
+ "title": "Regularly releasing open-source software",
+ "date": "1713225600",
+ "permalink": "/daily/2024/04/16/regularly-releasing-open-source-software",
+ "text": " As with client applications, I recommend releasing new versions of open-source software as often as possible.<\/p> This makes it easier for users to update to newer versions as they can easily see the changes, and less risky as it's easier to roll back or diagnose the problem if there is an issue.<\/p> If you've added a feature or fixed a bug, fewer people will likely use it if it's not within a released version of your project and sat on a development branch or waiting for a tagged release. This means they're not benefiting from the changes and getting value from them.<\/p> You can add feature flags to hide work-in-progress features that you don't want people to use yet, but you don't want to block yourself from releasing a new version until it's complete.<\/p> Regular releases encourage smaller and simpler changes rather than larger and more complex - and potentially breaking - changes.<\/p> I like to have a release day each month to release new versions of my open-source projects, which works well for me.<\/p> I have a recurring task on my To Do list to review my projects and tag and release any new versions that are needed.<\/p>",
+ "cta": "",
+ "tags": ["software-development","open-source"]
+ }, {
+ "title": "A note to open-source software maintainers",
+ "date": "1713139200",
+ "permalink": "/daily/2024/04/15/a-note-to-open-source-software-maintainers",
+ "text": " I recently updated a website to the latest version of Drupal.<\/p> Doing so introduced a bug in a contributed module I was using.<\/p> A fix was committed to the 2.x branch of the module, which is still in an unstable alpha phase, and the issue was closed.<\/p> There was no fix for the stable 1.x branch.<\/p> There was no fix backported to the 1.x branch.<\/p> There are breaking changes between the 1.x and 2.x branches that require me to update custom code that uses the module, which I don't want to do yet, and I don't want to update to an unstable version.<\/p> If you maintain open-source software, don't force people to update too quickly.<\/p> If you can, make it easy for people to update to the next major version by keeping breaking changes to a minimum and providing time and clear instructions for doing so.<\/p> If these are too difficult, it could discourage or prevent people from using your software.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php","open-source"]
+ }, {
+ "title": "What about updating custom modules and themes?",
+ "date": "1713052800",
+ "permalink": "/daily/2024/04/14/what-about-updating-custom-modules-and-themes",
+ "text": " Yesterday's email<\/a> was about using Drupal Rector and the Automated Project Update bot to update contributed modules.<\/p> But what about custom modules within your application?<\/p> To do this, I use the They scan your custom modules and themes and report any deprecated code within your custom projects - i.e. code that will be removed in a future major version - and tell you what new code to use instead.<\/p> Once you've removed any deprecations, your module or theme will be ready for the next major version of Drupal.<\/p> This is the approach I've used to upgrade numerous websites between major modern versions of Drupal, making small updates to existing code instead of having to rewrite it from scratch.<\/p>",
+ "cta": "d7eol",
+ "tags": ["software-development","drupal","php"]
+ }, {
+ "title": "Rector is not just for Drupal",
+ "date": "1712966400",
+ "permalink": "/daily/2024/04/13/rector-is-not-just-for-drupal",
+ "text": " I like framework-agnostic tools.<\/p> I like to reuse knowledge and tools across projects, whether I'm working with Drupal, Symfony, Laravel or Sculpin.<\/p> Rector is one of those tools.<\/p> Yesterday<\/a> I said I use it to create automatic updates to my Drupal module code, but it can be used for other PHP projects, too.<\/p> If you're upgrading a PHP library and want to use promoted constructor properties, for example, Rector can do that for you - and a lot more.<\/p> You define which rules or presets you want to use, run Rector on the code, and it will make those changes.<\/p> Having Rector do this work leaves me free to stay focused on other tasks.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php"]
+ }, {
+ "title": "Drupal Rector and the Project Update Bot",
+ "date": "1712880000",
+ "permalink": "/daily/2024/04/12/drupal-rector-and-the-project-update-bot",
+ "text": " In Wednesday's email<\/a>, I said I was resurrecting the Speakerdeck Field module, and the same version works on Drupal 8, 9, 10 and 11.<\/p> How do I know this?<\/p> One of my favourite PHP libraries is Rector - a tool for upgrading code to the newest versions of PHP or, in this case, Drupal using Drupal Rector.<\/p> It runs automatically on modules, including Speakerdeck Field, via the Project Upgrade Bot and generates a set of updates to apply.<\/p> CI pipelines with GitLab CI and a reliable test suite make it much easier to upgrade modules to the latest Drupal version and ensure they still work.<\/p>",
+ "cta": "d7eol",
+ "tags": ["software-development","drupal","php"]
+ }, {
+ "title": "Over 100 ATDC subscribers",
+ "date": "1712793600",
+ "permalink": "/daily/2024/04/11/over-100-atdc-subscribers",
+ "text": " Since launching my Automated Testing in Drupal email course, over 100 people have subscribed and received the free ten daily lessons where I explain how to start from scratch to build a Drupal module with automated tests and test-driven development.<\/p> Thanks to everyone who has taken the course so far and those who have provided feedback.<\/p> Automated testing and test-driven development were game changers for me and enabled me to deliver better projects.<\/p> If you'd like to take the course and learn how to do automated testing in Drupal, you can register for free<\/a> and get them direct to your inbox.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php","automated-testing","test-driven-development"]
+ }, {
+ "title": "Resurrecting the Speakerdeck Field module",
+ "date": "1712707200",
+ "permalink": "/daily/2024/04/10/resurrecting-the-speakerdeck-field-module",
+ "text": " This week, I've resurrected the Speakerdeck field Drupal module<\/a>.<\/p> It's a module I wrote in 2017 for the Drupal 8 version of my website to embed Speakerdeck slides on my talk pages.<\/p> My website now runs on Drupal 10, but the great thing is that the same code that worked for Drupal 8 also works for Drupal 9 and 10.<\/p> There was no rewrite for each major version, and as I'm not using any deprecated code, the same code works for all the modern versions mentioned, and it looks like it will for Drupal 11, too.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php","open-source"]
+ }, {
+ "title": "Paying it forward",
+ "date": "1712620800",
+ "permalink": "/daily/2024/04/09/paying-it-forward",
+ "text": " As well as building applications with PHP and Drupal, I spend a lot of time helping others and \"paying it forward\".<\/p> I write and contribute to open-source software and offer free online pair programming sessions to work on open-source projects.<\/p> I speak at conferences and meetups.<\/p> I create content, including videos and coding live streams.<\/p> I mentor at in-person events, such as DrupalCon, and for bootcamps, such as School of Code.<\/p> I've organised events, such as PHP South Wales and DrupalCamp Bristol, and reviewed session submissions for DrupalCon.<\/p> But I wouldn't have been able to do this without others doing the same when I was learning and getting into software development.<\/p> In July 2008, when I was evaluating technologies, I posted a question on a forum (I was learning HTML, PHP and MySQL then).<\/p> Someone replied and recommended using Drupal, which is how I learned about the project.<\/p> If they hadn't done that, I may not be in the position I am now.<\/p> I might not have found Drupal, contributed to it, worked for the Drupal Association, or spoken at DrupalCon.<\/p> Now, it's my turn to pay it forward.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php"]
+ }, {
+ "title": "Come for the software, stay for the community",
+ "date": "1712534400",
+ "permalink": "/daily/2024/04/08/come-for-the-software--stay-for-the-community",
+ "text": " One of my favourite Drupal phrases is \"Come for the software, stay for the community\".<\/p> I started using Drupal so I could build a website for the Tae Kwon-Do school I was training at, and then started to get involved with the community online and at events, such as local meetups and conferences, such as DrupalCamps and DrupalCon.<\/p> I started to contribute code to Drupal core and contrib projects and was a contribution mentor at my first DrupalCon in Prague in 2017 as well as others since, helping first time Drupal contributors.<\/p> I've got jobs and projects from my involvement with the community, including working for the Drupal Association itself.<\/p> I've met people and travelled to places I otherwise wouldn't have because of the Drupal community, as well as the wider PHP, open source and software development communities,<\/p> I came to Drupal for the code, and stayed for the code and the community.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php"]
+ }, {
+ "title": "Avoiding nesting",
+ "date": "1712448000",
+ "permalink": "/daily/2024/04/07/avoiding-nesting",
+ "text": " One of my goals when coding is to reduce the amount of nesting in the code I write.<\/p> I mean both in my PHP code where if conditions and foreach loops can be nested within each other, and CSS and Sass files, which support nesting CSS rules.<\/p> My aim is to have a maximum of two or three levels of indentation, though sometimes this isn't possible.<\/p> Doing so where I can, though, makes my code easier to read and understand and encourages other clean code approaches, such as having small and well-named functions.<\/p> In CSS or Sass, avoiding nesting makes it easier to find a rule I'm looking for instead of having to find how rules have been nested or names have been concatenated - making it hard to search or grep for a string.<\/p> This approach is part of \"object callisthenics\", which was introduced by Jeff Bay and includes other approaches that I like to follow, such as not using the There used to be a saying:<\/p> If you want to build a website, use I think I've heard it used with all the other content management systems, and it isn't about saying one is better.<\/p> It highlights that Drupal isn't just a content management system - it's a content management framework.<\/p> A framework you use to build your content management system to meet your needs.<\/p> Straight away after installing Drupal, you can create as many content types as you want with as many fields as you want to build as simple or complex a content model as you need.<\/p> You aren't restricted to the default page and article content types or a fixed set of fields.<\/p> That, along with Views - a built-in query builder to build pages and blocks of your content, user login and registration, and JSON:API to allow other applications, such as mobile apps, to access your data, to name a few things, makes Drupal a very powerful option.<\/p>",
+ "cta": "d7eol",
+ "tags": ["software-development","drupal","php"]
+ }, {
+ "title": "One Drupal fits all",
+ "date": "1712275200",
+ "permalink": "/daily/2024/04/05/one-drupal-fits-all",
+ "text": " Two of the main things I like about Drupal are its flexibility and scalability.<\/p> It works well for basic applications, such as personal websites and blogs (like mine) and for large, complex applications such as Drupal.org itself and many others.<\/p> I don't need to learn one tool for small or simple projects and another for large or complex projects.<\/p> Drupal is one size fits all.<\/p>",
+ "cta": "d7eol",
+ "tags": ["software-development","drupal","php"]
+ }, {
+ "title": "PHP attributes: coming soon to a Drupal version near you",
+ "date": "1712188800",
+ "permalink": "/daily/2024/04/04/php-attributes--coming-soon-to-a-drupal-version-near-you",
+ "text": " Since Drupal 10.2, the framework has started to adopt PHP attributes - a new feature since PHP 8 - as an alternative to annotations when defining plugins, such as blocks and queues.<\/p> From the 10.2 release notes:<\/p> Drupal core has started adopting PHP attributes, a modern PHP language feature, to provide better developer experience for plugin annotations. Contributed and custom code can begin adopting this improved API for their plugins, and Block and Action plugins can all be converted to the new API.<\/p><\/blockquote> It seems that what's been added so far is the first phase of converting the core's plugins, with more to follow in future versions.<\/p> There are also discussions about supporting both attributes and annotations in Drupal 11.<\/p> I love to see Drupal continuing to evolve and adopt new features from the PHP language and Symfony framework (such as autowiring and autoconfiguration), and I'm looking forward to using attributes in my projects.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php"]
+ }, {
+ "title": "Switching web servers using Build Configs",
+ "date": "1712102400",
+ "permalink": "/daily/2024/04/03/switching-web-servers-using-build-configs",
+ "text": " Have you been in a situation where you needed to switch something in a project, like the type of database or a payment provider?<\/p> Today, I decided to switch a project from NGINX to Apache.<\/p> Usually, this would involve using a different base Docker image, creating new configuration files, and changing things like the root directory for my project.<\/p> But, because I'd built this into Build Configs<\/a>, I was able to change a few lines in one file, and when I re-generated the configuration files, this project was running Apache.<\/p> This is an excellent example of why I built this tool: to save time and reduce duplication across my projects.<\/p> For this change, it did both.<\/p>",
+ "cta": "",
+ "tags": ["software-development","build-configs"]
+ }, {
+ "title": "Releasing a new project one page at a time",
+ "date": "1712016000",
+ "permalink": "/daily/2024/04/02/releasing-a-new-project-one-page-at-a-time",
+ "text": " How do you release a new project?<\/p> Do you build everything and release everything at once?<\/p> I've used the strategy of building and releasing it a page at a time and running two versions simultaneously.<\/p> The main live version stays running, and you use a tool like NGINX or Cloudflare as a gatekeeper to direct traffic to the correct application - either the current one or the new one - based on the requested page.<\/p> When a page is ready, you add it to the list of pages to serve from the new application to put it live.<\/p> If there's an issue, it is also easy to revert to the original page.<\/p> I've used this approach with my website and for client Drupal upgrade projects, where some pages are on Drupal 7 and some on Drupal 10.<\/p> It's not the right approach for every situation, but it's a useful one to have in the toolkit.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal"]
+ }, {
+ "title": "I'm attending LocalGov Drupal Camp",
+ "date": "1711929600",
+ "permalink": "/daily/2024/04/01/i-m-attending-localgov-drupal-camp",
+ "text": " On the 24th of April, I'll be attending a LocalGov Drupal Camp in Birmingham, UK.<\/p> As someone keen to get more involved and contribute more to the project, and who's local council recently switched to LocalGov<\/a>, I'm looking forward to the chance to learn and contribute on the day and afterwards.<\/p> If you want to attend also, there are a few remaining tickets available and you can register on Eventbrite<\/a>.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","localgov-drupal","drupalcamp"]
+ }, {
+ "title": "Making Git work the way you want",
+ "date": "1711843200",
+ "permalink": "/daily/2024/03/31/making-git-work-the-way-you-want",
+ "text": " Another question that followed my recent Git emails was, \" I assume you use rebase over merge?\"<\/p> The short answer is \"yes\". I like to keep the history of my repositories clean and simple to read by keeping the logs linear and not full of merge commits.<\/p> The longer answer is that I do merges, but only fast-forward merges, at least by default.<\/p> If, when merging, Git can fast-forward my branch to the latest commit without creating a merge commit, it will do so.<\/p> If not, I can then rebase my changes to make them linear and fast-forwardable. Alternatively, if the commits have already been pushed and cannot be overwritten, I can explicitly allow a non-fast-forward merge in that situation.<\/p> I have Git configured to work this way as that's how I want it to work, and that configurability is something I like about Git.<\/p> If you want to see how I have Git configured, my settings are in my dotfiles repository<\/a> (note this file is written in the Nix language as I use Nix to manage my configuration).<\/p> If you're working in a team, I'd suggest having a common configuration for everyone and defined rules for how you're going to use Git (branch names, merge or rebase, etc) to avoid inconsistencies.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Leaving a trail of breadcrumbs",
+ "date": "1711756800",
+ "permalink": "/daily/2024/03/30/leaving-a-trail-of-breadcrumbs",
+ "text": " A great thing about committing often is that you leave a trail of breadcrumbs for yourself, and you can easily find your way back.<\/p> If you are trying to get a test to pass or fix a bug and you get a bit lost, you can see what you've changed since your last commit instead of everything since you started working on your task.<\/p> If needed, you can discard your changes and reset to your last working commit to try again, similar to using the \"test and commit or revert\" approach.<\/p> If I commit too often, I can squash them before pushing.<\/p> If I don't commit often enough, I can regret not committing more regularly if I get stuck or lost.<\/p> I'd rather do the former.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "How I Git",
+ "date": "1711670400",
+ "permalink": "/daily/2024/03/29/how-i-git",
+ "text": " After Wednesday's email<\/a>, someone said, \"It sounds like you and I use git very differently.\" So, I wanted to explain what my typical Git workflow is.<\/p> I used to use Git Flow, but now, I almost never create a new branch when starting a new task.<\/p> I keep my workflow as simple as possible by using trunk-based development and working on a single branch as much as I can.<\/p> Before I start, I make sure any uncommitted changes are committed or reset and that the automated tests, static analysis, coding standards checks, etc., are passing so I know I'm starting from a good place.<\/p> Then, I start working on the task.<\/p> I like to work in small steps and make small, regular commits, but I don't always push each individual commit to the remote repository.<\/p> Sometimes, I'll make a number of \"work in progress\" commits and squash them into one before pushing them.<\/p> I want the time between making and pushing the commit to be as short as possible, and I want each commit to be deployable.<\/p> If I'm doing test-driven development, I'll typically commit each time a test is passing - whether it's adding a new test or extending one.<\/p> I run any tests often whilst writing code to ensure they pass, either using a watch command or a keybinding in Neovim.<\/p> I won't push a commit that would cause the code to not work, a test to fail, or block any other (potentially more urgent) changes from being pushed to production.<\/p> If I push a commit that breaks the CI pipeline, I'll fix it quickly, which is usually possible as the changes are small.<\/p> If not, I'll revert the commit to get back to a deployable state as quickly as possible.<\/p> If I'm going to add a feature flag, I'll usually know that in advance and avoid rushing to add one later if a more urgent task comes in.<\/p> By keeping each commit in a working and deployable state, a change can be feature flagged and deployed but not activated until the feature flag is enabled.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Starting to sprinkle JavaScript with Simulus",
+ "date": "1711584000",
+ "permalink": "/daily/2024/03/28/starting-to-sprinkle-javascript-with-simulus",
+ "text": " I've been watching the new \"Cosmic Coding with Symfony 7\" series on SymfonyCasts, and today's video was about Stimulus - a \"modest JavaScript framework for the HTML you already have\".<\/p> As I'm comfortable with other frameworks, such as Vue.js and Alpine.js, I've usually skipped videos about Stimulus, but today, it caught my eye.<\/p> I was intrigued by it and experimented with it by refactoring a component from a project I am working on.<\/p> I like the organisation Stimulus provides by using JavaScript classes and controllers while keeping things simple in the HTML code.<\/p> I initially did this in Fractal but then created an example project<\/a> using Stimulus with esbuild. It's now on my GitHub profile and includes other tools such as Nix, just, and Tmuxinator.<\/p> After this short evaluation, I like Stimulus and will use it on other components in this project, maybe using third-party Stimulus controllers - either directly or for inspiration.<\/p> Hat tip to Ryan Weaver and SymfonyCasts for showing Stimulus and Nick Janetakis for showing me esbuild.<\/p>",
+ "cta": "",
+ "tags": ["software-development","symfony","stimulus"]
+ }, {
+ "title": "Hotfixing without branches",
+ "date": "1711497600",
+ "permalink": "/daily/2024/03/27/hotfixing-without-branches",
+ "text": " Last month, I wrote an email explaining why I don't create branches<\/a>.<\/p> After sending that email, I received this question from a reader (shared with permission):<\/p> I'm trying to work at one feature at a time, so I usually don't need feature branches.<\/p> There's one thing that's difficult for me, maybe you could tell us something about this in one of your upcoming posts:<\/p> How do you deal with hot fixes?<\/p> I know, there's keeping features small, commit often and early. But sometimes this isn't possible, and a feature will take e.g. 3 days of work. Now, if there's an urgent bug in production that should be fixed asap, what are you doing?<\/p> Git stash probably won't help here, as there might be commits already that would deliver an incomplete feature.<\/p><\/blockquote> Whilst I don't create branches, I do use Git worktrees<\/a>, which allows me to have multiple versions of the code checked out at the same time - similar to having multiple clones of a repository.<\/p> Having multiple worktrees, you don't need to stash the code for your incomplete feature or worry about commits you haven't pushed yet.<\/p> You can create a new worktree, fix the urgent bug and switch back to your You'll still need to update the original worktree with your new changes which may result in conflicts - the same as merging or rebasing onto a branch.<\/p> My preferred approach is to use feature flags<\/a>, a.k.a. feature toggles.<\/p> My wrapping the incomplete feature in a feature flag, it can be deployed but won't be active until the flag is enabled - similar to writing a new Drupal module but not enabling it.<\/p> This is a technique I use often as it works well with trunk-based development and continuous integration and delivery.<\/p> When the incomplete feature is feature-flagged, you can fix the bug and deploy the hotfix without stashing or rebasing any changes, and you can leave the flag disabled.When the feature is ready, enable the feature flag to activate it and, if you need to turn it off again (maybe that's causing the next bug), you can easily disable it without needing to revert and deploy the code again.<\/p> You can just turn the feature flag back off.<\/p> I hope that helps!<\/p>",
+ "cta": "subscription",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Let someone else do the work",
+ "date": "1711411200",
+ "permalink": "/daily/2024/03/26/let-someone-else-do-the-work",
+ "text": " Yesterday, I was investigating a CSS issue.<\/p> It's a known issue due to some legacy code in the website's CSS that I'd fixed before but now needed to extend.<\/p> Instead of blindly following the same path and extending my previous fix, I decided to rethink the problem and my approach.<\/p> I re-read my documentation and re-reviewed potential solutions I'd evaluated previously.<\/p> I decided to take a different approach to solving the problem and found an open-source plugin that someone else had written that gave me the functionality I needed instead of writing it from scratch.<\/p> I read the code and did a short spike to see if it worked with the existing configuration.<\/p> After some experimentation, I got it to work and added it to the project.<\/p> The plugin I used was only 34 lines of code, but these are lines I didn't need to write or will need to maintain.<\/p> If it works, why would I write it myself?<\/p> I'd rather use what someone else has written and contribute if I find a bug or need a new feature.<\/p> That's the benefit of open-source software, and why I use Drupal, PHP, Sculpin, Tailwind CSS, Nix, and others.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php","open-source"]
+ }, {
+ "title": "Newport City Council running LocalGov Drupal",
+ "date": "1711324800",
+ "permalink": "/daily/2024/03/25/newport-city-council-running-localgov-drupal",
+ "text": " Last week, it was announced that Newport City Council's (my local council) website is now running on LocalGov Drupal - a distribution for council websites.<\/p> This increases the total to 44 councils in the UK and Ireland.<\/p> Newport is the first Welsh council to use it and the first LocalGov website to be bilingual, serving content in both English and Welsh.<\/p> It's great to see Drupal adoption continue to grow in Wales and within the Welsh public sector, and LocalGov being adopted by more councils across the UK and Ireland.<\/p> I think LocalGov Drupal is a great project and one I've contributed to previously<\/a>, and I plan to do more in the future.<\/p>",
+ "cta": "subscription",
+ "tags": ["software-development","drupal","php","localgov"]
+ }, {
+ "title": "Why I don't use a GUI for Git",
+ "date": "1711238400",
+ "permalink": "/daily/2024/03/24/why-i-dont-use-a-gui-for-git",
+ "text": " I've been a Git user since my first full-time Developer position in 2010.<\/p> I've used other version control systems, too, early in my career, but I settled on Git and haven't looked back.<\/p> I've used GUI tools for Git, such as Sourcetree and GitHub Desktop, but I prefer to use Git on the command line instead of a GUI or TUI.<\/p> As a Developer who uses a command-line-focused workflow and works mainly in a terminal, there is less context switching, but I want to focus on learning the tool itself rather than a wrapper around it.<\/p> Some GUIs add their own terminology or functionality, making it difficult for people to debug something on the command line if they experience an issue.<\/p> It's easier to solve problems if you understand the tool itself.<\/p> What if I had a favourite Git GUI that became no longer supported or maintained?<\/p> Would any time spent learning that GUI have been wasted?<\/p> This was also a reason why I switched to using Docker and Docker Compose instead of pre-built wrappers.<\/p> I want to better understand and be efficient with the underlying tool, not only someone else's implementation of it.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Write programs that do one thing and do it well",
+ "date": "1711152000",
+ "permalink": "/daily/2024/03/23/write-programs-that-do-one-thing-and-do-it-well",
+ "text": " Over the last few days, I've written about watchers and running commands such as automated tests when files are changed.<\/p> Some tools have this built in, whilst others don't.<\/p> I've used different tools to do this and recently switched to The previous one wasn't showing me the output from running Drupal automated tests, which I also like that it follows the UNIX philosophy of doing one thing well and working well with other programs.<\/p> For example, to run my automated tests when I change a file, I need to run To get the list of files, I use the I also like to do this with my application code. I like to write small modules and libraries with clear boundaries and responsibilities, do their tasks well, and work well with other parts of the application.<\/p>",
+ "cta": "",
+ "tags": ["software-development","unix","linux"]
+ }, {
+ "title": "Watching all the things",
+ "date": "1711065600",
+ "permalink": "/daily/2024/03/22/watching-all-the-things",
+ "text": " In yesterday's email<\/a>, I mentioned that I use watch commands such as For example, running This works well for tests, but for other checks, such as static analysis with PHPStan or coding standards with PHPCS, I have integrations in Neovim, and I get real-time feedback as I code.<\/p> If a line fails static analysis or coding standards, a diagnostic message is shown so I can fix it immediately, and I don't need to use a watcher or wait for my CI pipeline to fail.<\/p>",
+ "cta": "",
+ "tags": ["software-development","php"]
+ }, {
+ "title": "Git hooks - yay or nay?",
+ "date": "1710979200",
+ "permalink": "/daily/2024/03/21/git-hooks---yay-or-nay",
+ "text": " Many people are very for or against Git hooks - scripts that run automatically on events such as pre-commit and pre-push.<\/p> Commonly, they are used for running tasks such as altering a commit message or running before committing automated tests and static analysis before pushing a commit.<\/p> I'm on the fence.<\/p> I've used them and added support for them to Build Configs, but I don't feel strongly about them.<\/p> They are awkward to set up (you need to edit the configuration for them to work) and can be easily disabled or bypassed.<\/p> Some people think it's the Developer's responsibility to run the tasks before pushing changes or that they'll be run in a CI pipeline, so why would they need to be run locally?<\/p> As I write many small commits and push changes regularly, I can find hooks irritating and prefer to use watchers instead with tools like There are also tools like Captain Hook that are built to manage Git hooks. Maybe, I should investigate it more.<\/p> What do you think? Are you yay or nay for Git hooks?<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "What is legacy code?",
+ "date": "1710892800",
+ "permalink": "/daily/2024/03/20/what-is-legacy-code",
+ "text": " How do you define legacy code?<\/p> Is it code written by previous Developers who worked on the codebase?<\/p> Is it code you wrote last week or last month?<\/p> Is it code for features everyone no longer uses?<\/p> Is it the \"old\" part of the application that no one wants to work on?<\/p> It is any code that's not nice to work on or difficult to change?<\/p> Is it code written with different conventions to your current ones or in a different style?<\/p> Is it any code that doesn't have automated tests or wasn't written with test-driven development?<\/p> Is it code built with outdated tooling or frameworks (like CSS libraries) that were popular then but have since been replaced by something newer?<\/p> These are just some of the potential definitions I can think of.<\/p> The term \"legacy code\" and others, such as \"technical debt\", often mean different things.<\/p> What's your definition? Reply and let me know.<\/p>",
+ "cta": "",
+ "tags": ["software-development","clean-code"]
+ }, {
+ "title": "Drupal Commerce: not just for selling t-shirts and hats",
+ "date": "1710806400",
+ "permalink": "/daily/2024/03/19/drupal-commerce-not-just-for-selling-t-shirts-and-hats",
+ "text": " I recently had Ryan Szrama as a guest on the Beyond Blocks podcast<\/a>.<\/p> Ryan is the CEO of Centarro - the company behind Drupal Commerce, the eCommerce platform built on the Drupal CMS.<\/p> I've used Drupal Commerce for a number of projects since it was released in 2011, as well as Ubercart before that.<\/p> One of the major things I like about it is its flexibility.<\/p> The Commerce Kickstart distribution is a great way to create a demo Drupal Commerce project that shows a typical eCommerce store selling everything from books to hats, furniture and inflatable flamingos.<\/p> I've used Drupal Commerce for these typical scenarios but also for some non-typical ones.<\/p> I created a multi-site Drupal Commerce store for a gadget insurance company, dealing with many products and product variations. I built a custom Vue.js form that created an order with the required items before passing customers to a Drupal Commerce checkout flow.<\/p> I created a yearly photography competition website that photographers can enter by purchasing a product and uploading their photographs to the order. I built custom judging functionality, which allows jurors to score each entry and the site owner to see the totals and which submission won the competition.<\/p> I created an events management and booking website where each event was a product with different variations based on the different prices - early bird, regular and last minute. Each event had a maximum number of places and, potentially, a waitlist.<\/p> This website also included a loyalty scheme for event organisers and attendees, who received coupons after organising or attending a certain number of events.<\/p> Drupal Commerce can do a lot and isn't just selling t-shirts, hats, books or furniture.<\/p>",
+ "cta": "subscription",
+ "tags": ["software-development","drupal","drupal-commerce","php"]
+ }, {
+ "title": "Automated Drupal 11 compatibility fixes",
+ "date": "1710720000",
+ "permalink": "/daily/2024/03/18/automated-drupal-11-compatibility-fixes",
+ "text": " Yesterday, I received the first \"Automated Drupal 11 compatibility fixes\" email from the Rector-powered Project Update Bot.<\/p> It was for the Feature Toggle Twig module<\/a> that adds a For example:<\/p> The only change needed to make the module Drupal 11 compatible was updating the That's a great thing about modern Drupal compared to legacy versions - no major changes or rewrites to support a new major version!<\/p> I thought this was a great initiative in previous versions and I'm glad to see it again for Drupal 11, and it's great that it's being done with time before the Drupal 11 release as it gives maintainers the time to update their projects so as many modules as possible will be Drupal 11-compatible when it's released.<\/p> I look forward to getting more of these emails for my other contributed projects on Drupal.org<\/a>.<\/p>",
+ "cta": "d7eol",
+ "tags": ["software-development","drupal","php"]
+ }, {
+ "title": "Patches vs Merge Requests",
+ "date": "1710633600",
+ "permalink": "/daily/2024/03/17/patches-vs-merge-requests",
+ "text": " I'm aware of the ongoing changes to the issue queues on Drupal.org, I haven't contributed or committed as much recently, so I haven't had to use the new approaches, such as issue forks and GitLab merge requests.<\/p> Yesterday, though, I was able to do that whilst submitting the tests I wrote to the Content Access by Path module<\/a>.<\/p> I've made many contributions to projects on Drupal.org, including Drupal core and Drupal.org itself, so I'm very familiar with how the issue queues and the patch workflow work.<\/p> When I first started contributing, we were using CVS, and before the \"Great Git Migration\".<\/p> I'm also familiar with the pull or merge request approach from working on open-source and team projects on GitHub, GitLab and Bitbucket.<\/p> I can see how a merge request-based workflow on Drupal.org will lower the barriers for new contributors, which seemed to be the case at DrupalCon Lille.<\/p> I look forward to adapting and using it more now that the patch workflow is deprecated and will soon no longer work as everything will switch to merge requests and leverage more of GitLab's features.<\/p> Another step forward for Drupal.org and the Drupal project.<\/p>",
+ "cta": "d7eol",
+ "tags": ["software-development","drupal","php","open-source"]
+ }, {
+ "title": "Adding tests to the Content Access by Path module",
+ "date": "1710547200",
+ "permalink": "/daily/2024/03/16/adding-tests-to-the-content-access-by-path-module",
+ "text": " Last month, I released the Beyond Blocks podcast episode<\/a> where I spoke with Mark Conroy.<\/p> We spoke about a number of things, including the 'Content Access by Path' Drupal module he wrote, and I promised I'd write some automated tests for it as there weren't any at the time.<\/p> Yesterday on my Friday live stream<\/a>, I installed the module and learnt how it works whilst writing some automated tests.<\/p> As is common, the first test took a little while to do as I got the setup steps working and learned how the module worked. Once that was passing, adding the others was fairly straight forward.<\/p>git add -p<\/code> when typing commands, in scripts, I prefer to use the equivalent longer parameters, where possible.<\/p>
sort \"${result_file}\" --reverse --numeric-sort --output \"${result_file}\"<\/code><\/pre>
sort \"${result_file}\" -rn -o \"${result_file}\"<\/code><\/pre>
git revert<\/code> can do it for you.<\/p>
git revert<\/code> a try.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "Which commit has the largest message?",
+ "date": "1716249600",
+ "permalink": "/daily/2024/05/21/which-commit-has-the-largest-message",
+ "text": "
git shortlog --summary --all<\/code> (which shows all authors to a codebase and their number of commits), but couldn't find one, so I wrote a script to do it<\/a>.<\/p>
find<\/code>,
cut<\/code>,
sort<\/code> and
wc<\/code> in combination with Git.<\/p>
git log<\/code> in my website repository, I see commits like this:<\/p>
commit 0c91825c16217d0fe7eff4ea100a67550051c4a9Author: Oliver Davies <oliver@oliverdavies.dev>Date: Sat May 11 15:32:07 2024 +0200 Create a cached talk counter Create a cached version of the talk counter service that returns a cached result of the talk count for that day. This uses the Decorator design pattern to decorate the existing `TalkCounter` service and works as they both implement the same `TalkCounterInterface`.<\/code><\/pre>
0c91825c16217d0fe7eff4ea100a67550051c4a9<\/code>.<\/p>
git commit -m<\/code> to create commits using an inline message.<\/p>
-m<\/code> option to specify an inline commit message encourages writing short one-line commit messages instead of using the subject and body effectively to document more about the change, such as why it was needed, any other approaches taken and the consequences of the change within the commit.<\/p>
-m<\/code> or not, capture as much information in the message as you can, as future Developers on the project (including you) will thank you for it.<\/p>",
+ "cta": "",
+ "tags": ["software-development","git"]
+ }, {
+ "title": "The first test is the hardest",
+ "date": "1715817600",
+ "permalink": "/daily/2024/05/16/the-first-test-is-the-hardest",
+ "text": "
Here's the thing<\/h2>
.config\/git\/.config<\/code> file:<\/p>
[merge] ff = \"only\"[pull] ff = \"only\" rebase = true<\/code><\/pre>
git pull<\/code> to always include
--rebase<\/code> by default and to only allow fast-forward merges and pulls.<\/p>
git merge --ff<\/code> to allow a merge commit temporarily, but this is the exception instead of the default.<\/p>
man git-merge<\/code>.<\/p><\/blockquote>
Squash and merge<\/code> or
Create a merge commit<\/code> and will use rebase options, although I've seen where different commit IDs have been generated when merged in the UI, which is why I prefer to do merges locally.<\/p>
self::assertTrue(FALSE);<\/code><\/pre>
assert()<\/code> function that can be used anywhere.<\/p>
$node = Node::load(1);assert($node instanceof NodeInterface);assert($node->bundle() === 'page');<\/code><\/pre>
$node<\/code> is a node with the correct bundle type and I can continue.<\/p>
$node<\/code> is not the correct type or returns an unexpected bundle, the assertion will fail and an Exception will be thrown.<\/p>
assert()<\/code> before, give it a try.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php","automated-testing"]
+ }, {
+ "title": "Drupal 7.100.2",
+ "date": "1715040000",
+ "permalink": "/daily/2024/05/07/drupal-7-100-2",
+ "text": "
git add -i<\/code> to interactively add, though I usually go straight to
git add -p<\/code> to use
patch<\/code>.<\/p>
diff --git a\/source\/_daily_emails\/2024-05-06.md b\/source\/_daily_emails\/2024-05-06.mdindex 42fe48f..ef36a2b 100644--- a\/source\/_daily_emails\/2024-05-06.md+++ b\/source\/_daily_emails\/2024-05-06.md@@ -4,10 +4,12 @@ date: 2024-05-06 permalink: daily\/2024\/05\/06\/interactive-staging tags: - software-development- # - drupal- # - php- # - podcast+ - git cta: ~ snippet: | TODO ---++A major addition to my Git workflow has been the ability to interactively add hunks of code to be committed.++There's `git add -i` to interactively add, though I usually go straight to `git add -p` to use `patch`.+There's `git add -i` to interactively add, though I usually go straight to `git add -p` to use `patch`.(1\/1) Stage this hunk [y,n,q,a,d,s,e,?]?<\/code><\/pre>
$strict<\/code> parameter with
in_array<\/code>,
array_search<\/code>,
array_keys<\/code> and
base64_decode<\/code>.<\/li>
function add(int $a, int $b): void{ var_dump($a + $b);}add(1, '1');<\/code><\/pre>
declare(strict_types=1);<\/code><\/pre>
public function getPosts() { ... }<\/code><\/pre>
public function getPosts(boolean $onlyPublished) { ... }<\/code><\/pre>
getPosts()<\/code> could be named
getAllPosts()<\/code> and there could be a separate
getPublishedPosts()<\/code> method for only getting published posts:<\/p>
public function getAllPosts() { ... }public function getPublishedPosts() { ... }<\/code><\/pre>
true<\/code> or
false<\/code>s wherever the method is used.<\/p>",
+ "cta": "",
+ "tags": ["software-development","drupal","php"]
+ }, {
+ "title": "Re-evaluating old tools",
+ "date": "1714608000",
+ "permalink": "/daily/2024/05/02/re-evaluating-old-tools",
+ "text": "
var_dump()<\/code>,
dump()<\/code> and
dd()<\/code> that are used to debug code and print output to the screen.<\/p>
dpm()<\/code> and
kint()<\/code>, too.<\/p>
Enter Xdebug<\/h2>
git cherry-pick<\/code> isn't a command I use often, and definitely not in this scenario.<\/p>
develop<\/code> or
main<\/code> and get the latest changes.<\/p>
drupal-check<\/code> tool, which is built on PHPStan, and the Upgrade Status module.<\/p>
else<\/code> keyword and other good practices that I like to try and implement when possible.--<\/p>",
+ "cta": "",
+ "tags": ["software-development","clean-code"]
+ }, {
+ "title": "Drupal is a content management framework",
+ "date": "1712361600",
+ "permalink": "/daily/2024/04/06/drupal-is-a-content-management-framework",
+ "text": "
Option 1: Worktrees<\/h2>
main<\/code> worktree when you're finished.<\/p>
Option 2: Feature Flags<\/h2>
Here's the Thing<\/h2>
entr<\/code>.<\/p>
entr<\/code> does.<\/p>
find web\/modules\/custom | entr .\/run test<\/code>.<\/p>
entr<\/code> isn't concerned with how to find the list of files to watch - only what to do with them.<\/p>
find<\/code> command and provide the files to
entr<\/code>.<\/p>
nodemon<\/code>,
watchexec<\/code> and
entr<\/code> whilst developing to run commands automatically when I change code.<\/p>
find web\/modules\/custom | entr .\/run test<\/code> will re-run my test suite when any custom module changes.<\/p>
watchexec<\/code> and
entr<\/code>.<\/p>
Here's the thing<\/h2>
featureIsEnabled()<\/code> function to Twig to check if a feature toggle is enabled.<\/p>
{% if featureIsEnabled('foo') %} {# ... #}{% endif %}<\/code><\/pre>
What Changes Were Needed?<\/h2>
core_version_requirement<\/code> to
^10 || ^11<\/code> - allowing the module to support Drupal 10 and 11 at the same time as it uses no deprecated code.<\/p>
Here's the Thing<\/h2>