PHP Still Pays
PHP, Laravel, WordPress and the bills they quietly keep paying
Modern PHP, Laravel, Symfony, WordPress plugins, legacy PHP 5 codebases that refuse to die, and the jokes. Yes, we have heard them all. PHP 8 is actually good and we will fight you on it.
Community rules
Post the PHP version
5.6 and 8.3 are different languages. Say which one you are stuck on.
The "PHP is dead" joke has been made
Once per thread is enough. Bring something new or bring code.
Argue with the post, not the person
Disagree as hard as you like. Insults, slurs and dogpiling get removed, and repeat offenders get removed too.
Moderated by Ayesha Siddiqui
Discussions
- Primary
Routes, migrations, middleware, the ORM and the occasional rant
Showing 16 posts.
Posts in t/php_still_pays
Our worst week this year: a queue job that charged one customer three times
I post the wins here, so I should post this one too. UK ecommerce client, Laravel, a payment gateway the client chose (a smaller provider, not Stripe). Checkout puts a ChargeOrder job on the queue. The job had $tries = 3 with backoff, because "network errors happen". One evening the gateway was slow. The charge request took longer than our HTTP timeout, so the job threw a timeout exception. Laravel did exactly what…
- Pending, charging, paid, plus reconcile is exactly the state machine we use for bank debits. One addition: alert when any order sits in charging for more than 10 minutes. That alert has found two gateway outages before the gateway told us.
- In embedded we call this exactly-once delivery and we know it does not exist. Good that PHP people discover it too.
Laravel queue jobs do baar run ho rahi hain (jobs running twice), retry_after ka kya chakkar hai?
Aadab sab ko. School management software mein ek job hai jo ek school ke saare students ki fee receipt PDF banake parents ko email karti hai. Masla ye hai ki kabhi kabhi job do baar chal jaati hai, parents ko do emails jaate hain, aur principal sahab naraz hain. Setup: Laravel 11, PHP 8.3, QUEUE_CONNECTION=redis , worker: php artisan queue:work --tries=3 class SendFeeReceipts implements ShouldQueue { use Queueable;…
Accepted answer
Haan Faizan, bilkul wahi masla hai, and it is not basic, half the Laravel devs in Pakistan have sent double emails because of this. Redis queue mein jab worker job uthata hai, Laravel use retry_after seconds ke liye reserve karta hai (default 90, config/queue.php mein redis connection). Agar job 90 second mein khatam nahi hui, Laravel samajhta hai worker mar gaya aur job wapas queue mein daal deta hai. Doosra worker utha leta hai. Ab dono chal rahe hain. Exception koi nahi, isliye tumhe kuch dikhta nahi. Fix, isi order mein: 1. config/queue.php mein redis ka 'retry_after' => 600, aur worker ko --timeout=540. Rule: timeout hamesha retry_after se kam. Job class mein bhi public $timeout = 540; rakh do. 2. Asli fix: ek job mein 500 emails mat bhejo. Ek job per student dispatch karo, Bus::batch se, taaki progress aur failures dikhein. Har job 2 second ki hogi, retry_after ka sawaal hi khatam. 3. Idempotency: fee_receipts table mein (student_id, term) unique rakho aur sent_at save karo. Job shuru mein check kare, already sent hai to return. Phir job teen baar bhi chale, parent ko ek hi email jayega. Maine ye May mein Horizon wali post mein likha tha, wahan supervisor config bhi hai. Principal sahab ko mera salaam 😄
Anyone running Laravel Octane on FrankenPHP in production? Memory and singleton horror stories wanted
We are moving one high traffic client API (about 4M requests a day, mostly reads, Redis cached) from php-fpm to Octane with FrankenPHP. Staging benchmarks look great, p95 from 48ms to 14ms. But I have done this long enough to know staging lies. Setup: PHP 8.3, Laravel 11, Docker, 2 containers behind a load balancer. Questions for people who actually run it in production: Memory growth: do you set --max-requests ,…
Accepted answer
We've had Octane on FrankenPHP in prod since spring for an ecommerce API, about 2.5M requests a day, so a similar shape. What we learned: 1. --max-requests=500 to start. We went to 1000 after a month of watching memory. A PDF library leaked about 2MB per 100 requests, max-requests hides that nicely. Set a container memory limit as well, belt and braces. 2. Auditing singletons: grep every ->singleton( in your providers and ask "does this closure touch request(), auth(), session or anything tenant-ish". If yes, make it ->scoped( instead. Scoped bindings get reset between requests under Octane. Then grep app/ for static properties that aren't constants. That found two more for us. 3. Anything left over goes in the flush array in config/octane.php. 4. Write one test that sends two requests as two different tenants through the same app instance and asserts nothing bleeds. Cheap, and it caught a regression in July. 5. FrankenPHP over Swoole because there's no PHP extension to compile and the Docker image just works. You lose Octane::concurrently, which is Swoole only. We never missed it. Staging lies, aye, but mostly about memory. Run it for a week at 10% of traffic behind the load balancer before you switch everything.
La mise à jour WooCommerce du vendredi soir (ou comment j'ai appris à aimer WP-CLI)
Petite histoire, parce que je l'ai racontée trois fois cette semaine et que ça ira plus vite comme ça. Vendredi, 18h40. Une cliente boulangerie à Nantes m'appelle : "le site n'affiche plus rien quand on veut payer". Les commandes du samedi matin (le gros jour) passent par là. J'ai hérité du site il y a deux mois, et évidemment les mises à jour automatiques étaient activées pour tout. WooCommerce s'était mis à jour…
- the croissant invoice is the most european thing i've ever read. a taco truck client paid me in tacos once. i accepted. no regrets
PHP in 2026 is actually good. Here are my receipts.
Every few weeks somebody in a thread here tells me PHP is dead. I run an 11 person agency in Karachi. This month PHP paid 11 salaries, our office rent, two laptops and a very expensive coffee machine. If this is death, it is a well paid one. But "it pays" is not an argument about quality, so here are actual receipts. And at the end, what is still bad, because I am not a fan club. Receipt 1: the language grew up PHP…
- Desde Valencia: PHP y Laravel me pagan en USDT cuando el banco no funciona y se va la luz. Burda de bueno el post. PHP no está muerto, chamo, está trabajando.
- no generics in 2026. arrays that are maps that are lists. skill issue of the language designers 🦀
Meu cliente foi hackeado por um plugin 'nulled'. O que todo plugin WordPress precisa ter
oxente, que semana. cliente meu aqui em salvador, uma pousada, comprou um plugin "premium" de reservas num site desses de plugin pirata. de graça. massa né? três semanas depois o google search console mandou alerta e o site tava cheio de página de cassino em russo e japonês. barril. como eu achei primeira coisa, ver quem é admin: wp user list --role=administrator tinha um usuário "wp_support" que ninguém criou.…
- Pana, a mí me pasó igual con un cliente en Valencia, plugin 'premium' gratis y al mes la web vendía pastillas en ruso jajaja. Lo de wp core verify-checksums no lo conocía, burda de útil.
- Je ne parle pas portugais mais le code parle français 😄 nonce + capability + escape, c'est exactement ma checklist. Et les plugins nulled, j'ai nettoyé trois sites de boulangerie l'année dernière à cause de ça.
Enums, readonly and match: the PHP 8 features that made my juniors' code reviewable
Code review in my agency used to be 40% "what values can this string have?" and "who changed this amount after the controller?". PHP 8 killed most of those comments. Here are the features that did it, with the code we actually write now. 1. Backed enums (8.1) enum OrderStatus: string { case Pending = 'pending'; case Paid = 'paid'; case Shipped = 'shipped'; case Cancelled = 'cancelled'; public function label():…
- mehmet in a php thread is the most reliable service on this site. 100% uptime
- Ayesha baji, Money class wala example humare school fees module mein laga diya. readonly ki wajah se ek bug pakda gaya jahan controller fee amount badal raha tha 😅 Mehmet sahab ke liye arz hai: enum mein jo likha hai janab, wahi chalega, C mein 47 daal ke, kaun sambhalega 🙂
Stripe webhooks in Laravel: verify, queue, return 200, and stop double-fulfilling orders
Kids are asleep, so here's the webhook setup we've been running on an ecommerce client for two years. It came out of a Black Friday where we shipped some lucky customers two of everything. Not Cashier, plain stripe-php, because the client has a custom checkout. Why webhooks go wrong Stripe retries a webhook if you don't answer with a 2xx quickly, and it keeps retrying for up to three days. If your handler does the…
- Good that you verify the signature. Please also make sure the webhook secret is different for staging and production. I see the same secret everywhere in reviews, and rotate it when someone with access leaves.
The Dubai client who wanted us to rewrite in Node 'because PHP is old'
Story time, because this happened again this month and I want to have a link to send next time. Logistics client in Dubai. Laravel app we built in 2021, about 40k shipments a month, drivers, tracking page, invoices. They hired a new CTO from a big company. First meeting: "PHP is old, we need to rewrite in Node, NestJS, microservices. How long?" I did not argue. I said: give me two days to profile, and I will give…
- aye right. a CTO who wants a rewrite before he's run a profiler. pure mince. at least it wasnae kubernetes
- lol 820ms p95 and you call 140 a win. go would do 20ms. php cope
Multi-tenant Laravel for 40 koperasi: one database with tenant_id, or one database per tenant?
Hello all. I maintain a savings and loan system (koperasi simpan pinjam) used by 40 cooperatives. Same features for all, each has 2,000 to 20,000 members. Laravel 10, PHP 8.2, MySQL 8, one VPS. Now it is single database, every table has koperasi_id , and I use a global scope: class KoperasiScope implements Scope { public function apply(Builder $builder, Model $model): void { $builder->where(…
Accepted answer
Did this for 26 schools (a school ERP, same shape as your problem). My vote: one database, tenant_id, but do it properly. 1. Don't read the tenant from auth() inside the scope. Make a small TenantContext class, bind it as a singleton, set it in a middleware for web requests and inside the job's handle() for queued work (pass koperasi_id into the job constructor). The scope reads from TenantContext and throws if it is empty. Fail loud, never fail open. 2. Put the scope and a creating hook in one trait, BelongsToKoperasi, so a new model gets both by adding one line. The creating hook fills koperasi_id automatically. 3. Every unique index includes koperasi_id. member_number is unique per koperasi, not globally. Composite indexes start with koperasi_id. 4. One feature test per model: create data for two koperasi, act as one, assert you never see the other. Ours caught 3 leaks in the first month. 5. "We want our own copy": mysqldump with --where="koperasi_id=7" per table, zip it, done. A 2 minute script. Database per tenant is worth it when tenants need custom schema, when the regulator demands physical separation, or when one tenant is 100x bigger than the rest. For 40 koperasi with max 20k members you will spend more time running 40 migrations and 40 backups than you save. And the dinas report becomes 40 queries and a merge in PHP. yaar, not worth it. Ask the regulator question first though. If they say separate databases, the decision is made for you.
- Composer has more stability than half the packages on packagist 😂
Your Laravel app is not slow. Your loop is.
A Dubai client called last month. "The admin is slow, maybe we need a bigger server?" The orders page took 6.1 seconds. I opened Debugbar and the query counter said 1,214 . For one page. Of 50 orders. The server was fine. The loop was not. The code (simplified, but not by much) // OrderController@index $orders = Order::latest()->paginate(50); {{-- orders/index.blade.php --}} @foreach ($orders as $order) <tr> <td>{{…
- Ayesha baji, aaj preventLazyLoading on kiya, pehle hi page pe 14 exceptions aaye 😅 ab ek ek karke theek kar raha hoon. behad shukriya
- Spring Data JPA user here confirming: same bug, more XML kkkkk. The EntityGraph annotation is our with()
- Not my code but Nextcloud on my homelab run 8.3 :) does that count
Deploy Laravel di shared hosting cPanel tanpa drama (dan tanpa upload folder vendor lewat FTP)
Sugeng siang gan. Banyak klien saya koperasi dan toko kecil, budget hosting mereka 30-50 ribu sebulan, jadi VPS belum masuk akal. Ini langkah yang saya pakai sejak 2019 untuk deploy Laravel di shared hosting cPanel. Sudah dipakai di 20an klien, aman. 1. Struktur folder Jangan taruh semua isi project di public_html . Folder .env , vendor , storage jadi bisa diakses dari browser. Taruh project di luar:…
- bai this is what my capstone panel wants, deploy daw sa hosting nila 😭 used translate. the index.php path part worked po. salamat bai bayu HAHAHA
Laravel queues that don't fall over: Horizon + Supervisor on a cheap VPS
Every few weeks a junior in my team (or a freelancer in my DMs) shows me a production server where the queue worker is running inside screen . It works until the server reboots, or the worker eats a bad job and dies at 3am, and then the client calls me asking why no invoices went out since Tuesday. Uff. So here is the setup we actually use. Nothing fancy. A 2 vCPU / 4 GB VPS for about 12 dollars a month runs Redis,…
- Good post. In Java world we have same problem with visibility timeout on SQS, message come back while first consumer is still processing. Different language, same bug kkkk
- please, please do not "fix" the 403 on /horizon by making the gate return true. i have found open horizon dashboards on three bug bounty programs, one with full job payloads. bref, gate on email or on a role, voilà
- Java for 18 years, but my first paid project was PHP 4. Good luck with the topic.