Drupal Planet

Tag1 Insights: From Malloc to Match (Part 2): The Evolution of PHP Through the Lens of a Systems Programmer

Take Away Jeremy Andrews, Tag1's Founding Partner and CEO, follows PHP and Drupal from the Symfony-driven rebuild of Drupal 8, through PHP 7's engine rewrite that shrank a typical value from about 48 bytes to 16, to Drupal 11.4, which needs just over a third of the database and cache lookups of Drupal 11.0 on a cold cache. He also shows how fixes from a Tag1 audit of London.gov.uk cut its slowest requests from about 75,000 database queries to 10,000.

This is Part 2 of a two-part series. Part 1 traced PHP's evolution from a C library wrapper through Drupal 7, where the procedural model hit its ceiling. This part covers the Java-fication of Drupal, the engine-level work that saved PHP from itself, and what Drupal and PHP are getting right about performance now.

The year Drupal 8 development started, we were publishing Drupal Watchdog, a print magazine that we shipped to subscribers around the world. I mention this because the magazine's lifespan, 2011 through 2016, almost exactly tracks the development of Drupal 8. We started it because the Drupal ecosystem felt like it could sustain anything. By the time we wound it down, the community had been through the most disruptive transition in the project's history.

Nat Catchpole, one of the Drupal 8 branch maintainers then and a Drupal core and Tag1 developer now, has said this to people in person for years but never in writing: "[My] biggest regret of accepting the D8 maintainer position was that I didn't make a condition of acceptance deletion of the 8.x branch and starting again after a year or so of trying to stabilise and improve Drupal 7. If we'd done that we potentially could have developed the minor/major release cycle we eventually landed on for 8.x or at least started 8.x development on a better footing than immediately after another long major release cycle that had burned people out. I did try to stop the branch being opened in the first place when it was, but wasn't confident enough at the time to try to reverse that once it had happened."

Drupal 7 itself had come out of a three- to four-year release cycle with, as Nat puts it, more than a year of death march at the end, and Drupal 8 was branched before a Drupal 6 site could be safely updated to Drupal 7 (known bugs in the update path could lose data; in one, every comment body went missing). The release cycle Nat wished for is the one Drupal adopted. Since Drupal 8, new features have arrived in minor releases every six months, and a major release mostly removes code that was already marked for removal. That's why moving to Drupal 9, 10, and 11 meant upgrading a site, not rebuilding it.

In his keynote at DrupalCon Rotterdam 2026, Dries Buytaert named Drupal's reputation gap as the project's number one challenge: too many people are judging the Drupal of ten years ago.[1] Performance is one place the gap shows, and some of that reputation was earned in the years this post covers, when Drupal took on a heavy new architecture while PHP was still paying for the trades it made in the 90s.

Those trades (every request starting from scratch, and every value carrying its type at runtime, plus a reference count for strings, arrays, and objects) show up when we profile Drupal sites today. Spotting them takes the systems programmer's view described in Part 1 (looking one layer down, at the PHP engine, at how memory is laid out, and at what the CPU actually does), and working at that layer is one of the ways we find and fix Drupal performance problems. Performance work is how Tag1 started; in January 2008, a few months after we opened, we ran a webinar with MySQL AB on tuning MySQL for Drupal.[2] Drupal and PHP have done a lot about those trades since.

The Java Cure

Between 2010 and 2015, the PHP ecosystem went through a cultural transformation. Applications were getting larger and more complex, and the "include and run" model of the 90s couldn't keep up, so the community looked to Java Enterprise Edition for answers.

I should admit my bias up front. When my daughter was in single digits, someone mentioned in front of her that they programmed in Java, and she promptly volunteered, "Oh, my dad doesn't like Java at all. He thinks it's horrible!" She wasn't wrong. So I know how easy it is to turn the criticism of "Java-fication" into a cartoon, which is why I'll start with the very real problems it was solving. Drupal's hook system was limiting, the class registry was a hack, the lack of standardized autoloading meant every framework shipped its own autoloader, and without a per-project dependency manager you downloaded libraries by hand and committed them to version control. At the scale our larger clients were running, those things got in the way of building software you could maintain.

What arrived solved them: Composer for dependency management, the PSR standards for autoloading and coding style, and Symfony components for HTTP handling and routing. Composer mattered most, because it gave each project its own set of dependencies: it resolved version constraints, generated the autoloader, and made installs reproducible through a lock file. Drupal 8 embraced all of this. There was a push to get Drupal developers "off the island" and rejoin the wider PHP world, and Drupal 8 was eventually marketed as "proudly found elsewhere."[3] The trouble was how much of it Drupal took on at once, and how often Symfony's assumptions about an application didn't match Drupal's.

The Cost of Ceremony

Dependency injection is a valid pattern. Decoupling your classes from their dependencies makes testing easier and makes it possible to swap implementations. Drupal 8 adopted a set of Symfony components rather than the full framework (HttpFoundation, HttpKernel, Routing, EventDispatcher, DependencyInjection, and ClassLoader),[4] and the DependencyInjection component brought a style where configuration became a primary programming activity. Developers wrote YAML files to define services, and each module's mymodule.services.yml became as important as its PHP code.

Tracking the execution flow became an exercise in indirection. A call to \Drupal::service('mymodule.thing')->doSomething() involved looking up the service ID in the container, checking whether the service had already been instantiated, building it from its definition if not, resolving all of its dependencies (and theirs), and finally calling the method. I remember stepping through Drupal 8 in a debugger for the first time and losing track of where I was after about twelve levels of indirection. In Drupal 7, you could grep for a function name and find it. In Drupal 8, you needed to understand the container, the service definitions, the compiler passes, and the event system before you could figure out what code actually ran when a page loaded.

The interface explosion was another symptom. Java's influence brought the convention of creating a MyServiceInterface, a MyServiceBase abstract class, a MyService concrete implementation, and sometimes a null implementation as well. For a language where duck typing had been the norm (if an object has the method you call, the call works, whatever class the object is), this felt like a straitjacket. Each of those four was one more file for the autoloader to find and the opcode cache (OPcache) to hold. Class names started reading like Java parodies: Drupal 8 shipped with a ContainerAwareEventDispatcher, a ContainerFactoryPluginInterface, and a ContainerDerivativeDiscoveryDecorator.[5]

A "Hello World" module went from a three-line .info file and a few lines of .module code in Drupal 7 to a directory structure containing an info file, a routing YAML file, a Controller class, and often a service definition. Drupal 7's hook_menu mapped each path to a page callback function; in Drupal 8 the paths moved into routing.yml files and the callbacks became Controller and form classes. Access rules moved too, from a callback function on each path to requirements on each route, checked by separate services. hook_init was replaced by event subscribers listening to KernelEvents::REQUEST.[6]

For the Drupal community, this was a hard transition. Developers who had been productive contributors for years suddenly needed to learn Symfony's component architecture, the service container, YAML configuration syntax, and object-oriented design patterns they'd never needed before. It was like learning a second framework from scratch. We watched experienced Drupal developers leave the ecosystem during this period, because the jump was too large and too abrupt, even for people who agreed with where it was headed.

At Tag1 we felt this directly. We were a Drupal consulting company, our clients wanted to move to Drupal 8 well before they could, and their in-house developers were retraining in the meantime. What had been a creative, accessible tool started to feel more like enterprise middleware, and for a while building with Drupal felt closer to a day job than something you did because you enjoyed it.

Symfony's HttpFoundation was approved for Drupal core in August 2011, a few months after the Drupal 8 branch opened, and Drupal 8.0.0 was released on November 19, 2015.[7] Drupal 7 had shipped in January 2011, so that was nearly five years without a new major release. By the time Drupal 8 was out, some agencies we knew had moved to other platforms rather than wait. The ones that stayed invested heavily in retraining, and they ended up with a more capable Drupal.

The complexity moved from implementation to architecture. We spent less time reading hook implementations and more time debugging the container. Bootstrapping a Drupal 8 page meant loading a lot of classes and a big compiled container just to return a string of HTML, and memory use went up, not down.

I go back and forth on whether the Java cure was a net improvement. For a small site that needed five modules, Drupal 7 was simpler, and we still support clients running it through D7 Extended Support nearly two years after its January 2025 end of life. For a large enterprise platform with dozens of developers and hundreds of integrations, the structure of Drupal 8 was necessary even if the weight of it was hard to love.

