The test started with warm-ups and a specific sequence of moves. She and her classmates have been practicing this for the last couple of months. She was great.
The test ended with each student breaking a board in front of the class. At her level, she needed to kick the board to break it.
Now, some context: My child is 5. Her balance on one foot could use some work. Her kicks are on the more gentle side. This was the part of the test that she (and I!) was nervous about.
When it was her turn to break the board, she walked to the front of the class. She faces her instructor. She bowed. “Focus, sir! Confidence, sir!” she yelled. And then, she kicked. She touched the board, but it did not break.
She kicked again. And again. And again. And again.
The board did not break.
She was asked to take a break and give her foot a rest. She sat down, facing the front, which means her back was to the parent audience. I could still see she was not happy. Surprisingly, she did not cry.
At the end of the test, she got another chance to break the board. “Focus, sir! Confidence, sir!” She kicked. A small strip of the board broke off. Some of us giggle, but she is beaming. She broke the board!
This was by far the hardest board breaking she has ever experienced and many parents had ever seen. She knows she wasn’t the best, but she kept trying and trying and, in the end, she succeeded.
If a 5 year-old can maintain that kind of perseverance, so can an adult. Sometimes, things don’t work out the first time. But, if you don’t give up, eventually, you will break that board.
P.S. I am super proud of my kid!
]]>I am bringing this up because I was recently put into a situation (again) where I needed to revert a git repository back to a specific tag. This is not a task I do often, so off to the internet I went, entering the keywords “git revert by tag” into the search bar. This unearthed my previous post.1
I completely forgot about this post and, at first, was very excited. That is, until I actually read the entire post. I was disappointed, but determined to find a better way. The other top search results were not the step-by-step tutorial that I was hoping for, but, eventually, I found a method that I am much happier with.
My goal was to revert my repo to a previous tag, but, unlike last year’s “solution”, not destroy any commits along the way. The git log
looked something like this:
1
2
3
4
5
6
11111 (HEAD -> main, tag: LatestTag) Latest commit
22222 Another fun feature
33333 Fun feature
44444 (tag: PreviousTag) More changes
55555 Random changes
66666 (tag: MuchOlderTag) Changes
The log starts with the most recent commit and goes backwards in time. My most recent commit was tagged (LatestTag
), but I needed to revert this repo back to an older tag – PreviousTag
. This change would revert 3 commits: 11111
, 22222
, and 33333
.2
This first step is to revert the repo locally to its state at the PreviousTag
tag by rolling each commit back separately.
1
git revert HEAD..PreviousTag
A confirmation of each commit’s reversion appears as part of this revert
command. Git treats each reversion as its own commit, so you will need to review and save each commit as they pop up on your screen.
There is a chance that some merge conflicts will arise. I did not encounter that, but if you do, use whatever method you usually use to resolve conflicts.
Once git has finished rolling back each commit, a git status
looks something like this:
1
2
3
4
5
6
[main 33333a] Revert "Fun feature"
1 file changed, 3 insertions(+), 3 deletions(-)
[main 22222a] Revert "Another fun feature"
1 file changed, 1 insertion(+), 1 deletion(-)
[main 11111a] Revert "Latest commit"
1 file changed, 1 insertion(+), 1 deletion(-)
Three changes were reverted, resulting in three, separate commits.
Reverting one commit can be daunting. Reverting multiple commits is a big deal. I cannot stress this enough: Test your code before doing anything else!
Once you have verified that that your code works as expected, push the changes to remote.
1
git push origin main
Not only has the repo been reverted to its previous commit, but the commit history has been preserved and each reversion is saved as a separate commit (just in case you need to do more “fun” things with git).
Now, do yourself a favor and forget about last year’s post.3
My post was the third link on the search results page. Third. That is either embarassing or pretty amazing SEO. ↩
The commit hashes were (obviously) changed to something easier to read, so no one gets lost in a sea of characters. At the very least, this is easier on my eyes. You’re welcome. ↩
One may ask why I don’t just delete that old post. I don’t really believe in deleting my old content (unless it is truly harmful). That old post is not a great idea, but I also admit that in the post, so I hope readers will take that to heart. ↩
Eventually I do get to these side projects, but even this new year’s post is days late. This chronic problem has a host of reasons. The scattered feeling in my brain, because I have too many things to do and too little time to do it. The frustration that comes with feeling so scattered. The feelings of inadequacy. The imposter syndrome.
I will never blame motherhood for this, because being a mom is something I wanted for a long time. It is something I worked hard for and I love my child and every minute1 of her.
However, with motherhood came a partial loss of self. Being a mom is work, and there are only so many hours in a day. The time I had before for side projects is now time I spend teaching, mom-ing, managing a household of 2, and – of course – playing.2 The loss comes out of missing the time I had for these side projects.
As a developer, side projects allow me the space to do something unique and more aligned with my interests. They give me the chance to experiment and play in the geekiest way possible. This kind of experimentation is rarely possible in my (or any) day job.
Almost every developer I know has a side project (or three). The idea of a side project has become so ubiquitous, that questions like “What projects do you work on outside of work?” are standard in development job interviews.
My side projects allow me to explore other aspects of myself. They can help me answer the question of, “where do I see myself in 5 years?”3 They help me figure how who I am as a professional, compentent, and creative adult. This is important.
It is no secret that, in many societies, women are stereotypically expected to take on more household responsibilities. The BBC article, “The hidden load: How ‘thinking of everything’ holds mums back” discusses the toll this takes on women.
This article focuses on women in heterosexual relationships, but it touches on many points that I also feel as a single mother. The work it takes to be a mother is physically and mentially exhausting, because it never ends. A sub-heading in the article summarizes it perfectly as “invisible, unlimited work.”
It’s no wonder I’m exhausted.
Years ago, I was inspired by the idea of a yearly theme via a CGP Grey video.4 To that end, I decided my theme for 2024 is “Finding Myself”.
I started this quest in the latter half of last year5, but I want to make it official in this new year. What this looks like in practice is still to be determined, but my hope is to focus on the things that make me feel more like me:
New year, new more like me.
Well, most minutes. I do not enjoy tantrums. 0 stars. ↩
Play is important and fun! 5 stars. ↩
I actually cannot stand this as an interview question. I understand that you want access to the plans I have in my head, but those plans also change based on circumstances and 5 years is a lot of time. 1 star. ↩
CGP Grey is a YouTuber and podcaster. I first learned about him from the Hello Internet podcast (no longer active), and am now a fan of his YouTube channel and Cortex podcast with Myke Hurley. Check them out, if you are so inclined. 5 stars. ↩
Is that cheating? ↩
We could probably already open an art gallery. ↩
8:50 am
The red sedan pulls into the school driveway. “Bye kiddo,” it says. A child gets out of the car, ready to walk to the front doors. “Have a great day!” As the passenger door closes, the car moves forward, anxious to get the day started.
It immediately stops, blocked by traffic. A double line of cars obstruct the only path through the lot, waiting. The red sedan inches forward, to the side, then forward again, until, frustrated, it concedes defeat.
The Kiss and Ride line.
8:54 am
The red sedan is late. “My child is already standing at the door,’ it thinks. “I do not belong here.” It desperately looks for a way out. The parking spaces, littered with cars, offer no shortcuts. The red sedan, its tension rising as the minutes pass, is now trapped as more cars file in behind it.
9:00 am
The school doors open, exactly on time. The line begins to move. “Finally,” it thinks angrily. The red sedan races forward, only to stop short again. This parade of cars starts and stops, allowing children to get out as they approach the front. The line is fulfilling its purpose.
The red sedan opens its window. It makes a plea to the car next to it, but nothing can be done. The line has its rules. The red sedan must follow.
9:03 am
At last, the red sedan sees the front of the line. With the freedom of the parking lot exit in sight, it punches the gas and, again, races forward – into the red SUV.
The red sedan, finally, stops.
The story above is a true story (with some liberties taken to help the narration).1 Before I get into the details, no one was hurt. Teachers working the Kiss & Ride line called it a “small fender-bender”. I was neither the car in this story – merely a witness.
Kiss & Ride at our school is straight-forward. There is a single parking lot next to the school, which is a one-way loop. Cars drive into the loop and form a double line. The loop through the lot is wide enough for the double line, but this definitely does not allow room for anyone to pass through.
When the school doors open, the cars at the front of the line let their kids out, then leave, making room for the cars behind it to drive up and drop off.
When I drove my daughter to school this morning, we were behind the red sedan as it pulled into the lot. We saw the drop off and the attempts to get ahead.
One lane of the double line always moves faster than the other. I happened to be in the slower of the two, so at some point, the red sedan got ahead of us, which is how I was able to see the fender-bender.
Mornings are hard. They are hard for most people, but they are next-level hard for parents of school-aged children. If you know, you know.
Sometimes you plan your day perfectly. On other days, work is so ridiculous, you no longer recognize it. Or your child gets sick on the most exciting day of the week. Or, your heart gets broken by a passion project gone wrong.
And on those days, you fall apart.
Falling apart is hard. You don’t think through decisions. You lose patience. You forget your goals. You forget to breath.
I will never know really happened inside that red sedan. But, something did happen that made them lose control, even just for a second. And sometimes, when you do that, you hit a wall. Or, in this case, a car.
So, please, do yourself a favor. Be patient. Remember yourself. And don’t forget to breathe.
For example, cars don’t talk. However, in all seriousness, I have no idea what the driver was actually thinking or saying, but that car had a lot going on with it. Its feelings were pretty obvious, in a “drivers just know” kind of way. ↩
I am also naturally shy. As a child, I rarely spoke in class, not because I did not know the answer, but because I did not want to speak in front of my classmates. I once had a teacher ask me to say anything – as in literally the word “anything” – just to prove to her that I could speak.1
Things did not improve in my teen years. As a high school senior, I had to give a 45-minute presentation on Kepler’s Laws of Planetary Motion. I am fairly certain I said words. I am less certain they were coherant.2
The end of that senior year brought great relief when I learned that I ranked 3rd in my class.3 The top 2 ranking seniors were given the honor of speaking at graduation. I was blissfully spared from that nightmare(-to-me).
Fast forward to today. I speak at conferences. I actively participate in panel discussions. I teach and have led 3-hour-long classes.4
I enjoy speaking in front of a crowd.
I wish I had advice or a five-step program or “one weird trick” to share, but I do not. I started speaking in front of other people out of necessity. Teaching stemmed from the need to do something different after being laid off from a particularly toxic job.
Conference speaking stemmed from the desire to attend conferences. I was in a position where there was very little money for conference travel. I had to apply for funding and was advised that my application would have a better chance if I was selected as a speaker. So, naturally, I applied to speak at every conference I wanted to attend. And then I was accepted to speak at four conferences in one year.
Overboard? Yes. Worth it? Also yes.
My first speaking gigs were nerve wracking, but it quickly became easier. I teach and speak about topics that are interesting to me. I trust (hope?) that those who hear me find my talks equally interesting. I have fun at every speaking gig and will continue to speak for as long as these events will have me.
High-school-me could never have imagined this, but today-me couldn’t be prouder.
I complied and only said the word “anything” to her. In retrospect, I don’t think that helped. ↩
My presentation also included visual aids in the form of styrofoam balls painted as the planets of our solar system. There is a non-zero chance that I juggled Jupiter and Saturn during this presentation. Also, thank you universe for allowing my high school years to be before the era of smart phones and TikTok. There’s no need to relive that. ↩
I wish I could truthfully say I did that on purpose. ↩
I also lost my voice once – mid-sentence – while teaching one of those 3-hour classes. ↩
The calmness of my space makes me feel calm. Except for right now.
The last few months have been utter chaos. Health issues of aging parents. Plans for a long-distance move for one of those parents. My daughter’s last day of preschool. The demolition and subsequent re-decoration of her room, making way for her first “big girl bed”. Her first sleepover, in which no one slept. Her birthday. Her upcoming first day of public school.
All of this, in addtiion to my full-time-grown-up-job-that-pays-the-bills, have left me zero time to write.
This, of course, put my brain into the perfect position to subconsciously consider my current, slow-going, passion project. I have been flooded with ideas that I cannot ignore. Check out this idea for chapter 5. Here is a detailed plan for how to research this. Oh! Don’t forget to write about this too. I have this perfectly formed passage that you can use at the start of chapter 2!
My brain is failing to grasp the fact that I have not had time to even write a haiku, much less a full novel.
The answer is clear: I either need to tell my brain to stop thinking entirely, or quit my job and spend the next 6 months writing.1
This is a terrible idea. I do not need to do this because full-time-grown-up-job-that-pays-the-bills, well, pays the bills. ↩
I recently had to replace my roof. The details are a much longer, unrelated story. Here are the highlights:
My claim is still open, due to some of the longer, unrelated details. As a result of that, my insurance adjuster called me late last week to give me a status update. During that conversation, he also said that he needed to send out an invoice for some tarping work.
None of this is directly related to the phishing attempt. However, it does serve as context to what happened today.
This morning, I received an email from Ann1 with the subject line “Find attachment from We Make Roofs LLC”. The email contents said that Ann shared a document with me, via Adobe, and included a link for me to access the file. This was not unusual for them. During the roof replacement process, I received a lot of documents this way – contracts, receipts, etc.
Given my conversation last week with the adjuster, my first thought was that We Make Roofs LLC was trying to bill me for the aforementioned tarping work. Annoyed (especially since I thought my side of the financials was done), I clicked the link to figure this out.2
Red flag #1: The link took me to a page that redirected me to another page. The second page immediately asked for a user name and password. I never created an account with We Make Roofs LLC and our previous shared files never required passwords.
I replied to the email, which went to ann@wemakeroofs.com
3, to ask what was going on.
Red flag #2: A few minutes later, I got a second email from We Make Roofs LLC. This time it was from Patricia, with the subject line “Urgent_ImportantDocs 05-01-2023”. This email didn’t go to me directly, but to “Undisclosed Recipients”. This email practically screamed “I’m a phish!”
However, that conversation from the insurance company was still in the back of my mind. I called We Make Roofs LLC, using the contact information I already had for them, to figure out what was going on.
First of all, Ann and Patricia are real people. Ann is a customer liaison whom I’d spoke with before. Patricia is the head of the billing department.
We Make Roofs LLC is a smaller, local company, and they answered my call right away. The emails were definitely not a legitimate communication attempt. The person I spoke with said that Ann’s email had been compromised, but they did not think Patricia’s had as well. They had questions when I told them about the emails I had received and promised to relay my experience to their IT department.
Whether the timing of this phish was intentional or not (given my recent communication with insurance), it almost got me. Almost. I’m sure it worked on someone. I am both impressed and really angry.
In conclusion:
Stay safe out there.
Names of both people and companies have been changed. ↩
Yes, I know. Bad. Bad. Bad. Bad. Bad. Never click on links in an email, but the context, timing, and my annoyed state made me think this was real. My deepest apologies to every security person I know. ↩
Again, I changed all the names. This is not a real email, but the email I replied to was a legit email for the real company. ↩
I know, I hate making phone calls too, but sometimes it’s good for you. Trust me. ↩
I recently came across a bug in our code. This codebase is regularly deployed to production and we know production does not have this bug. This gave us a good idea of when the bug was introduced.
Very long story short, the decided goal was to revert this repository back to the last known good commit. (Seriously, it’s a much longer story, irrelevant to this one.)
To illustrate, here is an example of (basically) what the git log looked like:
1
2
3
4
5
6
11111 (HEAD -> master) Latest commit
22222 Another commit
33333 Start of the bug
44444 Last known good commit
55555 Random good commit
66666 Another random good commit
The goal is to cleanly revert several commits, back to the “last known good commit.”
The first step is to tag the relevant commits for this reversion. This includes the latest commit, the first buggy commit, and the last known good commit. Bookmarking these commits allows me to find them more easily later.
1
2
3
4
5
6
7
8
git tag -a LatestCommit 11111
git push origin LatestCommit
git tag -a BuggyCommit 33333
git push origin BuggyCommit
git tag -a BeforeBuggy 44444
git push origin BeforeBuggy
I can make sure my tags are in place by running git log --pretty=oneline
. My commits should now look like this:
1
2
3
4
5
6
11111 (HEAD -> master, tag: LatestCommit) Latest commit
22222 Another commit
33333 (tag: BuggyCommit) Start of the bug
44444 (tag: BeforeBuggy) Last known good commit
55555 Random good commit
66666 Another random good commit
This is the part that is a bit odd. I am sure there is a better way to accomplish a clean reversion. However, this was the method that worked for me.
The first part of this step is to revert HEAD
to point to the commit where the bug was introduced. I could checkout the tag insead of resetting, but that would put me into a detached head state, which is something I almost always want to avoid.
1
git reset --hard BuggyCommit
Locally, this moves HEAD
to that BuggyCommit
and removes all commits that came before it. My git log now looks like this:
1
2
3
44444 (HEAD -> master, tag: BeforeBuggy) Last known good commit
55555 Random good commit
66666 Another random good commit
That is a bit scary, but a quick look at the remote branch in GitHub reassures me that the other commits still exist.
A git status
command also tells me that my local branch is behind its remote counterpart by all of the commits that came after this BuggyCommit
. This makes sense, given that these commits still exist remotely. I have not actually reverted any changes. In order to officially revert this branch to the last known good commit, we must revert the current buggy commit.
1
git revert BuggyCommit
This will create a reversion commit for everything that was part of the commit tagged BuggyCommit
. In order to get this all into main
, we need to force a push of this reversion.
1
git push origin +main
Now, the git log looks like this, both locally and remotely.
1
2
3
4
12345 (HEAD -> master) Revert "Last known good commit"
44444 (tag: BeforeBuggy) Last known good commit
55555 Random good commit
66666 Another random good commit
The other commits after that last known good commit have vanished. Well, that’s terrifying.
Yes and no. The repository’s log has permanently changed to remove those newer commits. However, believe it or not, we were smart earlier.
Remember step 1 above, where we also created those LatestCommit
and BuggyCommit
tags? Those tags – and the changes they contain – still exist. The magic of tags. I’m still in shock.
Was this smart? No.
Was this interesting? Omg, yes!
That being said, there are definitely some things that could have been done differently here.
main
branch before starting any of this. If anything, this fun experiment proved that git, in the wrong hands, will wipe away your data forever. At least if you are not careful. The tags I created saved that data. However, a better backup would have been to create a new branch of main
, before I started any part of this experiment.git revert
on all of the commits. With the method above, I only created a revert commit of the first commit after the last known good state. The rest of the commits were never officially reverted. They simply vanished. It would been more thorough to use git revert
on all commits individually to create reversion commits for each one. That way, the log history would remain preserved.Git is a scary place. I recommend none of the processes in this post (except for maybe the retrospective), so use the knowledge here at your own risk. I wrote all of this up because I did something ridiculous and found it interesting. Don’t be like me. Be like… the opposite of me. Or, at least the opposite of the me who wrote this post. Good luck and good day.
]]>Regardless, it has been a while. Gutenberg was still in development and significantly changing with every new (alpha) release. Meanwhile, our team at work was planning to migrate our 300+ sites from Drupal to WordPress. We knew that Gutenberg had to be part of that plan.
I wrote a lot of blocks in those early days.1 All of them were static blocks. I did not know dynamic blocks were a possibility until we needed to write our own custom “Latest News” block.
As an agile team, we tweaked those original blocks multiple times. This is what fueled my dislike of static blocks.
I was not the only person on my team with a dislike of static blocks. We were all tired of the hassles of the deprecation errors that inevitably occurred. Then one day we asked, how can we convert these static blocks to dynamic blocks?
The answer was two-fold:
Easy peasy. Right? Right. Let’s do this!
We can’t convert a static block to a dynamic block, so the first step is to create that new dynamic block. This step is relatively straight-forward.
There are only two required changes: create an additional registration function in PHP and replace the JavaScript save()
function with a PHP render()
function. The JavaScript side of the block registration, as well as the edit()
function can remain the same.
If you are undertaking a larger block refactor and need some help, I recommend reading through the WordPress how-to guide on creating dynamic blocks or my own article about how I write a custom WordPress block.
This migration step is more complex, but very important, part of the process. There is no reason for both the static and dynamic versions of a block to exist. The end goal is for the dynamic block to completely replace the original static block. Therefore, all instances of the static block need to be transformed into the new dynamic version.
My solution was to write a one-off script in PHP to handle the migration. For illustrative purposes, let’s pretend that we want to replace the existing core Paragraph (static) block with a new dynamic Paragraph block.2
The static version of the block is saved with the HTML for the paragraph enclosed in Gutenberg block indicator comments. For example:
1
2
3
<!-- wp:paragraph -->
<p>And the seasons they go round and round</p>
<!-- /wp:paragraph -->
The goal of the migration is to re-save this block in the post content, so that the markup is removed and all content is saved as meta data inside the block indicators. For example:
1
2
3
<!-- wp:my/new-paragraph
{"content": "And the seasons they go round and round"}
/-->
My goal was to auto-migrate all Paragraph content at once. I am going to present the entire script first, then explain it further below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class MigrateParagraph {
private const OLD_P_REGEX =
'/<!-- wp:paragraph --><p>(.*)</p><!-- \/wp:paragraph -->/sU';
/**
* __construct()
*/
public function __construct() {
add_action( 'init', [ $this, 'migrate' ] );
}
/**
* migrate()
*
* Migrate all posts on the site.
*
* @return void
*/
public function migrate(): void {
// First, get all posts.
$posts = $this->get_all_posts();
// Loop through the posts.
foreach ( $posts as $post ) {
// Get all instances of the old Paragraph block
// in this post.
preg_match_all(
self::OLD_P_REGEX,
$post->post_content,
$matches
);
// The matches are stored in an array with 2 items.
// * [0]: All matched text (the entire block)
// * [1]: The saved content in the matched text.
// This is the paragraph content
// Check to see if one of the array items has a
// length. If not, there is nothing to migrate.
// Move on.
if ( !$matches[1] ) { continue; }
// Loop through all of the paragraphs by looping
// through the saved content (index 1).
for ( $i=0; $i<count($matches[1]); $i++ ) {
$new_paragraph = '<!-- wp:my/new-paragraph
{"content":' . $matches[1][$i] . '"} /-->';
// Replace the old block (index [0] in the matches
// array) with the new block content.
$post->post_content = str_replace(
$matches[0][$i],
$new_paragraph,
$post->post_content
);
}
// Update the post with the migrated content.
wp_update_post( $post );
}
}
/**
* get_all_posts()
*
* Get every post, so that we can look through them
* for migratable blocks.
*
* @return array
*/
private function get_all_posts(): array {
// Arguments to get all posts and pages, and
// reusable blocks.
$args = [
'numberposts' => -1,
'post_status' => 'any',
'post_type' => ['post', 'page', 'wp_block']
];
// Return an array of post objects.
return get_posts( $args );
}
}
new MigrateParagraph;
Alright, that was a lot. Details time.
The beginning of the script defines the regular expression used to find all existing static Paragraph blocks in the post content:
1
2
private const OLD_P_REGEX =
'/<!-- wp:paragraph --><p>(.*)</p><!-- \/wp:paragraph -->/sU';
This regular expression locates the block by its block indicator comments and isolates the paragraph content (using the (.*)
capture). That paragraph content is saved in the dynamic block’s meta data.
The next part of the script gets all posts across all relevant post types, so that we can loop through each post to look for the Paragraph block. In this example, we want to collect a list of all posts, pages, and wp_blocks
, which is the post type used for reusable blocks.3
The script then loops through all posts and checks to see if the static Paragraph block exists in that post. If at least one Paragraph block is found, the migration continues. Otherwise, the script moves on to the next post in the loop.
This is the tricky part. Once the loop finds at least one static Paragraph block n the post, that match is saved to an array. This array contains two items:
In the context of the example above, the array will look like this:
1
2
3
4
5
6
7
8
9
10
11
12
[
[0] => [
[0] => "<!-- wp:paragraph -->
<p>And the seasons they go round and round</p>
<!-- /wp:paragraph -->",
[1] => ...
],
[1] => [
[0] => "And the seasons they go round and round",
[1] => ...
]
]
The two array items in the array mirror each other. So, the matched text in the first item in array index [0]
contains the captured paragraph content in the first item in array index [1]
.
Now that we have a list of all blocks to be migrated, we can convert them into the format used by the dynamic version of our new Paragraph block. We loop through each of these matches and perform the conversion in two steps.
First, we create the code for the new block, using the content in array index [1]
:
1
2
$new_paragraph = '<!-- wp:my/new-paragraph
{"content":' . $matches[1][$i] . '"} /-->';
Next, we use the string replacement PHP function to replace the corresponding match text from array index [0]
with the code for the new block:
1
2
3
4
5
$post->post_content = str_replace(
$matches[0][$i],
$new_paragraph,
$post->post_content
);
Once we have finished the migration for a particular post, we use the wp_update_post($post)
function to save our changes to the database. This has the added benefit of creating a new revision for the post. This is great as a backup, just in case somethng goes wrong or we need to refer back to the post’s pre-migration state.
The Paragraph example above is a very basic overview of how I have been writing static-to-dynamic block migrations. A migration for a more complex example will naturally be more complex. I have written two of these migrations so far for two very different blocks. Each migration has been different, involving custom development for each.
There is no concrete formula for a migration like this because we are 100% in edge-case territory. Ideally, you will need to do very, very few of these types of migrations, if any at all. My hope for this article is to give you an idea of what you are in for if you happen to need to perform a similar migration. YMMV. Good luck!
I’m going to tell you a dirty secret. I did not know React when I was writing those early blocks. As a matter of fact, I still don’t know React. My “React” skillset is extremely limited to the Gutenberg API. So, now you know. React experience is not actually a requirement to work with custom blocks. You’re welcome? ↩
This is a terrible idea to do in reality. Please do not replace the core Paragraph block on your own site. Not only is it an extremely useful and solid block, it is also the default block used by the WordPress. If a content editor just starts typing in the post editor, that content automatically goes into a Paragraph block. Removing and replacing this block will really mess up the editor. ↩
Do not forget about reusable blocks! Reusable blocks are saved as references in the post content of a page or post. Their content is not directly saved to a page or post. (If a reusable block is updated, that update is applied to all pages and posts on which it appears. This is exactly why a reusable block is saved by reference.) Therefore, we also need to include reusable blocks as a post type for these types of content migrations. ↩