Laravel 8: the interesting parts are not on the marketing page

->refundedTwice(). That is the line from Laravel 8 I care about, and it is nowhere on the release page. Jetstream gets the screenshots. Class-based factories and the queue changes get my attention, because both are about production. Factories used to be closures registered through a global function. Now they are classes with states: Order::factory() ->paid() ->has(OrderItem::factory()->count(3)) ->create(); Test data quality decides test quality. Most integration suites on our project test the happy path fifty times, because the default factory returns a fresh, valid, boring record. The bugs live somewhere else. An order refunded twice. A user registered before the migration added the column. A subscription that expired in the middle of renewal. A factory state gives such a monster a name, and once the name exists, people write tests with it. Named ugly data is the cheapest test improvement I know. ...

September 11, 2020 · 2 min · Murat Useinov

Sanctum and choosing how much auth you need

Airlock lived for about a week. Laravel 7 shipped it this month, a trademark scare followed, and it became Sanctum. Fast rename, same idea. And the idea is good, because it names a problem people solve badly. The problem: your own SPA needs to talk to your own API. For years the reflex was OAuth2. Install Passport, stand up an authorization server, issue JWTs to a frontend on the same domain as the backend. All that machinery to authenticate first-party code against itself. OAuth is a delegation protocol, it lets a third party act for a user. When there is no third party, you are running a passport office for your own family. ...

March 26, 2020 · 2 min · Murat Useinov

Eloquent observers and hidden control flow

Three thousand welcome emails. A colleague ran a user import on one project last month: loop over a CSV, $user->save(), go home. Next morning we found the script had also warmed the search index three thousand times and invalidated cache after every row. Nobody wrote that in the import script. The observers did. Laravel 5.8 came out yesterday, and reading the changelog brought that evening back, so here is the note. ...

February 27, 2019 · 2 min · Murat Useinov

One payment is enough

Charge the card, send the receipt, ack the job. The worker lost its Redis connection between step one and step three. The queue delivered the job again. The customer paid twice. Support learned some new words from him. Retries are not an edge case. Laravel retries failed jobs by design, and you want that, because networks blink. So every job with a side effect must answer one question: what happens if this runs twice. “It will not run twice” is not an answer. It will. ...

January 21, 2019 · 2 min · Murat Useinov

Anatomy of email verification

email_verified_at, a timestamp. Laravel 5.7 came out this month with email verification built in, and this column is the first thing I noticed. A boolean would cost the same and answer less. A timestamp answers not only whether, but when, and when a support ticket arrives half a year later, “when” is the question. The tutorials say: implement MustVerifyEmail, put the verified middleware on routes, done. True, and boring. The interesting part is how the feature is put together. It is a small example of a cross-cutting feature done right. ...

September 18, 2018 · 2 min · Murat Useinov

Where the checkout logic goes

Validate input, reserve stock, create the order, charge the card, fire events. Five steps in one checkout action, and the fat model versus fat controller argument offers only two rooms for them. Both rooms are wrong. Put it all in the controller and you cannot run checkout from anywhere except HTTP. No console command, no queue job, no test without the kernel. Put it in the Order model and the model now knows about payments, stock and notifications, a strange set of friends for an Eloquent class. ...

August 13, 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

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

Laravel Dusk and what browser tests are for

Laravel 5.4 came out on Tuesday, and the first thing I installed was Dusk. Browser tests without a Selenium server: it talks to ChromeDriver directly, the API is fluent, and a failed test leaves a screenshot behind. My first test was the login path: $this->browse(function ($browser) { $browser->visit('/login') ->type('email', 'user@example.com') ->type('password', 'secret') ->press('Log in') ->assertPathIs('/home'); }); It passed on the third run. The first run failed because the button on the test box had different text. The second failed because a JS animation was slower than the wait. This is the normal life of a browser test. It sees the real application, and the real application moves. ...

January 26, 2017 · 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