Built in public
Dev log
What shipped, what broke, and what it taught us.
-
The route that matched, ran, and found nothing
TetherPHP could register five HTTP verbs and had been able to for six releases. It could only read the body of one of them, and nothing anywhere said so.
-
The Responder that had nothing to do
TetherPHP is built on Action-Domain-Responder, and its Responders were forwarding their argument and returning. The array they forwarded was the reason — its keys were the view's variable names, so my templates were shaping my domain classes.
-
A kernel that could not be tested
The most important method in the framework had no test coverage. Not because I skipped it — because it called exit(), and you cannot assert on a function that ends the process.
-
extract() and the path you already checked
Three lines that validate a file path and then hand the caller a way to change it. A textbook time-of-check-to-time-of-use bug hiding in an ordinary view renderer.
-
The review my tests could not do
Eighty passing tests and a clean static analysis run told me the framework was healthy. Then I read it line by line and found that HEAD requests returned 500 and any URL with a query string returned 404.
-
Every generated command had the same name
The code generator wrote a hardcoded command name into every file it produced. Generate two commands and one of them quietly stopped existing.
-
Splitting the monorepo, and the three bugs it was hiding
TetherPHP's core was generated by force-pushing a directory into another repository. Turning that around surfaced three failures that had been invisible the whole time.