The Drop Times: A Reddit Discussion on Changes In and Beyond Drupal
kevinquillen.com: Update on List field data integrity issues in Drupal 10.2
Chapter Three: 15 Tips for Writing Better Web Copy
DrupalEasy: Using GitHub Copilot in Visual Studio Code to create a PhpUnit test
Like many folks, I've been fascinated by the incredible evolution of AI tools in 2023. At the same time, I've been working to figure out exactly where AI fits into my various roles as a Drupal developer, trainer, and business owner.
In this blog post, I detail a recent exploration I made into using AI (GitHub Copilot, to be precise) to generate a PhpUnit test in a Drupal contrib module that I maintain.
tl;dr I was impressed.
Prerequisites
For this exercise, I used Visual Studio Code with the GitHub Copilot extension installed. I am using the Copilot Business plan ($19/month, but there is a free trial available).
The task
The Markdown Easy contrib module includes a very simple service class with no dependencies that implements a single method. Normally, Drupal service classes would require a kernel test (due to dependencies,) but in this case a simple unit test will do the job.
While using Drush's generate command has generally been my preferred method for scaffolding a test class, I found that using Copilot provides a much more advanced starting point. But, like anything else generated via AI, knowledge of the right way to perform a task is not optional. Code generated by AI might be correct, but blind confidence in what the AI provides will surely get you into trouble sooner, rather than later.
The getFilterWeights() method that we tested is a relatively simple method that returns an array of filter weights for three filters related to the configuration of the Markdown Easy text filter. The method takes a single parameter: an array of filters assigned to a text format. This method ensures that the configured order of filters related to Markdown Easy provides a secure experience.
Therefore, it makes sense that the unit test should pass in several sets of filters to the getFilterWeights() method and ensure that the returned array is correct - a fairly simple test that utilizes a data provider. To be honest, if I wasn't experimenting with using Copilot to generate tests, I probably wouldn't have this test, as it is almost trivial.
Regardless, adding test coverage to custom or contrib modules is a fantastic way of building modules where sustainability is a top priority. This is one of the reasons why writing PhpUnit tests is a prominent aspect of the Professional Module Development course that I teach.
Using the Copilot contextual menu - attempt 1
In this example, I placed my cursor at the end of the method name and then navigated the contextual menu to Copilot | Generate tests
By default, this will attempt to create the unit test class in markdown_easy/src/MarkdownUtilityTests.php, which is not the best-practice for location for tests in Drupal modules. Luckily, we can modify the location via the Create as option.
This results in a Save as dialog box in which we can specify the proper location for the test:
The test file is created in the proper place, but (at least in my test) it oddly didn't include an opening <?php tag, nor a namespace. Luckily, Copilot didn't make me work too hard on the namespace, as it correctly suggested the proper namespace via its autocomplete feature:
Once I manually added these (as well as declare(strict_types = 1);), the resulting test was:
<?php
declare(strict_types = 1);
namespace Drupal\Tests\markdown_easy\Unit;
use Drupal\markdown_easy\MarkdownUtility;
use Drupal\Tests\UnitTestCase;
/**
* Tests the MarkdownUtility class.
*
* @group markdown_easy
*/
class MarkdownUtilityTest extends UnitTestCase {
/**
* Tests the getFilterWeights method.
*/
public function testGetFilterWeights() {
$markdownUtility = new MarkdownUtility();
// Test case 1: Empty filters array.
$filters = [];
$expectedWeights = [];
$this->assertEquals($expectedWeights, $markdownUtility->getFilterWeights($filters));
// Test case 2: Non-empty filters array.
$filters = [
'filter1' => 10,
'filter2' => 5,
'filter3' => 15,
];
$expectedWeights = [
'filter1' => 10,
'filter2' => 5,
'filter3' => 15,
];
$this->assertEquals($expectedWeights, $markdownUtility->getFilterWeights($filters));
// Test case 3: Filters array with duplicate keys.
$filters = [
'filter1' => 10,
'filter2' => 5,
'filter1' => 15,
];
$expectedWeights = [
'filter1' => 15,
'filter2' => 5,
];
$this->assertEquals($expectedWeights, $markdownUtility->getFilterWeights($filters));
}
}
As-is, this test does not pass, as Copilot missed the fact that the method will only return the weights of specific filters, so using filter1, filter2, etc.. doesn't help much...
Using the Copilot contextual menu - attempt 2
In this attempt, I highlighted the entire getFilterWeights() method and then selected Generate tests from the contextual menu. The result (without any changes on my part) was much better:
<?php
namespace Drupal\Tests\markdown_easy\Unit;
use Drupal\Tests\UnitTestCase;
use Drupal\markdown_easy\MarkdownUtility;
/**
* Unit Test coverage for MarkdownUtility.
*
* @coversDefaultClass \Drupal\markdown_easy\MarkdownUtility
*
* @group markdown_easy
*/
class MarkdownUtilityTest extends UnitTestCase {
/**
* Tests the getFilterWeights method.
*
* @covers ::getFilterWeights
*/
public function testGetFilterWeights(): void {
$markdownUtility = new MarkdownUtility();
// Create a sample array of filters.
$filters = [
'filter_html' => [
'status' => true,
'weight' => 10,
],
'filter_autop' => [
'status' => true,
'weight' => 20,
],
'markdown_easy' => [
'status' => true,
'weight' => 30,
],
];
// Call the getFilterWeights method.
$result = $markdownUtility->getFilterWeights($filters);
// Assert that the weights are set correctly.
$this->assertEquals(10, $result['filter_html']);
$this->assertEquals(20, $result['filter_autop']);
$this->assertEquals(30, $result['markdown_easy']);
}
}
Not only did Copilot get the opening PHP tag and the namespace correct, but the quality of the test was much better, and passed without any changes. However; this test was generated with only a single set of test data, so I decided to try again…
Using the contextual menu - attempt 3
In this attempt, I again highlighted the entire getFilterWeights() method and then proceeded to select Generate tests again from the contextual menu. The result was much better (but again oddly missing the opening PHP tag):
namespace Drupal\Tests\markdown_easy\Unit;
use Drupal\Tests\UnitTestCase;
use Drupal\markdown_easy\MarkdownUtility;
/**
* Tests the MarkdownUtility class.
*
* @group markdown_easy
*/
class MarkdownUtilityTest extends UnitTestCase {
/**
* Tests the getFilterWeights method.
*/
public function testGetFilterWeights() {
$markdownUtility = new MarkdownUtility();
// Test case 1: All filters are disabled.
$filters = [
'filter_html' => ['status' => FALSE],
'filter_autop' => ['status' => FALSE],
'markdown_easy' => ['status' => FALSE],
];
$expectedWeights = [
'filter_html' => NULL,
'filter_autop' => NULL,
'markdown_easy' => NULL,
];
$this->assertEquals($expectedWeights, $markdownUtility->getFilterWeights($filters));
// Test case 2: Only filter_html is enabled.
$filters = [
'filter_html' => ['status' => TRUE, 'weight' => 10],
'filter_autop' => ['status' => FALSE],
'markdown_easy' => ['status' => FALSE],
];
$expectedWeights = [
'filter_html' => 10,
'filter_autop' => NULL,
'markdown_easy' => NULL,
];
$this->assertEquals($expectedWeights, $markdownUtility->getFilterWeights($filters));
// Test case 3: All filters are enabled with different weights.
$filters = [
'filter_html' => ['status' => TRUE, 'weight' => 10],
'filter_autop' => ['status' => TRUE, 'weight' => 20],
'markdown_easy' => ['status' => TRUE, 'weight' => 30],
];
$expectedWeights = [
'filter_html' => 10,
'filter_autop' => 20,
'markdown_easy' => 30,
];
$this->assertEquals($expectedWeights, $markdownUtility->getFilterWeights($filters));
}
}
In this attempt, the test was created with three sets of data, and passed without any changes (other than adding the opening PHP tag). But, can we use Copilot to make it better? Ideally, the three sets of input data should be provided as part of a PhpUnit data provider method.
Refactoring the test method to use a PhpUnit data provider
To achieve this, I placed my cursor inside the test method, and selected Copilot | Start inline chat, and entered the prompt: "Can we refactor this method to use a data provider?" The result displayed the proposed result in a Visual Studio Code diff (similar to what is used when resolving Git code conflicts,) with the option for me to either Accept, Discard, or Regenerate (represented by a circle-arrow icon) the proposed solution. I accepted, and the resulting code took a little bit of massaging to make correct. While Copilot did properly create the data provider method and updated the test method, it left a bunch of the original test method code at the bottom of the class, outside of any method. Once I (easily) removed this artifact, the test passed as expected.
The resulting code was:
<?php
declare(strict_types = 1);
namespace Drupal\Tests\markdown_easy\Unit;
use Drupal\markdown_easy\MarkdownUtility;
use Drupal\Tests\UnitTestCase;
/**
* Tests the MarkdownUtility class.
*
* @group markdown_easy
*/
class MarkdownUtilityTest extends UnitTestCase {
/**
* Data provider for testing the getFilterWeights method.
*
* @return array
* An array of test cases.
*/
public function filterWeightsDataProvider(): array {
return [
[
[
'filter_html' => ['status' => FALSE],
'filter_autop' => ['status' => FALSE],
'markdown_easy' => ['status' => FALSE],
],
[
'filter_html' => NULL,
'filter_autop' => NULL,
'markdown_easy' => NULL,
],
],
[
[
'filter_html' => ['status' => TRUE, 'weight' => 10],
'filter_autop' => ['status' => FALSE],
'markdown_easy' => ['status' => FALSE],
],
[
'filter_html' => 10,
'filter_autop' => NULL,
'markdown_easy' => NULL,
],
],
[
[
'filter_html' => ['status' => TRUE, 'weight' => 10],
'filter_autop' => ['status' => TRUE, 'weight' => 20],
'markdown_easy' => ['status' => TRUE, 'weight' => 30],
],
[
'filter_html' => 10,
'filter_autop' => 20,
'markdown_easy' => 30,
],
],
];
}
/**
* Tests the getFilterWeights method.
*
* @dataProvider filterWeightsDataProvider
*/
public function testGetFilterWeights(array $filters, array $expectedWeights) {
$markdownUtility = new MarkdownUtility();
$this->assertEquals($expectedWeights, $markdownUtility->getFilterWeights($filters));
}
}
So, that's not too bad for a few minutes of work! But, as a stickler for clean code, there was still some work ahead for me to get an acceptable PhpStan report.
Code quality changes
Overall, the quality of the code that was provided by Copilot was really good. But, this comes with the caveat that I utilize the PHP Sniffer & Beautifier Visual Studio Code extension (configured to use Drupal coding standards,) so it could be that code generated by Copilot is automatically formatted as it is generated (I really have no idea.). The bottom line is that I had zero phpcs issues in the code generated by Copilot.
For PhpStan, I normally try to achieve level 6 compliance - this can be especially tricky when it comes to "no value type specified in iterable type" issues. Without getting into the details of solving issues of this type, I decided to let Copilot have a go at updating the docblock for the filterWeightsDataProvider() method - and much to my surprise, it was able to provide a reasonable fix:
The process to update the docblock for the testGetFilterWeights() method wasn't as simple, as it was missing the parameter information, so I added that manually and then used Copilot in a similar manner to solve the PhpStan issue.
There was an additional, minor, PhpStan issue that I solved manually as well. With that, I had achieved zero PhpStan level 6 issues, a clean phpcs report, and a passing test! 🎉
This new test has been committed to the module.
Lesson learned
- Context matters (a lot) when generating tests (any code, really) using Copilot. In my (limited) experience, the files open in Visual Studio Code, where the cursor is, and what is highlighted makes a significant difference in the results provided.
Do not be afraid to use the Regenerate button (the circle-arrow icon) when generating any code, including tests. I have found that if I don't like the initial result, regenerating often results in a much better option the second or third time around.
- The Start Inline Chat option in the contextual menu is rapidly becoming my new best coding friend. Do not be afraid to experiment with it and use it more than you think you should. I find it very useful for making code more concise, suggesting documentation descriptions, and giving me a headstart in scaffolding entire methods.
- This should go without saying, but don't trust anything that is generated by Copilot. This is a tool that you should look at to save time, but not solve problems for you.
Header image generated using ChatGPT4.
Thanks to Andy Giles and Cindy Garcia for reviewing this blog post. Cindy is a graduate of both Drupal Career Online and Professional Module Development. Andy is the owner of Blue Oak Interactive.
Palantir: Planning Your Drupal 7 Migration: Organizational Groundwork
How to ensure a smooth Drupal 7 migration with content, design, and technology audits
In this article, we focus on tackling the organizational challenges that come with the technical realities of the migration away from Drupal 7 to newer, up-to-date versions of the content management system. We outline a strategic blueprint for a successful Drupal 7 migration, centered around three critical audits:
- Content audit: Evaluating which content should be carried over to the new platform.
- Design audit: Seizing opportunities to enhance the site’s design during the rebuild.
- Technical audit: Refining the site architecture and meticulously planning the technical aspects of the migration.
This is the perfect catalyst and opportunity for your organization to assess and not just transition, but to reconstruct your digital presence to meet your future goals and challenges.
As the clock ticks towards January 2025, the End of Life (EOL) for Drupal 7 looms on the horizon. Moving away from Drupal 7 marks a critical juncture. Since Drupal 8 and above are significantly different in architecture from Drupal 7, the move requires a comprehensive migration. The good news is that the upgrade paths from Drupal 8 and above are smoother, so the step up from Drupal 7 represents a unique, one-time effort, which will pay itself off in longer site life cycles and higher ROI on that investment.
The core principle guiding this migration strategy is the alignment of technical implementation and design with the initial content audit. This approach ensures that the rebuild is driven by content needs, rather than forcing content to conform to a predetermined site structure.
Some key organizational challenges issues when navigating this technical shift away from Drupal 7 revolve around governance, starting by understanding the existing content on your website, assigning responsibility for it, and ensuring its relevance and quality throughout the migration process.
This technical inflection point can and should also spark a broader debate about the extent of redesign and transformation needed during the migration. IT and marketing teams should be discussing, in a nutshell, “What do we have? What can be better? What do we need to make that happen?”
Palantir.net is a Drupal Association–Certified Migration Partner. We have the technical expertise, experience, and strategic insight required to help steer you through this vital transition. Whether through our Continuous Delivery Portfolio (CDP) or web modernization services, Palantir is ready to assist you in navigating the complexities of the D7 EOL upgrade.
Content audit: Decluttering the house
At the heart of any successful migration lies a well-executed content audit, a responsibility primarily shouldered by the Marketing team. This vital process streamlines the migration by identifying what content truly needs to be transferred to the new platform and what can be retired.
The essence of a content audit
The key questions to address during the audit are: What content do we need? What can we do without? These decisions should be data-driven, relying on analytics to assess the relevance and usage of the content. Key metrics include page views, content validity, and user engagement.
If you don’t like having a cluttered house, you don’t just decide to build a slightly bigger house, then move all your clutter into it. It would be a better idea to take a look at what’s actually cluttering your house first, then decide what you need to build. In the same way, letting content drive your technical decisions is the better approach.
The complexity of a given migration is often more dependent on the variety of different content types than the volume of content. Once a system for migrating a specific type of content, like blog posts, is developed, it can run autonomously while the technical team focuses on other content types. For this reason, a site with numerous content types requires a more intricate migration plan compared to one with fewer types. A content audit can help reduce the number of content types and with it the resulting effort needed.
Tips for conducting a successful content audit
The following considerations can help make your content audit a smooth and effective process:
- Develop a comprehensive content inventory: Start by cataloging every piece of content on your website. This step is crucial as it allows you to see the full scope of your existing content and understand what requires improvement, discarding, or migration. Document key details of each content piece, such as URLs, titles, purpose, format, number of internal links, and other relevant information.
- Make data-driven decisions: Use tools like Google Analytics and Google Search Console to review content performance, examining metrics like traffic, engagement, bounce rates, time on page, and conversion rates. This quantitative analysis helps inform your content strategy and guides decisions on what content needs updating or removal.
- Complement data with qualitative decisions: Compare your content against benchmarks that align with your goals and audience needs. Assess the content for user experience, compliance with your style guide, and potential improvements. Decide on actions for each content piece, such as keeping, updating, consolidating, or removing, based on their relevance, quality, and performance.
- Involve a content strategist: An expert content strategist can help with all the above tasks and aid you in preparing a migration framework. They will help align your content with your marketing and branding goals, as well as UX design and information architecture. If you don’t have an internal content strategist, Palantir can provide one if we help you with your migration.
Content as opportunity for a redesign
Conducting a content audit does more than just streamline the migration process. It can also unveil opportunities for a redesign of your site’s information and content architecture, aligned with a new content strategy. This process is not just about elimination, but also about discovery — uncovering what content is most valued by users. Not only are you finding out what you don’t need, but you’re hopefully finding out what's really important as well.
Given that moving from Drupal 7 to Drupal 10 essentially entails a complete site rebuild, there lies a golden opportunity to design the site around the content. This approach ensures that the site architecture complements and enhances the content, rather than forcing content to fit into a pre-existing structure.
The insights won here feed into the second crucial stage of a Drupal 7 migration: the design audit.
Design audit: An opportunity for enhancing UX
A design audit is where you and your Marketing team evaluate the current design’s effectiveness and explore the potential for a redesign. It goes hand-in-hand with a content audit.
Design audit objectives
- Evaluate current design effectiveness: Before deciding on a redesign, critically assess how well your current design serves your content and users. Does it facilitate easy navigation? Is it aesthetically pleasing and functionally efficient?
- Consider compatibility with Drupal 10: Drupal 10 brings new features and capabilities. The design audit of a Drupal 7 website usually reveals a rigid, template-based layout system, limiting content display versatility. By migrating to Drupal 10 and utilizing its advanced Layout Builder, the redesign can offer dynamic, user-friendly layouts, enhancing user engagement and providing flexibility for content strategy adaptations.
Note that, while migrating away from Drupal 7, you essentially rebuild your site. Even if you choose to retain the existing design, adapting the look and feel to the newer Drupal version will require some level of reworking as well. If your existing design seems incompatible or would require extensive modifications, it might be more efficient to opt for a new design. - Align design with content strategy: The design should complement and enhance the content, not overshadow it. A design audit should involve close coordination with content strategists to ensure that the design facilitates the content’s purpose and enhances user engagement.
- Explore modern design trends: Technology and design trends evolve rapidly. Use this migration as an opportunity to refresh your website’s look and feel to stay relevant and appealing to your audience.
- Accessibility enhancement: Focus on improving the overall user experience for everyone. This includes optimizing the site for various devices and improving accessibility, for instance, compliance with A11Y guidelines.
Palantir not only offers technical expertise in migration processes but also provides skilled designers who can seamlessly collaborate with your team. Our designers are adept at working alongside content strategists. They ensure that you end up with a cohesive system that effectively supports and enhances your content strategy, ensuring that every aspect of your site’s design is driven by and aligned with your overall content goals.
Technical audit: Engineering a future-ready framework
Next up, your internal IT team should perform a comprehensive technical audit. If necessary, this stage can overlap with the content audits. However, we recommend that your migration should be primarily driven by the insights gained from your content audit.
The ultimate goal of the technical audit is preparing for a new Drupal environment. This means understanding how the identified technical elements will function in the new system and planning for any necessary adaptations or developments.
Data architecture audit
The technical audit begins with a detailed analysis of how data is structured in the current Drupal 7 site. This involves examining the entity relationships and the architecture of data storage. Understanding how different pieces of content are interlinked and how they reference each other is essential. This step not only overlaps with the content audit but also sets the stage for a smooth technical transition to Drupal 10.
Custom functionality and integration evaluation
A critical aspect of the technical audit is assessing any custom functionalities or third-party integrations present in the current system. This includes custom plug-in modules, single sign-on mechanisms, and other unique features. Each custom element that you migrate over is something you have to maintain, potentially throughout the lifetime of the site. The decision to migrate these elements should be based on their current value and necessity. During the audit, aim to determine which functionalities are essential and how they can be adapted or redeveloped for Drupal 10.
Driving collaborative decision-making
Collaboration between the IT/technical, marketing, content strategy, and design teams is vital in deciding what to keep (and migrate) and what to discard — regarding site content, architecture, code, and functionality. The technical audit, outlining the functionalities and integrations of the current site, guides the planning and decision-making process following the insights you gain from the content and design audits.
Conclusion: Charting the course of a Drupal migration
As we’ve seen, the journey away from Drupal 7 involves three main audits:
- the content audit, which acts as a decluttering exercise;
- the design audit, seizing opportunities to enhance user experience;
- and the technical audit, engineering a future-ready framework.
The content audit is the central pillar, and content strategy should drive the technical implementation and design decisions. This approach ensures a migration process where content seamlessly integrates into an efficient, updated site structure, rather than being confined by it.
Palantir is here to help and guide you through a successful migration from Drupal 7 to the future of your online digital presence. We are a Drupal Association–certified migration partner, with years of experience with intricate processes. Our expertise in content strategy, design innovation, and technical proficiency makes us an ideal full-service partner for navigating the complexities of a D7 end-of-life upgrade.
If you’re considering this critical step in your digital strategy, we invite you to explore how Palantir’s Continuous Delivery Portfolio (CDP) and web modernization services can transform your digital presence.
Content Strategy Design Drupal Drupal CDP StrategySpecbee: An Introduction to PHP Standard Recommendation (PSR)
LN Webworks: Why Media Business Should Choose Drupal As Their First Priority CMS: 5 Big Reasons
Media has changed a lot with new technology. Organizations need to connect with their audience using modern methods. Big media companies use digital tech to reach people on different platforms and make more money.
Top media networks have many websites and social media pages to reach more people. They create websites for different groups. In the age of Web 3.0, having a strong online presence is crucial. Drupal helps with that, improving the customer experience and increasing conversion by 25% for one media client we worked with.
Why is Drupal Preferred Over Other CMS?
Media relies on content, and that content needs to bring in revenue in a scalable way. To achieve this while keeping things easy to manage, you need the right Content Management System (CMS). Considering the constant flow of new content, choosing the right CMS is crucial.