PHP: Hypertext PreprocessorPHP 8.4.0 Alpha 2 available for testing (18.7.2024, 00:00 UTC)
The PHP team is pleased to announce the second testing release of PHP 8.4.0, Alpha 2. This continues the PHP 8.4 release cycle, the rough outline of which is specified in the PHP Wiki.For source downloads of PHP 8.4.0 Alpha 2 please visit the download page.Please carefully test this version and report any issues found in the bug reporting system.Please DO NOT use this version in production, it is an early test version.For more information on the new features and other changes, you can read the NEWS file, or the UPGRADING file for a complete list of upgrading notes. These files can also be found in the release archive.The next release will be Alpha 3, planned for 1 Aug 2024.The signatures for the release can be found in the manifest or on the QA site.Thank you for helping us make PHP better.
PHP: Hypertext PreprocessorPHP 8.4.0 Alpha 1 available for testing (5.7.2024, 00:00 UTC)
The PHP team is pleased to announce the first testing release of PHP 8.4.0, Alpha 1. This starts the PHP 8.4 release cycle, the rough outline of which is specified in the PHP Wiki.For source downloads of PHP 8.4.0 Alpha 1 please visit the download page.Please carefully test this version and report any issues found using the bug tracking system.Please DO NOT use this version in production, it is an early test version.For more information on the new features and other changes, you can read the NEWS file, or the UPGRADING file for a complete list of upgrading notes. These files can also be found in the release archive.The next release will be Alpha 2, planned for 18 Jul 2024.The signatures for the release can be found in the manifest or on the QA site.Thank you for helping us make PHP better.
Evert PotCreating a fake download counter with Web Components (2.7.2024, 16:07 UTC)

Over the years I’ve written several open source libraries. They’re mostly unglamorous and utilitarian, but a bunch of them obtained got a decent download count, so I thought it would be fun to try and get a grand total and show a ‘live’ download counter on my blog.

This is how that looks like:

My open source packages were downloaded roughly 138945563 times.

Like most live counters, this number isn’t tracked in real-time. Instead it just uses a start number and updates the number based on an average number of downloads.

One day it might be nice to make it live, but this is a static blog and this would need proper hosting and a database.

Why is this number so high?

This data comes from NPM and Packagist, and they both count any download. So this number doesn’t represent necessarily 138 million users, but simply this many downloads by any means including bots, CI environments and so on. I think for both package managers the goal was probably not to have a realistic representation of users, but rather a number that makes developers feel good. And I like that. It’s nice to see a number go up and it’s still a nice proxy for relative popularity.

The Web Component

This seemed like a good use-case for a web component. Always wanted to build one! I was surprised how easy it was.

This is how this looks in the HTML:

  My open source packages were downloaded roughly
    <download-counter inc-per-day="157444" date="2024-06-29T15:34:00Z" >
  </strong> times.

What’s nice is that if Javascript is not enabled, or Web Components are not supported, this will just fall back on showing the static number.

I included 3 parameters:

  • The last recorded download count (in the element value)
  • When that number was recorded (date)
  • Average number of downloads per day.

I wanted to include the date because I only intend to update these numbers rarely, so we need to know how many downloads have elapsed since the last time.

Writing the web component was surprisingly straightforward too. Here is it in its fully glory:

class DownloadCounter extends HTMLElement {