What has changed since is how much of that weight the community has paid down, and the compiled container is a good example. Because every PHP request starts from scratch, Drupal can't keep its service container in memory between requests, so it compiles the container once and caches the result for the requests that follow. The price comes due whenever the container has to be rebuilt, and installing a module rebuilds it. Nat recently wrote up what that meant for Drupal core's own tests. Until Drupal 11.2, an install did 50 or 60 container rebuilds; installing modules in batches brought that to 11 or 12 and took tens of seconds off every install, and Drupal 11.4 made the remaining ones more conditional, cutting a functional test install from 11 container rebuilds to 8, with work under way to reach 2.[8] Core's test suite installs Drupal thousands of times on every run, so the time saved on each install adds up. The drop in core's CI pipelines (the automated test runs on every proposed change), from about 55 minutes to between 5 and 7 minutes, came mainly from running tests in parallel, slowest first, and splitting up the slowest test classes, but the installer work is why the functional tests now reserve 80 CPUs instead of 128 and finish just as fast.

Caching With Metadata

Drupal 8's new architecture also made possible its biggest performance gain. If every request has to start from scratch, the next best thing is to skip as much of that work as possible, so Drupal 8 built caching into how each piece of a page is assembled. Each piece declares three things: cache tags, which name the data it depends on (saving a node invalidates only the pieces tagged with it); cache contexts, which say what it varies by, such as language or user permissions; and a max-age, which says how long it can be cached. With that metadata, Drupal can cache a page in pieces and leave the personalized parts as placeholders. The dynamic page cache keeps a copy of each page with its placeholders still in it, so on a cache hit a request mostly just has to fill in the placeholders. Drupal can fill them in before it sends the page, or send the page first and stream the personalized parts in afterwards, which is what BigPipe does. More and more, it fills them in before sending, using parts it has already rendered and saved in the render cache.[9]

All of this depends on the metadata being accurate, because a cache tag that's broader than it needs to be throws away cached work that was still good. When we audited London.gov.uk for the Greater London Authority, many page requests took from several seconds to over a minute, and the slowest requests on a cold cache ran about 75,000 database queries, with menu rendering a large part of it.[10] The Group module was adding a list cache tag (one invalidated whenever any group relationship changes) to its access checks, so creating a group relationship for any node invalidated the cached menu (almost entirely links to nodes) and every page it sat on. Webform was loading every form on the site just to build its asset library definitions, and APCu (PHP's in-memory cache on each web server) needed tuning. Several of the fixes went upstream: core gained a menu block option to skip computing the active trail (which links lead to the current page) for menus the theme doesn't style, and the Webform and Group fixes were committed to those modules. After the first phase of that work, those slowest requests ran about 10,000 database queries on a cold cache instead of 75,000, and application response times improved by 87%.

The Engine Room

While the framework layer was getting heavier, the PHP internals team was doing something that made me pay attention to the language again in a way I hadn't since reading the PHP source in the early 2000s. They went after the engine itself: smaller structs, better cache locality, and less pointer chasing. The result shipped as PHP 7.0 on December 3, 2015, a couple of weeks after Drupal 8.

The work started in early 2014 as an experimental branch at Zend called phpng (PHP Next Generation) and went public that spring. It was controversial because it touched nearly everything in the engine, but the benchmarks won the argument. In August 2014 the internals team voted 47 to 2 to merge it, and it became the foundation of PHP 7.[11]

The version number has its own history. PHP 6 had been in development for years with the ambitious goal of native Unicode support throughout the engine. The plan was to represent every string internally as UTF-16 using the ICU library, which would have solved the endless character encoding headaches that plagued web developers. But the implementation was enormously complex, it required changes to virtually every string-handling function in the language, and it was slow and memory hungry. In March 2010 the project was abandoned. Some of the work developed alongside it, such as namespaces, had already shipped in PHP 5.3. When it came time to name the next major release in 2014, the internals team voted to skip 6 entirely, in part because books and articles about the never-released PHP 6 were already in circulation.[12]

Zval Compaction

In PHP 5, the zval struct was 24 bytes on a 64-bit system:

struct _zval_struct { zvalue_value value; /* 16 bytes */ zend_uint refcount__gc; zend_uchar type; zend_uchar is_ref__gc; };

That number understates the cost. PHP 5 zvals were almost always allocated on the heap individually, and each one carried an extra garbage collection pointer plus the allocator's own overhead, which worked out to about 48 bytes per zval in practice.[13] The refcount was attached to the zval itself, so even temporary values paid for it, and the value field often pointed at yet another heap allocation, meaning accessing a variable involved double indirection and frequent CPU cache misses.

PHP 7 compacted the zval to 16 bytes. In its 7.0 form:

struct _zval_struct { zend_value value; /* 8 bytes */ union { struct { zend_uchar type; zend_uchar type_flags; zend_uchar const_flags; zend_uchar reserved; } v; uint32_t type_info; } u1; /* 4 bytes */ union { uint32_t next; /* hash collision chain */ uint32_t cache_slot; /* ...and other context-specific uses */ } u2; /* 4 bytes */ };

Integers and floats still live inside the zval, as they did in PHP 5, and null, true, and false are encoded in the type alone, but now none of them need a heap allocation or a refcount. The refcount moved out of the zval and into the thing being pointed to (strings, arrays, objects), so zvals themselves can live on the stack or be embedded contiguously in other structures. That u2 union is a nice systems touch: it's the four bytes of padding the struct would have had anyway, reused for whatever the surrounding context needs, such as the collision chain in a hash table. Nearly every line of PHP touches a variable, so nearly every line got faster when the zvals for the most common types went from roughly 48 bytes in a separate allocation to 16 bytes stored inline, with one fewer pointer to chase. If you've ever profiled a hot path and found the CPU spending its time waiting on cache misses, you understand why this mattered.

The HashTable Rewrite

The HashTable got the same treatment. The PHP 5 HashTable allocated every bucket separately, and each bucket carried four pointers just for linking: two for a doubly-linked list that preserved insertion order and two for the collision chain. Iterating meant chasing pointers from one bucket to the next, with no guarantee that adjacent buckets were anywhere near each other in memory, and every bucket pointed to a zval that was itself a separate allocation.

The phpng rewrite put the whole table in one allocation: a hash index followed by a contiguous array of Bucket structs, with the zval embedded directly in each Bucket. In C++ terms, this was the equivalent of replacing std::list with std::vector. Iterating a PHP 7 array became a linear scan of contiguous memory, and the CPU prefetcher could finally do its job. A write-up of the new implementation measured the per-element cost dropping from 144 bytes in PHP 5.6 to 36 bytes in PHP 7 (32 for packed arrays).[14]

The PHP 7.0 release announcement promised that PHP 7 would be "up to twice as fast as PHP 5.6," and Zend measured Drupal 8 running 72% faster on PHP 7 than on 5.6, with WordPress executing roughly a quarter of the CPU instructions per request.[15] We were running load tests at Tag1 during this period for several clients, and the PHP 7 upgrade was one of those rare changes where the numbers just got better across the board without anyone touching a line of application code. The language was still paying for dynamic types and reference counting on nearly every operation, but the underlying data structures were no longer actively hostile to the hardware.

The AST

PHP 7 also introduced an Abstract Syntax Tree step between parsing and compilation, which the internals team approved in a 47 to 0 vote.[16] Previously the parser emitted opcodes directly, which meant the compiler couldn't see the full picture of what it was compiling. The AST decoupled parsing from code generation, which made smarter optimization passes possible, and together with the companion Uniform Variable Syntax RFC it gave PHP 7 consistent left-to-right rules for variable expressions. The static analyzers most PHP developers know, PHPStan and Psalm, build on the userland PHP-Parser library (userland meaning written in PHP, not C) instead of the engine's own AST; Phan is the one that reads the engine's AST directly, through the php-ast extension. Between them, these tools gave developers a measure of compile-time safety for a language that had never had it, catching type errors and dead code before the application ran.

Modern PHP and Its Governance

