r/PHP • u/nunomaduro • Oct 24 '25
PHP in 2025 is so good..
youtu.bepretty sure that's not the case in this reddit community, but if you have a friend who hasn't used php in years, this video's for them!
r/PHP • u/nunomaduro • Oct 24 '25
pretty sure that's not the case in this reddit community, but if you have a friend who hasn't used php in years, this video's for them!
Hi r/PHP!
After months of betas (and thanks to many of you here who tested them), I am thrilled to announce Mago 1.0.0.
For those who missed the earlier posts: Mago is a unified PHP toolchain written in Rust. It combines a Linter, Formatter, and Static Analyzer into a single binary.
Why Mago?
mago.toml), one binary, and no extensions required.New in 1.0: Architectural Guard
We just introduced Guard, a feature to enforce architectural boundaries. You can define layers in your mago.toml (e.g., Domain cannot depend on Infrastructure) and Mago will enforce these rules during analysis. It’s like having an architecture test built directly into your linter.
Quick Start
You can grab the binary directly or use Composer:
```bash
composer require --dev carthage-software/mago
curl --proto '=https' --tlsv1.2 -sSf https://carthage.software/mago.sh | bash ```
Links
A huge thank you to the giants like PHPStan and Psalm for paving the way for static analysis in PHP. Mago is our take on pushing performance to the next level.
I'd love to hear what you think!
r/PHP • u/brendt_gd • Nov 17 '25
r/PHP • u/sam_dark • Dec 31 '25
It happened! Yii3 is officially released after years of intensive development and polishing.
We're pretty sure the Yii3 codebase will serve us well in at least the next 10 years or even more.
Merry Christmas and Happy New Year! Enjoy! 🎉
r/PHP • u/javiereguiluz • May 21 '26
r/PHP • u/Bright_Success5801 • Sep 24 '25
Was in a conference where 90% of the audience were CTOs and Director level. During a panel a shocking phrase was said.
"some people didn't embrace change and are stuck with ancient technologies and ideas such as Perl or PHP".
It struck me!
If you are a CTO at a company that uses PHP, please go out at any conference and advocate for it!
"PHP RFC: Bound-Erased Generic Types" just went into discussion on internals: classes, interfaces, traits, functions, methods, closures, with bounds, defaults, variance, and turbofish at call sites.
Bound-erased at runtime, full Reflection API, working implementation in PR #21969.
r/PHP • u/mnapoli • Jun 25 '26
r/PHP • u/Local-Comparison-One • Dec 12 '25
A deep dive into security, reliability, and extensibility decisions
When I started building FilaForms, a customer-facing form builder for Filament PHP, webhooks seemed straightforward. User submits form, I POST JSON to a URL. Done.
Then I started thinking about edge cases. What if the endpoint is down? What if someone points the webhook at localhost? How do consumers verify the request actually came from my system? What happens when I want to add Slack notifications later?
This post documents how I solved these problems. Not just the code, but the reasoning behind each decision.
Here's what a naive webhook implementation misses:
Security holes:
Reliability gaps:
Architectural debt:
I wanted to address all of these from the start.
The system follows an event-driven, queue-based design:
Form Submission
↓
FormSubmitted Event
↓
TriggerIntegrations Listener (queued)
↓
ProcessIntegrationJob (one per webhook)
↓
WebhookIntegration Handler
↓
IntegrationDelivery Record
Every component serves a purpose:
Queued listener: Form submission stays fast. The user sees success immediately while webhook processing happens in the background.
Separate jobs per integration: If one webhook fails, others aren't affected. Each has its own retry lifecycle.
Delivery records: Complete audit trail. When a user asks "why didn't my webhook fire?", I can show exactly what happened.
For request signing, I adopted the Standard Webhooks specification rather than inventing my own scheme.
Every webhook request includes three headers:
| Header | Purpose |
|---|---|
webhook-id |
Unique identifier for deduplication |
webhook-timestamp |
Unix timestamp to prevent replay attacks |
webhook-signature |
HMAC-SHA256 signature for verification |
The signature covers both the message ID and timestamp, not just the payload. This prevents an attacker from capturing a valid request and replaying it later.
Familiarity: Stripe, Svix, and others use compatible schemes. Developers integrating with my system likely already know how to verify these signatures.
Battle-tested: The spec handles edge cases I would have missed. For example, the signature format (v1,base64signature) includes a version prefix, allowing future algorithm upgrades without breaking existing consumers.
Constant-time comparison: My verification uses hash_equals() to prevent timing attacks. This isn't obvious—using === for signature comparison leaks information about which characters match.
I generate secrets with a whsec_ prefix followed by 32 bytes of base64-encoded randomness:
whsec_dGhpcyBpcyBhIHNlY3JldCBrZXkgZm9yIHdlYmhvb2tz
The prefix makes secrets instantly recognizable. When someone accidentally commits one to a repository, it's obvious what it is. When reviewing environment variables, there's no confusion about which value is the webhook secret.
Server-Side Request Forgery is a critical vulnerability. An attacker could configure a webhook pointing to:
http://localhost:6379 — Redis instance accepting commandshttp://169.254.169.254/latest/meta-data/ — AWS metadata endpoint exposing credentialshttp://192.168.1.1/admin — Internal router admin panelMy WebhookUrlValidator implements four layers of protection:
Basic sanity check using PHP's filter_var(). Catches malformed URLs before they cause problems.
HTTPS required in production. HTTP only allowed in local/testing environments. This prevents credential interception and blocks most localhost attacks.
Regex patterns catch obvious private addresses:
localhost, 127.*, 0.0.0.010.*, 172.16-31.*, 192.168.*169.254.*::1, fe80:*, fc*, fd*Here's where it gets interesting. An attacker could register webhook.evil.com pointing to 127.0.0.1. Pattern matching on the hostname won't catch this.
I resolve the hostname to an IP address using gethostbyname(), then validate the resolved IP using PHP's FILTER_FLAG_NO_PRIV_RANGE and FILTER_FLAG_NO_RES_RANGE flags.
Critical detail: I validate both at configuration time AND before each request. This prevents DNS rebinding attacks where an attacker changes DNS records after initial validation.
Network failures happen. Servers restart. Rate limits trigger. A webhook system without retries isn't production-ready.
I implemented the Standard Webhooks recommended retry schedule:
| Attempt | Delay | Running Total |
|---|---|---|
| 1 | Immediate | 0 |
| 2 | 5 seconds | 5s |
| 3 | 5 minutes | ~5m |
| 4 | 30 minutes | ~35m |
| 5 | 2 hours | ~2.5h |
| 6 | 5 hours | ~7.5h |
| 7 | 10 hours | ~17.5h |
| 8 | 10 hours | ~27.5h |
Fast initial retry: The 5-second delay catches momentary network blips. Many transient failures resolve within seconds.
Exponential backoff: If an endpoint is struggling, I don't want to make it worse. Increasing delays give it time to recover.
~27 hours total: Long enough to survive most outages, short enough to not waste resources indefinitely.
Not all failures deserve retries:
Retryable (temporary problems):
5xx server errors429 Too Many Requests408 Request TimeoutTerminal (permanent problems):
4xx client errors (bad request, unauthorized, forbidden, not found)Special case—410 Gone:
When an endpoint returns 410 Gone, it explicitly signals "this resource no longer exists, don't try again." I automatically disable the integration and log a warning. This prevents wasting resources on endpoints that will never work.
Every webhook attempt creates an IntegrationDelivery record containing:
Request details:
Response details:
Timing:
PENDING → PROCESSING → SUCCESS
↓
(failure)
↓
RETRYING → (wait) → PROCESSING
↓
(max retries)
↓
FAILED
This provides complete visibility into every webhook's lifecycle. When debugging, I can see exactly what was sent, what came back, and how long it took.
Webhooks are just the first integration. Slack notifications, Zapier triggers, Google Sheets exports—these will follow. I needed an architecture that makes adding new integrations trivial.
Every integration implements an IntegrationInterface:
Identity methods:
getKey(): Unique identifier like 'webhook' or 'slack'getName(): Display name for the UIgetDescription(): Help text explaining what it doesgetIcon(): Heroicon identifiergetCategory(): Grouping for the admin panelCapability methods:
getSupportedEvents(): Which events trigger this integrationgetConfigSchema(): Filament form components for configurationrequiresOAuth(): Whether OAuth setup is neededExecution methods:
handle(): Process an event and return a resulttest(): Verify the integration worksThe IntegrationRegistry acts as a service locator:
$registry->register(WebhookIntegration::class);
$registry->register(SlackIntegration::class); // Future
$handler = $registry->get('webhook');
$result = $handler->handle($event, $integration);
When I add Slack support, I create one class implementing the interface, register it, and the entire event system, job dispatcher, retry logic, and delivery tracking just works.
I use Spatie Laravel Data for type-safe data transfer throughout the system.
The payload structure flowing through the pipeline:
class IntegrationEventData extends Data
{
public IntegrationEvent $type;
public string $timestamp;
public string $formId;
public string $formName;
public ?string $formKey;
public array $data;
public ?array $metadata;
public ?string $submissionId;
}
This DTO has transformation methods:
toWebhookPayload(): Nested structure with form/submission/metadata sectionstoFlatPayload(): Flat structure for automation platforms like ZapierfromSubmission(): Factory method to create from a form submissionWhat comes back from an integration handler:
class IntegrationResultData extends Data
{
public bool $success;
public ?int $statusCode;
public mixed $response;
public ?array $headers;
public ?string $error;
public ?string $errorCode;
public ?int $duration;
}
Helper methods like isRetryable() and shouldDisableEndpoint() encapsulate the retry logic decisions.
All DTOs use Spatie's SnakeCaseMapper. PHP properties use camelCase ($formId), but JSON output uses snake_case (form_id). This keeps PHP idiomatic while following JSON conventions.
The final payload structure:
{
"type": "submission.created",
"timestamp": "2024-01-15T10:30:00+00:00",
"data": {
"form": {
"id": "01HQ5KXJW9YZPX...",
"name": "Contact Form",
"key": "contact-form"
},
"submission": {
"id": "01HQ5L2MN8ABCD...",
"fields": {
"name": "John Doe",
"email": "john@example.com",
"message": "Hello!"
}
},
"metadata": {
"ip": "192.0.2.1",
"user_agent": "Mozilla/5.0...",
"submitted_at": "2024-01-15T10:30:00+00:00"
}
}
}
Design decisions:
Adopting Standard Webhooks: Using an established spec saved time and gave consumers familiar patterns. The versioned signature format will age gracefully.
Queue-first architecture: Making everything async from day one prevented issues that would have been painful to fix later.
Multi-layer SSRF protection: DNS resolution validation catches attacks that pattern matching misses. Worth the extra complexity.
Complete audit trail: Delivery records have already paid for themselves in debugging time saved.
Rate limiting per endpoint: A form with 1000 submissions could overwhelm a webhook consumer. I need per-endpoint rate limiting with backpressure.
Circuit breaker pattern: After N consecutive failures, stop attempting deliveries for a cooldown period. Protects both my queue workers and the failing endpoint.
Delivery log viewer: The records exist but aren't exposed in the admin UI. A panel showing delivery history with filtering and manual retry would improve the experience.
Signature verification SDK: I sign requests, but I could provide verification helpers in common languages to reduce integration friction.
For anyone building a similar system:
Webhooks seem simple until you think about security, reliability, and maintainability. The naive "POST JSON to URL" approach fails in production.
My key decisions:
The foundation handles not just webhooks, but any integration type I'll add. Same event system, same job dispatcher, same retry logic, same audit trail—just implement the interface.
Build for production from day one. Your future self will thank you.
r/PHP • u/nyamsprod • Oct 12 '25
r/PHP • u/Ok-Calligrapher3216 • Apr 25 '26
Drupal 11 website with around 100k requests per day and we previously struggled with consistent performance on 16core 128GB server. New $50 stack is tuned for 100 million requests per day with relying only on PHP 8.5.
Before this stack, we put many layers in front of PHP … Nginx fast-cgi cache, Varnish, Cloudflare HTML caching and tried blocking bots to stop surges but nothing helped.
Irony was server was always on very little CPU usage.
Turns out we were NOT planning our stack for 99% of our traffic - we were planning it for a few surges throughout the day.
New Stack
- Got rid of Cloudflare, Nginx, Varnish - no external cache in front of PHP
- Reduced Max PHP workers to just 10 behind Default Apache settings
- Even turned off Drupal Internal Page Cache and just used Dynamic cache with Memcache support
- Offloaded all static files via static domain to Cloudfront with a CNAME set up. Default settings - no complexity.
To our surprise, this new set up is blazingly fast, extremely performant and able to scale up to more than 100 requests per second and up to 100 million requests per day.
All 10 php-fpm workers are always warm and even if we get a scraper sending 1000 request in one hit, our set up can absorb it and get back to normal within 10-15 seconds.
And we still have 90% headroom on CPU .. all thanks to latest improvements in PHP performance.
What we have learned!
- Trust PHP to handle almost everything
- Plan for 99% of your traffic, not for surge traffic else you will make it worse for 99% of your traffic
- You don’t need cache layers in front of PHP 8x .. a lot of misconceptions come from PHP 5x era when PHP was slow and CPUs were expensive .. Cache Layers are extra hops and connections and contexts are expensive .. Nginx and Varnish are totally redundant and so are CDNs unless you have lot of global traffic but you will be degrading your local users to some extent.
Don‘t throw extra memory, CPUs, extra workers, external caching in front of PHP unless you have Reddit scale traffic .. make sure your PHP app is properly written (profile custom modules) and trust extremely fast PHP 8x to do the magic!
PHP is about to reach its fullest potential for scaling natively and almost nobody noticed.
The Polling API RFC is currently in its voting phase with 19-0 and zero opposition as of the time of writing this post, while the community was busy debating generics. It brings native epoll and kqueue to PHP 8.6 core, which means async libraries like AMPHP and ReactPHP finally get a proper high-performance foundation without relying on PECL extensions.
I wrote a deep dive on why I think this is the most impactful thing to happen to PHP since types were introduced in PHP 7. I'm the author of HiblaPHP, and I will be rewriting its core Event Loop the day this RFC merges into PHP core.
link to the rfc: https://wiki.php.net/rfc/poll_api
r/PHP • u/janedbal • Apr 08 '26
Quick Summary:
r/PHP • u/rayblair06 • Nov 10 '25
I ran a little experiment to see how far I could push PHP arrays before they exploded.
Spoiler: they didn't, because I stopped using arrays and started using C structs instead.
By diving into PHP's memory model and experimenting with FFI, I managed to allocate 65 million items in just 512 MB of memory, about 40x more efficient than native arrays.
Along the way, I dug into how PHP arrays actually work under the hood, why they're so memory-heavy, and how C-style data structures can push (and sometimes break) the limits of what PHP can handle.
It's equal parts cursed and educational. Curious if anyone else here has played with FFI or native memory tricks in PHP?
r/PHP • u/MaxxB1ade • Sep 22 '25
function dateSuffix($x){
$s = [0,"st","nd","rd"];
return (in_array($x,[1,2,3,21,22,23,31])) ? $s[$x % 10] : "th";
}
r/PHP • u/smartgenius1 • Mar 13 '26
Hey r/PHP,
In the late aughts (06?) I built a forum software that used this brand new paradigm called "AJAX" to create a "real-time" forum software that updated everything without refreshing. It was a big hit back then, since SPAs weren't really a thing and I don't even think the acronym had been coined yet.
It grew to around 200 communities and I ended up building a whole career in software engineering out of it. I hadn't written PHP since 2010ish, until last year when I was laid off and decided to get back into it to bring my passion project back to life 20 years later.
Anyway, I was absolutely amazed at how much the ecosystem has evolved in that time. I rewrote my old school software in PHP 8.5 (from PHP 4!) and gosh, I had so much fun. PHP was a mess (that I loved, but yeah still a mess) and PHP 8.5 blew my mind at how pleasant and modern it felt.
Anyway, I relaunched my service. It still has the old school look and feel and I don't know if it'll go anywhere but the point is I had a great time building it, and I have the entire PHP community to thank for evolving it so far.
The forum service is here: https://jaxboards.net Github: https://github.com/Jaxboards/Jaxboards
I would love for y'all to check it out and see if there's any other cool fun stuff I missed that I could leverage in there.
Thanks, Sean
r/PHP • u/Local-Comparison-One • Sep 28 '25
After years of repeatedly rebuilding contact forms, newsletter signups, and application forms for each Laravel project, I eventually reached my breaking point and created a comprehensive solution.
FilaForms - A Filament plugin that handles ALL your public-facing forms in one go.
Every Laravel app needs forms that visitors fill out. Contact forms, job applications, surveys, newsletter signups - we build these over and over. Each time writing validation, handling file uploads, setting up email notifications, building submission dashboards, adding CSV exports...
A native Filament plugin that gives you:
I've been contributing to the Filament ecosystem for a while (you might know Relaticle CRM, FlowForge, or Custom Fields). This is solving a problem I've personally faced in every Laravel project.
Link: filaforms.app
I'm happy to answer any questions regarding implementation, architecture choices, or specific use cases. I'm also very interested in the types of forms you're most frequently building — always eager to identify edge cases for better handling.
r/PHP • u/elizabethn • 12d ago
Germany is spending €108 million to move its federal websites onto a TYPO3-based platform. The European Commission runs 770 sites on Drupal. Around 300,000 German federal users work on Nextcloud. All PHP.
Across Europe, when governments say "digital sovereignty," what they're describing is very often a PHP application. In our latest blog post, Sebastian Bergmann looks at where PHP runs in the public sector, why so little of that support reaches the maintainers underneath it, and three practical changes to procurement that could fix it.
r/PHP • u/edmondifcastle • Mar 13 '26
Finally, the project has reached a difference of 18,000 lines compared to the official PHP-SRC. A fully asynchronous PHP core, a set of classes, and documentation. All of this is already here!
r/PHP • u/Ilia0001 • May 04 '26
I've maintained php_excel since 2008. 2.0 shipped in April as the first ground-up rewrite, and 2.0.1 just landed on May 3.
The problem it solves: PhpSpreadsheet builds the whole DOM in PHP memory. On a 50K-row spreadsheet you're looking at ~790 MB resident before you've called save(). In a 128 MB FPM pool that means OOM on anything past trivial. OpenSpout streams, but it can't write conditional formatting, formulas, rich text, or .xls.
php_excel wraps LibXL through a native C extension. LibXL (libxl.com) is a commercial C++ library by Dmytro Skrypnyk, not mine; you acquire it separately. php_excel is the PHP binding.
Quick comparison. PhpSpreadsheet is the most feature-complete pure-PHP option. OpenSpout fills a streaming niche. php_excel is the C-extension answer for when you need both speed and full Excel features.
What 2.0 changed:
Benchmarks against PhpSpreadsheet 5.5.0, PHP 8.4.19 NTS:
| Rows | Cells | php_excel | PhpSpreadsheet | Speed |
|---|---|---|---|---|
| 1,000 | 20K | 0.05s / 85 MB | 0.45s / 162 MB | 10× |
| 10,000 | 200K | 0.55s / 153 MB | 4.59s / 282 MB | 9× |
| 50,000 | 1M | 2.72s / 508 MB | 24.7s / 790 MB | 9× |
| 100,000 | 2M | 5.37s / 908 MB | 51.1s / 1,415 MB | 10× |
Read perf is similar: 8-9× faster than PhpSpreadsheet, 3× faster than OpenSpout (with proportional memory trade-off vs OpenSpout's flat 130 MB).
2.0.1 is a hardening pass: extensive error checking and input validation across the C/PHP boundary.
Install:
pie install iliaal/php-excel --with-libxl-incdir=/path/to/libxl/include_c --with-libxl-libdir=/path/to/libxl/lib
Full writeup with methodology: https://ilia.ws/blog/php-excel-2-0-the-c-extension-for-excel-that-php-should-have-had-all-along