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

标签:#laravel

找到 54 篇相关文章

AI 资讯

Code Quality Gates: Laravel Pint and PHPStan in CI

The moment you add a code quality gate to CI, something quietly changes on your team. Developers stop arguing about code style in reviews because the robot handles it. Static-analysis errors stop reaching production because they can't pass the gate. You stop inheriting other people's technical debt because new errors are blocked immediately, even if old ones exist. That last point is the PHPStan baseline trick. We'll get to it. The Code Quality Job This job runs on every push and is intentionally fast — no database, no migrations, just PHP and a vendor folder: quality: name: Code Quality runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup PHP 8.4 uses: shivammathur/setup-php@v2 with: php-version: '8.4' extensions: mbstring, pdo, bcmath, zip, intl coverage: none tools: composer:v2 - name: Cache Composer dependencies uses: actions/cache@v4 with: path: api/vendor key: php-8.4-composer-${{ hashFiles('api/composer.lock') }} restore-keys: php-8.4-composer- - name: Install dependencies run: composer install --no-interaction --prefer-dist --no-progress - name: Check formatting (Laravel Pint) run: ./vendor/bin/pint --test - name: Static analysis (PHPStan) run: ./vendor/bin/phpstan analyse --memory-limit=2G --no-progress pint --test runs in read-only mode — it checks without modifying files. Exit code 1 if any file has style drift. Run ./vendor/bin/pint locally (without --test ) to auto-fix before pushing. Configuring PHPStan Add a phpstan.neon to your API root: parameters: level: 8 paths: - app - Modules excludePaths: - vendor ignoreErrors: - identifier: missingType.iterableValue - identifier: missingType.generics Level 8 is strict. On a fresh project, aim for it from day one. On an existing codebase, start at level 4 and raise it gradually. The Memory Problem PHPStan at level 8 on a large codebase needs more than 512MB of RAM. Use --memory-limit=2G in CI. Locally, add a shortcut to composer.json : "scripts": { "analyse": "php -d memory_limit=2G vendor/bi

2026-07-25 原文 →
AI 资讯

I built a browser-based pixel-art & animation editor with Vue, Laravel and AI

I'm a solo dev, and for the past few months I've been building Pixanima — a pixel-art and animation editor that runs entirely in the browser, with an optional AI assistant baked in. It just launched, and I wanted to share the parts that were technically interesting: making a general image model output clean pixel art, an atomic credit system, AI inbetweening for animation, and why the whole business model falls out of one architectural fact. What it is Draw pixel art with layers, groups and effects, animate it on a frame timeline with onion-skin, and export to GIF / sprite sheets / PNG — all client-side, no install. On top of that, an AI assistant turns a text prompt into sprites, seamless tiles and palettes, re-poses characters, and generates in-between animation frames. The frontend is Vue 3 driving an HTML canvas; the backend is Laravel 12 (PHP 8.4) . Here's what I learned. Everything runs in the browser — and that decided the business model The entire editor is client-side. Projects live in IndexedDB ; nothing is uploaded. That's great for privacy and speed, but it has a consequence a lot of people miss: you cannot meaningfully gate a client-side feature. If drawing, layers and export all run in the user's browser, any "pro" paywall around them is both unenforceable and, honestly, hostile to a price-sensitive hobbyist community. So I flipped it: the editor is 100% free, forever . The only paid thing is AI — because AI is the only part with a real marginal cost, and it requires a backend (which conveniently also protects the code that costs money to run). More on that below. Making a general model output clean pixel art The naive approach — prompt a diffusion model with "pixel art, 16 colors" — gives you pixel-art-ish mush: anti-aliased edges, hundreds of colors, no real grid. Useless as an actual sprite. The fix is a post-processing pipeline. The model just produces raw input; the "pixel art" is made deterministically afterward with PHP's GD: 1. Generate a norma

2026-07-24 原文 →
AI 资讯

Inertia and API responses living together in harmony

I love InertiaJS to the point where it's becoming a personality trait I tend to want to use it for everything, but adding Inertia to an existing Laravel API gets awkward fast. Same thing happens in the other direction: you start with a full Inertia frontend and then realize you want to expose some of that data as a public API too. The naive solutions are: Sprinkle if ($request->wantsJson()) into your controllers Maintain two separate routes that return the exact same data Neither feels right. So I made inertia-split Starting fresh with Inertia: serve both from the same controller class ProjectController extends Controller { use HasHybridResponses ; public function index () { return $this -> respond () -> component ( 'Projects/Index' , [ 'projects' => Project :: all (), ]); // Inertia request → renders the Svelte/Vue/React component // API request → returns JSON } } The controller doesn't check anything. Inertia requests get an Inertia response, API clients get JSON. Existing API? Don't touch it If you just want to make an existing API method Inertia-aware, one annotation is enough: #[InertiaComponent('Users/Show')] public function show ( User $user ): array { return [ 'user' => $user ]; } The method body stays exactly as it was. Inertia requests get the component rendered with your data as props. Everything else gets the same JSON as before. Methods without the annotation are completely unaffected. Wait, how does this even work? The package can out Inertia's ResponseFactory for its own in the service provider (opt-in): $this -> app -> singleton ( ResponseFactory :: class , HybridResponseFactory :: class ); // checks if it's an Inertia request and returns appropriate response Good old OOP. Thank you polymorphism. Wrap-up Whatever the direction of your problem, making Inertia and API endpoints use the same controller is a big win. You're still responsible for writing routes and wiring middlewares, but this should save a lot of time and effort. Still in beta, use accor

