PHP Reddit
31 subscribers
308 photos
40 videos
25.3K links
Channel to sync with /r/PHP /r/Laravel /r/Symfony. Powered by awesome @r_channels and @reddit2telegram
Download Telegram
C-level APCu key isolation based on FPM pool names (Zero-allocation)

Hey everyone,

APCu is arguably the best in-memory key-value store for single-node PHP applications. It’s blazingly fast because it runs within PHP's own master process. But it has one massive, well-known architectural flaw in multi-tenant environments: It lacks pool isolation.

If you run multiple independent applications on the same server, each in their own PHP-FPM pool (with their own system users), they still share the exact same APCu memory segment. Pool A can read, modify, or delete Pool B's keys.

The standard solution is relying on PHP developers to manually prefix their keys (e.g., $cache->set('app1_config')). Not only is this annoying to maintain, but it offers zero security if an application gets compromised—a malicious script can just iterate and modify out the neighbor's cache.

I decided to fix this at the C level.

I wrote a patch for the APCu extension that introduces a transparent memory hook. It automatically namespaces every cache key based on the active PHP-FPM pool, completely invisible to the PHP userland.

How it works under the hood (The C Magic):

Instead of allocating new heap memory (malloc/free) on every web request—which would destroy APCu's legendary speed—I engineered a zero-allocation memory reuse strategy:

Out-of-Band Pool ID: When an FPM worker spawns, the C code reads /proc/self/cmdline to safely extract the exact pool name (falling back to geteuid() if procfs is restricted).

Worker-Lifetime Persistence: On the worker's very first APCu call, it allocates a single, persistent zend_string buffer (default 256 bytes) that survives the request shutdown and is immune to PHP's garbage collector.

Raw memcpy & Zend Spoofing: On every subsequent cache request, the code uses a fast memcpy to drop the user's requested key directly into this persistent buffer right after the static pool prefix. It then mutates ZSTR_LEN and forcefully resets the hash (h = 0) to trick APCu into recalculating the hash for the new, secured string.

The Result:

A script in Pool A calls apcu_store('db_config', $data). Pool B calls the exact same thing. In physical RAM, they are securely locked away as pool_A_db_config and pool_B_db_config. No application intervention required. Zero performance penalty.

I've documented the exact architecture, installation instructions, and how to maintain the patch on future APCu releases.

GitHub Repo: https://github.com/Samer-Al-iraqi/apcu-fpm-pool-isolation

I'd love to hear feedback from other extension developers or anyone dealing with shared-hosting/multi-tenant PHP architectures!

https://redd.it/1sg9rln
@r_php
I got tired of importing themes and tweaking CSS by hand, so I built a visual theme builder for my Laravel Starter Kit

