今日已更新 339 条资讯 | 累计 19899 条内容
关于我们

标签:#laravel

找到 46 篇相关文章

AI 资讯

Manticore Speaks MySQL - So I Made It a Laravel Database Driver Instead of a Scout Engine

The problem I've been working with Manticore Search for about two years at EricaPRO , building the search layer for two financial data platforms. For the past year, that work has been a Laravel API. Manticore was never the problem. It's fast and it's stable. The problem was the gap between Manticore and Laravel. I had already built a package for this — laravel-manticore-search , a fluent builder over Manticore's HTTP/JSON API. It works, and it's still in use. But it's a client wrapper. Every feature had to be implemented manually. Every new filter or facet meant more custom architecture around the client, and none of the things Laravel gives you for free — models, migrations, pagination, casts — applied to it. Scout doesn't close that gap either. Scout gives you search() . No full query builder, no migrations for your indexes, sync is your problem. That's not a criticism — Scout abstracts over engines with completely different APIs, so it exposes the lowest common denominator. It just wasn't what I needed. What I needed was simple to describe and annoying to not have: something plug and play. Something Laravel way. Point Eloquent at Manticore and use Eloquent. The insight Manticore speaks the MySQL wire protocol. Out of the box, port 9306. You can connect to it with any MySQL client and run SQL. I had been using that port for two years without thinking about what it meant for Laravel. Because here's the thing: all of Eloquent — models, query builder, migrations, pagination, chunking — sits on top of a Connection and a Grammar . The grammar compiles builder calls into SQL for a specific dialect. That's the entire mechanism behind Laravel supporting MySQL, Postgres, SQLite and SQL Server with one codebase. So the real question was never "how do I re-implement Eloquent on top of Manticore's client?" It was "how thin can a Manticore grammar be?" If Manticore accepts MySQL-protocol connections and mostly-MySQL SQL, then a Laravel database driver — a custom connection plu

2026-07-14 原文 →
AI 资讯

Getting the public IP in PHP — no dependencies, no API key

Getting the public IP in PHP — no dependencies, no API key PHP is still one of the most widely deployed server-side languages, running a significant share of the web's backend code. If you're building a PHP application that needs the public IP address — for geolocation, DDNS, diagnostics, or country detection — this article covers the common patterns using IPPubblico.org : free, no key, HTTPS, JSON and plain text endpoints. Use case 1 — Your server's own public IP (one-liner) The simplest case: a PHP script that needs to know its own public IP. <?php $ip = trim ( file_get_contents ( 'https://ipv4.ippubblico.org/' )); echo $ip ; // 203.0.113.42 file_get_contents works if allow_url_fopen is enabled (it is by default on most servers). If not, use cURL (see below). Use case 2 — With cURL (recommended for production) file_get_contents has no timeout control and minimal error handling. For production code, cURL is better: <?php function getPublicIP (): ?string { $ch = curl_init ( 'https://ipv4.ippubblico.org/' ); curl_setopt_array ( $ch , [ CURLOPT_RETURNTRANSFER => true , CURLOPT_TIMEOUT => 5 , CURLOPT_FOLLOWLOCATION => true , CURLOPT_SSL_VERIFYPEER => true , ]); $response = curl_exec ( $ch ); $httpCode = curl_getinfo ( $ch , CURLINFO_HTTP_CODE ); curl_close ( $ch ); if ( $response === false || $httpCode !== 200 ) { return null ; } return trim ( $response ); } $ip = getPublicIP (); echo $ip ?? 'Unavailable' ; Use case 3 — Full geolocation data When you need country, city, ISP and timezone in addition to the IP: <?php function getIPInfo ( ?string $ip = null ): ?array { $url = 'https://ippubblico.org/?api=1' ; if ( $ip !== null ) { $url . = '&ip=' . urlencode ( $ip ); } $ch = curl_init ( $url ); curl_setopt_array ( $ch , [ CURLOPT_RETURNTRANSFER => true , CURLOPT_TIMEOUT => 5 , CURLOPT_SSL_VERIFYPEER => true , ]); $response = curl_exec ( $ch ); $httpCode = curl_getinfo ( $ch , CURLINFO_HTTP_CODE ); curl_close ( $ch ); if ( $response === false || $httpCode !== 200 ) { retur

2026-07-14 原文 →
AI 资讯

Browsershot Alternatives: HTML to Image in Laravel Without Puppeteer