  connectedCallback() {
    this.count = +this.textContent; = new Date(this.getAttribute('date')); = (+this.getAttribute('inc-per-day')) / (3600 * 24 * 1000)


  calculateCurrentDownloads() {

    const currentDownloads =

Truncated by Planet PHP, read more at the original (another 20389 bytes)

Rob AllenUsing Monolog's TestHandler (25.6.2024, 10:00 UTC)

When I write integration tests with PHPUnit, I find it helpful use Monolog's TestHandler to check that the logs I expect are generated.

It's reasonably common that classes that write to a log take a PSR\Log\LoggerInterface in their constructor and then use that for logging. This usually logs to the error handler for pushing to Sentry or whatnot. This makes testing easy!

For testing we can use the TestHandler which stores the logs it receives and then they can then be inspected.

We set up like this:


namespace Test\Integration;

use Monolog\Handler\TestHandler;
use Monolog\Logger;
use PHPUnit\Framework\TestCase;

class FooTest extends TestCase
    protected TestHandler $testHandler;
    protected Logger $logger;

    public function setUp(): void
        $this->testHandler = new TestHandler();
        $this->logger = new Logger('test', [$this->testHandler]);

    // tests here

Then we can use it in a test like this:

public function testFoo(): void
    $foo = new Foo($this->logger);

    $result = $foo->bar();


    $this->assertTrue($this->testHandler->hasInfoThatContains('Created Baz'));

TestHandler has a number of methods that allow you to determine if a specific log has been written, along with getRecords(), clear(), etc to allow full inspection and control of the test log. Peruse the source for the class to see all the options, noting particularly the set of convenience methods in the DocBlock.

If your system under test uses a PSR-3 logger, then Monolog's TestHandler is a lovely way to test that your logs are as expected.

Cees-Jan KiewietUpdating (PHP) packages to ReactPHP Promise v3, and test your types with PHPStan (18.6.2024, 00:00 UTC)

With version 3, react/promise adds template types to communicate what kind of data the promise will hold once resolved. Add react/async 4.2 with to that and return type hints are a thing and both PHPStan and Psalm will understand them.

Photo of coloured lab veils

Photo by Kindel Media

Derick RethansXdebug Update: May 2024 (11.6.2024, 15:30 UTC)

Xdebug Update: May 2024

In this monthly update I explain what happened with Xdebug development in the past month. These are normally published on the first Tuesday on or after the 5th of each month.

GitHub and Pro/Business supporters will get it earlier, around the first of each month.

On GitHub sponsors, I am currently 44% towards my $2,500 per month goal, which is set to allow continued maintenance of Xdebug.

If you are leading a team or company, then it is also possible to support Xdebug through a subscription.

In the last month, I spend around 23 hours on Xdebug, with 22 hours funded.

Xdebug 3.4

I created the first alpha release of Xdebug 3.4 — with some preliminary PHP 8.4 support. The main reason for this was to test out the PIE tool, that James Titcumb is working on as part of his work for the PHP Foundation.

This new tool uses composer-style definition files. It also means that extensions like Xdebug can now be found on Packagist.

PIE is still in early development, but I shall be continuing to test out while it matures.

The 3.4.0alpha1 release itself adds a new XDEBUG_IGNORE HTTP variable to prevent Xdebug from initiating a debugging session for this request only. This feature was requested by the Symfony project, to prevent debugging sessions for the debugging toolbar.

Native Xdebug Path Mapping

In the previous update I wrote about Xdebug's new projects section, and the first project that I am hoping to fund through this: Native Xdebug Path Mapping.

After my announcement, several companies and individuals reached out. The funding is now nearly there, with good hopes that it will conclude soon.

You could be part of this too!

Once we cross the line, I will start with the implementation, which I intend to include into Xdebug 3.4.

Xdebug Videos

I have created no new videos since last month, but you can see all the previous ones on my channel.

If you have any suggestions, feel free to reach out to me on Mastodon or via email.

Business Supporter Scheme and Funding

In the last month, no new business supporters signed up.

Besides business support, I also maintain a Patreon page, a profile on GitHub sponsors, as well as an OpenCollective organisation.

If you want to contribute to specific projects, you can find those on the Projects page.

Xdebug Cloud

Xdebug Cloud is the Proxy As A Service platform to allow for debugging in more scenarios, where it is hard, or impossible, to have Xdebug make a connection to the IDE. It is continuing to operate as Beta release.

Packages start at £49/month, and I have recently introduced a package for larger companies. This has a larger initial set of tokens, and discounted extra tokens.

If you want to be kept up to date with Xdebug Cloud, please sign up to the mailinglist, which I will use to send out an update not more than once a month.

Become a Patron!
Christian WeiskeApache+PHP: Content-Length header is missing (8.6.2024, 13:09 UTC)

I received a photo in the Conversations XMPP app on my Android phone, but the image was not shown. Instead I got a message

Bildgröße auf prüfen

which translates to

Checking image size on

The other XMPP client Dino showed the images, though.

In Conversations bug report #240 it was observed that the Content-Length header was missing, and my server exhibited the same problem:

$ curl -I '
HTTP/1.1 200 OK
Date: Sat, 08 Jun 2024 12:38:47 GMT
Server: Apache/2.4.59 (Debian)
Access-Control-Allow-Methods: GET, PUT, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 7200
Access-Control-Allow-Origin: *
Content-Security-Policy: "default-src 'none'"
X-Content-Security-Policy: "default-src 'none'"
X-WebKit-CSP: "default-src 'none'"
Content-Type: image/jpeg

No Content-Length. I'm using the mod_http_upload_external Prosody module for file uploads together with the share_v2.php provided by it. That PHP script does set a Content-Length header, but nobody receives it!

Even a PHP script that only sends out a Content-Length header does not work:

header('X-Test: 23');
header('Content-Length: 42');
$ curl -I
HTTP/1.1 200 OK
Date: Sat, 08 Jun 2024 13:18:34 GMT
Server: Apache/2.4.59 (Debian)
X-Test: 23
Content-Type: text/html; charset=UTF-8

The header is missing.

The cause

Then I found Apache bug report #68973: Content-Length header missing in 2.4.59 is a breaking change which explained the symptom I experienced:

Apache version 2.4.59 fixed security issue CVE-2024-24795 by preventing CGI-like scripts (such as PHP) from sending out Content-Length headers.

A new environment variable ap_trust_cgilike_cl was introduced that restores to the old behavior.


I re-enabled the Content-Length header in my PHP applications by creating an apache configuration file

SetEnv ap_trust_cgilike_cl 1

enabling it and restarting apache2:

$ a2enconf cweiske-content-length
$ systemctl reload apache2
Davey ShafikIntroducing Bag 1.0: Immutable Values Objects for PHP (19.5.2024, 02:15 UTC)

For the last couple of years I’ve been using Value Objects in my projects to bring language-level strict types to what would typically be array data structures in my code. From method inputs to JSON API responses, value objects have almost entirely replaced arrays throughout. The ability to get runtime type checking and IDE auto-complete has eliminated many potential bugs, from key typos, to assigning an incorrectly typed value by accident: what type is an “amount” property in a credit card transaction API response? An integer of cents (or other minor units), a Money object such as brick/money or moneyphp/money? Or worst of all, a float?

About 18 months ago, I started using the excellent spatie/laravel-data v3 package for a new project, but I quickly realized there were a few features missing, most notably, factory support. Additionally, the collection class didn’t extend the Laravel Collection class and is anemic by comparison.

Note: spatie/laravel-data v4 adds support for factories, and has a slightly more capable DataCollection class. See below for details.

So I extended the base Data class and added factory support, following a similar pattern to Eloquent factories, including support for Sequences, and I extended DataCollection to add some missing functionality, and mostly, it was good.

Enter Spatie/Laravel-Data v4

Earlier this year, Spatie released v4 with support for Laravel 11 (and up until last month, no support for Laravel 11 in v3), with significant changes, including support for Factories, and a better DataCollection class. Unfortunately, the Factories built into v4 were incompatible with my own, and didn’t have the same feature set, and while better, the updated DataCollection class was still lackluster.

The upgrade process was difficult, and while ultimately successful, I was unhappy with the outcome. Then I decided that I would much prefer if my value objects were immutable, which was impossible using either v3 or v4, and ultimately, that, along with the difficult upgrade path to v4, led me to create Bag.

What is Bag?

Bag is a new library built from scratch — inspired by spatie/laravel-data — that provides immutable value objects for PHP. Built on top of Laravel’s excellent Validation and Collection classes, as well as Eloquent Factory Sequences, it is the value object library I wanted spatie/laravel-data to be.

Additionally, I leaned harder into the use of Attributes, for identifying Collection classes to be used for each value object class, wrapping, hiding data in both toArray() and toJson()/jsonSerialize(), and for identifying transformers (what Spatie calls “magical data object creation“).

I simplified input/output casting (as opposed to casting being for inputs, and transformers being for output),

I also added support for Variadics, something that the Spatie library does not allow. For a more detailed comparison of the two libraries, see the Bag documentation here.


Despite having a few more features, simple benchmarks of Bag do show it as being about 40-45% faster than spatie/laravel-data v4, and a whopping 70-78% faster than v3.

Benchmark Methodology

The benchmark script was intentionally very simple, using Laravel’s Benchmark class to get the average of 10 runs of a loop (default: 1000 iterations) that creates instances of a Bag or Spatie value object. I ran it both with 1,000 iterations and 10,000 iterations.

The value objects have the following features:

  • Class-level Input/Output Name Mapping to/from SnakeCase
  • A single property input name mapping from CamelCase
  • A single property input name mapping from an alias
  • A property with integer and required validations
  • A property input/output case from/to DateTime/formatted date string

You can see all the code for the benchmark

Truncated by Planet PHP, read more at the original (another 3402 bytes)

Matthew Weier O'PhinneyInitializing ZendHQ JobQueue During Application Deployment (7.5.2024, 20:19 UTC)

In the past few years, I've transitioned from engineering into product management at Zend, and it's been a hugely rewarding experience to be able to toss ideas over the fence to my own engineering team, and have them do all the fiddly tricky bits of actually implementing them!

Besides packaging long-term support versions of PHP, we also are publishing a product called ZendHQ. This is a combination of a PHP extension, and an independent service that PHP instances communicate with to do things like monitoring and queue management.

It's this latter I want to talk about a bit here, as (a) I think it's a really excellent tool, and (b) in using it, I've found some interesting patterns for prepping it during deployment.

What does it do?

ZendHQ's JobQueue feature provides the ability to defer work, schedule it to process at a future date and time, and to schedule recurring work. Jobs themselves can be either command-line processes, or webhooks that JobQueue will call when the job runs.

Why would you use this over, say, a custom queue runner managed by supervisord, or a tool like Beanstalk, or cronjobs?

There's a few reasons:

  • Queue management and insight. Most of these tools do not provide any way to inspect what jobs are queued, running, or complete, or even if they failed. You can add those features, but they're not built in.
  • If you are using monitoring tools with PHP... queue workers used with these tools generally cannot be monitored. If I run my jobs as web jobs, these can run within the same cluster and communicate to the same ZendHQ instance, giving me monitoring and code traces for free.
  • Speaking of using web workers, this means I can also re-use technologies that are stable and provide worker management that I already know: php-fpm and mod_php. This is less to learn, and something I already have running.
  • Retries. JobQueue allows you to configure the ability to retry a job, and how long to wait between retries. A lot of jobs, particularly if they rely on other web services, will have transient failures, and being able to retry can make them far more reliable.

So, what about queue warmup?

When using recurring jobs, you'll (a) want to ensure your queue is defined, and (b) define any recurring jobs at application deployment. You don't want to be checking on each and every request to see if the queues are present, or if the recurring jobs are present. Ideally, this should only happen on application deployment.

When deploying my applications, I generally have some startup scripts I fire off. Assuming that the PHP CLI is configured with the ZendHQ extension and can reach the ZendHQ instance, these scripts can (a) check for and create queues, and (b) check for and create recurring jobs.

As a quick example:

use ZendHQ\JobQueue\HTTPJob;
use ZendHQ\JobQueue\JobOptions;
use ZendHQ\JobQueue\JobQueue;
use ZendHQ\JobQueue\Queue;
use ZendHQ\JobQueue\QueueDefinition;
use ZendHQ\JobQueue\RecurringSchedule;

$jq = new ZendHQ\JobQueue();

// Lazily create the queue "mastodon"
$queue = $jq->hasQueue('mastodon')
    ? $jq->getQueue('mastodon')
    ? $jq->addQueue('mastodon', new QueueDefinition(
        new JobOptions(
            60, // timeout
            3, // allowed retries
            30, // retry wait time
            false, // validate SSL

// Look for jobs named "t

Truncated by Planet PHP, read more at the original (another 4534 bytes)

Derick RethansLocal Whispers (7.5.2024, 14:15 UTC)

Local Whispers

For most of the videos that I make, I also like to have subtitles, because sometimes it's easier to just read along.

I used to make these subtitles with an online service called, but they stopped allowing uploading of video files.

And then I found Whisper, which allows me to upload audio files to create subtitles. Whisper is an API from OpenAI, mainly known for ChatGPT.

I didn't like having to upload everything to them either, as that means that they could train their model with my original video audio.

Whisper never really worked that well, because it broke up the sentences in weird places, and I had to make lots of edits. It look a long time to make subtitles.

I recently found out that it's actually possible to run Whisper locally, with an open source project on GitHub. I started looking into this to see whether I could use this to create subtitles instead.

The first thing that their documentation tells you to do is to run: pip install openai-whisper.

But I am on a Debian machine, and here Python is installed through distribution packages, and I don't really want to mess that up. apt-get actually suggests to create a virtual environment for Python.

In a virtual environment, you can install packages without affecting your system setup. Once you've made this virtual environment, there's actually Python binaries symlinked in there, that you can then use for installing things.

You create the virtual environment with:

python3 -m venv `pwd`/whisper-local
cd whisper-local

In the bin directory you then have python and pip. That's the one you then use for installing packages.

Now let me run pip again, with the same options as before to install Whisper:

bin/pip install -U openai-whisper

It takes quite some time to download. Once it is done, there is a new whisper binary in our bin directory.

You also need to install fmpeg:

sudo apt-get install ffmpeg

Now we can run Whisper on a video I had made earlier:

./bin/whisper ~/media/movie/xdebug33-from-exception.webm

The first time I ran this, I had some errors.

My video card does not have enough memory (2GB only). I don't actually have a very good video card at all, and was better off disabling it, by instructing "Torch" that I do not have one:


And then run Whisper again:

./bin/whisper ~/media/movie/xdebug33-from-exception.webm

It first detects the language, which you can pre-empt by using --language English.

While it runs, it starts showing information in the console. I quickly noticed it was misspelling lots of things, such as my name Derick as Derek, and Xdebug as XDbook.

I also noticed that it starts breaking up sentences in a odd way after a while. Just like what the online version was doing.

I did not get a good result this first time.

It did create a JSON file, xdebug33-from-exception.json, but it is all in one line.

I reformatted it by installing the yajl-tools package with apt-get, and flowing the data through json_reformat:

sudo apt-get install yajl-tools
cat xdebug33-from-exception.json | json_reformat >xdebug33-from-exception_reformat.json

The reformatted file still has our full text in a line, but then a segments section follows, which looks like:

"segments": [
        "id": 0,
        "seek": 0,
        "start": 3.6400000000000006,
        "end": 11.8,
        "text": " Hi, I'm Derick. For most of the videos that I make, I also like to have subtitles, because",
        "tokens": [
                            50363, 15902, 11, 314, 1101, 9626, 624, 13, 1114, 749, 286,
                            262, 5861, 326, 314, 787, 11, 314, 635, 588, 284, 423, 44344,
                            11, 780, 50960
        "temperature": 0.0,

Truncated by Planet PHP, read more at the original (another 4630 bytes)

LinksRSS 0.92   RDF 1.
Atom Feed   100% Popoon
PHP5 powered   PEAR
ButtonsPlanet PHP   Planet PHP
Planet PHP