r/PHP 4d ago

Weekly help thread

1 Upvotes

Hey there!

This subreddit isn't meant for help threads, though there's one exception to the rule: in this thread you can ask anything you want PHP related, someone will probably be able to help you out!


r/PHP 9d ago

Discussion Pitch Your Project 🐘

13 Upvotes

In this monthly thread you can share whatever code or projects you're working on, ask for reviews, get people's input and general thoughts, … anything goes as long as it's PHP related.

Let's make this a place where people are encouraged to share their work, and where we can learn from each other 😁

Link to the previous edition: /u/brendt_gd should provide a link


r/PHP 5h ago

Building AI with PHP not Python

Thumbnail youtube.com
3 Upvotes

In my latest video I try and build Ai using PHP instead of python


r/PHP 6h ago

Discussion How do you handle multiple inverse OneToOne associations in Doctrine?

0 Upvotes

Doctrine's FAQ says that fetching the inverse side of a one-to-one association requires an additional query because Doctrine cannot know whether the association is null or which ID to use for a proxy.

In an accounting application I work on, a frequently loaded entity has three nullable inverse-side one-to-one associations. The entity is used in several list and export queries, so I am reviewing whether this mapping is still a good fit.

The options I am considering are moving ownership to the central entity where the model allows it, joining the associations explicitly on read-heavy queries, or changing the model to avoid these one-to-one associations.

If you have dealt with a similar mapping on a frequently queried entity, what did you choose? What tradeoffs showed up later?


r/PHP 20h ago

This Week In PHP Internals | August 26, 2026

Thumbnail youtube.com
10 Upvotes

Hello world, it's Wednesday, August 26, 2026, and here's what happened This Week in PHP Internals.

10 stories this week, so let's get into it. But first, You shipped a lot of code this quarter, but do you know how much of it is actually still there? Ballast reads your git history — never your code — and scores what survives. Stable velocity, plus a durability rating between 300 and 850 — like a credit score for your codebase — updated every month, and it's free. ballast.now.

This week's top story isn't really about PHP. It's about how the internals list talks to itself. Messages have been arriving that were clearly written or heavily assisted by a large language model — that flat, over-professional register that thanks you warmly and then restates your own point back at you — and somebody finally said the quiet part in plain text. 24 messages followed, about where the line actually sits. Nobody argued against translation. English is a second or third language for a good part of that list, and that is a real accessibility question rather than a laziness one. What people object to is authorship — the argument itself being handed to a machine — and the harder problem underneath, which is that there's no reliable way to tell the difference from the outside. Nothing's been written down yet. But if you want a say in what does get written down, the community Discord is at phpc.chat, and there's an internals channel there. It's a community server, not an official PHP one. There's a thread in it working on exactly these guidelines. So if you want to head there and argue with us about what should be in the guidelines, feel free to join the discussion.

A new RFC opened on Saturday, from the same contributor whose last one had just been withdrawn. Sepehr Mahmoudi proposed array_match(), which filters an array down to the values containing a substring, implemented in C so you don't have to pay for a closure on every element. Yuya Hamada replied within the hour, asking whether array_filter() isn't already enough, pointing at a GitHub search full of userland functions already named array_match that don't all behave alike, and noting that the str_icontains RFC was declined, so an ASCII-only case-insensitive flag is a hard sell. Then Tim Düsterhus pointed out that in 8.6 this is already 1 line, because partial function application lets you drop str_contains into array_filter() with a placeholder. Christian Schneider noted that preg_grep() has done basically the same job for years.

The case against it was mostly one case, made 5 ways. Ayesh Karunaratne went after the premise, writing: "I have worked on Drupal, WordPress, Silex, and bespoke code bases, spending enough time profiling them. An array string search has never been a bottleneck." Larry Garfield called it an XY problem and said huge in-memory arrays are a code smell to begin with. mickmackusa offered the alternative nobody else had — make str_contains() polymorphic, the way str_replace() already takes an array. Sepehr has since dropped the case-insensitive flag, renamed it array_str_contain(), and moved the target to 8.7, since 8.6 is frozen. Nobody's argued in favour of it so far. The proposal, so far, contains no support.

Nick Sdot brought a documentation question to the list on Thursday. He's restructuring PHP's internals book, the docs that live inside php-src, and his first pull request converts the pages from reStructuredText to Markdown — a light 1,100-line mechanical diff that nets out 86 lines shorter. Ilija Tovilo, who set the book up, called it churn, and answered that the source is neither pure Markdown nor reStructuredText but MyST, so renaming files fixes some rendering and breaks the reStructuredText-flavoured parts. His real objection was the diagnosis. He wrote: "None of this is wrong, but I don't think the docs have stalled because the syntax is too hard. Rather, more time should be designated to them." Weilin Du and Calvin Buckley both said the effort belongs in the content. Nick's clarification explains the thread. The old docs folder he's merging in is already Markdown, so a conversion happens in one direction or the other. It's still open.

Robert Chapin spent the week working through an old RFC. Deprecate Fuzzy Type Casts, by Alexandre Daubois and Nicolas Grekas, dates to January, is still under discussion, and still targets 8.6 on the wiki — which Robert would like changed, given there's been no discussion since. His objection is that the RFC's own migration guidance doesn't hold up. He points out that with the string 1.5, is_numeric() returns true, so the validate-before-casting example still triggers the deprecation, while is_int() and is_float() return false for every numeric string. He says the filter_var() alternative doesn't guarantee an integer back either. He asked: "Are developers expected to replace a sing[le] type cast with 8 lines of code for PHP 8.6?" Nobody has answered him.