Cross-posted from the HTML to Image blog , where the original lives. Browsershot is the package most Laravel developers reach for when they need to turn HTML into an image. It wraps Puppeteer, drives real Chrome and produces pixel-accurate output. On your machine it works first time. Then you deploy, and the first render throws Failed to launch the browser process! . The problem is not Browsershot's code. The problem is what it demands from the machine it runs on. What Browsershot actually asks of your server Browsershot is a PHP package with a second runtime hiding inside it. To run it in production you need Node.js, the Puppeteer npm package, a Chrome or Chromium binary, the long tail of shared libraries Chrome links against ( libnss3 , libatk , libgbm and friends on a slim Debian image) and a font set wide enough to cover whatever your templates contain, emoji included. That is manageable on a full VPS you control. It falls apart in the places Laravel apps increasingly run: Laravel Vapor and serverless. The PHP Lambda runtime ships neither Node nor Chrome, and you cannot apt-get your way out of a Lambda. The Puppeteer on Lambda guide covers just how deep that particular hole goes. Shared and managed hosting. No root, no system packages, no browser binary. Browsershot is simply off the table. Slim Docker images. php:8.3-fpm-alpine carries none of Chrome's dependencies. Adding Chromium, its libraries and fonts costs a few hundred megabytes and a permanent maintenance line in your Dockerfile. CI pipelines , where every job downloads a browser before your test suite can touch a render. The dependency does not stay contained either. Even Spatie's newer packages inherit it: spatie/laravel-og-image renders through laravel-screenshot , which drives Browsershot underneath, so the Node and Chrome requirement follows the whole family wherever it goes. The usual workarounds The first workaround is the fat container: bake Chromium, the shared libraries and a font stack into y

2026-07-11 原文 →
AI 资讯

Dev Log: 2026-07-09 — one source of truth, three times over

TL;DR Three unrelated repos, one recurring theme: derive from a single source of truth instead of duplicating it. Shipped a registry-driven sidebar section switcher (public), converged a multi-system password flow, and pushed on a customer-data identity engine. Details on the first two live in their own posts today. 1. Registry-driven sidebar switcher (public) Added a section switcher to the kickoff starter kit. The sidebar, the switcher, and breadcrumbs all read the same config/menu.php list, and the active section is picked by longest URL-prefix match — so a detail page like /admin/roles/42/edit keeps its parent selected. Full write-up in the focused post. 2. One canonical password flow Converged two apps that each rolled their own password-change/reset logic onto a single shared engine, with a fixed order (directory → external DB → local app) and no config-toggle to skip backends. A password that syncs to some systems is worse than one that fails outright, so partial success is now impossible by construction. Also fixed a subtle status bug — an unreachable backend reports skipped (a runtime fact), not disabled (a config state that no longer exists) — and added an audit log so "did it sync?" is a query, not a guess. Separate post today goes deeper. 3. Identity resolution engine (customer data work) Steady progress on a CDP-style identity layer: an idempotent, header-versioned ingest endpoint that queues incoming records, then a resolution engine that can resolve, merge, unmerge, and quarantine profiles. Two things I care about here: PII handling: sensitive identifiers are encrypted at rest with a blind index for lookups, and masked in audit trails — you can search on a value without storing it in the clear. Right-to-erasure: an erasure cascade plus an erasure log, so a deletion request actually propagates and leaves a defensible record that it did. ingest -> queue -> resolve -> profile | +-> merge / unmerge / quarantine No code from this one here — it's teaching t

2026-07-09 原文 →
AI 资讯

When a password sync is 'partly done', it's a bug: converging on one canonical flow

TL;DR Two apps each had their own password-change/reset logic, plus config toggles to enable/disable backends. That combination quietly allowed partial syncs. Fix: one shared engine, one canonical order , backends mandatory (no config-disable), and every attempt written to an audit log. Lesson: for a write that spans several systems, "configurable steps" is a footgun. Make the flow fixed and make failure loud. The setup A user changes their password. Behind the scenes that single password has to land in several systems — a directory, an external database, and the app's own store. Two separate apps were doing this, each with slightly different code, and each with config flags like sync_oracle => true|false to turn backends on and off. Sounds flexible. It's actually a trap. Why configurable backends are a footgun TL;DR: a password that updates 2 of 3 systems is worse than one that updates none — because now the systems disagree and nobody gets an error. The moment a backend is optionally skippable, "skipped" and "failed" blur together. Someone flips a flag in one environment, forgets it in another, and now prod and staging run different flows. Debugging a login failure means first reverse-engineering which steps actually ran. Before After Each app had its own reset logic One shared engine, both apps call it Backends toggle via config Backends are mandatory, always run Order implicit / differed per app One canonical order: New directory → external DB → local app "Did it sync?" answered by guessing Every attempt logged with per-service status The canonical order The order isn't cosmetic. It runs most-authoritative-first, so if a downstream step fails you haven't already told the user their new password works. change/reset request | v [ New Directory ] --ok--> [ External DB ] --ok--> [ Local App store ] | | | fail fail fail | | | +----> stop, record per-service status, surface the failure Same engine, same order, both apps. A change API and a reset flow are just two entr

