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

标签:#HP

找到 185 篇相关文章

AI 资讯

The Laravel 13 Features That Matter in Real Projects

The Laravel 13 Features That Matter in Real Projects Laravel 13 shipped on March 17, 2026, and the upgrade story is unusually simple: zero application-level breaking changes from Laravel 12, one hard requirement (PHP 8.3), and several features that are genuinely useful in production rather than just impressive in release notes. This post focuses on the features you will actually reach for on real client projects — not an exhaustive tour. For the full release overview, upgrade checklist, and breaking changes reference, see Laravel 13: Features, Upgrade Guide, and Breaking Changes . Prerequisites: PHP 8.3+, Laravel 13.x (latest stable: 13.14.0 as of June 2026), Composer 2.x. 1. PHP Attributes on Models and Controllers Laravel 13 adds PHP 8-style #[Attribute] support across 15+ framework locations. The old property-based syntax still works — this is purely additive. On Eloquent Models: use Illuminate\Database\Eloquent\Attributes\Table ; use Illuminate\Database\Eloquent\Attributes\Fillable ; use Illuminate\Database\Eloquent\Attributes\Hidden ; #[Table('posts', primaryKey: 'id', incrementing: true, timestamps: true)] #[Fillable('title', 'body', 'user_id')] #[Hidden('deleted_at')] class Post extends Model {} On Controllers: use Illuminate\Routing\Attributes\Controllers\Authorize ; use Illuminate\Routing\Attributes\Controllers\Middleware ; #[Middleware('auth')] class CommentController extends Controller { #[Middleware('subscribed')] #[Authorize('create', [Comment::class, 'post'])] public function store ( Post $post ) { } } When to actually use this: Attributes shine on large domain models where $fillable , $hidden , $casts , and relationship declarations are scattered across the class. Collocating table definition and mass assignment rules at the top of the file improves readability at a glance. On small CRUD models, the tradeoff is extra import lines for minimal gain. Common mistake: Mass-converting every existing model to attribute syntax in a single PR. It creates a lar

2026-08-10 原文 →
开发者

How to Secure Your WordPress Dashboard and Prevent Clients from Breaking Their Sites

A guide on using Admin Extension Access Control to lock down WordPress plugins and prevent unauthorized changes. How to Secure Your WordPress Dashboard and Prevent Clients from Breaking Their Sites If you are a freelance web developer or run an agency, you have probably experienced the dread of a client accidentally bringing down their WordPress site. You spend weeks building a robust, performant website, only for an unauthorized user to log into the dashboard, start deactivating essential plugins, or install poorly coded extensions that break everything. WordPress is fantastic because of its flexibility, but out of the box, any Administrator can touch everything . To solve this problem, I want to introduce a lightweight solution: Admin Extension Access Control . What is Admin Extension Access Control? Admin Extension Access Control is a WordPress plugin designed to give you granular control over who can see, modify, install, or delete plugins on your site. Built for modern environments (PHP 8.1+ and WordPress 6.0+), it allows you to configure strict role-based access rules without writing custom PHP functions in your functions.php file every time. Key Features Global Lockdown : Completely remove the plugins page for specific user roles. Granular Permissions : Restrict the ability to add, delete, activate, deactivate, or install plugins on a per-role basis. Exempt Users Whitelist : Designate trusted administrators (like yourself) who bypass all lockdown rules. Only exempt users can configure the access control settings. Dashboard Cleanup : Hide the plugins menu item from unauthorized users to keep the dashboard less confusing for clients. How It Works Once installed and activated, the user who activates the plugin is automatically added to the Exempt Users list. This prevents you from accidentally locking yourself out. From the settings panel, you can select which roles should be restricted from managing plugins. For example, you can give your client an "Administrat

2026-08-10 原文 →
AI 资讯

Building a Multi-Vendor Home Services Marketplace with Laravel: Architecture, Workflows and Key Decisions

