Sand Fox
-
Count files by extension
TL;DR:
-
Peso Framework
Another thing I've been secretly building is the Peso Framework, a tool to query currency conversion rates.
It started when I was pissed that
florianv/exchanger
, a tool I was perfectly happy with previously, got a broken release (2.8.2) got abandoned for almost a year now, and still isn't fixed as of this post.Peso consists of 3 components:
A service (data source)
An integration (data consumer)
A glue library (
peso/core
) that contains the Service contract, standard request/response objects, and useful tools to develop services and integrations
A simple example:
<?php use Peso\Peso\CurrencyConverter; use Peso\Services\EuropeanCentralBankService; require __DIR__ . '/vendor/autoload.php'; // caching omitted for simplicity $peso = new CurrencyConverter(new EuropeanCentralBankService()); echo $peso->getHistoricalExchangeRate( 'EUR', // base 'PHP', // quote '2025-06-13', // date (Y-m-d string or DateTime or arokettu/date Date) ), PHP_EOL; // '64.706'
Advantages over the Exchanger:
Caching moved to the Service side making it more effective
No way for a service to mistake a historical exchange rate request with a current exchange rate request
Also currency conversion requests for services that support them
Ability to add new request types without breaking the backward compatibility
Services reside in their own separate packages so the main package is not a huge garbage dump of dead and unsupported services like Cryptonator
-
JSON5 Tools
Presenting a couple of JSON5 tools I built.
The first one was done a year ago, a docker CLI image to convert JSON5 to JSON: arokettu/json5-to-json.
A simple tool to convert your configs on the build phase without installing anything into your images.
The second one is a builder/serializer that is aimed at creating pretty configs in PHP: arokettu/json5-builder.
While it can just serialize some data into JSON5, the goal is to create nice configs with comments and custom formatting.
<?php use Arokettu\Json5\Json5Encoder; use Arokettu\Json5\Values\CommentDecorator; use Arokettu\Json5\Values\HexInteger; require __DIR__ . '/../vendor/autoload.php'; $config = new CommentDecorator( [ 'bareKeys' => '<- Look, no quotes!', 'value' => new CommentDecorator( new HexInteger(0xFFF), commentAfter: 'This is a very important value' ), 'notAvailableInJSON' => [NAN, INF], 'end' => 'auto trailing comma ->' ], commentBefore: 'This is my cool JSON5 config!', ); echo Json5Encoder::encode($config);
-
A Case for JSON6
Just a required follow up for the previous post: JSON5 sucks too, like any other possible format. Just look at this:
{ certificate: "\ -----BEGIN PRIVATE KEY-----\n\ MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAt6wGEsfdarMo9KET\n\ sNv9gpBWkGfhIq3/Jhr14+3d8TXprmTk/XWReo5DA3+SbB7ebF3xUtN/4K5lUkcG\n\ 1AbdywIDAQABAkBuMbfnFqAhvuFoeydMKYAcECrPMnOhEdENdIlnuTM53kBf18f/\n\ KaWRWv8ViuZ31GpArY8cBzj0YG30vE49Il8ZAiEA7PpotzcpjNcMKnwEBm3q0c6H\n\ 3RRZD6i32Q/vXcQBeB0CIQDGaj4BJ/fuqiHvKzetfjhpOD+sYr3VDgEyjTcojhzZ\n\ BwIgTBRYafmbrUuc7EbERAwlxxW3KJLPxOc1nsou3rt+fUECIQCjBGUfJAyDDUtG\n\ KpbbN0n3wRwncRUQuCnps7Zu3pv6/wIhAIIcKty7O0T7auctlpMSdyw6vp0w818x\n\ namFjg3cbBs4\n\ -----END PRIVATE KEY-----\n" }
-
Is JSON5 a better JSON?
Yes it is, at least when it comes to configuration files.
As I said earlier, JSON is a good serialization language but a terrible config language. JSON5 builds on its strong sides: it's backwards compatible with JSON and is still a subset of JavaScript and it is still relatively easy to parse. Then it adds all the good stuff like unquoted keys, trailing commas, and a must-have thing for any config, comments. For me it has only two real downsides, both really minor. First is, as most config-oriented languages, it's tricky for automated modification, a feature it shares with TOML and YAML. The second one is JSON6, while ES6 syntax sugar is nice, it's just a 15th competing standard, and the worst, competing with itself.
-
Do Not Use Exception Class Directly
It is a bad practice to use the
Exception
class directly in PHP. That means 2 points:Do not throw
Exception
directlyDo not catch
Exception
directly
Subclassing
Exception
is not a direct use in this context.Do not throw
Exception
There are a lot of specific
Exception
descendants in PHP. See documentation for the full list. It's not necessary to use and remember all of them, many have unclear use cases, but at very least you should separate your exceptions intoRuntimeException
andLogicException
. As the doc says,LogicException
means code misuse. ThrowLogicException
in cases when the upstream must be fixed because it uses your code incorrectly. ThrowRuntimeException
in cases that are not predictable by the upstream, like service failures or invalid user input.In case you use exceptions for flow control, you need your own custom exceptions anyway. In this case you can extend the base
Exception
, extendingException
directly is not a sin, especially if your exceptions are not error conditions.Do not catch
Exception
Assuming you follow the previous point, there is no need to catch
Exception
's.If you don't guard against a specific situation, catch all
RuntimeException
's.LogicException
's should go to your logger.In a global error handler that writes your logs, you need to catch Errors too, so you need to go even further and catch
Throwable
.If you use exceptions for flow control (please don't haha) you already catching your own exceptions, and if you follow the previous point, they are already separated from actual error conditions and won't be caught mistakenly by a wrong level.
Notable exceptions to the rule:
You use PHP below 7.0 and don't have
Throwable
. Well, it's 2024 already so this is another call for you to upgrade. Otherwise assume point 2 allows you to catch a baseException
.You use a library that throws plain
Exception
instances. Well, bad for you, but maybe it's a good idea to report this thing as a problem.
-
Ignoring SSL Errors in Chrome
Very useful when you are developing anything WebAuthn related.
--ignore-certificate-errors
will disable any SSL warnings like self-signed certs.--user-data-dir
will isolate this unsafe Chrome instance from your regular browsing. -
Generating UUIDv7 in PostgreSQL
But what about Postgres? We can do it too. Hopefully generators for new UUID versions will be added to uuid-ossp but it is not yet the case.
The easiest way for now is to use pgcrypto, so let's start with that:
Now port the solution from the MySQL post:
select lpad(to_hex((extract(epoch from now()) * 1000)::bigint), '12', '0') || '7' || -- version substr(encode(gen_random_bytes(2), 'hex'), 2) || to_hex((floor(random() * 4) + 8)::int) || -- variant bits substr(encode(gen_random_bytes(8), 'hex'), 2) as uuid;
Like with MariaDB, Postgres doesn't care about dashes on insertion, so this value can be directly applied to a field with the uuid type.
And again, to make it a formatted value, use a type cast:
select (lpad(to_hex((extract(epoch from now()) * 1000)::bigint), '12', '0') || '7' || substr(encode(gen_random_bytes(2), 'hex'), 2) || to_hex((floor(random() * 4) + 8)::int) || substr(encode(gen_random_bytes(8), 'hex'), 2))::uuid as uuid;
And to have a bytea value, use decode:
-
Generating UUIDv7 in MySQL and MariaDB
A quick way to generate UUIDv7 in MySQL 8 and MariaDB 11:
select concat( -- Use NOW(3) to get milliseconds lpad(hex(unix_timestamp(now(3)) * 1000), 12, '0'), '7', -- version substr(hex(random_bytes(2)), 2), hex(floor(rand() * 4 + 8)), -- variant bits substr(hex(random_bytes(8)), 2) ) as uuid;
This will generate a hex string that can be inserted into MariaDB's UUID type as is, dashes will be added automatically.
If you need it as a binary value, wrap with unhex:
select unhex(concat( lpad(hex(unix_timestamp(now(3)) * 1000), 12, '0'), '7', substr(hex(random_bytes(2)), 2), hex(floor(rand() * 4 + 8)), substr(hex(random_bytes(8)), 2) )) as uuid;
If you specifically need a valid RFC 4122 string representation, use a bin formatter function (MySQL) or a type cast (MariaDB):
-- MySQL select bin_to_uuid(unhex(concat( lpad(hex(unix_timestamp(now(3)) * 1000), 12, '0'), '7', substr(hex(random_bytes(2)), 2), hex(floor(rand() * 4 + 8)), substr(hex(random_bytes(8)), 2) ))) as uuid; -- MariaDB select cast((concat( lpad(hex(unix_timestamp(now(3)) * 1000), 12, '0'), '7', substr(hex(random_bytes(2)), 2), hex(floor(rand() * 4 + 8)), substr(hex(random_bytes(8)), 2) )) as uuid) as uuid;
-
De-hybridize Torrent
A feature that may be desired for some anti-V2 trackers. While I would like to see the torrent community going the other way around, some trackers choose not to allow version 2 torrents and sometimes even hybrids.
The torrent file library now allows cleaning unnecessary or undesired metadata versions from a torrent file.
The intended use and the primary future scope is actually the other way around: the ability to remove version 1 metadata to make use of easy mutability of version 2 torrents to add or remove files in existing torrent structures.