The PHP 8.x era, which started in November 2020, brought features developers had long used in other languages: union types, named arguments, attributes, and the match expression in 8.0; enums and fibers in 8.1; property hooks in 8.4; and a pipe operator in 8.5. PHP 8.6 is due on November 19, 2026, and brings partial function application.[17] I learned PHP from Drupal's code in 2002, on PHP 4, and the language today barely resembles it. Getting those features in took years, and sometimes several failed votes.

The RFC Process

PHP evolves through RFCs (Requests for Comments), and since 2019 every RFC's main vote has needed a two-thirds majority to pass (language changes already did).[18] Voting is open to people with php.net accounts who have contributed to the project, plus a few community representatives, and in practice the voting pool is modest. This process has produced some excellent features and some baffling outcomes. There is no BDFL (Benevolent Dictator for Life) and no corporate owner making unilateral decisions. PHP stopped being Rasmus Lerdorf's project alone in the PHP 3 days, when he and the two developers who rewrote its parser decided to build it together, and it has been a group effort ever since (he still contributes, including a fix to the opcode cache as recently as March 2026).[19] The upside of deciding by vote is real community ownership; the downside is that a motivated minority can block a controversial feature, and "no" voters don't always explain their reasoning, which makes iteration difficult.

Property hooks, which let a class run its own code when a property is read or written, are a good example of how long things can take. The first draft of a property accessor syntax was written in 2009. Its successor, the accessors RFC, came to a vote in January 2013 and failed 34 to 22, short of two thirds. A decade later, in January 2023, the related asymmetric visibility RFC (letting anyone read a property while only the class itself can change it) failed 14 to 12. Then in April 2024, property hooks passed 42 to 2, a revised asymmetric visibility RFC passed 24 to 7 that August, and both shipped in PHP 8.4 that November.[20]

