TIL – Timing Attack

Question: Why should we use hash_equals in PHP rather than just the simple string comparison?

Answer: Actually, it’s not something PHP specific. Instead, it’s universal among all programming languages. But why? With the typical string comparison, the false result will be emitted as soon as a non-matched character is found. By trying to compare the response time in a fast network (such as local LAN) from different string lengths and content, attackers can slowly verify the correct secret string. hash_equals ensures that there is no difference in terms of response time.

References:

PHP and JavaScript: Produce the Same Result for Now and Future

Problem

In WooCommerce Payments, we have a long-standing issue with displaying fee details in order notes. Before that, my team has already had logics in JavaScript to extract data from JSON (fetching via REST API request), and dynamically display fee details in a React component. The issue I am working on is to build static HTML content (order note) from the same output but this code must be used in PHP.

Approaches

The first option seems reasonable and avoids duplication. It’s updating the current JavaScript code to generate HTML, which can be used for both order notes and React components. However, we quickly noticed that this can be complicated due to:

  1. Plenty of changes must happen so that the current generation logic can be switched from React component to HTML.
  2. React consuming and displaying HTML content is not safe. Function name dangerouslySetInnerHTML declares that safety concern explicitly.
  3. For PHP to get this HTML content, it needs to send a request to the site itself for triggering the JavaScript code. It requires even more thoughts to how to approach that correctly and safely.

And then, the second option is to write new PHP code reflecting the same JavaScript code. In other words, PHP and JavaScript codes are duplicated for this purpose.

Way to Final Solution

When decided to write PHP code, a new goal is risen: how to ensure that PHP and JavaScript can really produce the same result. Then, when a developer makes changes in PHP or JavaScript, they will need to do the same for the other language.

I approach that requirement by running unit testings for both PHP and JavaScript with the same fixtures and test results, which are actually encoded in JSON files.

With the current code, I refactor the JavaScript code first so that each line of fee details are handled individually. Then, based on that, I add JSON test files (based on the current JavaScript code result), add up new PHP code and its tests. All of these have been done in the second PR. The different fixtures actually aid me a lot in writing and tweaking PHP code to get the final result, which is pretty stable and almost have no problem.

Updates

  • August 2023, we found a bug, fixed it, and added a new fixture file. But the fix is still simple and effective for the long future.
  • September 2023, there are some changes in JavaScript logic, and developers can pick up that quickly to fix the PHP part too.

Payment: Webhook Reliability

What is the issue?

WooCommerce Payments, like any other payment service, uses webhook to notify relevant data to merchant sites. However, sometimes merchant sites can not receive these webhook events due to multiple issues. This can be a big issue if these events are related to disputes or payment status. One example is that merchants are not notified when disputes happen.

Continue reading “Payment: Webhook Reliability”

WooCommerce Payments – Embracing Stricter L-2 Support

As WooCommerce Payments continues to grow, ensuring that we provide a stable, secure, and efficient experience for merchants is a top priority. One area we’re evaluating is the policy around which versions of WordPress we support—specifically, whether to adopt a stricter L-2 policy, meaning official support only for the latest two major WordPress versions. This shift would help streamline development and testing, but it comes with tradeoffs we want to carefully consider.

Why Haven’t We Implemented a Stricter L-2 Policy Yet?

The main reason we’ve held off on enforcing a stricter L-2 policy is to support as many merchants as possible. Some stores don’t update WordPress immediately for a variety of reasons—compatibility concerns, customizations, or just caution around change. Our current “loose” L-2 policy reflects this: we say we support the latest two versions, but in practice, we don’t block older versions from running WooPayments, and our CI still runs tests against them in some cases.

Maintaining this broader support helps minimize friction for merchants and gives them more flexibility in how and when they upgrade.

Challenges If We Do Not Enforce a Stricter L-2

While broad compatibility is beneficial for users, it creates real challenges for development and testing:

  • Increased Maintenance Overhead: Supporting older WordPress versions often means keeping legacy compatibility code around, which increases the complexity of our codebase.
  • Testing Complexity: Different WordPress and PHP combinations require specific versions of PHPUnit. Our CI pipeline has to include hacks to install and configure the right versions, making the system fragile and hard to maintain.
  • Reduced Developer Velocity: Supporting many combinations means slower builds, more bugs slipping through, and more time spent troubleshooting issues that don’t affect the majority of users.
  • Inability to Adopt Modern APIs: We’re often blocked from using newer WordPress features until we drop support for versions that don’t include them.

In short, the more versions we support, the more technical debt we accumulate—and that makes it harder to build new features confidently and efficiently.

How Do We Make the Decision?

To make a responsible choice, data must drive the decision. Specifically, we need to understand how many active merchants are running WooPayments on older versions of WordPress. If that number is small—say, under 1%—then the impact of enforcing a stricter L-2 policy would be minimal.

This data could come from usage tracking, opt-in telemetry, or marketplace stats. The goal is to quantify the tradeoff: how many users would be affected vs. how much complexity we remove by dropping support.