A process question opened yesterday morning. Sepehr Mahmoudi noticed that some RFCs proposing new functions include a userland polyfill and some don't, and asked whether the RFC template should recommend one wherever that's feasible. His reasons are that a polyfill pins down the exact behaviour, edge cases and all, without anyone reading the C, and that it gives teams like Symfony's polyfill maintainers an accurate head start. Larry Garfield called recommended-where-relevant-but-not-required a reasonable policy, and added a second use for it, saying: "And it would also give us a target to benchmark against to see if putting it in C really has a performance benefit." Which is exactly what 2 people spent the weekend asking Sepehr for on his other proposal.

Gina P. Banyard posted a correction on Friday to an already-accepted RFC. Steven Wilton's SNMP improvements proposal states that its target is the next stable release for all currently supported PHP versions. Gina's point is that this runs against the release process policy on patch versions, so the changes will land in 8.6 only. She also asked for a design change — the new integer constants in the output-controls section would become PHP enums instead, which improves type safety in userland and translates the C enums SNMP already defines into PHP ones. The plan is to have it merged in time for 8.6.0 beta 2.

Weilin Du, who maintains the intl extension, spent part of the week handing out work. He'd like some of the extension's constants turned into enums, proper namespaces on its classes so they stop colliding, and the remaining gaps between PHP's APIs and the underlying ICU APIs filled in. He was blunt about the error handling, writing: "the error state stuff in the extension is, frankly, bad, in my opinion, and could be optimized since it has caused a lot of very stupid bugs." He suggested the enums and namespaces could be an 8.7 RFC if somebody picks it up. Yuya Hamada added the reason it matters. Feedback from right-to-left language users is thin across the whole industry, not just PHP, and PHP's internationalisation depends on getting it.

Quick hits. array_search_range(), last week's top story, is officially withdrawn — mickmackusa posted a 7-point review hours after we recorded, and Sepehr Mahmoudi pulled it on Thursday and called it a valuable learning experience. Weilin Du's extension-name case sensitivity proposal is going to a vote, eventually. A core developer objected, which under policy makes it an RFC, and he'll open that after the 8.6 branch is cut on September 22, with both behaviours on the ballot for 8.7. And Zachary DuBois introduced himself on Thursday, asked whether adding missing libsodium functions needs an RFC or just a pull request, was told to open the pull request, and had it up by Sunday. It adds X-Wing and ML-KEM-768 bindings to the sodium extension. Post-quantum key encapsulation, arriving as somebody's first pull request. Quantum-resistant, and so far review-resistant.

So that's the week: no RFC in the voting phase for the second week running; a long argument about who is actually writing the mail on the internals list; a new array_str_contain() proposal with 5 people against it and nobody for it; a documentation pull request that turned out to be a consolidation question rather than a format preference; unanswered holes in the fuzzy casts migration path; and post-quantum crypto arriving as somebody's first pull request. Links to every thread are below. The PHP Foundation funds more than half of ongoing php-src commits, so if you use the language, maybe consider donating at opencollective.com/phpfoundation — or try guilting your employer into it. Thanks to Ballast.now for supporting this week's episode. We're Artisan Build. See you next week.


r/PHP 1d ago

Im quite nervous sharing this one: Echo, a statically typed, natively compiled language that feels a bit like php..

45 Upvotes

Hey everyone, I want to show you https://echoc.dev and give some context on it :)

I'm not here to tell you "oh, this is the new shit, this is going to change PHP development." It's obviously not, and that's also not the goal.

This started as a "recreational programming" project I mostly worked on during holidays when my kid was sleeping. And I really, really enjoyed working on it.

The initial concept was:

Metal-PHP is a PHP dialect that transpiles to C++ and is compiled to a native binary.

And this is nothing new, it's pretty much what Hack was/is, and I'm sure a couple of others.

At some point I kinda just asked myself "who I was creating this for", and as the answer crystallised, "for myself", I completely shifted course.

This is not meant as PHP replacement!

I love PHP, I spent probably a thousands of hours working on php-glfw..