2026-07-23 原文 →
AI 资讯

Ebook Reviewer Wanted: Help Me Find What's Gone Stale

Technical books have a shelf life that nobody prints on the cover. A cookbook from 2018 still works. A history book from 2018 still works. A Laravel book from 2018 will teach you a middleware pattern that was replaced twice, recommend a package whose maintainer archived it, and show you a test suite in a syntax that no longer runs. I've written a series of ebooks on PHP, OOP, SOLID, design patterns, Laravel conventions, testing with Pest, application architecture, and AI-assisted development. There are also two aimed at kids learning to code. Every one of them was correct when I wrote it. That is a much weaker claim than "correct now," and I'd rather find the gap myself than have a reader find it for me. So: I'm opening the series to technical reviewers before the final edition ships. Applications close Sunday, 26 July. Why I can't do this alone Not because I lack the time. Because I lack the distance. When you write a technical book, you build a mental model of the reader and then you write to that model for months. The model is always partly wrong, and you are the last person who can see how. You skip a step because it's obvious to you. You keep an example that made sense when you drafted the chapter and no longer matches the surrounding code. You cite a package because it was the right answer for a project you shipped two years ago, and you never checked whether it's still maintained. None of this shows up on a reread. Rereading your own work is mostly pattern-matching against your own memory. You see what you meant, not what you wrote. A reviewer who has never seen the manuscript reads what's actually on the page. That's the whole value, and it's not something more effort on my side can substitute for. What the job actually is You pick one book from the series. You read it properly — not skim it — and you flag what's broken. Four things in particular: Things that are outdated. The framework moved, the syntax changed, there's now a cleaner way to do the same thin

2026-07-22 原文 →
AI 资讯

Supercharge Your Algorithmic Trading with CoinQuant PHP: The Ultimate SDK for Laravel and PHP 8.1+

Are you tired of wrestling with raw cURL requests, parsing Server-Sent Events (SSE) by hand, and building complex polling loops just to interact with trading APIs? If you are a PHP developer or a Laravel enthusiast looking to dive into algorithmic trading, your life is about to get a whole lot easier. Meet coinquant-php , the official PHP and Laravel SDK for the CoinQuant Public API. CoinQuant is revolutionizing the way we approach algorithmic trading. It takes a trading idea described in plain English and turns it into a backtestable strategy using advanced AI. However, integrating such powerful tools into your own applications can often be a daunting task. That is where coinquant-php steps in. This robust SDK wraps the public API, providing a seamless, developer-friendly experience that lets you focus on what really matters: building profitable trading strategies. In this comprehensive guide, we will explore why coinquant-php is a game-changer for PHP developers, delve into its standout features, and show you how to get started in minutes. Why Choose CoinQuant PHP? The landscape of algorithmic trading is often dominated by Python, but PHP remains a powerhouse for web development, especially with frameworks like Laravel. coinquant-php bridges the gap, allowing PHP developers to leverage the cutting-edge AI capabilities of CoinQuant without leaving their preferred ecosystem. 1. Built for Modern PHP The library is designed for the modern era of PHP. It requires PHP 8.1+ and embraces strict typing, ensuring your code is robust and free from legacy baggage. Whether you are building a standalone script or a complex enterprise application, the SDK provides a solid foundation. 2. First-Class Laravel Integration If you are using Laravel 10, 11, 12, or 13, you are in for a treat. The package auto-registers its ServiceProvider and Facade , meaning there is absolutely zero manual wiring required. You can simply install the package and start using the CoinQuant facade anywhere

2026-07-22 原文 →
AI 资讯

How to Generate Verifiable PDF Certificates in Laravel

