PHP Reddit – Telegram
PHP Reddit
34 subscribers
286 photos
36 videos
24.7K links
Channel to sync with /r/PHP /r/Laravel /r/Symfony. Powered by awesome @r_channels and @reddit2telegram
Download Telegram
How do you stay competitive / find new opportunities as a remote PHP/Symfony dev (4y exp)

Hey everyone,

I’ve been working fully remotely as a PHP/Symfony developer for about 4+ years now. I’m starting to look around for new opportunities, but the market feels tighter than it used to be.

For those of you doing remote backend work:

Where are you finding the best-quality remote PHP/Symfony roles lately?
Are there specific job boards, agencies, or platforms worth focusing on?
What actually helps you stand out when applying as a remote dev? (Portfolio? OSS contributions? Certifications? Something else?)
Are companies shifting away from fully remote for PHP roles, or is it just more competitive now?

I’m solid with Symfony, API Platform, Docker, CI/CD, and general backend architecture, just trying to figure out the smartest path to land the next good remote gig.

Any advice from people who’ve navigated this recently would be super appreciated!

https://redd.it/1pk7lq5
@r_php
AI: Coding models benchmarks on PHP?

Hi,

Most coding benchmarks such as the SWE line heavily test coding models on Python.

Are there any benchmarks that evaluate PHP coding capabilities? Vanialia PHP and through frameworks.

Many thanks

https://redd.it/1pk9reg
@r_php
Need laptop suggestions under ₹50,000 – good battery, office work + light gaming

Hey everyone,

I'm looking to buy a laptop under **₹50,000** (can stretch a little if it's really worth it).
My requirements:

* Good **battery life**
* Smooth for **office work** (Google Workspace, MS Office, browsing, multitasking)
* Should handle **light gaming** like Valorant, GTA 5 (medium settings are fine)
* Minimum **8GB RAM** (preferably upgradable)
* **SSD** is must
* Ryzen 5 / Intel i5 level performance
* Decent build quality and not too heavy

If anyone is using a good laptop in this range or has recommendations, please suggest.
Looking for something balanced — not a heavy gaming machine, but also not a weak office laptop.

Thanks in advance! 🙏