2026-07-09 原文 →
AI 资讯

Dev Log: 2026-07-07

TL;DR Collapsed a billing model from à-la-carte features to plans-only, in four safe phases. Unified authorization across web, API, and MCP so all three obey one permission layer. Fixed a legacy Oracle password-sync writing to the wrong column. Four repos moved today. Here's the thread that ties most of them together: one source of truth beats two. Billing: plans-only Spent most of the day migrating a SaaS off per-feature à-la-carte subscriptions and onto plans-only entitlements. The interesting part isn't the model — it's doing it without a billing outage: seed plans, switch reads to plans, backfill every org, then delete the old machinery. Expand/contract, four deployable phases. Full write-up in the focused post. One permission layer for three surfaces An ops tool exposed the same actions three ways — web UI, API, and an MCP server for agent access. The bug: each surface checked authorization slightly differently, so an MCP tool could allow something the web UI blocked. The fix was to make the MCP tools gate on the same permission layer as everything else, so: web ─┐ API ─┼─► one permission check ─► allow / deny MCP ─┘ TL;DR: web ≡ API ≡ MCP — three doors, one lock. Also added a dedicated support-engineer role scoped for debugging without handing over the keys to everything, plus identity/diagnostics/SLA read tools so an agent can answer "why didn't this notification send?" without shell access. Before After Each surface authorizes its own way Single permission check, shared MCP tool could out-permission the UI MCP bound to the same guard No debug-scoped role support_engineer role, read-only diagnostics Legacy Oracle password sync Smaller but sharp: a password reset was writing to the wrong Oracle column and also touching a date_modified field it had no business updating. Routed the student reset to the correct password column and dropped the stray write. Lesson with legacy schemas — the column that looks right and the column the app actually reads from are not a

2026-07-08 原文 →
AI 资讯

Killing à-la-carte: migrating a feature-gating model to plans-only

TL;DR Moved a SaaS from à-la-carte feature subscriptions (pay per feature) to plans-only (pay for a tier, get its features). Did it in four phases so nothing broke mid-flight: seed plans → gate on plans → migrate orgs → delete the old machinery. Lesson: model migrations are safest as expand → migrate → contract , not a big-bang swap. The problem The old billing let an org subscribe to individual features à la carte. Flexible on paper, painful in practice: entitlement logic had two sources of truth (per-feature subscriptions and an implicit plan), and every gate had to check both. Time to collapse it into one model — you buy a plan , the plan carries the features. The trap with this kind of change is the temptation to rip out the old columns and ship. Do that and every in-flight subscription, every gate check, and every webhook that still speaks the old language breaks at once. The phased plan I ran it as four ordered migrations. Each phase is deployable on its own and leaves the app working. Phase What it does Why this order F1 Seed Plans into the prerequisite chain New model must exist before anything reads it F2 Gate features on the plan, not the feature-sub Reads switch over while writes still dual-run F3 Deploy op migrates existing orgs onto a plan Backfill — nobody left on the old model F4 Remove the à-la-carte machinery Contract — safe only after F3 This is the expand/contract pattern applied to a domain model, not just a schema. Expand (F1) adds the new thing alongside the old. Migrate (F2–F3) moves reads then data. Contract (F4) deletes the old thing once nothing points at it. F1 seed F2 gate on plan F3 backfill orgs F4 drop features ┌────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ plans │ --> │ reads: plan │ -> │ every org on │ -> │ delete a-la- │ │ exist │ │ writes: both │ │ a real plan │ │ carte code │ └────────┘ └──────────────┘ └──────────────┘ └──────────────┘ safe safe safe safe Gating on the plan The gate collapses to a single question

2026-07-08 原文 →
开发者

Dev Log: 2026-07-05