Building a Multi-Vendor Home Services Marketplace with Laravel: Architecture, Workflows and Key Decisions Building a home services marketplace looks straightforward until you start mapping the actual workflows. A customer searches for a service, chooses a provider, selects a time slot, enters an address, pays, and receives confirmation. Simple enough. But behind that booking are several systems working together: customers, providers, services, locations, schedules, bookings, payments, invoices, notifications, and administration. For Laravel developers, the real challenge isn't creating another CRUD application. It's designing these components so the marketplace remains maintainable as providers, locations, services, and bookings grow. This article explores some of the most important architecture and development decisions to consider when building a multi-vendor home services marketplace with Laravel. 1. Think of It as Three Connected Applications A useful starting point is to stop thinking about the marketplace as one application. In practice, you're creating experiences for three different types of users: Customers Service Providers Marketplace Administrators Each has different responsibilities and permissions. Customer Experience Customers typically need to: Register and manage their account Select their location Discover services Find available providers View service details Choose an appointment date and time Save service addresses Create bookings Make payments View booking history Access invoices The customer interface should remain simple even if the system behind it is complex. A typical booking flow may look like: Location → Service → Provider → Date & Time → Address → Payment → Confirmation Every unnecessary step increases friction. 2. The Provider Side Is a Different Product The provider dashboard deserves just as much attention as the customer interface. A service professional or company may need to manage: Business profile Services Pricing Service areas

2026-08-09 原文 →
AI 资讯

Building Robust Crypto Data Pipelines in PHP: Introducing the Token Terminal SDK

The cryptocurrency and decentralized finance ecosystems generate an overwhelming amount of data every single day. For developers building financial dashboards, algorithmic trading tools, or market research platforms, accessing clean, standardized, and reliable data is absolutely critical. Token Terminal has established itself as a premier provider of fundamental financial data for the crypto space, offering institutional-grade metrics across various blockchains and decentralized applications 1 . However, integrating complex third-party APIs into enterprise PHP applications often requires writing significant amounts of boilerplate code to handle edge cases, rate limits, and unexpected response structures. To solve this problem and streamline the developer experience, the PHP community now has access to a dedicated solution: the tokenterminal-php SDK. This new open-source package provides a robust, fully-typed, and developer-friendly PHP 8.1+ client for the Token Terminal API v2 2 . Designed with modern PHP standards and framework integration in mind, it abstracts away the complexities of the underlying HTTP transport, allowing developers to focus entirely on building their applications rather than wrestling with API mechanics. The Challenge of Integrating Financial APIs When working with comprehensive financial data APIs like Token Terminal, developers frequently encounter several architectural challenges. First, there is the issue of rate limiting. Token Terminal enforces a strict limit of 1,000 requests per minute 3 . When building data pipelines that ingest historical metrics across hundreds of assets, hitting this limit is practically guaranteed. A naive implementation will simply crash or drop data, requiring manual intervention. Second, the cryptocurrency space moves rapidly. Projects frequently rebrand, merge, or migrate to new smart contracts. The Token Terminal API handles this gracefully by issuing HTTP 308 Permanent Redirects when a requested project ID ha

2026-08-09 原文 →
开发者

Free, Zero-Dependency YouTube Website Embed (Self-Hostable PHP/JS)

Hey DEV community! 👋 If you've ever tried to embed a dynamic YouTube channel feed, a live stream detector, or playlist carousel on a client site, you've probably run into two major issues: Expensive SaaS widgets that slap watermarks on your site unless you pay a monthly fee. Leaking your YouTube API Key directly in the frontend script. To solve this, we built YT Widget —a free, self-hostable, dependency-free JavaScript library that handles YouTube feeds, playlists, channel stats, and live stream status seamlessly. 📦 Where to Get It The project is fully open-source and ready for your production projects: Source Code & Contributions: scott8462 / YT-Widget A free, self-hostable, dependency-free JavaScript library for embedding YouTube feeds, playlists, channel stats, single videos, and live stream status on any website. YT Widget — Free Open-Source YouTube Website Embed A free, self-hostable, dependency-free JavaScript library for embedding YouTube feeds, playlists, channel stats, single videos, and live stream status on any website — just like SociableKIT, but 100% free and open-source. Created and provided free to the developer community by R&S Development . ✨ Features 📺 5 Widget Types feed : Latest channel uploads grid or list live : Auto-detects live broadcasts and embeds the live player — shows a custom Offline Card with recent uploads when offline playlist : Show videos from any YouTube playlist stats : Channel metrics cards (Subscribers, Views, Videos count) single : Responsive single video player with metadata 🔒 Secure PHP Server Proxy ( proxy/ ) : Keep your YouTube API key hidden server-side with built-in CORS, rate limiting, and 5-minute response caching. 🎨 Full Color Customization Light & Dark themes Custom Accent / Button… View on GitHub Alternative Downloads & Mirrors: Download on SourceForge ✨ Core Features 📺 5 Widget Types: Switch layouts instantly ( feed grid/list, live stream detector, playlist fetcher, profile stats , or single responsive video). 🔒 Se

