GDPR: where the user actually lives

The row in users. Then orders, with the delivery address and phone. Then nginx access logs with emails inside GET parameters of an old unsubscribe endpoint. That is how far I got in the first ten minutes of drawing where one user’s data lives on one project, and the map did not fit on one page. May 25 is close and everyone discusses consent banners. The lawyers can have the banners. The engineering part is the right to erasure. “Delete the user” sounds like one DELETE statement until you sit down with a pen. ...

May 9, 2018 · 2 min · Murat Useinov

MySQL 8.0: finally, modern SQL

@row := @row + 1. I have a report in production that stands on that trick, and the trick was never guaranteed to work. Evaluation order of user variables in SELECT is undefined, it just happened to behave. MySQL 8.0 went GA last week, and for me the release is about SQL. Window functions and CTEs, the things Postgres people stopped noticing years ago, are here. The classic task: top three orders per customer. In 5.7 you had a self-join nobody could read a month later, or the variable trick. Now: ...

April 24, 2018 · 2 min · Murat Useinov

.env is not a secrets store

A .env file committed to git, “temporarily”. Database password, API keys, mailer credentials, all in one file. Symfony 4 moved configuration to environment variables, and this is the second project this month where I see the same thing. .env is a developer convenience. It exists so local setup does not require exporting fifteen variables by hand before running the app. That is the whole job of this file. .env.dist goes to git with placeholder values, .env stays in .gitignore with your local ones. This part is not negotiable. ...

March 14, 2018 · 2 min · Murat Useinov

Rector: refactoring by machine

Search for ->fetch( in a project with old Kohana code and count the hits. A method call on a model, a comment, a line in a test fixture, an unrelated class that happens to have a method with the same name. That is what regex refactoring looks like. Regex does not know a method call from a string literal. Rector does. A young tool I found this month: it parses PHP into an AST with nikic/php-parser, applies transformation rules, prints the code back. Rename a class across the whole project. Change a method call, add an argument everywhere. The AST sees that this fetch is called on that type and touches only those places. Mechanical change becomes exact. ...

February 8, 2018 · 2 min · Murat Useinov

Horizon, or the queue becomes visible

redis-cli llen queues:default and hope. That was my queue monitoring for years. Horizon replaces it with a real dashboard, and I did not know how much I needed one until I saw it. The mental shift matters more than the UI. dispatch() is the beginning of the work, not the end. The job still has to wait in Redis, run, maybe fail, maybe retry. All of that was invisible. Now it is on one screen: throughput, wait time per queue, failed jobs with the full payload and the exception. ...

January 20, 2018 · 2 min · Murat Useinov

Sodium in PHP 7.2 core

PHP 7.2 came out on the last day of November. Mcrypt is out of core, libsodium is in. That trade alone makes it a good release. Search any forum for “php encrypt” and you find the same folk recipe: openssl_encrypt with AES-256-CBC, an IV made from who knows what, no authentication of the ciphertext. Every choice in that recipe is a place to be wrong, and CBC without a MAC is wrong in a way that has published attacks. The developer is not careless. The API hands an application developer decisions that belong to a cryptographer. ...

December 10, 2017 · 2 min · Murat Useinov

Symfony 4: the framework as recipes

A kernel, an index.php, a config folder with almost nothing in it. That is a Symfony 4 project on day one, and 4.0 came out today together with 3.4 LTS. The code changes are the smaller story. How a project starts is the bigger one. The Standard Edition is gone. You start from the empty skeleton and ask for what you need. composer require orm pulls Doctrine, and the recipe writes the config files, registers the bundle in bundles.php, adds the variables to .env. You watch your own repo grow file by file as you add dependencies. ...

November 30, 2017 · 2 min · Murat Useinov

PostgreSQL 10: partitions I can finally use

DROP TABLE events_2017_10. That is how you delete October now, and it is the reason I am reading PostgreSQL 10 release notes on a Thursday evening instead of waiting a year like usual. Partitioning existed before, through inheritance, CHECK constraints and an insert trigger you wrote yourself and hoped was right. Now the database owns it: CREATE TABLE events ( id bigserial NOT NULL, created_at timestamptz NOT NULL, payload jsonb ) PARTITION BY RANGE (created_at); CREATE TABLE events_2017_10 PARTITION OF events FOR VALUES FROM ('2017-10-01') TO ('2017-11-01'); The win is the data lifecycle. An events table grows forever, and deleting a year of history with DELETE is a night of I/O plus a bloated table in the morning. With partitions, retiring a month is one DROP. Instant. And a query that filters by created_at visits only the partitions in range, the planner skips the rest. ...

October 26, 2017 · 2 min · Murat Useinov

First run of PHPStan on legacy code

Level 0, a legacy codebase, a few hundred errors on the first run. That was my week with PHPStan. Most of the output was noise about magic the tool cannot see. But in the first hour of reading I found three real bugs, live in production for months. One: a repository method returns an entity or null, and a caller chains a method right on the result. The not-found branch was never written. It survived because that path needs a deleted record, and deleted records are rare. Rare is not never. ...

September 9, 2017 · 2 min · Murat Useinov

Laravel 5.5 and package auto-discovery

Laravel 5.5 came out yesterday, an LTS: two years of bug fixes, three of security fixes. Good news for projects that outlive the hype cycle, which is most of them. The feature in every screenshot is package auto-discovery. A package declares its service provider in its own composer.json, and after composer require it is registered. No editing of config/app.php, no provider line copied from the readme. Convenient. Now look at what got deleted. Installing a package used to be two steps, and the second step was consent: a line in my own config saying this code runs inside my application at boot. Now composer require is the whole ceremony. The list of code that runs at boot lives in vendor/, spread over other people’s files. php artisan package:discover shows it, and dont-discover in composer.json switches it off per package. But the default flipped from explicit to implicit. ...

August 31, 2017 · 2 min · Murat Useinov