[A complete theme builder for Saucebase laravel Starter kit.](https://reddit.com/link/1sgp4dn/video/ory7ede9z5ug1/player)

Hey everyone,

I've been using [tweakcn](https://tweakcn.com/) to generate themes for my projects, and it's a great tool , big inspiration for what I ended up building. But the workflow was always the same: generate a theme there, export it, import it into my project, then spend ages manually adjusting variables until everything actually looked right in context.

So I decided to build a visual theme builder directly into my project. If you're not familiar, I'm working on an open-source module-first Laravel SaaS boilerplate called Saucebase ([intro post here](https://www.reddit.com/r/laravel/comments/1ri1uc0/open_sourcing_a_modulefirst_laravel_saas/)). The theme builder is one of the modules.

Here's what it does:

* Live editor with color pickers, font selectors, shadow and radius sliders, you see changes instantly in your actual app, not a separate preview
* Dark/light mode support with per-field sync (link a value across modes or keep them independent)
* Shadow system where you tweak 6 base values and it computes 8 shadow scale strings automatically using `color-mix()`
* Same idea for border radius and letter-spacing with one base value, computed scale
* Google Fonts loaded on demand (Its use a static list for now, possibly I will integrate with the google fonts api)
* 15 built-in themes (named after food to keep the joke with the name, like beetroot, coffee, kiwi…)
* When you're happy, save a JSON, run `php artisan saucebase:theme:apply`, rebuild and done! Happy days

Important to note: **this is a developer tool, not an end-user feature.** You use it in your dev environment to design and bake your theme, then commit the result. In production, it's just plain CSS variables, no runtime overhead, no third-party dependency.

The most fun part to build was the dark/light mode editing experience. You can edit both modes from the same screen and toggle between them live. And for any color field, there's a little sync toggle, lock it and when you change that color in light mode, it automatically mirrors to dark mode (or vice versa). Sounds simple but getting the sync state right, deciding what should link by default and what shouldn't, and making it all feel smooth took way more iterations than I'd like to admit.

You can play with it on the demo: [https://demo.saucebase.dev/](https://demo.saucebase.dev/)

Documentation is still WIP, but the editor itself should be pretty self-explanatory. Would love to hear what you think, especially around the UX of the editor and whether the workflow makes sense. Open to feedback and suggestions.

https://redd.it/1sgp4dn
@r_php
How are people linking Stripe payments back to the original visitor session in PHP apps

I'm trying to answer a simple question for a small Laravel SaaS I run: which traffic sources actually produce paying users. Traffic itself is easy. GA4, Plausible, whatever, they all show pageviews, referrers, UTMs. The annoying part is when the user leaves your app for Stripe Checkout. Once payment completes, the signal you get back is a server side webhook. At that point you're holding a Stripe event, not the browser session that originally came from Twitter or HN or Google. So the problem becomes: how do you reliably connect those two worlds. Most straightforward approach I came up with was attaching a visitor id to the checkout session. Flow looks like this:

1. small JS snippet generates visitor_id
2. store source + UTM info in DB
3. attach visitor_id to Stripe metadata
4. when checkout.session.completed fires, map payment to visitor

That works, but it means you end up maintaining a mini attribution system (sessions table, source storage, webhook mapping). I use Plausible for traffic analytics because it's simple and privacy friendly, but it doesn't solve the visitor to Stripe revenue join either. While searching I found Faurya which basically productizes this exact idea: tracks the visitor session + source data and connects it to Stripe events so you can see which channels actually produce revenue. How are people here handling this in real projects? Are you storing visitor IDs in Stripe metadata, pushing conversion events back into GA4, doing attribution inside your own DB, or just not worrying about it at this stage?

https://redd.it/1sgqhdg
@r_php
How to set up automatic SSL for every site in a multi-site CMS —
wildcard subdomains + custom domains, zero manual cert management
/r/cms/comments/1sgubts/how_to_set_up_automatic_ssl_for_every_site_in_a/

https://redd.it/1sgudgg
@r_php
Flow PHP PostgreSql Symfony Bundle

Working with PHP, PostgreSql and Symfony?

You might want to check Flow PHP Symfony PostgreSql Bundle - it's the latest package I have been working on as a part of Flow PHP project.

https://flow-php.com/documentation/components/bridges/symfony-postgresql-bundle/

Features:

\- query builder with full PostgreSql syntax support

\- migrations

\- schema definition in php/yaml

\- SQL AST Parser/Deparser

\- client that supports static analysis types narrowing, no more return array<mixed>

https://redd.it/1sfmlha
@r_php
Array intersection benchmarks

I’m trying to optimize my hot code path where array intersection is used a lot. I got curious and decided to compare the various intersection algorithms that I know of.

<?php

// Source - https://stackoverflow.com/a/9276284
// Posted by kingmaple, modified by community. See post 'Timeline' for change history
// Retrieved 2026-04-08, License - CC BY-SA 4.0

// Source - https://stackoverflow.com/a/53203232
// Posted by slaszu, modified by community. See post 'Timeline' for change history
// Retrieved 2026-04-08, License - CC BY-SA 4.0

iniset('memorylimit', '2048M');

function formatBytes(int $bytes): string {
$units = 'B', 'KB', 'MB', 'GB';
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return sprintf("%.2f %s", $bytes, $units$i);
}

function benchmark(callable $fn, string $label): array {
gccollectcycles();
gcmemcaches();
memoryresetpeakusage();
$mem = -memory
getpeakusage();
$time = -hrtime(true);
$fn();
$time += hrtime(true);
$mem += memorygetpeakusage();
return [
'label' => $label,
'time
ms' => $time / 1e6,
'memused' => $mem,
];
}

function manual
intersect($arrayOne, $arrayTwo) {
$index = arrayflip($arrayOne);
foreach ($arrayTwo as $value) {
if (isset($index[$value])) {
unset($index[$value]);
}
}
foreach ($index as $value => $key) {
unset($arrayOne[$key]);
}
return $arrayOne;
}

function flipped
intersect($arrayOne, $arrayTwo) {
$index = arrayflip($arrayOne);
$second = array
flip($arrayTwo);
$x = arrayintersectkey($index, $second);
return arrayflip($x);
}


function runBenchmarks(int $n): void {
echo "\n=== Array Intersection Benchmark for " . number
format($n) . " elements ===\n";

// Generate test arrays
$one = ;
$two = ;
for ($i = 0; $i < $n; $i++) {
$one = rand(0, 1000000);
$two = rand(0, 100000);
$two = rand(0, 10000);
}

$one = arrayunique($one);
$two = array
unique($two);

$results = ;

$results = benchmark(
fn() => $res = manualintersect($one, $two),
'manual
intersect()'
);

$results = benchmark(
fn() => $res = arrayintersect($one, $two),
'array
intersect()'
);

$results = benchmark(
fn() => $res = flippedintersect($one, $two),
'flipped
intersect()'
);

// --- Print Table ---
echo strrepeat('-', 60) . "\n";
printf("%-25s | %-14s | %-15s\n", 'Method', 'Time (ms)', 'Memory');
echo str
repeat('-', 60) . "\n";

foreach ($results as $r) {
printf("%-25s | %11.3f ms | %15s\n",
$r'label',
$r'time_ms',
formatBytes($r'mem_used')
);
}
echo strrepeat('-', 60) . "\n";
}

// Run for various sizes
foreach ([20, 20000, 200000, 1000000] as $n) {
runBenchmarks($n);
}

I run this on PHP 8.4 on Core I7 11700F

=== Array Intersection Benchmark for 20 elements ===
------------------------------------------------------------
Method | Time (ms) | Memory
------------------------------------------------------------
manual
intersect() | 0.007 ms | 1.98 KB
arrayintersect() | 0.029 ms | 3.02 KB
flipped
intersect() | 0.002 ms | 3.97 KB
------------------------------------------------------------

=== Array Intersection Benchmark for 20,000 elements ===
------------------------------------------------------------
Method | Time (ms) | Memory
------------------------------------------------------------
manualintersect() | 1.169 ms | 1.75 MB
array
intersect() | 41.300 ms | 1.88 MB
Array intersection benchmarks

I’m trying to optimize my hot code path where array intersection is used a lot. I got curious and decided to compare the various intersection algorithms that I know of.

<?php

// Source - https://stackoverflow.com/a/9276284
// Posted by kingmaple, modified by community. See post 'Timeline' for change history
// Retrieved 2026-04-08, License - CC BY-SA 4.0

// Source - https://stackoverflow.com/a/53203232
// Posted by slaszu, modified by community. See post 'Timeline' for change history
// Retrieved 2026-04-08, License - CC BY-SA 4.0

ini_set('memory_limit', '2048M');

function formatBytes(int $bytes): string {
$units = ['B', 'KB', 'MB', 'GB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return sprintf("%.2f %s", $bytes, $units[$i]);
}

function benchmark(callable $fn, string $label): array {
gc_collect_cycles();
gc_mem_caches();
memory_reset_peak_usage();
$mem = -memory_get_peak_usage();
$time = -hrtime(true);
$fn();
$time += hrtime(true);
$mem += memory_get_peak_usage();
return [
'label' => $label,
'time_ms' => $time / 1e6,
'mem_used' => $mem,
];
}

function manual_intersect($arrayOne, $arrayTwo) {
$index = array_flip($arrayOne);
foreach ($arrayTwo as $value) {
if (isset($index[$value])) {
unset($index[$value]);
}
}
foreach ($index as $value => $key) {
unset($arrayOne[$key]);
}
return $arrayOne;
}

function flipped_intersect($arrayOne, $arrayTwo) {
$index = array_flip($arrayOne);
$second = array_flip($arrayTwo);
$x = array_intersect_key($index, $second);
return array_flip($x);
}


function runBenchmarks(int $n): void {
echo "\n=== Array Intersection Benchmark for " . number_format($n) . " elements ===\n";

// Generate test arrays
$one = [];
$two = [];
for ($i = 0; $i < $n; $i++) {
$one[] = rand(0, 1000000);
$two[] = rand(0, 100000);
$two[] = rand(0, 10000);
}

$one = array_unique($one);
$two = array_unique($two);

$results = [];

$results[] = benchmark(
fn() => $res = manual_intersect($one, $two),
'manual_intersect()'
);

$results[] = benchmark(
fn() => $res = array_intersect($one, $two),
'array_intersect()'
);

$results[] = benchmark(
fn() => $res = flipped_intersect($one, $two),
'flipped_intersect()'
);

// --- Print Table ---
echo str_repeat('-', 60) . "\n";
printf("%-25s | %-14s | %-15s\n", 'Method', 'Time (ms)', 'Memory');
echo str_repeat('-', 60) . "\n";

foreach ($results as $r) {
printf("%-25s | %11.3f ms | %15s\n",
$r['label'],
$r['time_ms'],
formatBytes($r['mem_used'])
);
}
echo str_repeat('-', 60) . "\n";
}

// Run for various sizes
foreach ([20, 20000, 200000, 1000000] as $n) {
runBenchmarks($n);
}

I run this on PHP 8.4 on Core I7 11700F

=== Array Intersection Benchmark for 20 elements ===
------------------------------------------------------------
Method | Time (ms) | Memory
------------------------------------------------------------
manual_intersect() | 0.007 ms | 1.98 KB
array_intersect() | 0.029 ms | 3.02 KB
flipped_intersect() | 0.002 ms | 3.97 KB
------------------------------------------------------------

=== Array Intersection Benchmark for 20,000 elements ===
------------------------------------------------------------
Method | Time (ms) | Memory
------------------------------------------------------------
manual_intersect() | 1.169 ms | 1.75 MB
array_intersect() | 41.300 ms | 1.88 MB
flipped_intersect() | 0.634 ms | 2.55 MB
------------------------------------------------------------

=== Array Intersection Benchmark for 200,000 elements ===
------------------------------------------------------------
Method | Time (ms) | Memory
------------------------------------------------------------
manual_intersect() | 8.781 ms | 16.00 MB
array_intersect() | 290.759 ms | 16.00 MB
flipped_intersect() | 6.196 ms | 20.00 MB
------------------------------------------------------------

=== Array Intersection Benchmark for 1,000,000 elements ===
------------------------------------------------------------
Method | Time (ms) | Memory
------------------------------------------------------------
manual_intersect() | 35.547 ms | 58.00 MB
array_intersect() | 882.681 ms | 42.00 MB
flipped_intersect() | 26.764 ms | 58.00 MB
------------------------------------------------------------

The built-in functions mock me!

https://redd.it/1sh13gh
@r_php
I got tired of coding the same CRUDs and admin panels for years, so I open-sourced my own PHP framework (built on CodeIgniter 4)

Hey everyone.

If you build software for the educational or administrative sector, you know the drill: ever-changing requirements, massive databases, and the headache of rewriting the exact same logic for views, tables, pagination, and permissions for every new system.

It got to a point where my job felt like 80% repetitive boilerplate and 20% actual business logic.

To fix this and keep my sanity, I decided to build a higher-level layer leveraging the speed of CodeIgniter 4 and MariaDB. The core philosophy is simple: Configuration over Programming. I wanted to be able to define a "Data Dictionary" (a simple array) and have the system automatically render the dashboard, filters, data exports, and handle security (SQLi, XSS, RBAC) without touching a single manual view.

The result is Ragnos, a framework I use daily for production systems, which I've decided to release 100% Open Source for the community.

Also, because everything is based on configuration arrays, its declarative architecture is perfect for using AI (ChatGPT/Claude) to generate entire modules in seconds.

Where to check it out? You can find the project's philosophy, initial docs, and the direct link to the GitHub repository here: 🔗**ragnos.build**

For those who want to dive deep into the architecture or implement it at an enterprise level, I also just published the complete official manual (Ragnos from Zero to Pro) on Leanpub, but the heart of this launch is the free open-source tool.

I’d love for you to take a look at the code, install it, break it, and give me your feedback. If you find the tool useful, dropping a star on the GitHub repo helps tremendously with project visibility.

Thanks for reading and happy coding!

https://redd.it/1sh28gf
@r_php
This media is not supported in your browser
VIEW IN TELEGRAM
[Showcase] I got tired of building SaaS billing from scratch, so I made an open-source Laravel package with a Filament admin panel. Sets up in 15 mins.

https://redd.it/1shjxj5
@r_php
Tired of terminal hopping? I built a native GNOME extension to manage Symfony CLI servers 🐘🐧
https://redd.it/1shnh7v
@r_php