TL;DR 23 commits across 4 repos, one theme: opening apps to the outside world, safely. Public: kickoff v1.32.0 ships SDK-free support-widget integration stubs. Private: external intake channels (token-authed API, cookie-free widget, signed webhooks) on a helpdesk product; signed public API + rebuild webhooks on an event platform. Everything today was about external surfaces — letting the outside in without leaving the door unlocked. What shipped Where What kickoff v1.32.0 (public) SDK-free support-widget integration stubs: settings class + migration, Livewire admin settings page, Blade component, docs, Pest coverage Helpdesk product (private) External intake channels: token-authed API, magic-link requester view, cookie-free embeddable widget, signed outbound webhooks, hardening pass from an adversarial review Event platform (private) Signed public event API + landing-page rebuild webhooks, persona nav overhaul, 15 new MCP tools, offline PWA check-in, plan-limit enforcement Event platform docs (private) Tracker updates + before/after UX screenshots Stubs, not SDKs kickoff now ships a support-widget integration as stubs — settings class, migration, admin page, Blade component — copied into your app. No composer dependency for glue code: you own it, you can read it, you can change it. For ~100 lines of integration code, a stub beats a package. Intake is three problems The helpdesk work was the day's core: letting outside systems and end users create tickets. Every inbound surface splits into the same three problems — who gets in (token auth, magic links), what they can do (rate limits, severity clamps, single-use entry), and what you send back out (signed, idempotent webhooks). An adversarial review caught four real issues before launch; that story gets its own post, next. Static pages, fresh data The event platform got a signed public API plus webhooks that fire on content changes — so landing pages can be static builds that rebuild themselves when an event changes. C

2026-07-06 原文 →
AI 资讯

A Cookie-Free Embeddable Support Widget: What Adversarial Review Caught

TL;DR Built an embeddable support widget for a helpdesk product: no cookies — a short-lived bearer token in a header, hashed at rest. Entry is an HMAC-signed assertion from the host page. An adversarial review caught four real holes before launch. Outbound webhooks: sign the exact bytes, dedupe key for idempotency, SSRF guard on destination URLs. The requirement: end users file tickets from pages the product doesn't own. That means an embeddable widget — and embeddable means everything you know about sessions stops working. Why cookie-free The widget lives on customers' domains, so any cookie it sets is a third-party cookie — blocked or partitioned by modern browsers. Fighting that means flaky sessions, so: no cookies at all. The entry exchange mints a short-lived session token the widget sends in a header, and the server caches the session keyed by sha256(token) — a cache dump yields nothing replayable. Sessions last 60 minutes, and expiry shows a real recovery path in the UI instead of dying silently. customer backend widget (on customer page) helpdesk API | signs ref|email|name | | | into HMAC assertion ---> |-- redeem assertion (single use) ->| | |<-- session token (60-min TTL) ---| | |-- X-Widget-Token: ... ---------->| What the adversarial review caught Finding Fix Replay burn keyed by client-chosen nonce Burn by HMAC signature — a leaked assertion can't mint extra sessions `\ ` accepted inside signed fields Origin check failed open when Origin/Referer absent Fall back to the unspoofable Sec-Fetch-Dest header to enforce embedding Widget could request critical severity Clamp effective severity (including the channel default) to the widget's allowlist My favourite is the delimiter one. If you sign ref|email|name and accept | inside a field, two different identity tuples can share one valid signature. Canonicalization bugs, not crypto bugs. Webhooks out: sign the exact bytes Outbound webhooks get composed once at enqueue time and stored; the delivery job re-encod

2026-07-06 原文 →
AI 资讯

Dev Log: 2026-07-04

TL;DR Two Laravel backends started serving Flutter apps on the same day — an events platform (auth, orders, offline check-in) and a helpdesk product (ops mode for agents). gatherhub-web moved to plans-only pricing with a comparison matrix driven by one data file. A hardening pass: payment-safe queues, gateway reconciliation, one heavyweight dependency dropped. Two mobile APIs in one day Coincidence, but a useful one: two products I'm building both needed their Laravel backends to serve mobile apps this week. The events platform got the full foundation — token auth (login/refresh/logout/me), participant orders, mobile payment with status polling, push-device registration, and an offline-first staff check-in flow. That last one is the interesting bit; I wrote it up as its own post. The helpdesk product went the other way: its API was client-only, and today it became role-aware. The same endpoints now serve ops agents working tickets from their phones, with abilities deciding what each role sees. One API surface, two personas, no duplicated /admin routes. The lesson that repeated in both: API Resources are the contract. The moment a mobile dev consumes your endpoint, every field you accidentally leak becomes a field you can't remove. Plans-only pricing (public) gatherhub-web , the Next.js marketing site, dropped à-la-carte feature pricing for three plans and gained a plan comparison matrix. Everything renders from a single plans.ts — the matrix, the pricing cards, the enterprise page — so the marketing site can't drift from what's actually sold. A pricing page is a contract too; it deserves a single source of truth as much as your API does. Hardening pass Change Why Bulk email blasts isolated to their own queue one big send must never delay a payment webhook Reconciliation command for stuck pending orders webhooks fail silently; polling the gateway is the safety net maatwebsite/excel → spatie/simple-excel for exports streams rows instead of building sheets in memory, s

