Private services: the container steps back

$container->get('app.mailer') stopped working this week. Symfony 3.3 is out, and in the new configuration services are private by default. The container refuses the call. Everyone is writing about autowiring. This is the change I like more. That call was always a smell. A class that pulls dependencies from the container by string id is a class with secrets. Its constructor says nothing, and to test it you boot half the framework, because it may ask for anything at any moment. ...

May 30, 2017 · 2 min · Murat Useinov

Order status is a graph

if ($order->status == 'paid') in the shipping module. Another one in the refund module. A slightly different one in the admin panel. Then someone cancels a shipped order from an admin button, because the button just writes the string, and the warehouse learns about it a week later. Symfony 3.2 is out this week and ships a Workflow component. Finally a framework says out loud what every order table has been whispering for years: status is a state machine, not a string column you assign wherever convenient. ...

November 30, 2016 · 2 min · Murat Useinov

The queue will run your job twice

The job called the payment provider. The call succeeded. Then the worker timed out before it marked the job done. The queue did what queues do: it retried. The provider did what it was asked: it charged again. The customer did what customers do and wrote an angry email. Nobody made a mistake here. The queue promises at-least-once delivery, and “at least” is written in the contract. Network partitions, worker crashes, deploy restarts. Sooner or later every job runs twice, and the jobs that hurt are exactly the ones with external effects: payments, emails, webhooks, API calls. ...

October 3, 2016 · 2 min · Murat Useinov

Laravel 5.3: the app is not a website anymore

Mail::send() inside the checkout controller. A second copy inside the API controller. Then someone adds the chat webhook to one copy and forgets the other. Every project has this code. I have written it more than once. Laravel 5.3 came out this week, and the two big pieces, Notifications and Passport, both point the same way. The application is no longer a thing that renders HTML. It is a core that talks to browsers, mobile clients and third parties, and HTML is one of the outputs. ...

August 26, 2016 · 2 min · Murat Useinov

Freezing the Kohana layer

Kohana 3.3, on a project that earns money every day. The framework is effectively finished. The repository barely moves, the community left years ago. Nobody will approve a rewrite, and I have stopped asking. This is a normal situation and it deserves a better plan than “someday we migrate”. The plan we settled on is a freeze. Pin the exact framework version and vendor it. Not “3.3.*”, the exact commit. The build must be reproducible in five years, when the original download link is dead. ...

March 4, 2016 · 2 min · Murat Useinov

Symfony 3.0 is not a rewrite

Symfony 2.8 and 3.0, released today, both at once, on purpose. The pairing is the whole message. 2.8 is the last of the 2.x line and an LTS. 3.0 is roughly 2.8 with the deprecated code deleted. Same features, cleaner body. This turns a scary major upgrade into two steps. Step one, move to 2.8. Minor upgrade, low risk. Then make the deprecation log empty. The phpunit bridge prints every deprecated call your code and tests touch. Burn the list down during normal sprints, item by item, each fix small and releasable on its own. ...

November 30, 2015 · 2 min · Murat Useinov

Redis cache and Redis queue are different databases

Two lines from redis.conf on one project: maxmemory 2gb maxmemory-policy allkeys-lru Correct for a cache. Then sessions moved into the same instance, because Redis was already there. Then the job queue, same reason. One process, three tenants. This works until the day it does not. Cache data is disposable by definition, and those two lines embrace that. Memory fills up, Redis evicts the coldest keys, the application rebuilds them on demand. Persistence is optional. After a restart a cold cache is an inconvenience, not an incident. ...

August 21, 2015 · 2 min · Murat Useinov

Laravel 5.1 and the dependency ladder

PHP 5.5.9. That is the minimum for Laravel 5.1, which came out this week, the first Laravel with LTS: two years of bug fixes, three of security fixes. Two weeks ago I wrote the same about Symfony 2.7. Two frameworks arriving at the same idea in one summer is no coincidence. PHP applications got old enough to have a lifecycle. Draw the ladder for your project once. OS at the bottom. Then the PHP version the OS ships, or the one you build. Then the framework. Then the packages on top. Every rung has its own end-of-life, and the rungs are coupled. If production sits on some old distro with 5.4, the 5.1 upgrade is suddenly an ops project. And above you, half the packages will require 5.1 in their next major, so staying behind slowly cuts you off from fixes. ...

June 14, 2015 · 2 min · Murat Useinov

Symfony 2.7: LTS is the feature

Three years of bug fixes. Four years of security fixes. Symfony 2.7 came out this weekend, and that is the line from the announcement I care about. The changelog has new things in it, but the important word is on the label: LTS. For a pet project this is boring. For a product with paying users and a team, this is the actual feature. A big application does not upgrade for fun. Every framework upgrade is testing time, regression risk, and a sprint that produces nothing visible. Business asks a fair question: what do we get. “Newer version” is not an answer. “We keep receiving security fixes until 2018 without touching anything” is. ...

May 31, 2015 · 2 min · Murat Useinov

Three ways to get a dependency in Laravel

Same repository, three ways to get it in Laravel 5. // 1. Constructor injection public function __construct(OrderRepository $orders) { $this->orders = $orders; } // 2. Facade $order = Orders::find($id); // 3. Service locator $orders = App::make('App\Repositories\OrderRepository'); All three work. The container resolves everything either way. The difference is in what the class tells you about itself. With constructor injection the dependencies are in the signature. You open the class, you read the constructor, you know what it needs. A test passes a mock and never touches the container. ...

March 9, 2015 · 2 min · Murat Useinov