We’re also considering what the test matrix should look like. It’s not just about which versions are supported, but also which ones we validate in CI. A leaner, more focused CI pipeline makes our code more reliable and our releases more predictable.

What We Will Do Next

Our next steps will focus on data gathering and planning:

  1. Collect usage data to see which WordPress versions are most common among WooPayments users.
  2. Define our support baseline based on that data, likely aiming to officially support only the latest two versions.
  3. Simplify our CI strategy to only cover supported versions, removing outdated PHPUnit workarounds.
  4. Introduce runtime checks in WooPayments to block usage on unsupported WordPress versions with helpful error messages.
  5. Communicate the change clearly and in advance so merchants and developers have time to adjust.

This is part of our broader effort to modernize the WooPayments plugin, reduce technical debt, and provide a better experience for everyone—from merchants to developers.

Testing PHP Static Functions with PHPUnit: Challenges and Solutions

Static methods in PHP can be convenient, but they pose significant challenges when it comes to unit testing. This post explores why testing static methods is hard, why it’s advisable to avoid them in testable code, and what strategies can be employed to mitigate these issues.

Why Testing Static Methods is Hard

Static methods are inherently tied to their class and cannot be easily replaced or mocked during testing. This tight coupling makes it difficult to isolate the method under test, leading to tests that are less reliable and harder to maintain.

Why We Should Avoid Static Methods in Testable Code

Using static methods can lead to code that is tightly coupled and difficult to test. This is particularly problematic when static methods perform actions like logging, caching, or interacting with external systems.

For instance, in this WooCommerce Payments PR, I encountered difficulties testing a Logger class that relied on static methods. The inability to mock these methods led to challenges in writing effective unit tests.

Strategies for Testing Static Methods

While it’s best to avoid static methods in testable code, there are scenarios where they are necessary. In such cases, consider the following strategies:

1. Use Dependency Injection – Most Preferred

Dependency Injection (DI) allows injecting dependencies into classes, making them more testable. By injecting a logger or cache handler, it’s possible to replace these dependencies with mocks during testing.

As highlighted in the blog post on Dependency Injection, DI helps in achieving loose coupling and makes unit testing more straightforward.

2. Wrap Static Methods

If you must use static methods, consider wrapping them in instance methods. This approach allows you to mock the wrapper during testing, providing greater flexibility.

3. Use Callables

Another approach is to pass callables (like closures) into your methods. This technique enables you to replace static method calls with mock functions during testing. But it’s not ideal as it makes things complicated.

Conclusion

Try best to avoid injecting static methods for objects you’d like to test with PHP. The only good reason I have seen for it so far is to for utils/helpers. Using Dependency Injection is the most favorable approach to write more testable and maintainable code.

xDebug – nginx – php-fpm

A few times, I could not watch xDebug on my favorite IDE. I did check xDebug config as well as IDE, and followed up a tons of things, then it still did not work.

Then, I found out this myth: port 9000 conflict between xDebug client_port and php-fpm listen port.

It’s so frustrated that my IDE PHPStorm does not tell anything about this conflict, then I did not know anything about this.

How to solve this issue?

  • Disable php-fpm. This works for me as my server is run on Docker. The current php-fpm process is left intact due to my previous setup with the local PHP on my machine.
  • Change port in either xDebug or php-fpm, obviously.

I am using brew to manage PHP on my MacOS laptop, so this is the guide I needed to change the php-fpm port for PHP 7.4. In short, I needed:

  • Open file: /usr/local/etc/php/7.4/php-fpm.d/www.conf
  • Update 9000 to 9999 (or whatever port that does not create conflicts) in this line:
    listen = 127.0.0.1:9999
  • Restart brew service: brew services restart php@7.4 

How to know if it’s a conflict?

Use the following command to list out the current process(es) using port 9000, which is specific for Mac but I guess you can figure it out for other OS:

sudo lsof -PiTCP -sTCP:LISTEN | grep 9000

Based on this answer.

PHP: in_array is very slow for a huge array

Definitely not something new but this experience gave me good reason to understand how a function works under the hood.

in_array() goes through all items in a provided array and see if any existing item exists in the array. It’s not an issue if the array has only a few items. However, when it has thousands or even million items, this function is no longer usable!

Instead, a good idea is to flip values to become keys and vice versa. It’s pretty simple if all values are unique then we can just use array_flip() for this purpose and search with new keys (or old values). Good trick indeed! The reason under the hood is that array keys are implemented with Hashtable.

Otherwise, it may be a bit more complicated and we may need to do some other tricks but it can still work!

This conversation suggests using isset(). However, basically it’s still my idea above with a different way telling about that. Even though, it is still a good read!

PHP 8.0: Non-strict Comparisons for a String and a Number

Non-strict comparisons in PHP are always not easy to work with, and here is the last piece of this common issue:

  • PHP < 8.0: “one operand is a number and the other one is a numeric string, then the comparison is done numerically”.
  • PHP >= 8.0: “Non-strict comparisons between numbers and non-numeric strings now work by casting the number to string and comparing the strings.”
Continue reading “PHP 8.0: Non-strict Comparisons for a String and a Number”