[](https://www.reddit.com/submit/?source_id=t3_1pjqqb4)

https://redd.it/1pkjhfd
@r_php
Looking for a Laravel/PHP Equivalent to Bubble or Emergent

Is there anything like Bubble, Lovable, or Emergent that works natively with Laravel/PHP?
Curious what the ecosystem has.

https://redd.it/1pkpshb
@r_php
Building a Production-Ready Webhook System for Laravel

**A deep dive into security, reliability, and extensibility decisions**

When I started building [FilaForms](https://filaforms.app/), 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.

# Why Webhooks Are Harder Than They Look

Here's what a naive webhook implementation misses:

**Security holes:**

* No protection against Server-Side Request Forgery (SSRF)
* No way for consumers to verify request authenticity
* Potential for replay attacks

**Reliability gaps:**

* No retry mechanism when endpoints fail
* No delivery tracking or audit trail
* Silent failures with no debugging information

**Architectural debt:**

* Tight coupling makes adding new integrations painful
* No standardization across different integration types

I wanted to address all of these from the start.

# The Architecture

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.

# Choosing Standard Webhooks

For request signing, I adopted the [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification rather than inventing my own scheme.

# The Spec in Brief

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.

# Why I Chose This

**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.

# Secret Format

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.

# Preventing SSRF Attacks

Server-Side Request Forgery is a critical vulnerability. An attacker could configure a webhook pointing to:

* `http://localhost:6379` — Redis instance accepting commands
* `http://169.254.169.254/latest/meta-data/` — AWS metadata endpoint exposing credentials
* `http://192.168.1.1/admin` — Internal router admin panel

My `WebhookUrlValidator` implements four layers of protection:

# Layer 1: URL Format Validation

Basic
sanity check using PHP's `filter_var()`. Catches malformed URLs before they cause problems.

# Layer 2: Protocol Enforcement

HTTPS required in production. HTTP only allowed in local/testing environments. This prevents credential interception and blocks most localhost attacks.

# Layer 3: Pattern-Based Blocking

Regex patterns catch obvious private addresses:

* Localhost: `localhost`, `127.*`, `0.0.0.0`
* RFC1918 private: `10.*`, `172.16-31.*`, `192.168.*`
* Link-local: `169.254.*`
* IPv6 private: `::1`, `fe80:*`, `fc*`, `fd*`

# Layer 4: DNS Resolution

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.

# The Retry Strategy

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|

# Why This Schedule

**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.

# Intelligent Failure Classification

Not all failures deserve retries:

**Retryable** (temporary problems):

* Network errors (connection refused, timeout, DNS failure)
* `5xx` server errors
* `429 Too Many Requests`
* `408 Request Timeout`

**Terminal** (permanent problems):

* `4xx` client errors (bad request, unauthorized, forbidden, not found)
* Successful delivery

**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.

# Delivery Tracking

Every webhook attempt creates an `IntegrationDelivery` record containing:

**Request details:**

* Full JSON payload sent
* All headers including signatures
* Form and submission IDs

**Response details:**

* HTTP status code
* Response body (truncated to prevent storage bloat)
* Response headers

**Timing:**

* When processing started
* When completed (or next retry timestamp)
* Total duration in milliseconds

# The Status Machine

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.

# Building for Extensibility

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.

# The Integration Contract

Every integration implements an `IntegrationInterface`:

**Identity methods:**

* `getKey()`: Unique identifier like `'webhook'` or `'slack'`
* `getName()`: Display name for the UI
* `getDenoscription()`: Help text explaining what it does
* `getIcon()`: Heroicon identifier
* `getCategory()`: Grouping for the admin panel

**Capability methods:**

* `getSupportedEvents()`: Which events trigger this integration
* `getConfigSchema()`: Filament form components for configuration
* `requiresOAuth()`:
Whether OAuth setup is needed

**Execution methods:**

* `handle()`: Process an event and return a result
* `test()`: Verify the integration works

# The Registry

The `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.

# Type Safety with DTOs

I use Spatie Laravel Data for type-safe data transfer throughout the system.

# IntegrationEventData

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 sections
* `toFlatPayload()`: Flat structure for automation platforms like Zapier
* `fromSubmission()`: Factory method to create from a form submission

# IntegrationResultData

What 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.

# Snake Case Mapping

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 Webhook Payload

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:**

* **Event type at root**: Easy routing in consumer code
* **ISO8601 timestamps**: Unambiguous, timezone-aware
* **ULIDs for IDs**: Sortable, URL-safe, no sequential exposure
* **Nested structure**: Clear separation of concerns
* **Optional metadata**: Can be disabled for privacy-conscious users

# Lessons Learned

# What Worked Well

**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.

# What I'd Add Next

**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.

# Security Checklist

For anyone building a similar system:

* \[ \] SSRF protection with DNS resolution validation
* \[ \] HTTPS enforcement in production
* \[ \] Cryptographically secure secret generation (32+ bytes)
* \[ \] HMAC signatures with constant-time comparison
* \[ \] Timestamp validation for replay prevention (5-minute window)
* \[ \] Request timeout to prevent hanging (30 seconds)
* \[ \] No sensitive data in error messages or logs
* \[ \] Complete audit logging for debugging and compliance
* \[ \] Input validation on all user-provided configuration
* \[ \] Automatic endpoint disabling on 410 Gone

# Conclusion

Webhooks seem simple until you think about security, reliability, and maintainability. The naive "POST JSON to URL" approach fails in production.

My key decisions:

1. **Standard Webhooks specification** for interoperability and security
2. **Multi-layer SSRF protection** including DNS resolution validation
3. **Exponential backoff** following industry-standard timing
4. **Registry pattern** for painless extensibility
5. **Type-safe DTOs** for maintainability
6. **Complete delivery tracking** for debugging and compliance

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.

https://redd.it/1pkr2ie
@r_php
Bumping Slim framework from 2 to 3

In case you are stuck at slim 2 and want to move to slim 3, maybe it could be helpful for you.

I just wrote an article how you could do to move to slim 3, you can check out here

I hope it could help you with some ideas how to move forward.



https://redd.it/1pkwdr8
@r_php
Two methods vs one to fetch data

What is your preferred way of handling situations like when you need to fetch multiple records from the database using Doctrine DBAL and already have a method to fetch one. Do you make the one method hybrid,
function getbyID(int|array id) {
#if int add this param
#if array add array param IN()

Less code but mixed.

Or do you make it separated? More code but clearer methods?
function getById(int myid) {...}
function getByIds(array idlist) {...}

Which one you use and why? Following best practices and of course, having compromises sometimes.

https://redd.it/1plpys5
@r_php
Weekly /r/Laravel Help Thread

Ask your Laravel help questions here. To improve your chances of getting an answer from the community, here are some tips:

What steps have you taken so far?
What have you tried from the documentation?
Did you provide any error messages you are getting?
Are you able to provide instructions to replicate the issue?
Did you provide a code example?
Please don't post a screenshot of your code. Use the code block in the Reddit text editor and ensure it's formatted correctly.

For more immediate support, you can ask in the official Laravel Discord.

Thanks and welcome to the r/Laravel community!

https://redd.it/1pmkh1s
@r_php
What's your go-to approach for structuring large Laravel projects?

Hey fellow Laravel devs! 👋

I’ve been working on some fairly large projects lately and I keep running into the same challenge:
“How do I structure my Laravel apps so that they stay maintainable as they grow?”

Some things I’ve experimented with:
- Modular folder structure for features
- Service Providers for reusable logic
- Domain-driven design patterns in Laravel

I’d love to hear from you:
- How do you organize large Laravel projects?
- Any tricks or best practices for keeping code clean and scalable?
- Packages or tools you swear by for project organization?

Sharing some real-life examples would be amazing!
Let’s make it easier for the community to handle big Laravel apps.

Thanks in advance for your insights! 🙌

https://redd.it/1pmnfxh
@r_php