The PHP Foundation, founded in November 2021, changed how PHP development is funded. Nikita Popov, who had been working on PHP full time at JetBrains, decided to focus on LLVM instead (he is now LLVM's lead maintainer), and his departure showed how much of the project depended on a handful of people. JetBrains, Automattic, Laravel, Acquia, Zend, Symfony, and others set up the Foundation to pay people to work on PHP internals, and by the start of 2026 it was funding eleven developers.[21]

The Generics Question

The feature most obviously missing from modern PHP is generics. The first generics RFC was written in 2016, targeting PHP 7.1, and never came to a vote. A nearly complete experimental implementation in 2020 and 2021 ran into fundamental problems: PHP checks types at runtime, so generics the runtime actually enforced would mean checking generic parameters on every call, and useful type inference (working out a variable's type without being told) is hard when the compiler only ever sees one file at a time. The prototype stalled.

The community has spent years working around this. PHPStan and Psalm read generic types written into docblocks (structured comments above a function or class), so the analyzer checks them even though the runtime never does. But the language itself can't express something as basic as "this is a collection that only contains User objects" in a way the runtime enforces.

The Foundation has kept pushing. It published a survey of the options in 2024, and in 2025 it floated generics checked only at compile time, limited to interfaces and abstract classes, where PHP can check them once when it loads a class rather than on every call.[22] That design trades full coverage for a version the engine can afford. Then in June 2026 a different proposal, bound-erased generics, was voted down 19 to 7. It would have let code declare generic types while the runtime treated each type parameter as the bound it declared (for example, that T implements Countable), leaving the rest to static analyzers. Opponents worried that syntax the engine doesn't enforce would weaken PHP's type guarantees, and that code written against it would break if enforced generics arrived later.[23] PHP still has no generics.

PHP has gotten fast, but the way it runs code limits which features it can add. Rust, by contrast, handles generics through monomorphization at compile time: the compiler generates specialized code for each concrete type, with zero runtime cost (the price is paid in build times instead, and the Rust project's own annual survey calls slow compilation a perennial concern of its users).[24] PHP settles most types only when the code runs, so it has nothing to specialize ahead of time.

The JIT

PHP 8.0's JIT compiler ran into the limits of web workloads. It built on OPcache and used DynASM (the code generator from LuaJIT) to turn Zend opcodes into native machine code, with two modes: a function JIT and a tracing JIT that watches for hot code paths and compiles those. On paper, that should make PHP much faster.

In practice, Drupal, WordPress, and Laravel spend most of their time waiting on database queries, cache calls, and the network. The PHP code in between is glue, and the functions it relies on (json_encode(), str_replace(), PDOStatement::execute()) are already compiled C. The exception is a cold cache, right after a deploy or a cache clear, when Drupal does real CPU work: processing YAML files and the attributes in its PHP code, compiling Twig templates, and building combined CSS and JavaScript files, then merging all of that into large metadata arrays that have to be written to the cache. Each result is cached as soon as it's built, so few requests pay that cost, though the ones that do are slow.[8] Even though the JIT keeps its compiled code in OPcache shared memory and reuses it from one request to the next, a typical page gives it very little CPU-bound PHP to speed up. The JIT RFC was upfront about this, reporting WordPress going from 315 to 326 requests per second, about 3.5%, and the 8.0 release announcement said typical application performance was "on par with PHP 7.4."[25] PHP 8.4 replaced the original implementation with a new JIT, which put the x86 and ARM back ends behind one shared code generator, but the JIT still isn't enabled by default.[26]

The JIT shines for computational tasks: math-heavy PHP code, parsers, image processing. The same RFC measured more than 4x on a Mandelbrot benchmark. But that's not what most PHP developers build, and it's not what most Drupal sites do.

Features That Landed

PHP 8.x did more for the developer through smaller, more targeted features.

The match expression in 8.0 brought strict comparison and expression-based matching. PHP 8.0 also fixed the worst of loose comparison (0 == "foo" is finally false), but switch compares loosely, so "1e3" still matches case "1000": because both look like the number 1000. match uses === and doesn't have that problem.[27] Enums arrived in 8.1, giving the language proper enumerated types whose cases are objects. They're limited compared to Rust's enums, where each case can carry its own data, but they replaced a generation of class constants pretending to be enums. Real pattern matching (testing a value's shape and pulling it apart in one step) is the subject of an RFC under discussion since December 2025, but it missed the PHP 8.6 feature freeze on September 22, 2026.[17]

Attributes in 8.0 gave PHP a native replacement for the docblock annotation hack that Drupal 8 and Symfony relied on. Previously, metadata lived in comments (/** @Annotation */) that libraries like Doctrine Annotations pulled out through reflection and ran through their own regex-driven tokenizer and parser, then cached. That this worked at all says a lot about PHP developers' willingness to abuse any available mechanism. Attributes moved metadata into native syntax that the engine parses and stores with the compiled code, where reflection can read it without anyone parsing comments.

Fibers, which arrived in 8.1 alongside enums, are PHP's building block for asynchronous code (code that starts something slow, like a database query, and does other work while it waits for the answer). A fiber lets a function pause partway through and resume later, with the program itself deciding when to switch. The limitation is that the standard library is still blocking. A file read, a database query through PDO, or almost any other I/O call stops the whole process until it returns, so no other fiber can run in the meantime. True async requires an event loop, and PHP doesn't ship one. Libraries like Revolt and Amp fill the gap, but the ecosystem hasn't broadly adopted them. A "True Async" RFC that would have brought coroutines and a scheduler into the language was withdrawn in July 2026, and a follow-up RFC proposing the engine-level scheduler core it would have been built on is still under discussion.[28]

The async gap shows how deeply the shared-nothing model (every request starts from scratch and shares nothing with the others) is embedded in the language. In Go, the runtime includes a scheduler and goroutines are a first-class primitive. In Node.js, the event loop is the runtime. In Rust, the async runtime (typically Tokio) is a library, but the language's async/await syntax and the Future trait are built in. In PHP, fibers provide the mechanism for pausing and resuming execution, but everything around them (the event loop, the async I/O, the scheduler) has to come from userland libraries.

Fibers themselves are well engineered and do exactly what they were designed to do, and Drupal has put them to good use. But PHP's ecosystem was built over 30 years of synchronous request-response assumptions, and it can't broadly adopt async patterns until the standard library itself becomes non-blocking, a change of such magnitude that it may never fully happen. Projects like Swoole and FrankenPHP take a different approach: they replace PHP-FPM (the process manager that runs PHP behind a web server) and handle concurrency in the server layer. Swoole is a C extension that runs its own long-running coroutine server and swaps PHP's blocking functions for coroutine-aware versions; FrankenPHP embeds PHP in the Go-based Caddy web server and keeps PHP synchronous inside Go workers. Both are pragmatic, but they move concurrency outside the language.

I started learning Rust in 2020, and my first project was Goose, a load testing tool written in Rust and inspired by Locust, the Python framework. We'd been using Locust for client load tests at Tag1, and early on, before we'd even optimized the code, Goose ended up generating at least eleven times as much traffic as Locust per CPU core.[29] The bigger thing for me was how Rust's async model worked. After years inside PHP's concurrency constraints, it felt like someone had finally gotten the tradeoffs right: real concurrency with memory safety and zero-cost abstractions. It changed how I think about PHP's limitations, and Goose is now the primary tool we use to load test Drupal sites.

The Full Circle

I came to Drupal a few months before 4.0.0, and looking back from 11, its arc mirrors PHP's own: procedural hooks in Drupal 4 through 7, the Symfony-driven enterprise architecture of Drupal 8 and 9, and the gradual simplification in Drupal 10 and 11 as PHP itself provided better tools.

Drupal 9, released on June 3, 2020, was deliberately undramatic: Drupal 8 with deprecated code removed and dependencies updated. The community had learned from the Drupal 7-to-8 chasm that major version upgrades needed to be manageable, and for most sites the upgrade from 8 to 9 meant running Composer and fixing whatever Drupal 8 had already warned them about. After the long Drupal 8 cycle, this was a relief.

Drupal 10, released on December 15, 2022, continued the pattern. It required PHP 8.1, meaning the codebase could start using readonly properties, enums, fibers, and intersection types, and it moved to Symfony 6 and CKEditor 5. Drupal 10.2 then began replacing docblock annotations with attributes for plugin discovery, and added fiber support to the renderer and to BigPipe.[30]

Drupal 11, released on August 2, 2024, moved to PHP 8.3 as its minimum and Symfony 7 underneath, and its 11.1 release that December brought the biggest change to how you write a hook since Drupal 8:

Drupal 7: You defined a function with a magic name, and the framework found it by building function names from strings and checking whether they existed.

function mymodule_node_insert($node) { // Your code here }

Drupal 8: For anything the old hooks couldn't express, you created an EventSubscriber class implementing an interface, registered it as a service in YAML, and defined which events it listened to. A single hook's worth of functionality required a class, a YAML entry, and knowledge of the Symfony event system.

Drupal 11.1 and later: You put a method in a class under your module's src/Hook directory and mark it with an attribute, and the framework discovers it when it builds the container.[31]

namespace Drupal\mymodule\Hook; use Drupal\Core\Hook\Attribute\Hook; use Drupal\node\NodeInterface; class MymoduleHooks { #[Hook('node_insert')] public function nodeInsert(NodeInterface $node): void { // Your code here } }

That brings us back around. You're once again labeling a function with what it does, but now it's backed by the engine's native attribute system instead of string concatenation and function_exists() checks. The method is type-hinted, and the hook class is an ordinary autowired service: the container reads its constructor's type hints and passes in the services the class asks for.

Drupal 7's hooks were simple but fragile, and Drupal 8's event subscribers were solid but verbose. Drupal 11's attribute-based hooks are simple and solid, because the language itself now provides machinery that the framework used to build by hand. The framework still carries weight from the Symfony layer and still loads a lot of classes to handle a request, but the developer experience is finally converging on something that's both structured and humane. A new contributor can look at an attribute-based hook, understand what it does, and write one without reading a chapter on the Symfony event system first.

Drupal is also getting faster again, and PHP's newer features are part of the reason. Drupal 11.3, released in December 2025, used fibers and rendered more of each page in placeholders so that database and cache operations could be combined, cutting both by roughly a third on a cold cache. Nat called it the biggest performance improvement in a decade. Core's automated performance tests, which run on Gander (the framework we built with the Google Chrome team), have driven many of Drupal's recent gains: they show when a change makes Drupal faster, sometimes point to the cause of a problem, and keep those gains from slipping back.[32] Drupal 11.4, released in July 2026, halves database queries compared to 11.3 across a wide range of requests, and on a completely cold cache it needs just over a third of the database and cache lookups of Drupal 11.0. Nat's DrupalCon Rotterdam session on the work explains how it was done.[33]

Drupal 12 is in beta now and scheduled for release the week of December 7, 2026. It will require PHP 8.5 and Symfony 8, and Drupal 10 reaches end of life that same week. Drupal's policy is to require the newest stable PHP release when a major version reaches its first beta. PHP 8.5 gets security fixes through the end of 2029.[34]

What PHP Got Right

Rasmus started what became PHP in 1994 as a set of C tools for his personal home page. Today PHP can be strictly typed (if you ask it to be), it has a JIT, enums, fibers, attributes, and property hooks, and according to W3Techs it runs on 69.8% of websites whose server-side language is known.[35] The biggest performance jump came from PHP 5.6 to PHP 7, achieved mostly through the kind of data structure work that systems programmers respect.

PHP survived its own worst tendencies. register_globals was eventually turned off by default and then removed in PHP 5.4. The mysql_* functions that taught a generation of developers to write SQL injection vulnerabilities were deprecated in 5.5 and removed in 7.0, leaving PDO and mysqli with prepared statements.[36] The chaos of every framework shipping its own autoloader, and of libraries downloaded by hand, gave way to Composer and PSR standards. The language governance, messy as it can be (property hooks took fifteen years from first draft to release), has consistently moved in the direction of safety and rigor.

The community deserves credit too. PHP developers are often dismissed by people in other ecosystems, but they produced WordPress, Drupal, Laravel, Symfony, MediaWiki, and much of the rest of the web's content layer. When something is broken, people build workarounds, and the best of them become standard practice. PHPStan and Psalm brought static analysis to a dynamic language because developers wanted type safety and weren't willing to wait for the runtime to provide it. Composer exists because PEAR, PHP's older package installer, put libraries in one system-wide location by default and made it awkward to give each project its own dependencies.

The shared-nothing, request-response model remains PHP's core strength and its core limitation. Every request starts fresh, loads the application, processes the request, returns the response, and throws everything away. There's no shared state to corrupt, no memory leaks that accumulate over hours of uptime, and no concurrent access bugs to debug at 3 AM. But there's also no way to hold a connection pool, maintain a websocket, or handle concurrent streams without bolting on something that works against the fundamental architecture.

Swoole, RoadRunner (Temporal, which runs its PHP workers under RoadRunner, recently gained a Drupal integration Károly Négyesi wrote for a Tag1 client), and FrankenPHP are pushing PHP into long-running process territory (Laravel Octane will run on any of them). In 2025, FrankenPHP moved under the PHP Foundation, so the organization that funds much of PHP's core development now backs long-running PHP too. FrankenPHP remains a separate project from PHP itself, though. When it moved, the Foundation told the internals mailing list there were no plans to merge it into PHP the way PHP-FPM is, and that doing so would take an RFC. FrankenPHP's developers contribute changes upstream, such as one merged in July 2026 that makes PHP's command line available to programs that embed PHP, as FrankenPHP does.[37] Swoole, RoadRunner, and FrankenPHP are all still swimming against the current of how the language and most of its ecosystem were designed.

For a C programmer looking at PHP in 2026, the language is tolerable in a way it wasn't in 2004. The engine is well optimized by people who clearly understand cache lines and struct packing, and the frameworks still carry real weight, though Drupal has spent its last few releases measuring that weight and taking it out.

Whether you should use it depends on what you're building. PHP is excellent for content-driven web applications, request-response APIs, and anything where the shared-nothing model is an advantage rather than a limitation. It's the wrong tool for high-concurrency services, real-time systems, or infrastructure that needs deterministic performance. The language knows this, and the community mostly knows this. The arguments happen at the boundaries, where an application starts as a content site and evolves into something that needs persistent connections or sub-millisecond response times.

Drupal turned 25 in January.[38] I've been part of the community since 2002, long enough to have complained about every major architectural decision and then watched most of them work out in the end. The project survived its own ambition, the loss of a lot of contributors, a release cycle of nearly five years, and the kind of technical debt that would have killed a less persistent community, and it came out the other side with a modern, typed, attribute-driven architecture running on an engine that a C programmer can respect.

Rotterdam has a saying, "Niet lullen maar poetsen" (stop talking, start working), and in his keynote Dries argued that Drupal has been very good at the working part, and now needs to talk louder about what is working.[1] On performance, there's a lot to talk about. When Rasmus profiled Drupal at the OSCMS Summit in 2007, more than half of each request (as I remember it) went to loading Drupal before it could start on the page. Since then PHP 7 rebuilt the engine around the hardware, Drupal 8 built caching into how every piece of a page is rendered, and core now measures itself and uses the numbers to take that cost back out.

If you run a Drupal site today, the 11.3 and 11.4 gains ship with Drupal, so staying current is how you get them, and with Drupal 12 requiring PHP 8.5 a PHP upgrade is coming either way.[34] Look at what your pages cost on a cold cache and not only a warm one: it affects a small share of requests, but it can make a big difference for new visitors and for how responsive a site feels after a deploy or a cache clear.[8] When a page is slow, measure it before guessing. When the fix belongs upstream, send it there, the way the London.gov.uk fixes went into core, Webform, and the Group module.

References

[1] Drupal.org, "DriesNote Rotterdam: Drupal is light-years ahead of its reputation", September 29, 2026.

[2] Tag1, "Achieving Optimal MySQL Performance for Drupal", January 2008.

[3] Larry Garfield, "Getting off the island in 2013"; Drupal.org, "Proudly found elsewhere".

[4] Fabien Potencier, "Symfony2 meets Drupal 8", Symfony blog, March 22, 2012.

[5] api.drupal.org: ContainerAwareEventDispatcher, ContainerFactoryPluginInterface, ContainerDerivativeDiscoveryDecorator.

[6] Drupal change records: hook_menu replaced by routing and related systems; hook_init() removed. Drupal 7 module requirements: Writing .info files; #1793520, "Add access control mechanism for new router system".

[7] Drupal issue #1178246, "Add Symfony2 HttpFoundation library to core" (August 30, 2011 comment recording Dries's approval) and change record 1320400 (October 2011); Drupal 8.0.0 release.

[8] Nathaniel Catchpole, "Optimizing Drupal Core CI", Tag1, September 17, 2026; Drupal issue #3571994, "Reduce container rebuilds in functional tests" (committed March 11, 2026).

[9] Dries Buytaert, "Making Drupal 8 fly", February 19, 2015; Drupal.org documentation, Cache API and Cache tags.

[10] Tag1, "Greater London Authority" case study; Drupal issue #3529464, "Make menu trail behaviour in SystemMenuBlock optional" (committed February 2026); Webform issue #3497954, "Excessive memory use when building dynamic library definitions" (committed September 2025); Group issue #3538509, "Try to remove group_relationship list cache tags in group_entity_access()" (committed July 2026).

[11] phpng RFC ("a team of developers led by Dmitry Stogov"); "Move phpng to master" RFC, vote closed August 14, 2014, 47 to 2; PHP 7.0.0 release announcement.

[12] Johannes Schlüter, "Future of PHP 6," March 12, 2010; Name of Next Release of PHP RFC (2014).

[13] Nikita Popov, "Internal value representation in PHP 7, part 1", May 2015.

[14] Nikita Popov, "PHP's new hashtable implementation", December 2014.

[15] PHP 7.0.0 release announcement; Zend Technologies, PHP 7 performance infographic (2015). The phpng RFC itself cited gains of 20% to 110% on WordPress, Drupal and SugarCRM.

[16] Nikita Popov, Abstract Syntax Tree RFC, 2014 (vote held August 18 to 25, 2014, accepted 47 to 0); Nikita Popov, Uniform Variable Syntax RFC, 2014.

[17] PHP 8.5 release announcement, November 20, 2025; PHP 8.6 release schedule; Partial Function Application v2 RFC.

[18] Abolish Narrow Margins RFC, drafted 2016, accepted February 2019; Voting process RFC.

[19] The PHP Documentation Group, "History of PHP", php.net ("Andi, Rasmus, and Zeev decided to collaborate in the development of a new, independent programming language"); Rasmus Lerdorf, php-src commit "Fix GH-21411: opcache_compile_file() fails to early-bind classes without parents", March 19, 2026.

[20] Property get/set syntax draft (2009); Accessors RFC v1.2 (2013); Asymmetric visibility RFC (2023); Property hooks RFC (2024); Asymmetric visibility v2 RFC (2024) and its vote result, August 10, 2024; PHP Foundation, "PHP 8.4: How Property Hooks Happened"; PHP 8.4 release announcement.

[21] JetBrains, "The New Life of PHP: The PHP Foundation", November 22, 2021; Phoronix, "Red Hat Engineer Nikita Popov Now The Lead Maintainer For LLVM", October 2024; PHP Foundation, Impact and Transparency Report 2025.

[22] PHP Foundation, "State of Generics and Collections", August 2024; PHP Foundation, "Compile time generics: yay or nay?", August 2025.

[23] Seifeddine Gmati, Bound-Erased Generic Types RFC, May 2026, declined 19 to 7 in a vote that closed June 28, 2026; vote discussion.

[24] rustc dev guide, "Monomorphization" ("it comes at the cost of compile time"); Rust Survey Team, "2024 State of Rust Survey Results", Rust Blog, February 13, 2025 ("slow compilation was at the top of the list, as it seems to be a perennial concern of Rust users"); Rust Survey Team, "2025 State of Rust Survey Results", Rust Blog, March 2, 2026.

[25] Dmitry Stogov and Zeev Suraski, JIT RFC, 2019; PHP 8.0 release announcement.

[26] Dmitry Stogov, JIT based on IR framework RFC, 2023; PHP 8.4 release announcement.

[27] Ilija Tovilo, Match expression v2 RFC; Nikita Popov, Saner string to number comparisons RFC.

[28] Fibers RFC (Aaron Piotrowski and Niklas Keller); True Async RFC (status: Canceled; PHP.Watch lists it as withdrawn, last updated July 22, 2026); Concurrency Support in the PHP Engine RFC, July 23, 2026.

[29] Tag1, "Goose Attack: A Locust-inspired Load Testing Tool In Rust", May 2020; Tag1 TeamTalk #016, "Introducing Goose, a highly scalable load testing framework written in Rust", June 16, 2020; Tag1, "Choosing the right load testing tool: JMeter vs. Locust vs. Goose", February 2021.

[30] Drupal 10.0.0 release; change records on attribute-based plugin discovery and PHP Fibers in rendering.

[31] Drupal 11.0.0 release; Drupal 11.1.0 release; change record "Support for object oriented hook implementations using autowired services".

[32] Drupal core blog, "Drupal 11.3.0: Biggest performance boost in a decade", December 17, 2025; Tag1, Gander; Tag1, "Performance Testing Leads To Major Improvements for Drupal CMS, and all of Drupal", January 2025; Nathaniel Catchpole, "From Reactive to Proactive: Automated Performance Testing Success Stories", DrupalCon Vienna session, October 16, 2025.

[33] Gábor Hojtsy, "Drupal 11.4.0 is now available", Drupal.org, July 1, 2026; Nathaniel Catchpole, "How we made the biggest performance gains in a decade in Drupal core", DrupalCon Rotterdam session, September 30, 2026.

[34] Drupal 11.4.0 release; Drupal 12.0.0-beta1 release, September 30, 2026; Drupal core release schedule; Gábor Hojtsy, "Announcing Drupal 12.0.0 platform requirements", January 14, 2026; Drupal issue #3406215, "Default to requiring the latest stable PHP release available when a new major version reaches the first beta window"; Drupal issue #3588847, "Update to Symfony 8.1" (committed July 28, 2026); php.net, Supported Versions (PHP 8.5 security support until December 31, 2029).

[35] W3Techs, "Usage statistics of PHP for websites", retrieved September 29, 2026.

[36] PHP 5.4.0 release announcement; mysql_connect manual page.

[37] PHP Foundation, "FrankenPHP is now officially supported by The PHP Foundation", May 15, 2025; Jakub Zelenka, "FrankenPHP moving under the PHP GitHub organization", PHP internals mailing list, May 13, 2025 ("At this time, there are no plans for tighter integration into php-src the way PHP-FPM or the CLI SAPI are"); php-src pull request #21385, "Make php-cli functionality available in embed build," merged July 28, 2026; Károly Négyesi, "Beyond Batch and Queue: Temporal integration with Drupal," Tag1, July 8, 2026 (Temporal workers run under RoadRunner); Laravel Octane documentation.

[38] Drupal.org, "Get ready to celebrate Drupal's 25th birthday on Jan 15th, 2026".

Jacob Rockowitz: Vibing Drupal - My Tips and Tricks for 10x Drupal Development using AI

Looking back on a year of AI experimentation, I want to document my tips and tricks for using AI with Drupal development. Early in this journey, after reading books like Beyond Vibe Coding, listening to the creator of Claude Code, and attending Drupal events, I saw and heard how people using AI dramatically increased their productivity.

I keep coming back to Kent Beck's quote: "The value of 90% of my skills just dropped to $0. The leverage for the remaining 10% went up 1000x. I need to recalibrate." It led me to conclude that my goal when using AI is to 10x my productivity by focusing on that remaining 10%: experience, judgment, and understanding of how Drupal works.

Here are my tips and tricks to 10x my productivity using the remaining 10% of my skills. I am highlighting those that can 2x productivity and lead you toward 10x and beyond.

Funny side note: when I asked AI to review this post, it was really unhappy that the 2x tips math didn't add up to 10x, but you get the goal of this post. My hope is that you'll scan or consume this list, take something away, and keep up with our fast-moving times.

AI

Models

I like the output and price of GPT-5.6 Luna. I’d heard that in Ultra mode it rivals the more expensive models, but when I tried it, the code was worse and it took longer.

Rather than building a complex evaluation setup, I run an occasional bake-off: one simple prompt where I already know what a good plan or code looks like. I also ask the AI to research the cost, efficiency, and gossip around my available models. That’s how I learned GPT-6 Luna costs half as much as GPT-5.6 Luna,...Read More

BloomIdea: Quality incidents for Drupal Commerce: from the customer's complaint to the repaired item on its way back

A customer writes in: the sole of the left shoe has come away, three weeks after delivery. Someone answers the email, someone else notes it in a spreadsheet, the repair is agreed by phone with the workshop, and when the pair is ready, a shipment has to be made for an order that shipped a month ago. Six months later, nobody can say whether gluing fails more often than stitching, or on which model.

Commerce Quality Incident turns that complaint into a record in the store itself, attached to the order, the order item and the SKU that failed, with its own number, workflow and history. It is free software under GPL-2.0-or-later, published on drupal.org, and it builds on what a Commerce site already has: orders, State Machine, Commerce Log and Commerce Number Pattern. Version 1.1.0 adds the last step: the shipment that takes the repaired or replaced item back to the customer.

What you can build with it
  • One record per complaint, numbered like the orders (QI-2026-00001), reached from the order's own admin page, where every incident of that order is listed.
  • A defect report you can count. The category and subcategory come from a fixed list in the form, so "Manufacturing defect: Gluing" is the same thing every time, along with a severity from low to critical.
  • Categories that fit what you sell. The default plugin covers any product; a Footwear & Bags plugin ships as a worked example; your own takes one PHP class.
  • An incident that cannot be closed without saying how it was resolved.
  • The repair or replacement order, created from the incident with Commerce Make-to-Order, with its priority taken from the incident's severity.
  • The return shipment, created from the incident with Commerce Shipping, new in 1.1.0.
  • Defects found in production or at receiving, logged with no order at all.
The shipment Commerce would not build

The return shipment needed a submodule of its own because of how Commerce Shipping guards against shipping an item twice. Its shipment form only offers order items that no other shipment carries. A repaired pair travels back for an order item that already went out on the order's first shipment, so a shipment added there for the return has no item in it. Carriers that declare contents refuse it: UPS, for a parcel leaving the European Union, answers 128039 Invalid number of products.

The QI-Shipping submodule builds the shipment from the incident instead. It carries one unit of the incident's order item, goes to a copy of the order's shipping address, offers the shipping method the order last used, and points back to the incident in a source_incident field, which Views can list on an order's shipments. The incident page then shows the shipment with its state, items and tracking code, together with whatever operations other modules add to it, such as sending it to the carrier. A site that needs its own fields filled on the shipment, a reason code for instance, subscribes to one event and sets them before it is saved.

What it refuses to do

Each of these limits is there because the record has to be trustworthy months later, when someone counts.

The return shipment's item and address are shown in the dialog, not chosen. The shipment goes where the order went, for the item the incident is about. Sending it elsewhere is an edit to the shipment, made on purpose by someone who can see what they are changing.

There is one active return shipment per incident. The button disappears while one exists and comes back only when it is canceled, and canceled shipments stay listed on the incident. A second shipment created by a double click, or by two people on the same morning, is a parcel the carrier collects and the customer never expected.

Closing asks for resolution notes. An incident marked closed with nothing written in it is the spreadsheet again.

Where it came from

We built it for one of our clients, a footwear brand whose shoes are made by hand after they are ordered, and the Footwear & Bags category plugin is that store's list of what goes wrong with a shoe. It was in production there before its first public release in July, which brought the generic category plugin, its own tests and the cleanup for drupal.org. The return shipment came from the same store, which created its first return through the new submodule on the day 1.1.0 was released.

The make-to-order side of the same workflow has its own post: with both modules, a complaint becomes a repair order in the workshop and then a shipment, and each record links to the others.

Get it composer require drupal/commerce_quality_incident drush en commerce_quality_incident

It requires Drupal 10.3 or 11, PHP 8.1, Commerce 2.x or 3.x and State Machine. The two submodules are optional and enabled the same way: commerce_quality_incident_mto needs Commerce Make-to-Order, commerce_quality_incident_shipping needs Commerce Shipping. Stable releases are covered by Drupal's security advisory policy.

The project page has the full documentation. If your store handles complaints, warranty claims or repairs in a way the module does not fit yet, tell us in the issue queue.

The Drop Times: The Next Test of Drupal’s Contribution Model

Drupal has long relied on organisations and individuals who contribute beyond their immediate needs. Yet that responsibility remains unevenly distributed. According to figures published by the Drupal Association in February 2025, just 219 of the 1,440 organisations active on Drupal.org accounted for 96% of recorded contributions in 2024. The challenge is not simply attracting more contributors, but making responsibility for Drupal's upkeep more widely shared.

Two proposals outlined by Drupal Association Interim CEO Tiffany Farriss on 28 October 2026 suggest how that might happen. A broader Drupal Certified programme could recognise makers, patrons and advocates, extending recognition beyond agencies. Alongside it, a dedicated maintainers fund could help organisations without development capacity financially support the people maintaining software they depend on.

Together, these proposals revisit Dries Buytaert's long-standing makers-and-takers argument. They also raise questions about what counts as a fair contribution. Financial support cannot automatically substitute for technical responsibility, and certification must recognise different capacities without favouring organisations that can afford to pay. How funding reaches maintainers, and how their work is valued, will matter as much as the proposed framework itself.

Neither proposal has been implemented. Their significance lies in an attempt to move from contribution as an expectation placed on a committed minority towards a responsibility shared across the ecosystem. Drupal will remain freely available, but sustaining that freedom requires continuing work. The real test is whether more beneficiaries can help support it without compromising the openness and independence that make Drupal worth maintaining.

Follow The DropTimes on LinkedIn and Bluesky, or join #thedroptimes on Drupal Slack.
Allen Jason wrote and curated this issue of Editor’s Pick.

Drupal Association blog: In the Open: Security in the age of AI

In the Open is a weekly series from Drupal Association Interim CEO Tiffany Farriss, sharing what she is hearing across the Drupal community and where the Association is headed. In this episode: how AI is changing vulnerability discovery for everyone, what the Drupal Security Team and the Alpha-Omega funded SEIR program have achieved in five months, and how site owners, hosts, and organizations can help.

"In Drupal now that finding these bugs is really cheap, fixing them and fixing it safely is not."

The following is a transcript of Tiffany’s remarks, lightly edited for readability.

What’s changing for everyone

Today I want to talk about security. I think Drupal’s story is about a community that is adapting pretty well to some pretty trying times.

I want to talk about what’s changing for everybody, what we do, what we’ve seen so far, and then how you can help and get involved.

It’s a completely new situation in the last 12 months. AI-assisted analysis is making finding vulnerabilities cheap and automatable in commercial software and open source software. It’s not a Drupal problem. It’s not an open source problem. It’s an everyone problem.

AI is astonishingly good at finding vulnerabilities. And it’s not just individual vulnerabilities, it’s about chaining them together. In COVID, we all learned about the Swiss cheese model of safety, the idea being that the holes in the cheese don’t line up in the various layers. But AI is just so good at figuring out how to chain one vulnerability to another one, to another one, and it navigates it at a pace and in a way that we just haven’t ever seen before.

From an open source perspective, we know that open source is in almost everything. 96% of all applications use open source libraries, and every vendor’s product inherits the security of all of the dependencies, whether they’re in your software or they’re in open source.

One of the core problems that all of open source is looking to solve, and that I’m looking to solve for Drupal, is that companies expect enterprise-grade security from people who are fundamentally volunteers. They’re either volunteer maintainers or, in the case of Drupal, a largely volunteer security team.

To address this, Alpha-Omega, which is a project of the Linux Foundation, has been funding security experts in residence for a broad swath of the ecosystem. They’ve been doing it for PHP, Node, Ruby, Rust, Python, Perl, Erlang, FreeBSD, Eclipse, and now Drupal. I think Drupal is the first one of our layer of open source software to be included, and we’re super, super grateful.

And it’s not just about the funding that we’ve gotten from Alpha-Omega. It’s the fact that we’ve been invited into this cohort to share ideas and resources with each other. That makes it feel like every ecosystem is really working on this together.

How Drupal security works today

For those who aren’t as familiar with what Drupal already does, we have one of the most amazing security teams. If you see any member of the security team, you need to thank them, because as a largely volunteer effort they have protected millions of sites for over two decades.

One of the things people are often surprised about is that not only do we have very mature practices, but we use our own scoring method and we have our own CVE numbering authority, which a lot of folks aren’t. Our coverage includes core and then contributed projects that opt into security coverage.

Several years ago, our security team came up with the idea of Drupal Steward. Drupal Steward is web application firewall rules that protect against any critical or highly critical issue that is preventable by a WAF, or web application firewall. We distribute those rules to any of the trusted Drupal Steward partners. There was also a community tier that allowed people to put the Drupal Steward WAF in front of their WAF. That last product hasn’t been as successful. People haven’t really been able to put their WAF behind our WAF. So really, the folks who end up being protected are the ones who get it through their hosts.

In the first several years of Drupal Steward being available, it wasn’t activated very much. We didn’t have very many vulnerabilities that were preventable by a WAF or that were that critical, and the pace was much, much slower. This year it has been activated, I think, three times already. Given what we’re seeing across all of open source, I do expect there will be more activations to come.

If you’re already working with one of the partners that is a member of Drupal Steward, you’re protected. But we’re also working on a new way to distribute our WAF rules through CDX, and I hope to be able to share more about that in the future.

I want to talk a little bit about those partners who are members of Drupal Steward, because they’re ones who are known to us. The way the program is designed, these partners have members of the security team. That’s critical for us for a couple of different reasons.

One, it keeps our circle of trust really small. Advanced notice is only as safe as the smallest group that holds it. When we’re working through an organization that already has an existing security team member, the early details stay with an organization that already had access to them. They know they’re bound by the security team’s disclosure policy, and that policy makes it really clear that you can’t disclose this beyond what gets disclosed publicly to everyone. So there’s no inherent advantage.

But security work in Drupal takes Drupal expertise. Any of the fixes that we put into Drupal Steward require knowing how to block the exploit itself without blocking or breaking existing real sites. So the Drupal context is as important as the WAF rules.

What the last five months have shown us

Our team responds to reports. That’s the operating MO. One of the things the SEIR grant has allowed us to do was to actually begin doing some scans on different pieces of it. What we found, and this is in a blog post I released a few days ago, is that AI reporting has gotten so easy to do that we instead shifted the time from the folks who are in SEIR to really focusing on fixing, and then creating some very durable tools to help us manage this onslaught.

If you hadn’t caught up on the post: in the five months that we’ve been involved in the SEIR project, from June through the end of October, there were 256 advisories issued by our security team. That’s more than the previous 30 months combined. Prior to the start of this June wave, there were generally around eight per month.

We also found that the number of credited reporters since June has more than doubled since last year. There were 134 credited reporters since June alone, in this five-month period. In all of last year, there were 68. I think this gives us some sense of how fast these issues are being found and disclosed.

But I also think it’s important that we’re really clear that it doesn’t mean the code got worse. It just means that we’ve gotten better at finding these issues, and it’s now cheaper to do so.

In Drupal, now that finding these bugs is really cheap, fixing them, and fixing them safely, is not. That still takes deep Drupal knowledge. What we promise, both in our security team releases and in Drupal Steward, is that you fix the vulnerability but you also care that you can’t break anything. So the security fixes make the smallest change that closes the security gap. We want to make sure that there are no new APIs and no breaking changes when possible. If any of our updates break sites, it decreases the number of people who’d be willing to apply them. And an unapplied fix isn’t really a fix, is it? The cleaner, proper version can be worked on in a public issue queue once it’s disclosed and once we have a good patch.

It is important that everybody on the security team be first and foremost Drupal experts. So if you are someone who does Drupal security work, or you have worked with the security team on your own contrib modules, and you’re interested in levelling up and learning more, please reach out to the security team and see how you might be able to get involved.

Drupal’s threat model is its own thing. We have very specific trust boundaries and permissions that restrict access, and things that are meant just for our trusted administrators. What we find in reports that come from folks who don’t necessarily know Drupal, or even in fixes that come from folks who don’t know Drupal, is that they might miss those details. It impacts how you tell a real vulnerability from an intended behavior that Drupal already has.

The funded reviewer pool that we have comes from the existing security team, and this is why any of the hosting partners or CDNs who get active in Steward would have to have a security team member. Even as we scale the program, that’s still going to continue to be true. We need to focus on adding capacity without widening the circle, and make sure that we actually have folks who are there.

Who is carrying the load

Alpha-Omega has been providing the SEIR program for years with some of the projects, but this was Drupal’s first term in it. We’re in the first six-month engagement. What was different about the way that Drupal did it is really a reflection of that entire process. You have to fund not just one person to go in and find and fix bugs, you have to fund the whole security workflow. If we just focused on one researcher, everything would be stuck. You need to make sure that throughput is the primary focus. So we have several roles that are partially funded, instead of the usual single person funded full-time. I think our results five months in have shown that it made a big difference.

That SEIR team, which includes Drew Webber, Greg Knaddison, Michael Hess, and Neil Drumm, supported by both Tim Lehnen and myself, participated in about 87% of the advisories, including those two critical core fixes a few weeks ago. They’ve also started to focus on creating reusable tooling and on finding and fixing some upstream bugs that were in fact impacting us downstream. There have been maybe half a dozen hardening changes, and also fixes to issues in Drupal.org’s infrastructure itself. So I’m very grateful to them and their work, and to Alpha-Omega for funding it.

But it would be remiss of me not to note that it didn’t fund all of the work. This is a huge influx and a huge increase in the number of vulnerability disclosures that the team is working on. As we have seen throughout Drupal’s entire history, it’s largely funded and largely done by people who are volunteers. Almost every member of the security team, even those who weren’t funded by SEER, increased their contribution year over year, if I look at June through October of this year versus June through October of last year. It impacted maintainers as well. They needed to write, test, and ship fixes on timelines.

Some members of the team also have time that’s sponsored by their employers. We know that they likely go above and beyond that. I think that’s just always true, and I am deeply grateful for folks’ willingness to do that. Some were funded through the Drupal AI Initiative to do some work, and that allowed it to spill over into their security team subsidies. And then there’s also some Drupal 7 end of life work.

If you are one of the companies who encourage people to do this work, I’m thinking here of Tag1. The security team has told me that without Swan Colada’s participation, the last few months would have been impossible to manage. So I really appreciate their work, and also Tag1 for sponsoring some of it.

But it took more than 100 volunteers to work on all of the security issues that Drupal has had to endure over these last five months. So I thank you deeply. I thank any companies that are sponsoring you, either directly or indirectly, to do this work. I appreciate it.

But it’s really clear that we need to find ways to increase the amount of funding that goes not just to the security team, but also to the core release team. Every vulnerability that we address only matters once it’s been released to the public. We need to make sure that we aren’t just relying on people’s spare time indefinitely, and find a way to improve that.

What I’m looking at are ways that we can move beyond just relying on grants or individual organizations to fund huge chunks of people’s time, and thinking about how we might make it easier for other people to opt in, and how we can make Drupal Steward accessible to more people. At the end of the day, my priority, my mission as part of the DA, is to get as many Drupal sites as secure and as protected as possible. And I want to do that in a way that respects the time and the expertise of the people who build it: maintainers, core release managers, security team members, all of us. I think that’s how we protect the commons as we move forward.

How you can help

First and foremost, please make sure that you are covered by Drupal Steward. If you’re a site owner, you can ask your CDN or your host provider, “Are you a Drupal Steward partner?” If they’re not, please encourage them to become one. If you are a shared host, a Drupal platform, or a CDN, get in touch. We’re happy to have some conversations about how you can become a Drupal Steward partner. You pass that protection on to your customers, and then help us increase the capacity of our security team.

Also, if you haven’t yet, and I really hope you have by the time you watch this video, please patch PHP and keep your sites updated. Drupal Steward will buy you time for Drupal vulnerabilities, but it can’t replace anything upstream, and it certainly never replaces updating your sites to secure versions of the modules that we release.

And if your organization depends on Drupal, sponsor people who know Drupal, either in your own organization or in the community, to contribute to security specifically.

I want to end this with huge thanks, as always, to the volunteers who do our core release management, to those on the security team, and to all of the maintainers who’ve been involved in absorbing this AI wave. I unfortunately don’t see it abating anytime soon, but I think we can continue to collaborate and stay in conversation about how we can make it more manageable and more sustainable for all of us.

So with that, I want to thank Alpha-Omega again. I want to thank our colleagues over at the PHP Foundation for all of their work last week, Trail of Bits for helping us find our critical vulnerability, and OpenAI, as well as Anthropic, who have both been providing us with AI tools to manage this work.

Follow the conversation

New episodes of In the Open publish each week. Subscribe on the Drupal Association YouTube channel to follow along.

With thanks to Stephen Cross of Second Signal Media for producing this series.

Omitsis: Agentic Web Census, the monthly census of websites ready for AI agents

A growing share of the visits a website gets are AI assistants that read the page to do something on someone’s behalf, such as compare prices or fill in a form. We built Agentic Web Census to find out how ready the web is for them, and we publish it every month with open data. The Agentic Web Census home page, with the field to check a site. What Agentic Web Census is It is a free website that scores the whole web on how ready it is for AI agents. We call the score AWCS, short for Agentic Web Census Score, and it goes from 0 to 100%. We publish it for the web as a whole and...

Drupal Core News: PHPStan level 3, constructor property promotion, and other disruptive commits this week in advance of Drupal 12.0.0-rc1

Drupal 12.0.0 and 11.5.0 are scheduled for the week of December 7, 2026. In preparation for these upcoming releases, our schedule targets release candidates for these versions for less than two weeks from now, the week of November 9.

Before we create our release candidates, we will commit several "beta target" changes that are likely to conflict with existing core merge requests. These include issues to increase Drupal's PHPStan rule level to 3 for improved testing of coding standards and static analysis, and specific large cleanups like adopting constructor property promotion and removing a legacy test annotation format. New PHPStan failures are also likely on existing merge requests.

These beta targets need to be committed in a certain order, and will conflict with each other as well as many other merge requests in the Drupal core issue queue. Commits that introduce merge conflicts with the beta targets may be reverted in order to ensure these large changesets can land smoothly without needing to be repeatedly regenerated.

Once the beta targets are completed, Drupal 12.0 and 11.5 will enter their release candidate phase, and all disruptive changes (aside from critical dependency updates and similar issues) will begin targeting Drupal 12.1.

Webpro Company blog: Developer work is changing. Where does experience fit?

I have watched work shrink for myself and other developers. At the same time, AI lets me try ideas I previously lacked the time or money to pursue. I write about this change from my own experience: what needs rethinking and where I want to focus as a developer. I do not yet know what the field of development will look like. In my experience, two things are happening at once: there is less demand for some familiar work, while some new ideas have become more affordable to build. Both need to be taken into account. I am trying to understand which problems benefit most from my experience and where new tools fit. It is a question I am exploring through my everyday work. When work becomes part of your identity When you have spent years learning, rescuing projects and tracking down difficult…

Talking Drupal: Talking Drupal #576 - Inside & Outside Drupal AI

Today we are talking about AI, Drupal, and what's new with guests Dr. Christoph Breidert & Scott Falconer. We'll also cover Drupal AI Demo as our module of the week.

For show notes visit: https://www.talkingDrupal.com/576

Topics
  • Inside vs Outside AI
  • Agent Permissions and Scoping
  • Drupal Org and GitLab Future
  • Outside AI Examples Teaser
  • Why CMS Still Matters
  • Agent Swarms Expose Data
  • Shadow IT And Liability
  • CMS As Context Engine
  • Content Governance At Scale
  • Provenance And Regulation
  • MCP And Client Integrations
  • Rosetta Sprint Challenges
  • Keeping Up With AI
Resources Guests

Christoph Breidert - 1xINTERNET.com breidert Scott Falconer - managing-ai.com scott-falconer

Hosts

Nic Laflin - nLighteneddevelopment.com nicxvan John Picozzi - epam.com johnpicozzi

MOTW Correspondent

Martin Anderson-Clutz - mandclu.com mandclu

  • Brief description:
    • Have you ever wanted to try out all of Drupal's AI features on a realistic site, and then pop the hood to see exactly how they're configured? There's a demo for that.
  • Module name/project name:
  • Brief history
    • How old: project created on Oct 4, 2026 by Christoph Breidert (breidert), a Drupal AI Product Lead, with support from 1xINTERNET, FreelyGive, and Foster Interactive. The demo itself was revealed in the Driesnote at DrupalCon Rotterdam
    • The demo runs on Drupal CMS with the Drupal AI modules, running on Drupal 11.4
  • Maintainership
    • Looks to be actively maintained
    • Documentation: the project has a README that is really clear about what you can try and who has access to what
    • Number of open issues: 83, 2 of which are bugs
  • Module features and usage
    • The demo is a site for Northmoor University, a fictional coastal university, with realistic programs, news, events, people, and images
    • You launch your own copy for free through Drupal Forge. No install, no config, no credit card, and AI credits are included, courtesy of amazee.ai. Each demo instance lasts 30 days
    • Features include AI search with cited answers, page generation in Canvas, content review, auto-classification, translation, image validation, image generation and alt text, AI disclosure labels, observability, and an MCP server so tools like Claude can work with the site's content
    • Each feature has a step-by-step explainer page in an "AI Features" section, right in the demo site
    • The best part is you have full admin access, so every agent, prompt, index, and view is right there to inspect, tweak, or rebuild on your own site
    • Lots of the features are public recipes you can use today, like AI Answers, AI Image Alt Text, AI Content Classification, and AI SEO Optimizer
    • The big caveat: the ai_demo project is really a placeholder. It's the README plus the issue queue. The actual build (content, theme, demo-specific recipes, deployment) lives in a private GitLab repo for Drupal AI Partners
    • So the project's real job is feedback: issue templates for bugs, feature requests, feature-page fixes, and plans, each tagged by demo component. Fixes show up the next time you launch a new copy
    • The build was a big community effort: from August to late September, more than 100 contributors, 1,442 commits across 54 projects, and 616 merged MRs, adding roughly 596,000 lines of code
    • Back in episode #529 we covered the AI Ecosystem Recipe, and both are about getting the Drupal AI stack in front of you fast. The difference is mechanism: the recipe applies the AI modules to your own site for you to configure, while the demo is a fully-baked, hosted instance with content and prompts already tuned, so you can not only try out but also reverse-engineer a working setup

Pages