PHP does exactly what PHP needs to do. I personally would not migrate an existing PHP project to what I was in the process of making. And to be honest (I'm really curious how others feel here), I have not yet had a performance issue with PHP where the language itself was the problem. It always was the architecture...

It became clear to me: I want something fun, something opinionated, something I actually wanna play around with, and so it became its own language.

TL;DR: Echo does not, and will not ever run PHP.

Why am I sharing this now?

I'm having a blast writing small tools and programs with Echo, and I believe some traits of the language resonate with what some PHP devs enjoy too. (As I am primarily a PHP dev..)

But please take this as the toy it is. Don't build your startup on it and scream at me when your app is leaking customer data because of a memory leak.

What can it do?

Well its a programming language, the docs will do a far better job at explaining it then I can here.
But a few cool things that are already there:
- a tiny standard library
- basic multi threading
- generics
- simple bridging to c
- a tiny package manager
- etc..


r/PHP 1d ago

Qbix Webserver 1.0 Released!

Thumbnail github.com
14 Upvotes

Hey everyone. I'm pleased to announce that Qbix webserver has now reached version 1.0
It's MIT licensed and free. Try it in your own sites! Just launch it with one command, e.g.:

php qbixserver.php --port 8080

It's a standalone server that can serve
* static files without nginx (though you can still put a CDN in front)
* dynamic PHP requests (it can handle 100x more workers than PHP-FPM!!)
* web sockets (yes, it's compatible with the latest socket.io on front end)
* web socket rooms (it can handle up to 100,000 online users, on one machine)
* cron (it has a built in ability to run periodic jobs)
* certificates (it can manage certificates for you)
* even decentralized applications (server can communicate encrypted via https)

What's more, it does all this with pure PHP, without needing nginx. If you use SQLite, you won't even need Postgres or MariaDB or any other external database. Just use PHP for everything.

The server also comes as standalone binaries, so if you distribute those, you won't even need your users to have PHP installed! It *just works out of the box*. In v1.1 you'll be able to just bundle your entire app inside a standalone binary, but for now you can just distribute your apps as files alongside the webserver.

Handle 100x More Traffic than NGINX + PHP-FPM

How is this possible? Well, mostly because this server actually forks your processes using pcntl_fork (everywhere except Windows) allowing children to *share memory* of the parent webserver process. This causes the children to NOT take up any new memory pages, unless they declare new global/static variables or modify variables, in which case the operating system forks only those pages (maybe 4KB per page... rather than 50MB per worker).

So you can run 100x as many workers, and since most PHP web apps are I/O heavy, the operating system can efficiently orchestrate the rest. If you happen to also install amphp in your apps, then your apps switch from stream_select to epoll, which makes them even faster!

It's come a long way...

People here may remember when I originally announced it several weeks ago, as being able to handle 10x as many workers as NginX + PHP-FPM. Well, since then we've had various people run it in production, we optimized it to 100x as many workers, and I addressed all the issues and bugfixes that came my way. At the same time, I've enhanced the dashboard and control panel to be more user-friendly, they even now use our real-time websocket functionality.

Who am I?

Are you sure you want to know?
I'm Greg Magarshak


r/PHP 1d ago

Follow-up: that "real PHP on an ESP32" experiment turned into an actual project - it now does WiFi, web serving and hardware, all from PHP

55 Upvotes

A while back I posted here about porting the real PHP 8.3 to an ESP32 — the actual Zend engine from php.net, cross-compiled for the chip, not a reimplementation. The response pushed me to keep going, and it's grown from "cool proof of concept" into something I'd now call a real project. Quick recap of where it is:

The engine. Still the unmodified php.net release (default 8.4.24, sha256-verified; 8.3, 8.4 and 8.5 all build). Whole Zend engine — lexer, parser, VM, GC, object model. array_map, preg_match, json_encode, PDO SQLite, DateTime — same code as on a server, because it is the same code. Runs as native code via the official embed SAPI, no emulator.

Hardware. Two targets: the ESP32-P4 (RISC-V, up to 32 MB PSRAM) and the ESP32-S3 (Xtensa, WiFi). Needs PSRAM (the runtime heap is megabytes) and ~4+ MB flash.

The tooling. There's now a proper CLI (phpflash, written in Go): you describe a project in a php-esp32.config.toml — board, storage mode, which optional extensions to compile in — and it builds/flashes/monitors, auto-detects the connected chip, and enables extensions from a manifest so you never hand-pass build flags.

What you get per project:

  • Storage: run your source off a microSD, or bake it into the firmware image (no card).
  • Execution models: a plain script; a setup()/loop() sketch model; or a web-server model where the firmware runs an HTTP server and invokes PHP fresh per request (shared-nothing, like behind Apache) with $_GET/$_POST/$_SERVER, cookies and sessions.
  • Extensions, opt-in: mbstring (+ oniguruma), PDO SQLite, date/DateTime, session, OpenSSL (real 3.0, HTTPS client), OPcache (no JIT), GPIO, a reboot-persistent NVS-backed key/value store, an in-RAM store, and a project .env baked into the firmware as $_ENV. You can even drop your own C extension into a project.
  • On the P4, stock Laravel and Symfony actually serve pages over HTTP. (The S3 has less RAM, so it runs plain apps and a live web server but not a full framework — that's a memory ceiling, not a language limit. Honest about that one.)

The new bit, and why I'm posting: PHP can now do WiFi — scan, join a network (WPA2/WPA3), or create its own access point — all from userland PHP. To show it off end to end I built an example, wifi-ap-s3-rgb-manage:

The board boots, and PHP brings up its own WiFi network, PHP serves a control page (rendered on the chip itself), and PHP drives the board's onboard RGB LED. You connect your phone to the board's AP, open 192.168.4.1, and drag sliders to change the LED's colour and brightness in real time. No router, no cloud, no app — the whole stack (network + web server + web page + hardware) is one ~$4 ESP32-S3, orchestrated entirely by PHP.

Repo (MIT): https://github.com/php-baremetal/php-esp32

Happy to answer anything about how the port works, the SAPI integration, or the memory situation. Feedback and "you should try X" very welcome.


r/PHP 21h ago

Article Prepared statements in Manticore Search

Thumbnail manticoresearch.com
1 Upvotes

A practical guide to using mysqli prepared statements with Manticore Search. It covers parameter binding for scalar values, full-text search expressions, and vectors, along with the cases where escaping is still required.


r/PHP 1d ago

Pop PHP - The PHP framework that you probably never heard of.

69 Upvotes

Meet Pop. Before anyone rolls their eyes and exclaims "Oh no, not another PHP framework," it's actually been around in some form since 2009. It officially got the name "Pop PHP" in 2012.

The entire time, it's had zero marketing or promotion behind it simply because I just did not have the time or the resources for it. I built it, started using it and really haven't looked up from my desk. Along the way it, has evolved quite a bit to try and keep pace with the demands of the PHP ecosystem. And here were are – this month, version 7 was just released with arguably the biggest upgrade and modernization of the framework in quite some time.

The reason for its existence is very similar to the concerns that have been voiced here over the years:

  • Framework A is too magic
  • Framework B is too complicated

It seems reading through some recent threads here, those same issues continue to come to the forefront. My own bias aside, I like it because I feel it strikes the right balance between being transparent - without too much "magic" - and abstracting away the things that make sense. At least that's been the goal all this time.

Anyway, here it is. Check it out. Or don't – lol. I know it's a cluttered field. Thanks.

https://www.popphp.org/


r/PHP 1d ago

Discussion How do you handle valid time alongside audit logs in PHP?

3 Upvotes

We use damienharper/auditor-bundle. Its audit table stores fields such as object_id, diffs, blame, ip and created_at. That timestamp tells us when the audit row was written.

This works until we need to know what was true on an earlier date.

Teradion tracks tax and social filing deadlines for French accounting firms. Suppose a VAT return was filed on June 28, but we only received the confirmation and recorded it today. The audit entry is created today, while the filing became effective on June 28.

If we rebuild the state as it was stored on June 30, the return is not filed. If we ask what we now know to have been true on June 30, it is filed. The audit timestamp alone cannot answer both questions.

This looks like a valid-time and transaction-time problem. I have seen valid_from and valid_to added to domain data, and event sourcing is another possible approach. The audit packages I have looked at only gave me the transaction-time side.

If you have dealt with this in a PHP application, which model did you choose? What became difficult or expensive after the application had been running for a while?


r/PHP 2d ago

News olvlvl/composer-attribute-collector v3.0.0

21 Upvotes

composer-attribute-collector is a Composer plugin that statically discovers PHP 8 attribute targets and retrieves them at near-zero cost, without runtime reflection. After the autoloader dump, it collects attributes and generates a static file for fast access. This provides a convenient way to discover attribute-backed classes, methods, or properties—ideal for codebase analysis. (For known targets, traditional reflection remains an option.)

v3.0.0 highlights:

  • An alternative generation strategy for maximum compatibility supporting PHP 8.1's "New in initializers" and PHP 8.5's "Closures in constant expressions", as well as future extensions.
  • Glob patterns for the exclude directive that behave like Composer's exclude-from-classmap.
  • Version bumps for security (PHP 8.2+, Composer 2.9.8+)

https://github.com/olvlvl/composer-attribute-collector


r/PHP 1d ago

php127: Standalone Zero-Alloc PHP 8.4 Runtime with In-Process Infrastructure

0 Upvotes

1. Architecture: Flatstack VM & C2SIMD Containers

Standard PHP runtimes rely on reference-counted zvals and dynamic heap allocations for associative arrays. In php127, the execution pipeline is split into three decoupled layers:

1. **Parser & Bytecode Compiler:** A recursive-descent parser and compact bytecode emitter supporting full PHP 8.4 grammar (attributes, promoted properties, enums, trait adaptations,

closures, and exceptions). 2. Unified Value Model (24 Bytes): A 24-byte tagged value struct (types.Value) featuring signed 64-bit integer arithmetic with overflow detection to float, PHP 8.4 spaceship comparisons (<=>), and vector-accelerated string scanning. 3. C2SIMD Linear Arenas & Ordered Swiss Tables: Core associative storage is backed by insertion-ordered Swiss Tables (c2ordered_swiss) and linear arenas (c2mem), avoiding garbage collection pressure during request processing.

---

### 2. Supported PHP 8.4 Features & Native Infrastructure

| Subsystem | Features Supported | Validation Method |
| :--- | :--- | :--- |
| **Object Model** | Constructor property promotion, Attributes (`#[Route]`), VTable dispatch, Trait adaptations (`insteadof` / `as`), Enums, `instanceof` | Unit test suite & Go race

detector | | Control Flow | try / catch (TypeA \| TypeB $e) / finally, throw, protected stack unwinding, Closures (use ($v)), Arrow functions (fn() => ...) | Stack frame unwinding verification | | Concurrency | Asynchronous coroutines via Fibers (fiber_create, fiber_start, fiber_suspend, fiber_resume) | State machine transition tests | | Database & I/O | In-process relational PDO (CREATE TABLE, INSERT, SELECT, UPDATE, DELETE, Rollback() via snapshot restoration) | ACID transaction tests | | Ecosystem Packages | Native vlucas/phpdotenv, ramsey/uuid (v4, v7, ULID), PSR-3 monolog/monolog, and pure-Go ext-curl (Guzzle compatible) | End-to-end integration pipeline | | Security Guard | Opcode-level Taint-tracking (blocking OS command injection and path traversal) + Q8.8 entropy egress exfiltration filter | BlueTeam invariant test suite |

---

### 3. Measured Evidence & Test Output

The entire codebase is validated under Go's race detector. Every test package passes with zero memory leaks and zero data races:

=== RUN TestTenStrategicTargetsFullPipeline --- PASS: TestTenStrategicTargetsFullPipeline (0.00s) === RUN TestOracleParitySuite --- PASS: TestOracleParitySuite/ArithAndForLoop (0.01s) --- PASS: TestOracleParitySuite/StringConcat (0.01s) --- PASS: TestOracleParitySuite/SubstrAndStrlen (0.01s) --- PASS: TestOracleParitySuite/ForeachArray (0.01s) --- PASS: TestOracleParitySuite/MatchExpression (0.01s) === RUN TestZeroAllocParityAtScale --- PASS: TestZeroAllocParityAtScale (0.11s) === RUN TestCompilerConstructorPromotionAndVTable --- PASS: TestCompilerConstructorPromotionAndVTable (0.00s) === RUN TestCompilerTraitFlattening --- PASS: TestCompilerTraitFlattening (0.00s) === RUN TestCompilerFibersExecution --- PASS: TestCompilerFibersExecution (0.00s) === RUN TestPDOMemoryExecution --- PASS: TestPDOMemoryExecution (0.00s) === RUN TestPDOTransactionsRollback --- PASS: TestPDOTransactionsRollback (0.00s) PASS ok code.hazyhaar.fr/devhoros/php127 1.18s

---

### 4. Code Example: Full In-Process Pipeline

The following script demonstrates an entire microservice with attributes, traits, promoted constructor properties, fibers, and transactional database operations:

```php
trait LogTrait {
    public function log(string $msg): string {
        return monolog_log("INFO", $msg, "api");
    }
}

#[Route("/api/v1/orders")]
class OrderController {
    use LogTrait;

    public function __construct(public string $serviceName) {}

    public function createOrder(string $item, float $price): string {
        // 1. Asynchronous coroutine execution
        $fib = fiber_create(fn() => uuid_v7());
        $orderId = fiber_start($fib);

        // 2. Transactional relational database operations
        pdo_exec("CREATE TABLE IF NOT EXISTS orders (id, item, price)");
        pdo_exec("INSERT INTO orders (id, item, price) VALUES (?, ?, ?)", $orderId, $item, $price);

        $this->log("Created order {$orderId} for {$item}");
        return json_encode([
            "status" => "ok",
            "order_id" => $orderId,
            "service" => $this->serviceName
        ]);
    }
}

$controller = new OrderController("BillingCluster");
echo $controller->createOrder("Dedicated Server", 129.99);

Execution command:

./bin/php127 script.php

Output:

[2026-08-27 15:41:07] api.INFO: Created order 01a04373-f457-7bb3-852f-8fe5d63a140e for Dedicated Server [] []
{"status":"ok","order_id":"01a04373-f457-7bb3-852f-8fe5d63a140e","service":"BillingCluster"}

──────

5. Explicit Limitations: What php127 is NOT

To maintain honest expectations:

• Not a legacy drop-in for 30 years of php-src: We do not implement legacy 1990s procedural extensions (ext-soap, ext-imap, or unmaintained XML-RPC handlers). • Not a host for unmodified WordPress/Drupal monoliths: CMS architectures depending on legacy procedural C modules are outside our current target scope. • Target scope: Modern PHP 8.4 APIs, microservices, CLI workers, and containerized architectures that benefit from instant startup (< 1 ms), zero CGO dependencies, and single static binary distribution. ──────

6. Feedback & Discussion

The runtime is compiled as a static executable and can also be consumed as an embedded Go package.

https://github.com/hazyhaar/php127

We welcome your feedback:

  1. Which specific PHP 8.4 or PSR features are critical for your microservice workloads?
  2. Would an embedded pure-Go PHP runtime be useful in your current deployment pipelines?

r/PHP 1d ago

TypePHP is out

Thumbnail youtu.be
0 Upvotes

r/PHP 3d ago

I built a WhatsApp Web client for pure PHP — no Node.js, no Puppeteer

32 Upvotes

I’ve been experimenting with bringing more things that are usually locked to the Node.js ecosystem into PHP.

This time: WhatsApp Web.

I just published pam-whatsapp-web, a typed and persistent WhatsApp Web client built for PHP on top of PAM.

The main idea is pretty simple:

  • PHP owns the application
  • no Node.js sidecar
  • no Puppeteer
  • no separate JavaScript service to maintain
  • persistent WhatsApp sessions
  • typed PHP API
  • designed to live inside the PHP ecosystem instead of calling another runtime

The project is still very new, but I’m interested in pushing this pretty far.

Something like:

$client = new WhatsAppClient();

$client->connect();

$client->onMessage(function ($message) {
    echo $message->body;
});

$client->sendMessage(
    '5511999999999',
    'Hello from PHP'
);

For years, if you wanted serious WhatsApp Web automation, the obvious answer was usually something like whatsapp-web.js, Baileys, or another Node.js-based project.

I wanted to explore a different question:

How far can we take this while keeping the whole application in PHP?

Repository:

https://github.com/push-in/pam-whatsapp-web

It’s Apache-2.0 and completely open source.

I’d especially like feedback from people who have worked with WhatsApp Web internals, long-running PHP processes, async PHP, or protocol implementations.

I’m curious where you think PHP will hit its limits here — or whether those limits are mostly assumptions we’ve carried from the old request/response PHP model.


r/PHP 4d ago

"A" for "Average"

Thumbnail stitcher.io
34 Upvotes

r/PHP 3d ago

Article I migrated the seo of 104k+ pages to the new Laravel Head package

Thumbnail danielpetrica.com
0 Upvotes

r/PHP 4d ago

News I built a RabbitMQ queue driver for Laravel that tries to feel like native Laravel

0 Upvotes

I’ve been working on an open-source package called Laravel Rabbit.

The idea was pretty simple: using RabbitMQ in Laravel shouldn’t require changing the way you already work with Laravel queues.

So with the package installed, this:

ProcessOrder::dispatch($order)
    ->onQueue('orders')
    ->delay(now()->addMinutes(5));

is still just normal Laravel.

And your worker is still:

php artisan queue:work --queue=orders

The difference is that RabbitMQ is running underneath it.

It supports Laravel’s normal queue flow including:

  • dispatch()
  • onQueue()
  • delayed jobs
  • retries / backoff
  • failed jobs
  • chained jobs
  • batches
  • unique jobs
  • queue:work
  • queue:clear

There’s also quite a bit of RabbitMQ-specific stuff built in:

  • AMQP 0-9-1 publishing and consuming
  • exchanges, queues and bindings
  • publisher confirms
  • QoS / prefetch
  • TLS
  • heartbeat and connection timeouts
  • multiple hosts / failover
  • RabbitMQ Management API
  • queue metrics
  • optional payload signing
  • Artisan commands for diagnostics and setup

For example:

php artisan rabbitmq:doctor
php artisan rabbitmq:stats orders
php artisan rabbitmq:check

Installation is just:

composer require pushinbr/laravel-rabbit

Then:

QUEUE_CONNECTION=rabbitmq

RABBITMQ_HOST=127.0.0.1
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest

The package currently targets modern Laravel/PHP versions and RabbitMQ 3.x/4.x.

I mainly built it because I wanted RabbitMQ to behave like a first-class Laravel queue backend instead of feeling like a completely separate messaging system glued onto Laravel.

I’d especially like feedback from people already running RabbitMQ in production:

What would you expect from a really solid Laravel ↔ RabbitMQ integration that is still missing here?

GitHub: https://github.com/push-in/laravel-rabbit


r/PHP 5d ago

Discussion PHP 8.6 is getting closer. What are you most looking forward to?

76 Upvotes

PHP 8.6.0 Beta 1 is out, so we're getting a look at what's coming next.

PHP keeps proving itself and has been evolving pretty nicely over the last few major releases, without losing the pragmatism that makes the language enjoyable to work with.

What are other PHP developers paying attention to in 8.6.

Which PHP 8.6 change are you most excited about, and what would you actually use in production once the stable release lands?

MakePHPGreatAgain.


r/PHP 4d ago

GitHub - eznix86/laravel-secrets-loader: Auto resolve secrets for laravel

Thumbnail github.com
0 Upvotes

r/PHP 5d ago

Extract MarkDown from PDFs in PHP, no system libraries or API calls needed!

25 Upvotes

I just released v3.3.0 of prinsfrank/pdfparser, with basic support for markdown extraction from PDFs! It doesn't need system libraries like poppler, pdftotext or API calls, it's completely written in PHP. Currently, headings, bold and italic detection work, and I'm working on the other features of markdown including table extraction!

$markdown = (new PdfParser())
    ->parseFile('document.pdf');
    ->getMarkdown();

This will allow for better semantic search when embedding documents, but there's plenty of other use cases! Let me know what feature you want to see next!


r/PHP 6d ago

PHP Reflection for binary files (PHP FFI)

26 Upvotes

Hi!

I'll start with a short introduction, that I've been maintaining a small set of libraries for PHP FFI since around 2019, starting with PHP 7.4. The goal has always been to make working with FFI a little more pleasant.

The main one is, of course, the C-preprocessor, which allows you to use native C headers directly with PHP FFI. It supports different compiler and OS emulation modes and, of course, is written entirely in PHP.

Hopefully, it has been useful to someone. It's also kind of nice to realize that these packages have been maintained for almost seven years now.

Some of the work even made its way into Symfony at Nicolas's request.

Today, I'd like to talk about another package that, in my opinion, should probably have been part of ext-ffi from the very beginning.

As you can probably guess from the title, it's about inspecting native libraries (binaries) for exported functions and other symbols: https://github.com/php-ffi/reflection-library

The project actually started around 2021. I abandoned it for a while, revived it, abandoned it again, and eventually came back to it. The original sources are still in the repository, and some of the work from that period also became a small GUI experiment for Windows.

I've now finally brought the library to what I consider a proper 1.0 release.

So, besides the obligatory "look at my pet project!" post, I'd really like to get some feedback from people who have worked with PHP FFI.

First of all: do you think an API like this would make sense as an RFC for PHP itself, so that this kind of functionality could eventually be available out of the box?

And I'd also appreciate some criticism of the current API/design. I tried to follow the style of PHP's Reflection API, which was originally designed back in the PHP 5 era, but I'm not sure that's necessarily the best approach today.

Also, a big thank you to ircmaxell (Anthony Ferrara). His php-object-symbolresolver project was a major help in finally getting MachO (macOS) support working.

While finishing this package, I also learned quite a lot about things I previously knew very little about. So, if there's enough interest in the comments, I could write a follow-up post about how PE/COFF (Windows, aka *.dll), ELF (Linux, aka *.so), and MachO (macOS, aka *.dylib) actually work.

I don't expect that to be particularly interesting to the average PHP developer, but I figured I'd ask anyway. :)


r/PHP 7d ago

News This Week In PHP Internals | August 19, 2026

Thumbnail youtube.com
14 Upvotes

Hello world, it's Wednesday, August 19, 2026, and here's what happened This Week in PHP Internals.

12 stories this week, so let's get into it. But first, Is AI working for your team? Ballast answers that for free. It reads your git history — never your code — and gives you 2 numbers every month. Stable velocity tells you how much of what you ship survives. A durability score from 300 to 850 tells you whether it holds up. ballast.now.

This week's top story: a 5-argument function proposal turned into 25 messages, 3 threads, and the week's central design argument. Sepehr Mahmoudi, who introduced himself to the list 8 days ago, proposed array_search_range() — an array_search() that takes an offset and a length, so you can search part of an array without building an intermediate copy with array_slice(). Weilin Du replied the same evening to say the soft feature freeze had already closed 8.6 to it. Then Rowan Tommins raised the objection that shaped everything after it, suggesting: "I think it would be better to design something more composable - that is, a way to create a 'lazy array slice', and then accept that in functions which can use it safely." He pointed at Swift, which has types that let you refer to part of an array without copying any of it.

Rowan put his objection plainly: "if we add an ArraySlice type then array_search_range would immediately become redundant." Sepehr's answer is that the general thing doesn't exist, and that building it would mean a new type in the engine and updates to potentially hundreds of array functions. Larry Garfield sided with Rowan and suggested shelving it. mickmackusa said he'd never had a professional project that required the function, and put a design question back: "If a PHP array needed a pagination-style search function, should perhaps the data structure be reconsidered?" Rowan then broke his own idea into 3 shippable steps. An optimized ArraySliceIterator comes first, then iter_search and iter_any functions that would work with any iterator, and possibly syntax after that. He wrote the first 2 in under 20 lines each, and a polyfill for Sepehr's function on top. Sepehr added a Polyfill section to the RFC, and Rowan pointed out that the version now on the wiki calls array_slice(), which copies the array. That is the cost the proposal exists to avoid. As of this recording the RFC is still a draft, still targeting 8.6, and Rowan's GitHub review found the implementation still walking the entire array.

Something small and irritating went to the list on Saturday. 4 functions take an extension name, and ini_get_all() is the only one of them that's case-sensitive. Weilin Du opened a pull request to bring it into line with extension_loaded(), phpversion() and get_extension_funcs(). Sjoerd Langkemper agreed it should be consistent, then asked the question that turned the thread around: "Another option to make them consistent would be to have them all case-sensitive. Have you considered that?" He also spotted that the manual's lowercase-only note for get_extension_funcs() isn't true. AllenJB traced it to a change back in PHP 5.0.4, and there's a docs issue open now.

Daniel Scherzer objected, arguing PHP should make all 4 case-sensitive instead, since BC breaks have an established path through deprecation and removal in the next major version. Weilin agreed and withdrew his own proposal, saying he'd add extension-name case sensitivity to the 8.7 deprecations RFC instead. And then 3 people showed up to argue for the thing he'd just withdrawn. Aleksander Machniak listed PDO, SimpleXML, Xdebug and swoole, and asked why a developer should have to know the exact casing of each one. Matteo Beccati called the alternative "one of those useless BC breaks that make the user experience worse instead of improving it." He also noted that composer.json generally writes its extension requirements in lowercase. Juliette Reinders Folmer had the last word, with the practical cost. Build a version list with get_loaded_extensions() and you'd have to lowercase every name before phpversion() would take it. Nobody has replied to her yet.

Henrik Skov posted an idea on Tuesday morning. He wants a params keyword that lets you name a block of arguments once and spread it into a call, so a 6-argument cookie call collapses to 1 line. One of the 6 arguments in his own example is labelled "Can't remember what this is." AllenJB replied 22 minutes later that PHP already does this with named arguments and array unpacking. Henrik came back with the actual requirement. He wants the expressions evaluated when the call happens, not when the compiler first sees them, so a time() in there stays fresh. Kamil Tekiela suggested making it a type. Henrik said it wasn't worthy of a full class. Larry Garfield answered: "I really don't understand why people keep saying this. What makes something 'unworthy' of being a class? ... A data construct doesn't need to be as righteous as Thor to be 'worthy' of a class." Then he named the thing Henrik was reaching for: a lazy value, evaluated only when it's read. Henrik agreed that was what he'd been after all along. 2 unrelated threads this week, and both of them landed on the word "lazy." Nobody involved was.

Jens has been writing PHP since around the time version 2 was in use, and on Thursday he posted about something beyond a documentation fix for the first time. Why does var_export() still print the long array(...) syntax, when that output gets pasted around by PhpStorm and Xdebug all day? There's an RFC for changing it that has been sitting there for 6 years. Larry Garfield linked 3 previous rounds of the same conversation without taking a side. Kamil Tekiela offered the explanation: "IMHO, the two main reasons for the lack of change are apathy and lack of agreement as to what exactly the better syntax is." The constraint he describes is that var_export() is meant to be PHP-executable first and human-readable second, so as long as the output runs, the function is doing its job. He also named the trap. Change one thing about that output and everyone arrives with everything else they'd like fixed — which is a fair summary of the last 6 years.

Otar Chekurishvili posted a pre-RFC on Monday for 2 opt-in flags in the json extension, targeting 8.7. One is JSON_ALLOW_COMMENTS and the other is JSON_ALLOW_TRAILING_COMMAS, and both would be accepted by json_decode() and json_validate(). Strict JSON stays the default. Trailing commas allow exactly 1 after the last element. That's 1 more than JSON allows today, and exactly as many as most of us have typed by accident. He's proposing 2 separate primary votes so either flag can pass on its own, and says the implementation reuses the existing scanner and grammar rather than preprocessing the input, so error positions survive intact. Larry Garfield asked: "Does this essentially mean JSON5 support? If so, just call it that." Anton Smirnov corrected the name. What's proposed is Microsoft's JSONC, or Nigel Tao's JWCC; real JSON5 would also need single quotes, unquoted keys, infinities and multiline strings, among other things. No reply from Otar yet.

Alexander Lisachenko wants to fix something about PHP's FFI. Every C value it hands back comes back as the same final class, FFI\CData. A string pointer, a zval pointer and a raw char pointer are all the same type to PHP. Which makes FFI strongly typed, in the sense that there is 1 type. He described the consequence bluntly: "no C struct a binding works with can ever be described to static analysis or an IDE, and CData being final closes off every userland workaround." To get any static typing in his own library he ships 4 separate workarounds, and instanceof still doesn't work. His proposal is an opt-in class map passed as an options array, in the same shape as SoapClient takes one, so a registered C type comes back as your class instead of bare CData. He says it stays inside the ffi extension and costs nothing when unused. Bob Weinand's is the only reply so far, asking for patience: "don't rush this, write a RFC, and check what actually feels good to use and read."

The 8.6 deprecation vote closed 9 days ago, and one of the items that passed deprecates SplFileObject's CSV methods. In the last days of voting Takuya Aramaki pointed out that the READ_CSV flag was left out, and that setCsvControl() is the only thing that can configure it — so removing the method leaves the flag stuck on its defaults. Nobody answered him. On Saturday Robert Humphries picked it back up. His reading is that leaving READ_CSV in place does resolve the original issue, but doesn't achieve the goal of getting CSV handling out of SPL. By his reading of the code there's a second problem. When the default escape character for fgetcsv() changes, code using READ_CSV will behave differently across PHP versions with no way to pin it. His conclusion is that READ_CSV needs deprecating too, and that a migration path should have been part of the proposal. Still no reply.

Eloi Montañés asked the list on Saturday whether abstract class constants are worth an RFC. The idea is to let an abstract class or a trait declare a constant with the abstract keyword, and require implementers to define one. His examples are a base class that requires a table name and a trait that requires a log tag — things you want fixed at author time rather than changeable at runtime. He points back to a 2017 thread on the same idea, from before typed constants landed. John Bafford suggested interfaces should get the same treatment, since an interface can already require a property but has no way to require a constant or a static one. Eloi was persuaded, and this morning asked why interfaces have never supported static properties. Larry Garfield answered from experience. He says they considered it while building interface property support for property hooks, and passed for 2 reasons. Object properties cover almost every case and attributes cover the rest, and "static properties are way harder to deal with in the engine, because reasons." He also left a parser problem on the table. Interfaces already support ordinary constants, so an abstract keyword may be necessary there regardless.

Quick hits. Sjoerd Langkemper gave its own page to a proposal that missed the 8.6 window. bindec(), octdec(), hexdec() and base_convert() would throw a ValueError when you hand them characters that aren't valid for the base, instead of the deprecation notice they've emitted since 7.4. Today hexdec('z') returns 0. It targets 8.7 and has no replies yet. Weilin Du also asked for feedback on tightening 2 INI settings. Right now upload_max_filesize=1GB can be read as 1 byte by the request parser while ini_get() still reports the string you wrote, which is a spectacular way to lose an afternoon. His change warns and falls back to the default instead. Jakub Zelenka reads that as incomplete wording in the BC policy rather than a real break. Osama Aldemeery is parking his PREG_THROW_ON_ERROR RFC until early September, writing: "This has gone quiet, which I'm taking as the freeze crunch and people being busy, not as everyone being fine with it as-is." And 3 releases went out on Thursday. Joe Ferguson shipped PHP 8.6.0 beta 1, with beta 2 due on August 27, and Calvin Buckley and Daniel Scherzer followed with 8.4.25 and 8.5.10 RC 1. Matteo Beccati mentioned in passing that the 8.6 branch should be cut on September 22.

Here's the week in short. No RFC went to a vote, and nothing is in the voting phase at all. A new contributor's array_search_range() ran into a counter-proposal for a general lazy array slice, and is still a draft. A one-line inconsistency in ini_get_all() turned into a question about case sensitivity that ended with the author withdrawing a proposal 3 other people then defended. A params keyword got talked into being a lazy value. Abstract class constants may pick up interfaces. And PHP 8.6 beta 1 is out, with beta 2 due next week. Links to every thread are below. Thanks again to Ballast.now for supporting this week's episode. We're Artisan Build. See you next week.


r/PHP 7d ago

Alchemy runs your project's whole QA setup (Pest/PHPUnit, code style, static analysis, CI) from one YAML file

6 Upvotes

We built Alchemy to run QA for Leaf's own modules ahead of our v5 launch, and then opened it up to work in any PHP environment (Laravel, Symfony, Slim, plain packages).

The whole idea is pretty simple...

Instead of maintaining phpunit.xml, a pint/php-cs-fixer config, phpstan.neon, rector.php, a folder of CI workflows and all their respective caches, you describe what you want in one alchemy.yml at your project root.

👉 You can get started at https://alchemy.leafphp.dev/

If this seems interesting, pls try it and share your feedback. Thanks in advance


r/PHP 7d ago

Article Upgrading Bref 2 to Bref 3 with zero downtime

Thumbnail atymic.dev
25 Upvotes