When a learner finishes a course they want a certificate that behaves like a document: something they can print at full quality, attach to a job application and that an employer can check is genuine. In this tutorial you'll build exactly that in Laravel: a Blade-designed certificate rendered as a vector PDF, with a QR code that resolves to a verification page and an email that delivers it automatically. This post originally appeared on Accreditly as How to generate verifiable PDF certificates in Laravel . Keep the design in Blade The certificate is an ordinary Blade view. Everything lives in one file, styles inline, so the markup you preview in the browser is exactly what gets rendered. If you want a designed starting point rather than a blank page, the certificate of completion template is a good base to adapt. {{-- resources/views/certificates/template.blade.php --}} <!doctype html> <html> <head> <meta charset="utf-8"> <style> body { font-family: Georgia, serif; color: #1a2233; margin: 0; } .certificate { padding: 60px; border: 6px double #b28a2f; margin: 24px; text-align: center; } .heading { font-size: 15px; letter-spacing: 4px; text-transform: uppercase; color: #b28a2f; } h1 { font-size: 44px; margin: 24px 0 8px; } .course { font-size: 22px; margin: 4px 0 28px; } .issued { font-size: 15px; color: #5a6478; } .qr { margin-top: 36px; } .verify-url { font-size: 12px; color: #5a6478; } </style> </head> <body> <div class="certificate"> <p class="heading">Certificate of Completion</p> <h1>{{ $certificate->user->name }}</h1> <p class="course">has completed {{ $certificate->course }}</p> <p class="issued">Issued {{ $certificate->issued_at->format('j F Y') }}</p> <div class="qr"> {!! QrCode::size(110)->generate(route('certificates.verify', $certificate)) !!} </div> <p class="verify-url">{{ route('certificates.verify', $certificate) }}</p> </div> </body> </html> Two things are doing quiet work here. The design flows like a document rather than being pinned to fixed pixel

2026-07-22 原文 →
AI 资讯

Supercharge Laravel Boost with Neo4j MCP 🚀

AI coding assistants have transformed the way we build software. They can understand your code, generate features, and help with debugging but they usually have no idea what's inside your database. That's where Neo4j Laravel Boost comes in. It integrates the official Neo4j MCP server directly into Laravel Boost , giving any MCP-compatible AI client access to your live Neo4j database and graph tooling. Instead of managing multiple MCP servers, everything is exposed through your existing Laravel Boost server. Why Use Neo4j Laravel Boost? Once configured, your AI assistant can: 🔍 Inspect your live Neo4j schema 📝 Execute read and write Cypher queries 🧠 Query your Laravel container dependency graph 📊 Access Graph Data Science (GDS) procedures 🤖 Work through a single Laravel Boost MCP server Instead of relying solely on static code analysis, your AI assistant gains access to your application's graph data and architecture, making it much more capable. Installation Install the package using Composer. composer require --dev neo4j/laravel-boost Configure your Neo4j connection. NEO4J_URI=bolt://localhost:7687 NEO4J_USERNAME=neo4j NEO4J_PASSWORD=your-password Run the interactive setup. php artisan neo4j-boost:setup The setup wizard validates your Neo4j connection, configures the package, optionally installs the official Neo4j MCP binary, and can even spin up a local Neo4j Docker instance for you. Connect Your MCP Client Your AI client only needs a single MCP server configuration. { "mcpServers" : { "laravel-boost" : { "command" : "php" , "args" : [ "artisan" , "boost:mcp" ], "env" : { "APP_ENV" : "local" } } } } Whether you're using Cursor , Claude Code , or another MCP-compatible client, Neo4j tools become available alongside your existing Laravel Boost tools. Explore Your Laravel Dependency Graph One of the most interesting features of Neo4j Laravel Boost is the ability to export your Laravel service container directly into Neo4j. php artisan container:graph Once exported, yo

2026-07-21 原文 →
AI 资讯

Give your Laravel AI agent real-time web knowledge

One of the best things about the Laravel AI SDK is that it doesn't lock you into a provider. Swap OpenAI for Anthropic, add Gemini as a failover, and your agent code barely changes. That's the entire pitch, and it mostly holds up. Until your agent needs to browse the web. The provider-agnostic promise has a hole in it The SDK ships two provider tools for giving agents access to the web: WebSearch and WebFetch . They're convenient, a couple lines and your agent can search or fetch a page. But they're not really part of the SDK's unified layer, they're implemented natively by whichever AI provider you're using, which means their availability depends entirely on which model you picked. WebSearch works on Anthropic, OpenAI, Gemini, and OpenRouter. WebFetch only works on Anthropic and Gemini. So if you configure a failover chain that includes Groq, DeepSeek, Mistral, or xAI, and one of those becomes the active provider, your agent quietly loses the ability to browse. Not an error, not a warning, just an agent that stops citing sources or stops noticing the page it was supposed to check even changed. That's a rough thing to discover in production. You built a failover chain specifically so a rate limit or outage wouldn't take your feature down, and it turns out one of your fallback providers was silently missing a capability the whole time. Even when it works, it's pretty shallow Set the provider problem aside for a second. Even on a provider where WebSearch and WebFetch are both available, what you get back is raw: search results or fetched page content that the model has to reason over itself. There's no schema. There's no guaranteed citation tracking. If you want your agent to return a structured, sourced answer, that reasoning happens somewhere in the model's own inference, not in a layer you control or can test. For a lot of use cases, that's fine. For anything where the answer needs to be defensible or reproducible, a research assistant, a competitive brief, anythin

2026-07-17 原文 →
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 原文 →