2026-08-09 原文 →
AI 资讯

# Why I’m Rewriting a PHP Extension in C23, Not C++

I forked the DataStax Cassandra driver when it stopped compiling on PHP 8 and most of its maintainers had already moved on. My first instinct was to write the new parts in C++. I built a Zend wrapper class, used RAII throughout, and put smart pointers around zval s—the whole modern setup. It introduced memory bugs that took me days to track down, and I did not get a meaningful benefit in return. So the driver is being rewritten in C23. I want to explain why, because “just use C++; it’s safer” is the reflexive answer. For a PHP extension, I no longer think it is the right one. This is not an argument that C++ is a bad language. In an application where I own the allocator, error model, and object lifetimes, std::vector and std::unique_ptr earn their keep. A PHP extension is different: the Zend Engine owns those rules, and its rules are written in C. The problem is not that C++ cannot call the Zend API. Plenty of extensions do. The problem is impedance: each abstraction has to be taught PHP’s lifetime rules, and the teaching code can become more complicated than the work it was meant to simplify. These are the four places where that cost me real debugging time. PHP owns the allocator PHP has its own memory manager. Request-scoped memory is allocated with functions such as emalloc , ecalloc , and safe_emalloc , then released with efree . Zend tracks that memory and normally reclaims what remains at request shutdown. Persistent allocations use a separate API because they have a different lifetime. Plain malloc and free —and therefore ordinary new and delete —sit outside that request-memory model. The moment I put a std::vector<zval> in an extension, its backing storage uses the C++ allocator unless I replace it. The obvious fix is a custom allocator: template < class T > struct PhpAllocator { using value_type = T ; template < class U > PhpAllocator ( const PhpAllocator < U >& ) noexcept {} PhpAllocator () noexcept = default ; [[ nodiscard ]] T * allocate ( std :: size_t

2026-08-08 原文 →
AI 资讯

I stopped hardcoding locales, and adding a language became a one-line change

Most i18n tutorials stop at "put your strings in a JSON file". That covers labels. It does not cover the thing that actually breaks: URLs. I run a personality test site in five locales (French, English, Brazilian Portuguese, European Portuguese, Spanish). Same site, five language trees, roughly 96 articles each. The first version had the shape every project seems to grow by accident: $prefix = $locale === 'en' ? '/en' : '' ; That line, or a cousin of it, spread across controllers, views and helpers. Every one of them was correct when written and wrong the moment a third locale showed up. The bug it produces is the worst kind: nothing throws, the page renders, and the Spanish version quietly links to French URLs. The rule that fixed it One rule, enforced by a test: no locale literal anywhere outside the config file. Not in controllers, not in views, not in helpers. If code needs to know something about a locale, it asks the config. Adding a locale then becomes: content files, plus one entry in config/locales.php . Nothing structural. 'default' => 'fr' , 'available' => [ 'fr' , 'en' , 'pt-br' , 'pt-pt' , 'es' ], Three helpers cover essentially every call site: locale_prefix () // '' for the default locale, '/es' otherwise locale_url ( '/disc' ) // prefixed, canonical, ready to print locale_slug ( 'groupe' ) // the localized route segment The part nobody warns you about: slugs are data Labels translate. Slugs are a different problem, because a slug is simultaneously a URL, a cache key, an SEO asset and a foreign key into your own content. The decision that saved me: one locale is canonical, always. French, in my case. Every slug in every other language resolves back to a French slug before anything else happens. $slugs = app ( SlugService :: class ); $slugs -> toLocale ( 'quatre-tendances' , 'es' ); // 'cuatro-tendencias' $slugs -> resolveToCanonical ( 'cuatro-tendencias' ); // 'quatre-tendances' Without that pivot you get N-to-N translation tables and, eventually, two

2026-08-08 原文 →
AI 资讯

How We Evolved a Cultural Recommendation Feed From a Weighted SQL Ranker to a Narrative Affinity Model

Building a personalization engine for a multi-format content feed, without machine learning, and the testing process that forced us to rebuild it. TL;DR We run a collaborative cultural curation platform (think: user-submitted recommendations for movies, books, games, music, and long-form posts, all mixed into one feed) on a fairly ordinary PHP + MySQL stack. Over about a year we went through two full generations of the feed ranking algorithm. The first version solved the obvious problem (stop being purely chronological) but quietly failed at real personalization. The second version fixed that by rethinking what "user taste" even means, moving scoring out of SQL and into application code, and adding a layer of post-ranking business rules. This post walks through both generations, why the second one had to happen, and how we actually tested and calibrated a feed ranking system without a data science team or an ML pipeline. No exact weights, table names, or formulas below — just the engineering story. The starting problem: one feed, five content shapes Before personalization is even on the table, a multi-format feed has a normalization problem. Movies, books, games, music, and editorial posts live in different tables, with different columns, different publishing cadences, and engagement numbers on completely different scales. "1,000 likes" on a music post and "1,000 likes" on a book review are not the same signal. So the very first architectural decision — before any ranking logic existed — was building a unification layer that maps every content type into a shared shape (type, author, title, cover, category, engagement counters, timestamp) before any scoring happens. Everything downstream depends on that layer being consistent. Generation 1: a weighted ranker living inside a single SQL query The first real version of the algorithm — internally we called it the hybrid model — had a modest goal: get away from a purely chronological feed without building anything resembl

2026-08-08 原文 →
AI 资讯

Add toast messages in Laravel with Wiretoast

Fire toast notifications in Laravel from PHP, Alpine and plain JavaScript with one notify call, plus positioning, auto-dismiss and grouping, and no CSS framework in your bundle Here is a problem I hit on every project. A Livewire action finishes and I need to tell the user it worked, but the toast library I grabbed assumes Tailwind, or ships its own huge runtime, or only works from JavaScript when half my triggers actually live in PHP. Wiretoast is my answer to that, and this post is the fast path to using it. The problem You want to fire a toast from PHP, from Alpine, and from plain JavaScript with the same call, and you do not want to drag a CSS framework into your bundle to get it. How to install Start with Composer, then wire up the assets. I bundle with Vite, so I import the package CSS and JS into my entry files. // resources/js/app.js import ' @wiretoast/js/wiretoast.js ' ; import ' @wiretoast/css/wiretoast.css ' ; That @wiretoast alias is optional, and you set it up by pointing Vite at the vendor resources folder so the imports stay short. // vite.config.js resolve : { alias : { ' @wiretoast ' : path . resolve ( __dirname , ' vendor/edulazaro/wiretoast/resources ' ), }, }, Then the component goes once into your layout, and on the Vite path it injects no tags of its own. <x-wiretoast /> How to use it The fastest possible win is a one-liner in a Livewire component right after something succeeds. The helper is a component macro named notify , registered for you when Livewire is present. $this -> notify ( 'Profile updated' , 'success' ); Under the hood that dispatches a notify browser event, which is exactly what Alpine fires too. So the same toast from a purely front-end button looks like this. <button @ click= "$dispatch('notify', { message: 'Copied', type: 'info' })" > Copy link </button> The five types you can pass are success , error , warning , info and neutral , and a message can be a plain string or an object with a title and a message when you want a he

2026-08-06 原文 →
AI 资讯

Generate your entire Laravel CRUD stack with one Artisan command

TL;DR — composer require bouda/laravel-make-pattern → php artisan make:pattern Post → 9 consistent files in seconds. DDD-ready, rollback included, every stub is yours to override. The problem I kept running into Every new Laravel project starts the same way. You know the architecture you want: Repository, Service, Controller, some Form Requests, a Resource, a Policy, a test. You've written this stack dozens of times. And every time, you either: Copy-paste from a previous project — and immediately introduce inconsistency between how PostRepository is structured vs CategoryRepository . Write everything from scratch — which is slow and error-prone. Use make:model -a — which gives you the Model, Migration, Factory, Controller, but nothing about repositories, services, or policies wired together. None of these feel like the right answer when you want a clean, layered architecture. So I built laravel-make-pattern . What it does One command: php artisan make:pattern Post Generates 9 files : app/Models/Post.php app/Repositories/Contracts/PostRepositoryInterface.php app/Repositories/PostRepository.php app/Services/PostService.php app/Http/Controllers/PostController.php app/Http/Requests/PostStoreRequest.php app/Http/Requests/PostUpdateRequest.php app/Http/Resources/PostResource.php app/Policies/PostPolicy.php tests/Feature/PostTest.php All consistently named, all using the same conventions, all generated from stubs you own and can override . The generated code Here's what the repository looks like out of the box: <?php namespace App\Repositories ; use App\Models\Post ; use App\Repositories\Contracts\PostRepositoryInterface ; class PostRepository implements PostRepositoryInterface { public function all () { return Post :: all (); } public function find ( string $id ) { return Post :: findOrFail ( $id ); } public function create ( array $data ) { return Post :: create ( $data ); } public function update ( string $id , array $data ) { $model = $this -> find ( $id ); $model -> u

2026-08-06 原文 →
AI 资讯

Turn Claude Code into a Laravel expert with LaraClaude

Claude Code writes PHP in Laravel quite well, but it starts every session as a generalist. It does not know your project has three hundred migrations that should be thirty, that a @foreach two files over is firing an N+1, or that your modals follow one specific pattern. You end up re-explaining the same context constantly. LaraClaude packages that context as slash commands. It is a Claude Code plugin with over thirty Laravel skills, each a /lc: command. Install it once and you have audits, scaffolders and cleanup tools that already know Laravel. Here are the ones I run most. How to install LaraClaude installs through Claude Code's plugin system. Add the marketplace once, then install, so you get updates later: /plugin marketplace add edulazaro/laraclaude /plugin install laraclaude@edulazaro Or grab it directly from GitHub: /plugin install github:edulazaro/laraclaude You need Claude Code and a Laravel project. That is it for most skills; a couple that hit a live database also want Docker. Audit before you change anything Most skills default to a read-only report and only touch files when you add fix , so start by looking. /lc:find-n-plus-one scans your Blade views, Livewire components and controllers for a relationship accessed inside a loop, traces it back to the query that built the collection, and tells you the exact with() to add. /lc:find-n-plus-one /lc:security-audit is the other one I run on any project I inherit. It looks for SQL injection, XSS, mass-assignment and secrets committed to the repo, and like most fixable skills it takes a preview flag before it changes anything. /lc:security-audit # report /lc:security-audit fix --dry-run # preview the fixes /lc:security-audit fix # apply, with confirmation Clean up what has piled up Every long-lived Laravel app accumulates migration cruft: a create followed by twenty add_column and change_column files. /lc:consolidate-migrations groups them by table, classifies each table as safe to merge or not, and folds the A

2026-08-06 原文 →
AI 资讯

Add tags and categories to any model with Laraterms

Sometimes you need tags on a model. The usual answer is a tags table, a pivot, a slug and a belongsToMany , and you write it again in the next project with slightly different columns. Laraterms replaces that with a config entry and a trait, and it comes with the parts you normally bolt on later: hierarchy, per-tenant isolation and translations. This is the simple path first, then the two features you reach for next. How to install One package, its config and two migrations. composer require edulazaro/laraterms php artisan vendor:publish --tag = laraterms-config php artisan vendor:publish --tag = laraterms-migrations php artisan migrate Step 1: define a taxonomy A taxonomy is a kind of label, declared in config/laraterms.php . Start with a flat tags taxonomy; the file already ships one you can keep. 'taxonomies' => [ 'tags' => [ 'hierarchical' => false , 'max_terms_per_model' => null , 'scope' => 'tenant' , ], ], Step 2: tag a model Add the HasTerms trait and the model can hold terms. Attaching is find-or-create: pass a label, and the term is created the first time and reused afterwards. use EduLazaro\Laraterms\Concerns\HasTerms ; class Post extends Model { use HasTerms ; } $post -> attachTerm ( 'Laravel' , 'tags' ); $post -> attachTerms ([ 'Laravel' , 'PHP' ], 'tags' ); $post -> syncTerms ([ 'Laravel' , 'Vue' ], 'tags' ); // replace the tag set $post -> termsIn ( 'tags' ); // read them back Filtering by tag is a query scope, so it composes with the rest of your query. Post :: whereHasTerm ( 'laravel' , 'tags' ) -> get (); Post :: whereHasAllTerms ([ 'laravel' , 'tutorial' ], 'tags' ) -> get (); Hierarchical categories Set hierarchical => true on a taxonomy and its terms form a tree. Read the whole tree in one query, and walk a term's ancestry. 'categories' => [ 'hierarchical' => true , 'max_terms_per_model' => 1 , 'scope' => 'tenant' , ], use EduLazaro\Laraterms\Support\TermTree ; $tree = TermTree :: for ( 'categories' ); // roots with children, one query $term -> b

2026-08-06 原文 →
AI 资讯

This Week In PHP Internals | Aug 05, 2026

Hello world, it's Wednesday, August 5, 2026, and here's what happened This Week in PHP Internals. 13 stories this week, so let's get into it. But first, This week's episode is brought to you by Tideways . When a request is slow in production, Tideways takes you from symptom to root cause in minutes, with profiling, tracing, and monitoring built specifically for PHP. It installs in 5 minutes, there's no credit card required, and it's hosted in Germany. Start your free trial at tideways.com . This week's top story: the mass deprecation vote for PHP 8.6 is in its final week. All 35 ballots close Monday , August 10, and Gina P. Banyard posted the 1-week reminder so nobody gets caught out. Most of the 35 are passing comfortably. The interesting ones are the holdouts. list() is now deadlocked at 21 to 21 — a flat tie, nowhere near the 2/3 it needs. Reserving let stands at 22 to 11, which is exactly two-thirds — a single vote in either column decides it. The dechunk filter sits at 17 to 15 — still well short. The gettext _() alias is failing at 9 to 20, and reserving in , out , and inout is failing at 7 to 20, with 12 abstentions. Everything else you'd recognize from the list — the object-parameter cleanups, the is_double() family, spl_classes() — is cruising toward the finish. The thread itself turned into a corrections desk this week. Calvin Buckley relayed a note from Nora, who isn't on the list, pointing out: "The text for the metaphone deprecation isn't fully right. It lists \"linguistics\" as a replacement package, but that one actually uses php-src's metaphone internally too." Weilin Du, who proposed that item, conceded the docs point while standing by the idea, writing: "My point in deprecating it is to stop using ancient metaphone algo as a whole." Voters seem unbothered — metaphone stands at 19 to 6, with 15 abstentions. Rowan Tommins raised a bigger flag on reserving is : it would collide with Hamcrest, the test assertion framework, whose PHP port has 500 millio

2026-08-06 原文 →
AI 资讯

Add Livewire modals in Laravel with Wiremodal

Wiremodal is a framework-agnostic modal package for Laravel, which allows to handle modals, so you don't have co configure them in all your projects. It ships a few Livewire-side helpers that make exactly this pleasant. This post is the Livewire integration end to end: opening and closing from PHP, delivering a payload on open, the one trap to avoid, and the optional form panel for when a modal happens to be a form. How to install Pull the package in and get the assets onto the page. composer require edulazaro/wiremodal php artisan vendor:publish --tag = wiremodal-assets The service provider auto-registers and there is no config file. Point your layout at the published files: <link rel="stylesheet" href="{{ asset('vendor/wiremodal/css/wiremodal.css') }}"> <script src="{{ asset('vendor/wiremodal/js/wiremodal.js') }}" defer></script> If you bundle with Vite, skip the publish and import straight from the vendor directory instead, so a package update flows through without re-publishing anything: /* resources/css/app.css */ @import "../../vendor/edulazaro/wiremodal/resources/css/wiremodal.css" ; // resources/js/app.js import ' ../../vendor/edulazaro/wiremodal/resources/js/wiremodal.js ' ; Opening and closing from Livewire Define the modal once with the <x-wiremodal> component, give it a name , and fill the body and footer slots. Here is a delete confirmation: <x-wiremodal name="confirm-delete" title="Delete record?" size="sm"> <x-slot:body> <p>This action cannot be undone.</p> </x-slot:body> <x-slot:footer> <button type="button" data-wm-dismiss>Cancel</button> <button type="button" wire:click="destroy">Delete</button> </x-slot:footer> </x-wiremodal> The Cancel button carries data-wm-dismiss , and any element with that attribute closes the modal it sits in, so you never write a cancel handler. To open and close from the component itself, use the macros the package registers on every Livewire component: public function confirmDelete (): void { $this -> openModal ( 'confirm

2026-08-05 原文 →
AI 资讯

Your Laravel Models Aren’t the Problem. Hidden Workflows Are.

Most Laravel applications do not become difficult to maintain because somebody made one obviously terrible architectural decision. They get there through dozens of small decisions that looked completely reasonable at the time. A controller grows beyond a comfortable size, so part of its logic moves into an Eloquent model. Later, the application needs to send a notification after an order ships. Then inventory must be checked through an external API. Accounting requests an ERP integration, and marketing wants loyalty points awarded after dispatch. Each change adds only a few lines. Since the Order model is already available wherever the feature is being implemented, adding one more method feels natural: $order -> ship (); It is concise, expressive, and pleasantly object-oriented. Six months later, however, that innocent method may be updating several tables, calling two APIs, dispatching events, sending notifications, and deciding whether the entire operation is allowed. The model has quietly become the place where the application runs its business workflows. That is the real problem. It is not that the model is “fat.” It is that persistence, domain decisions, and application orchestration have been mixed together until nobody can change one without understanding all three. Business Logic in Eloquent Models Is Not Automatically Wrong The common advice to remove all business logic from Laravel models goes too far. Eloquent follows the Active Record pattern. Martin Fowler describes Active Record as an object that represents a database row, encapsulates database access, and adds domain logic related to that data. In other words, an object containing both state and behavior is not an architectural mistake by itself. A model should be allowed to answer questions about itself: class Order extends Model { protected function casts (): array { return [ 'paid_at' => 'datetime' , 'expires_at' => 'datetime' , ]; } public function isPaid (): bool { return $this -> paid_at !== nul

2026-08-04 原文 →
AI 资讯

When "select all" checkboxes don't actually select anything — verifying after `check()`, not just trusting it

WordPress's plugin and theme update screens both have a "select all" checkbox. Calling check() on it with Playwright succeeds — no error, no exception. But look at the individual checkboxes afterward, and sometimes none of them are actually checked. Note: Playwright's check() ticks a checkbox. The click itself can succeed even if the page's JavaScript handler never fires, leaving what the form actually submits out of sync with what the screen visually shows. What actually happens The "select all" checkbox is usually wired up with a JavaScript handler: clicking it is supposed to check every individual checkbox underneath it. Playwright's check(force=True) can force the DOM state of that one checkbox — but that only changes that checkbox's own state . It doesn't guarantee the JavaScript handler that's supposed to propagate the change to the individual checkboxes actually fires. # Looks like it worked, but the individual checkboxes are still unchecked select_all . first . check ( force = True ) page . click ( ' input[type= " submit " ][name= " upgrade " ] ' ) Clicking the update button submits whatever the form's actual state is — which is "nothing checked." Nothing updates. No error is thrown, so on the surface it looks like the run completed normally. The fix — verify right after checking, every time Right after checking "select all," confirm that the individual checkboxes underneath are actually checked. If they aren't, fall back to checking each one individually. sel_all_sel = ' input[type= " checkbox " ][id^= " plugins-select-all " ] ' select_all = plugin_form . locator ( sel_all_sel ) if select_all . count () > 0 : select_all . first . check ( force = True ) page . wait_for_timeout ( 500 ) # Verification step — confirm checkboxes are actually checked chk_sel_check = ' input[type= " checkbox " ][name= " checked[] " ]:checked ' any_checked = plugin_form . locator ( chk_sel_check ). count () > 0 if not any_checked : # Select-all had no effect; switch to individual s

2026-08-04 原文 →
开发者

Atomic Money: Making a PHP/MySQL Wallet Safe Under Concurrency

The lost-update bug that quietly corrupts homegrown wallet balances — and the five disciplines we used across PayWithToken to make money movement correct under concurrency. There is a bug that lives in a large share of the world's homegrown wallet systems. It doesn't throw an error. It doesn't show up in tests. It surfaces months later as a balance that is quietly, inexplicably wrong — and in a payments system, a wrong balance is either a customer who has lost money or a company that has given it away. This is the story of that bug, why the "obvious" wallet code causes it, and the handful of disciplines we used across PayWithToken to make money movement correct under concurrency. The bug: lost updates Here is wallet code almost everyone writes first. Credit a user's balance: // DON'T do this $row = $db->query("SELECT balance FROM users WHERE id = $id")->fetch(); $new = $row['balance'] + $amount; $db->exec("UPDATE users SET balance = $new WHERE id = $id"); Read the balance, add to it in PHP, write it back. It works perfectly — until two things happen at the same time. Picture a wallet at ₦1,000. Two credits of ₦500 arrive simultaneously — say a bank webhook and the user tapping "confirm" on their phone: Request A reads balance = 1000. Request B reads balance = 1000 (A hasn't written yet). A computes 1500, writes 1500. B computes 1500, writes 1500. Two credits landed; the balance rose by ₦500. ₦500 vanished. This is a lost update, and it is a race condition, which means it is invisible until you have real concurrent traffic — exactly when you can least afford it. The debit version of the same bug lets a balance go negative or double-spends a token. Fix #1: let the database do the arithmetic The read-modify-write happened in PHP, across three round trips, with a gap where another request could interleave. The fix is to make the update a single atomic statement and let the database's row lock serialise it: // DO this — one atomic statement $db->prepare("UPDATE users SET

2026-08-03 原文 →
AI 资讯

When `update-core.php` version scraping goes wrong — telling a plugin version number apart from WordPress core

On hosting without SSH access, a common pattern is to open update-core.php (the WordPress update screen) with Playwright and read "what version is WordPress core currently running" straight off the page text. In one deployment, the recorded core version came back as something like 1.7.11 — a number that has never existed for WordPress core. Note: update-core.php is the WordPress admin's "Updates" page, listing pending updates for core, plugins, themes, and translations all on one screen. What was actually happening That page doesn't only show the core version string — it's packed with version numbers belonging to pending plugin and translation updates too. A line like "Update Plugin X to 1.7.11" is typical. # First implementation — grabs the first N.N.N-shaped number on the page match = re . search ( r ' \d+\.\d+(?:\.\d+)? ' , page_text ) version = match . group ( 0 ) if match else None This naive regex grabs whatever N.N.N -shaped number appears first on the page. Because of how the page is laid out, a plugin's pending update can render above the core version message, so 1.7.11 (a plugin's version) ended up recorded as the WordPress core version. Why this is hard to catch The bug doesn't throw an exception — the regex matches successfully, just on the wrong value. Nothing about it looks broken until someone notices the report shows a version that WordPress core has never shipped (there's no 1.x series for core). The fix — a three-stage guard Prefer a dedicated selector first — look for specific DOM locations where core's update message actually renders, like p.response > strong or #wp-version-message strong Fall back to keyword-anchored regex — if no selector matches, only accept a number immediately following the words "WordPress," "バージョン," or "Version" Validate plausibility as a final check — whichever path produced a value, run it through a function that checks the major version number falls within 4–9 def is_plausible_wp_core_version ( ver : str ) -> bool : m =

2026-08-03 原文 →
AI 资讯

Building Laravel NATS: A Modern, Production-Ready NATS Integration for Laravel

Building Laravel NATS: A Modern, Production-Ready NATS Integration for Laravel When building distributed systems, one of the biggest challenges is enabling services to communicate reliably without creating tight coupling. Laravel has excellent support for queues, events, broadcasting, and jobs, but when it comes to NATS , the ecosystem has been relatively limited. That's exactly why I built Laravel NATS . Instead of being just another wrapper around an existing PHP client, Laravel NATS aims to provide a Laravel-first developer experience while exposing the full power of NATS for modern event-driven architectures. In this article I'll explain: Why I built Laravel NATS Why you should consider NATS How Laravel NATS works Features that make it production ready Code examples Real-world use cases What makes this package different from existing solutions What is NATS? NATS is a lightweight, high-performance messaging system designed for cloud-native applications. Unlike traditional queues, NATS focuses on: Extremely low latency High throughput Simple publish/subscribe messaging Request/Reply APIs JetStream persistence Horizontal scalability Instead of applications calling each other directly: Order Service │ ▼ Notification Service Applications publish events: Order Service │ ▼ NATS Server │ │ ▼ ▼ Email Analytics Every service becomes independent. Why Laravel Needed a Better NATS Package Most existing packages expose the underlying PHP client almost directly. That means developers still have to understand: client lifecycle connections serialization subscriptions queue consumers JetStream APIs Laravel developers expect something different. We are used to APIs like: Cache :: put (); Queue :: push (); Event :: dispatch (); The goal of Laravel NATS was to make NATS feel just as natural. Installing Laravel NATS Installation is straightforward. composer require zaeem2396/laravel-nats php artisan vendor:publish --tag = nats-config Then configure your environment: NATS_HOST=127.0.0

2026-08-03 原文 →