2026-07-05 原文 →
开发者

Offline-First Check-In: A Laravel API That Survives Venue Wi-Fi

TL;DR A gate check-in app can't depend on live Wi-Fi: scans must work offline and sync later. Four endpoints do it: manifest download, idempotent batch push, delta pull, online search. Client-generated UUIDs + a unique index make retries safe. Duplicates are a success status, not an error. The problem Physical event, staff scanning tickets at the door, venue Wi-Fi exactly as reliable as you'd expect. If your API sits in the hot path of every scan, the queue at the gate grows at the speed of the worst signal bar in the building. So the design flips the roles: the device owns check-in, the server owns convergence. Like a cashier who keeps a paper ledger when the till goes down — record now, reconcile later. The API surface Endpoint Purpose GET /staff/events/{uuid}/manifest paginated ticket snapshot, downloaded before gates open POST /staff/events/{uuid}/check-ins/batch push queued scans; safe to retry GET /staff/events/{uuid}/check-ins?since=<cursor> pull what other devices did GET /staff/events/{uuid}/participants?q= online fallback search (lost ticket, typo) The sync loop Device Server |--- GET manifest (before event) ------->| | scan offline, queue locally | |--- POST batch [{client_uuid, ts}] ---->| dedupe on client_uuid |<-- 200 {applied | duplicate per item} -| |--- GET check-ins?since=cursor -------->| scans from other devices |<-- delta + next cursor ----------------| Idempotency is the whole trick Every scan gets a UUID generated on the device at scan time . The server puts a unique index on it and inserts-or-ignores: public function batchCheckIn ( BatchCheckInRequest $request , string $uuid ): JsonResponse { $results = collect ( $request -> validated ( 'check_ins' )) -> map ( function ( array $scan ) { $checkIn = CheckIn :: firstOrCreate ( [ 'client_uuid' => $scan [ 'client_uuid' ]], [ 'ticket_id' => /* resolved from scan */ , 'checked_in_at' => $scan [ 'scanned_at' ]], // ... ); return [ 'client_uuid' => $scan [ 'client_uuid' ], 'status' => $checkIn -> wasR

2026-07-05 原文 →
AI 资讯

Laravel Middleware Execution Order Explained: Why Your Middleware Runs in the Wrong Order

Laravel middleware can be perfectly written and still behave unexpectedly. You may notice authentication running too late, permission checks failing, tenant initialization not working, logging middleware missing important data, or custom middleware executing in an order you didn't expect. In many cases, the middleware code itself is not the problem. The real issue is middleware execution order. Understanding how Laravel executes middleware is critical when building secure and scalable applications because every request passes through multiple layers before reaching your controller. Common Symptoms You may encounter problems such as: Authenticated users being treated as guests Permission middleware failing unexpectedly Tenant information not being available Request logging missing user details Rate limiting triggering before authentication Redirect loops after login Middleware appearing to be ignored completely These issues are often caused by middleware running in the wrong sequence. How Laravel Processes a Request A typical Laravel request follows this flow: Browser ↓ Global Middleware ↓ Middleware Group (Web/API) ↓ Route Middleware ↓ Controller ↓ Response ↓ Browser Each middleware layer can inspect, modify, allow, or block the request before it reaches your application logic. Because of this, execution order matters. Example Problem #1 Suppose you have two middleware: Authenticate User Log User Activity Your logging middleware expects an authenticated user. $user = auth()->user(); However, the log always shows null. Why? Because the logging middleware executes before authentication. The solution is ensuring authentication middleware runs first so user information is available when logging occurs. Example Problem #2 Multi-tenant applications often initialize tenant information through middleware. TenantMiddleware If another middleware accesses the database before tenant initialization, queries may use the wrong database connection. This can lead to: Incorrect data

2026-07-03 原文 →
AI 资讯

Migrating Laravel to Symfony Without Rewriting Your Domain

Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You've been handed the ticket everyone dreads: move the app off Laravel and onto Symfony. Maybe the team standardized on Symfony. Maybe a Messenger-and-Doctrine shop acquired you. Maybe someone decided Eloquent's global scopes had cost enough Friday nights. The estimate comes back in quarters. Someone quotes the phrase "big rewrite" and the room goes quiet, because everyone remembers the last big rewrite. Here is the question that decides whether this is a quarter or a year: how much of your business logic imports the framework? If your PlaceOrder logic reaches into request() , calls Order::create() on an Eloquent model, and opens a transaction with DB::transaction() , then the framework is the application and you are rewriting the application. If your business rules sit in plain PHP classes that never heard of Laravel, then the migration is an adapter swap, and adapter swaps ship one route at a time. What the framework actually owns Strip a typical Laravel app down and you find three categories of code. The first is your domain and use cases: the rules about what an order is, what placing one means, when it can be cancelled. This is the part the business pays for. It should not import a framework at all. The second is glue that translates the outside world into calls on that logic: controllers, form requests, Eloquent models, queue jobs, service providers. This is framework-specific by definition. The third is infrastructure your code talks to through interfaces: the database, the queue, the mailer, the payment gateway. A framework migration only touches the second category. The trouble is that most Laravel apps let the first and

2026-07-03 原文 →
AI 资讯

Eloquent Events vs Domain Events: Why the Framework Hook Isn't Enough

Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You wire a listener to Eloquent's saved event on the Order model. When an order is saved, send the confirmation email. It works in the demo. Then a support ticket lands: a customer got two confirmation emails for one purchase, and another got a refund receipt for an order that was never refunded. You dig in. The double email came from a background job that touched updated_at on the order to bump a cache. The bogus receipt came from an admin editing the shipping address, which saved the model, which fired saved , which ran a listener that assumed "saved means the order changed state." None of that was the customer's intent. All of it was persistence. That's the whole problem in one sentence. saved tells you a row hit the database. It does not tell you what happened in your business. What Eloquent events actually fire on Eloquent dispatches creating , created , updating , updated , saving , saved , deleting , deleted , and a few more. Every one of them is tied to a persistence operation on a single model. They fire because you called save() , update() , create() , or delete() , not because a business rule was satisfied. Here is the shape most teams start with: <?php namespace App\Models ; use Illuminate\Database\Eloquent\Model ; class Order extends Model { protected static function booted (): void { static :: updated ( function ( Order $order ): void { // "the order changed, email the customer" OrderMailer :: confirmation ( $order ); }); } } The listener assumes updated means "something the customer cares about changed." It doesn't. updated fires for any dirty column: a cached counter, a nightly touch() , an admin fixing a typo in t

2026-07-03 原文 →
AI 资讯

Laravel Nightwatch: First-Party APM and What It Actually Replaces

Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You already run three tools that half-cover this job. Pulse gives you a live wall on a local route. Datadog runs an agent and prices on host and usage volume, so the bill scales with your infrastructure. Sentry catches the exceptions after they already hurt someone. And none of them can tell you the one thing you actually asked: the checkout request that took 900ms at 14:03 dispatched a job, that job ran a query, and the query is what timed out. Laravel Nightwatch reached general availability in 2025 as the framework's own APM, aimed straight at that gap. It is worth knowing exactly what it captures, what it charges, and where its knowledge of your app stops and yours begins. What Nightwatch actually is Two moving parts. A Composer package inside your app, and a separate agent process that ships the data. composer require laravel/nightwatch The package writes events to a local socket. The agent listens on 127.0.0.1:2407 , batches what it receives, and sends it to Nightwatch's cloud. Because the agent runs outside your request cycle, the request thread is not blocked waiting on a network call to a telemetry backend. Laravel puts the added cost at under 3ms per request ; take that as a starting figure and measure your own before you trust it. # environment token per app + environment NIGHTWATCH_TOKEN = your-env-token # start the collector (keep it running under a # process monitor: Forge daemon, Vapor, supervisor) php artisan nightwatch:agent # confirm it is alive and receiving php artisan nightwatch:status One detail that bites people: the agent has to be running for anything to arrive. In local dev you start it by hand. In product

2026-07-03 原文 →
AI 资讯

Laravel Precognition: Live Validation That Reuses Your Backend Rules

Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You have two copies of the same rules. One lives in a StoreUserRequest on the server. The other lives in a Zod schema, or a Yup object, or a pile of required attributes, on the front end. They started identical. Then someone bumped the password minimum from 8 to 12 on the backend and forgot the client. Now the form says the password is fine, the user clicks submit, and a 422 bounces back with an error the UI never predicted. That drift is the whole reason live client-side validation is annoying to maintain. You are keeping two rulesets in sync by hand, and the sync breaks quietly. Laravel Precognition removes the second copy. The front end asks the server "would this pass?" before the user submits, and the server answers using the exact same validation rules the real request will run. What a precognitive request actually is A precognitive request is a normal HTTP request to your real endpoint, tagged with a Precognition: true header. Laravel sees the header, runs the route's middleware and validation, and then stops before your controller does any real work. It never writes a row. It never sends an email. It runs the rules and returns the verdict. Success comes back as 204 No Content with a Precognition-Success: true header. Failure comes back as a normal 422 with the same JSON error bag your form submit would produce. Same rules, same messages, same field names. There is no second schema to drift. The lifecycle is worth holding in your head: Front end sends the form state to the real URL with Precognition: true . Middleware runs. FormRequest validation runs. Laravel short-circuits: your controller body never executes. Response is

2026-07-03 原文 →
AI 资讯

Integrating Claude/OpenAI API into a Laravel App: A Practical Guide

After 12+ years of building PHP applications, I recently added AI-powered features to a production Laravel dashboard — automatic report summaries generated from raw analytics data. What surprised me wasn't how hard it was. It was how little good PHP-focused content exists on this topic. Almost every LLM tutorial assumes you're writing Python. So here's the guide I wish I had: integrating the Claude API and OpenAI API into a Laravel app, with a clean architecture you can actually ship to production. What we'll build: a ReportSummaryService that takes raw data and returns a human-readable summary — with a driver pattern so you can switch between Claude and OpenAI with one config change. Step 1: Get Your API Keys Claude: Sign up at the Claude Console , generate a key under Account Settings. OpenAI: Get a key from the OpenAI Platform . Add them to your .env : AI_PROVIDER=claude ANTHROPIC_API_KEY=sk-ant-xxxxx ANTHROPIC_MODEL=claude-sonnet-4-6 OPENAI_API_KEY=sk-xxxxx OPENAI_MODEL=gpt-5-mini ⚠️ Never hardcode API keys. Never commit them. If you've ever pushed a key to Git, rotate it immediately. (You know this. I'm saying it anyway.) Now register them in config/services.php — this is the Laravel way, so you can use config() everywhere and benefit from config caching: 'anthropic' => [ 'key' => env ( 'ANTHROPIC_API_KEY' ), 'model' => env ( 'ANTHROPIC_MODEL' , 'claude-sonnet-4-6' ), ], 'openai' => [ 'key' => env ( 'OPENAI_API_KEY' ), 'model' => env ( 'OPENAI_MODEL' , 'gpt-5-mini' ), ], 'ai' => [ 'provider' => env ( 'AI_PROVIDER' , 'claude' ), ], Step 2: Understand the Two APIs (They're 95% Similar) Both are simple REST APIs. You POST JSON, you get JSON back. Claude (Messages API): POST https://api.anthropic.com/v1/messages Headers: x-api-key: YOUR_KEY anthropic-version: 2023-06-01 content-type: application/json OpenAI (Chat Completions API): POST https://api.openai.com/v1/chat/completions Headers: Authorization: Bearer YOUR_KEY content-type: application/json The key differenc

2026-07-02 原文 →
AI 资讯

Stop Writing the Same Laravel Boilerplate: Generate a Complete Module with One Artisan Command

Stop Writing the Same Laravel Boilerplate: Generate a Complete Module with One Artisan Command Every Laravel developer has experienced this. You start implementing a new feature and immediately create the same files you've created dozens of times before: Model Migration Repository Service Form Request API Resource Policy Filter Status Enum Feature Tests Unit Tests Swagger/OpenAPI annotations The process is repetitive, time-consuming, and easy to get wrong. The Problem While Laravel provides excellent generators, building a production-ready API module still requires running many Artisan commands and wiring everything together manually. For large projects following Repository and Service Layer architectures, this becomes even more repetitive. The Solution I built Laravel Base , an open-source package that generates an entire production-ready module from a single command. php artisan make:module Product The generated module includes: ✅ Model ✅ Migration ✅ Repository Pattern ✅ Service Layer ✅ Form Requests ✅ API Resources ✅ Filters & Pagination ✅ Policies ✅ Status Enums ✅ Swagger/OpenAPI annotations ✅ Feature Tests ✅ Unit Tests Modern Development Experience The package is actively maintained and includes: Laravel 10–13 support PHP 8.1–8.4 compatibility GitHub Actions CI PHPStan static analysis Laravel Pint code style Automated releases Repository automation Why I Built It After working on multiple Laravel projects, I noticed I was spending too much time generating the same project structure instead of focusing on business logic. I wanted a tool that lets developers start implementing features immediately rather than setting up folders and classes. Feedback Welcome Laravel Base is open source, and I'd love to hear your thoughts. GitHub Repository: https://github.com/MuhammedMSalama/LaravelBase Packagist: https://packagist.org/packages/muhammedsalama/laravel-base The package was recently featured by Laravel News, and I'm continuing to improve it based on community feedbac

2026-06-30 原文 →
AI 资讯

Resolve the tenant from the user, not the request

TL;DR A multi-tenant app was resolving the active tenant from the request (subdomain/header) instead of the authenticated user . That makes the client the source of truth for "which tenant am I" — the wrong place for it. Fix: derive the tenant from the user's organization membership, enforce it in middleware, and fail closed. One test locks the behaviour. The bug, in one sentence The request was telling the app which tenant to load, and the app believed it. In a multi-tenant SaaS, every query is implicitly scoped: "give me this tenant's dashboards." If the tenant ID comes from something the client controls — a subdomain, a header, a route param — then the scoping is only as trustworthy as the client. That's a leak waiting to happen. Where the trust should live Think of it like a building pass. The request is someone saying "I'm here for floor 9." The membership record is the pass that says which floors you're actually allowed on. You check the pass, not the claim. Before After Source of truth request (subdomain / header) user's organization membership Who decides the tenant the client the server Failure mode user can land in a tenant they don't belong to resolution fails closed Testable? hard — depends on request shape yes — depends on the user The shape of the fix Resolve the tenant from the authenticated user's organization, in one middleware, before anything tenant-scoped runs: final class SetTenantContext { public function handle ( Request $request , Closure $next ): Response { $org = $request -> user () ?-> currentOrganization (); // No org, no tenant context. Fail closed, never guess. abort_if ( $org === null , 403 , 'No organization context.' ); Tenancy :: setCurrent ( $org -> tenant ); // server-derived, not request-derived return $next ( $request ); } } The key line isn't the setCurrent() — it's that the value comes from $request->user() , not from $request . The user is authenticated; the subdomain is not. request ──> [auth] ──> [SetTenantContext] ──> tena

2026-06-30 原文 →
AI 资讯

When a KPI reads 163 billion instead of 819

TL;DR A metrics engine had two query paths — a SQL push-down for big datasets, an in-memory aggregator for small ones. They drifted. The push-down path bound a metric parameter but never added it to the WHERE . With several metric series in one dataset, every query summed across all of them. A KPI that should read 819 read 163,667,603,769 . Fix: put the metric_key predicate in the shared base WHERE so every compile path inherits it, and regression-test both paths assert it. The setup: two paths, one contract A lot of analytics layers compute the same number two ways. For a big dataset you push the aggregation down to the database. For a small one — a preview, a draft dashboard — you pull the rows and aggregate in memory. Faster path, correct path. Both are supposed to return the same value. That's the contract. The dataset stores rows keyed by a metric_key , because one dataset can hold several series at once — say a plain row count and a count-distinct. Each series lives in the same table, told apart only by its key. The bug: a bound param is not a filter The in-memory aggregator filtered by metric_key correctly. The SQL compiler bound a metric parameter into the query... and never referenced it in the WHERE . With a single series in the dataset, it worked by accident — there was nothing else to sum. Add a second series and the math quietly breaks: the query sums across every series. In this case the second series stored hashed values around 1.9 billion each, so the KPI ballooned from 819 to 163 billion. Before After metric value bound, unused bound WHERE predicate (none on metric) metric_key = {metric:String} 1 series in dataset correct (by luck) correct N series in dataset sums across all isolated The lesson is small and easy to miss: binding a parameter only makes the value available — it does nothing until a predicate references it. When one path already returns sane-looking numbers, nobody goes looking. The real fix is parity, not a patch You could bolt the pr

2026-06-30 原文 →