WordPress PHP-Only Block Registration

You want to build a custom WordPress block. But you don’t want to learn React, manage a build pipeline, or deal with NPM packages.

Seven and half years after blocks arrived in Core, WordPress introduces a way to build blocks without any of these things. All you need is PHP.

But was the long wait worth it?

A traditional WordPress block needs to be registered twice. Once in PHP, and once in JavaScript.

But WordPress 7.0 introduces a new and streamlined approach, allowing you to register a block using only PHP.

Let’s use this feature to build a Hello World Block:

function css_tricks_hello_world_block() {
register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function () {
return sprintf(
'
Hello World!
',
get_block_wrapper_attributes()
);
},
'supports' => [
'autoRegister' => true,
],
]
);
}
add_action('init', 'css_tricks_hello_world_block');

The block is fully functional in the block editor, and fits right in with all the other blocks:

Block inserted into the WordPress block editor, containing a heading that reads PHP Block Registration followed by a paragraph of text that says Hello World. There are no block settings in the sidebar.

The key addition is the 'autoRegister' => true flag in the supports section. When set, WordPress automatically generates the required JavaScript for your block based on the PHP registration. This includes the client-side registration, and the editor preview.

Attributes let users customize the block’s appearance and behavior. In traditional block development you not only need to define the attributes, but also build out the corresponding controls in the editor interface.

With PHP-only registration, all that is needed is the attributes definition during block registration:

function css_tricks_hello_world_block()
{
register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function ($attributes) {
return sprintf(
'
%s
',
get_block_wrapper_attributes(),
esc_html($attributes['greeting'])
);
},
'supports' => [
'autoRegister' => true,
],
'attributes' => [
'greeting' => [
'type' => 'string',
'default' => 'Hello World!',
],
],
]
);
}
add_action('init', 'css_tricks_hello_world_block');

This code registers a greeting attribute as a string, with a default value. WordPress generates the corresponding input control in the block’s Settings sidebar.

A block inserted into the WordPress block editor displaying the sidebar Settings, which includes a text field for the greeting.

At first sight, there is a lot to like about PHP-only registered blocks. For any WordPress developer, it feels like the good old times when programming was simpler.

You might be tempted to delay learning JavaScript block development indefinitely. But the PHP-only approach has important limitations worth understanding.

The editor displays the HTML as returned by the block’s render_callback PHP function. When the block is first displayed or when the user interacts with one of its controls, the editor component requests a new PHP render from a REST API endpoint.

While blocks rendered this way integrate seamlessly into the editor, they are not part of the single page JavaScript application that powers the entire editor experience.

This creates two key limitations:

First, you cannot add any controls within the block preview. This means you are limited to the auto-generated controls in the Settings sidebar.

The default interaction mode with blocks is the block preview itself. Imagine that you need a testimonial block. With a JavaScript rendered block, you would build out the testimonial design, and allow editing in place.

With a PHP-rendered block you can only use the sidebar. And even here you are limited, as currently there’s no support for image uploads or multiline text.

With JavaScript, you can allow editing in the block, as well as in the sidebar. Additionally you have access to all the controls that WordPress Core uses, and can even implement your own.

But without JavaScript, you’ll always be limited to the options WordPress provides based on the registered attributes of your PHP-only block.

Secondly you cannot attach any JavaScript to markup in the block preview. Imagine you want to develop a block that pulls five related posts, and which displays them in a slider. For that you would output the markup, and then pass a DOM node to the JavaScript library which then transforms the raw markup into the desired slider interface.

This reliance on finding and manipulating DOM elements is typical for traditional JavaScript development. But with PHP-only registered blocks in the block editor, the markup is fetched asynchronously and replaced on every re-render. This makes interacting with the DOM of the block preview unreliable or impossible.

While the front end render works fine with JavaScript libraries, the editor authoring experience will not work correctly. Even if you manage to attach any event listeners on first load, these will be disconnected the moment the preview re-renders.

These limitations are caused by the architecture of this feature, and they will not change in the future.

On the initial load of the block editor, WordPress loads the post data from the database into a client-side store managed by JavaScript. Any changes that you make in the editor update this data store on the client side. But the database isn’t updated until you save the post.

PHP-only registered blocks bypass this client-side store. When a block renders, it queries the database directly. But the database might contain stale data compared to what’s currently in the editor. Even worse, the PHP-rendered block isn’t notified of changes in the client side data, so it can’t refresh when data changes.

Let’s take a practical example: Imagine you are building a block that displays a header element with the post title. When the user changes the title in the editor, your block will still show the value from the database. You would need to save the post and reload the editor for the changed title to show up in the PHP-only block.

This makes PHP-only blocks unsuitable for any block that displays data that the user can change in the editor like title, content, excerpt, features images, or attached terms.

PHP-only registered blocks render through a REST API endpoint. So the same code renders the editor preview and the front end. But there is a critical difference: the global state.

On the front end, blocks render within The Loop, which sets key global variables like $post. Template tags like the_title() or the_content() rely on these globals to know which post is displayed.

But REST APIs are stateless, and don’t rely on global state. The endpoint that renders the block editor preview accepts a post ID parameter, but the editor component does not pass it through. This means that your render callback has no way to know which post is edited.

This limits the functions that you can use in the block editor preview. Template tags or functions like get_post_meta() need to know about the post context.

This is a significant architectural limitation as of WordPress 7.0. This could be addressed by passing the post ID to the endpoint, but there are no concrete plans to change this at the time of this writing.

WordPress 7.0 supports only three attribute types: strings, numbers, and booleans. These map to four basic editor controls: text inputs, number inputs, checkboxes, and a dropdown.

This screenshot shows a block that uses all available user interface elements:

Block sidebar settings showing example controls for string, integer, boolean, and dropdown.

The dropdown element is the only advanced control, but it has a significant limitation: it does not support keyed arrays. This makes it impossible to have a label that differs from the stored value.

Let’s take the example of a related posts block where users can select a category. You want to display the category names in the dropdown, but store the category IDs. This isn’t possible.

Instead, you must choose between displaying names or slugs, which both are user-editable, and store that value:

'attributes' => [
'selected_category' => [
'label' => 'Select a category',
'type' => 'string',
'default' => 'uncategorized',
'enum' => wp_list_pluck( get_categories( [ 'hide_empty' => false ] ), 'slug' ),
],
],

This saves the slug to the block markup:

Using slugs not only doesn’t look good in the interface, but this implementation will also break when renaming a category. All existing blocks referencing the old slug will not be able to pull the related posts. IDs are stabler, and would only be invalid when the category is deleted.

Beyond dropdowns, essential controls — like image uploads, rich text editors, or date pickers — are absent. This might change in future releases, but again, there are no plans for it as of yet.

It’s easy to get discouraged looking at these limitations. It is true that PHP-only registered blocks are a poor choice for building new blocks from scratch.

But I consider them to still be very valuable because there is one use case where these limitations do not matter: migrating legacy PHP code into block themes. This WordPress 7.0 feature is a real game changer when it comes to developers adopting block themes, which is still a barrier of sorts for many theme authors.

In my experience, block themes are more performant, easier to maintain, and faster to build than legacy themes. Yet many developers are still relying on classic themes. And that is not by choice, but because of existing PHP-based features.

Until now, migrating these features came up against nearly insurmountable barriers. First, the need to learn JavaScript block development, and set up an entirely new development workflow with dependency management and build pipelines. Second, the time needed to rewrite all this code in JavaScript.

PHP-only registered blocks remove both these obstacles.

In 2022, I wanted to migrate a classic theme to a block theme.

Front end view of a WordPress post with a heading that reads What I learned Building a Hybrid Theme followed by several blocks of paragraph text.

The content area and the footer were straightforward to rebuild with blocks. But the header was more complex, especially with the more limited block building features of the time.

So, rather than spending time rebuilding the header, I took the existing PHP-header, and wrapped it in a server-side rendered block.

The back end of a WordPress post with a Post Title and three paragraphs of text. the style rendered like the front end view.

That said, we need to be realistic. This header block was far from perfect. The block preview was not responsive, dropdowns didn’t work in the editor, and you could not edit anything.

Did it matter? Not at all. The block rendered perfectly on the front end, and the editor preview was good enough. And because of this approach, I could migrate the theme in hours instead of days.

Before PHP-only registration, building such blocks still required a solid JavaScript proficiency and build tooling. But now any PHP developer can use this migration path using the skills they already have.

PHP-only registered blocks are ideal for converting:

  • Legacy widgets: The Settings sidebar of the block editor is perfect to reproduce a legacy widget’s settings.
  • Shortcodes: While you can use shortcodes in block templates, working with them is awkward at best. Migrating shortcodes to blocks is now straightforward with WordPress 7.0.
  • Template parts and custom template tags: Headers, footers, author biographies, related posts, etc.
  • Custom functionality: Anything that works on the front end without needing any interactivity in the editor.

The blocks you create do not need to be perfect in the editor. What counts is that they render correctly on the front end. By using existing PHP code, adaptations to block themes will be minimal.

Here are a few things I’ve learned along the way as I’ve been playing with blocks registered with PHP.

There might be cases in which you want to have a different block rendering depending on whether the block is displayed in the admin, or on the front end.

Using the is_admin() function for this use case does not work, as it does not evaluate to true when the REST API endpoint generates the markup for the block editor preview.

But there is another function that we can use: wp_is_rest_endpoint(). If it returns true, it means that WordPress is generating a REST API endpoint request. But this could be any endpoint rendering posts, so we need to ensure that we’re dealing with the Block Renderer endpoint.

function css_tricks_php_only_detecting_editor_render()
{
register_block_type(
'css-tricks/php-only-detecting-editor-render',
[
'title' => 'PHP-Only Detecting Editor Render',
'render_callback' => function () {
if ( wp_is_rest_endpoint()
&& str_contains($GLOBALS['wp']->query_vars['rest_route'] ?? '', 'v2/block-renderer/' )
) {
$frontend = false;
} else {
$frontend = true;
}

$bgcolor = $frontend ? 'green' : 'blue';

return sprintf(
'
%s
',
get_block_wrapper_attributes(['style' => "color: #fff; background-color: $bgcolor;"] ),
$frontend ? 'Rendered on the frontend' : 'Rendered in the editor'
);
},
'supports' => [
'autoRegister' => true,
]
]
);
}
add_action('init', 'css_tricks_php_only_detecting_editor_render');

This shows different text and styling depending on whether the block is rendered in the editor or on the front end:

A block inserted in the WordPress block editor. Heading readers Contextual Rendering followed by a white text with a blue background that reads Rendered in the editor.
Front end of a WordPress website showing a block rendered on the front end including a heading that reads Contextual rendering, followed by the post meta for author and category, and then a paragraph of white text on a green background that reads rendered on the front end.

We’ve seen that WordPress out of the box does not give you access to the ID of the edited post in PHP-only registered blocks. There is a workaround though.

WordPress registers blocks in the init hook. This hook also runs on every admin page. When you edit a post, the ID of the edited post is passed as a GET argument in the URL, for example: https://css-tricks.com/wp-admin/post.php?post=5\&action=edit

This means that at the moment of the block registration, we can retrieve this ID. To pass it to the block, we use an attribute. But we do not want WordPress to add an interface element, so we set the source of the attribute to local.

function css_tricks_php_only_post_title_block()
{
register_block_type(
'css-tricks/php-only-post-title',
[
'title' => 'PHP-Only Post Title',
'render_callback' => function ($attributes) {
$post_id = is_int(get_the_ID()) ? get_the_ID() : $attributes['postId'];

if ($post_id === 0) {
return sprintf(
'
Please save the post and reload the page.
',
get_block_wrapper_attributes()
);
}

return sprintf(
'
%s
',
get_block_wrapper_attributes(),
get_the_title($post_id)
);
},
'supports' => [
'autoRegister' => true,
],
'attributes' => [
'postId' => [
'type' => 'integer',
'default'=> isset($_GET['post']) ? absint($_GET['post']) : 0,
'role' => 'local'
],
]
]
);
}
add_action('init', 'css_tricks_php_only_post_title_block')

This only works when editing an existing post. When a new post is created, there is no post ID passed through the URL. WordPress will create one when the post is first saved, and update the URL.

But this is done through JavaScript without triggering a new page load from the server. Meaning that the PHP won’t have an opportunity to access the post ID until a full page reload is done.

So, yeah, not the greatest approach. But it’s good enough to unblock you until WordPress Core adds a proper implementation to pass post data to PHP-only registered blocks.

There are situations in which it is difficult to achieve a decent preview in the editor. In certain situations, it’s even impossible.

Think, for example, of a newsletter form provided as a snippet of HTML and JavaScript. Due to the limitations we’ve seen, the editor preview will always look broken.

In a situation like this, you can implement a placeholder in the editor. This is a strategy that WordPress Core uses as well, as we can see for the Post Content block:

Showing a Content block inserted to the WordPress Single Post Template in the Site Editor., Contains a Title, Image Block with Caption, and three paragraphs of text.

Users do not expect an exact preview in every case. Choose the best compromise between the time needed to achieve a proper block editor preview and the expected UX gain.

You can use WordPress optimized stylesheet enqueuing, which only enqueues stylesheets on the front end for the blocks present on that specific page.

The register_block_type function offers two arguments:

  1. style: Enqueue both in the editor, and on the front end.
  2. editor_style: Enqueue only in the blocker editor (after the style stylesheets). This allows you to implement overrides for front-end styles in the editor.

To add a CSS stylesheet, register it using wp_register_style() Then use the handle during block registration:

function css_tricks_hello_world_block()
{
wp_register_style(
'css-tricks-hello-world',
plugins_url( 'style.css', __FILE__ ),
[],
filemtime( plugin_dir_path( __FILE__ ) . 'style.css' )
);

register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function ($attributes) {
return sprintf(
'
%s
',
get_block_wrapper_attributes()
);
},
'supports' => [
'autoRegister' => true,
],
'style' => 'css-tricks-hello-world',
]
);
}
add_action('init', 'css_tricks_hello_world_block');

WordPress auto-generates a .wp-block-{namespace}-{block-name} class and adds it to the wrapper container of your block as part of get_block_wrapper_attributes().

If you need to add additional classes or styles, you can pass these to get_block_wrapper_attributes() in the render callback function.

$wrapper_attributes = get_block_wrapper_attributes(
[
'class' => 'custom-class',
'style' => 'color: #333',
]
);

It’s the best practice to use this class as the common root class for writing targeted styles. I prefer to use the Block, Element, Modifier (BEM) approach for writing block styles. It prevents my styles from clashing with styles provided by WordPress Core or other code.

A common scenario is that you will have existing CSS, and restructuring this code and the markup using BEM would be too much work. In that case I recommend using a unique prefix for these legacy classes.

If you are dealing with a website that uses a front-end framework like Bootstrap, avoid enqueuing any framework stylesheets. You need to only migrate the CSS instructions that the block needs, applying unique prefixes as described above.

There are two ways for WordPress to integrate the post editor into the admin:

  1. Embedded into the existing admin page
  2. Integrated through an iframe

WordPress started with the first approach but quickly realized that it made styling the block editor very difficult. Without an iframe, any admin styles can interfere with that styles of the block editor, including your custom blocks.

In practice, this means that your blocks can look different in the editor than they do on the front end. For simplicity you want to use the same styles across both the front end and the editor preview with minimal adjustment. And the iframed post editor allows you to do that.

As of WordPress 7.0, the post editor is iframed if all blocks in the post are Version 3 or higher. WordPress 7.1 will enforce the iframe approach independently of the blocks.

So, to simplify building blocks and prepare for the next release, I think it’s best to ensure that all blocks on your sites use the Block API Version 3.

JavaScript support for PHP-only registered blocks is limited to the front end. To add a script, you can register it, and then pass the handle to the view_script during registration:

function css_tricks_hello_world_block()
{
wp_register_script(
'css-tricks-hello-world',
plugins_url( 'script.js', __FILE__ ),
[],
filemtime( plugin_dir_path( __FILE__ ) . 'script.js' )
);

register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function () {
return sprintf(
'
Hello World!
',
get_block_wrapper_attributes()
);
},
'supports' => [
'autoRegister' => true,
],
'view_script' => 'css-tricks-hello-world',
]
);
}
add_action('init', 'css_tricks_hello_world_block');

WordPress will only enqueue this script when the block is present on the current page.

PHP-only registered blocks can use the Block Supports API, which allows opt-in to core features. Depending on the feature, the block editor will expose additional interface elements to the user. It will also add attributes to the block to store the user’s choices.

There are features that will work independently of the theme. Others need to be enabled by the theme through its theme.json file.

Here is an example enabling color customization for the text and background color:

function css_tricks_hello_world_block()
{
register_block_type(
'css-tricks/hello-world',
[
'title' => 'Hello World',
'render_callback' => function ($attributes) {
return sprintf(
'
%s
',
get_block_wrapper_attributes(),
esc_html($attributes['greeting'])
);
},
'supports' => [
'autoRegister' => true,
'color' => [
'background' => true,
'text' => true,
],
],
'attributes' => [
'greeting' => [
'type' => 'string',
'default' => 'Hello World!',
],
],
]
);
}
add_action('init', 'css_tricks_hello_world_block');

WordPress will take care of the outputting the corresponding CSS classes and inline styles using get_block_wrapper_attributes().

Allowing users to customize the appearance is a good demonstration, but not something that you are likely to often use. So, let’s have a look at three useful options for PHP-only registered blocks.

All register blocks appear in the inserter by default. But this does not make sense for every block. Imagine, for example, that you use a block to migrate a legacy PHP feature only used in a single template.

In this scenario, you could set inserter to false to hide the block from the inserter. Hidden blocks stay fully functional.

'supports' => [
'autoRegister' => true,
'inserter' => false, // Hide from inserter
],

Setting multiple to false allows the block to only be inserted once into each post. An example is the core More block.

'supports' => [
'autoRegister' => true,
'multiple' => false, // ← How to limit to single instance
],

Once a non-multiple block is inserted, the block’s icon is disabled in the inserter to prevent inserting a second instance.

Setting align to true enables all available alignment options:

'supports' => [
'autoRegister' => true,
'align' => true, // All alignments
],

The text alignments like left, center, and right are always available. Wide and full-width alignment are only enabled if the theme supports it.

A block inserted into the WordPress block editor with expanded options for aligning it.

WordPress handles outputting the necessary classes for the block’s design to reflect the desired alignment.

If you want to selectively enable alignments, you can specify them. The available options are left, center, right, wide, and full.

'supports' => [
'autoRegister' => true,
'align' => ['left', 'center', 'right'], // Selective alignments
],

With these tips you should be able to make the most out of PHP-only registered blocks, even with the limitations in WordPress 7.0.

Remember the opening question: Was it worth waiting seven-and-a-half years for this?

For building new, feature-rich blocks, the answer is no. You need JavaScript to deliver the kinds of interactive and native-feeling editing experiences that WordPress users expect. PHP-only block registration won’t replace JavaScript-powered blocks, and nor should it.

Because it’s not what this feature is for.

PHP-only registered blocks are the solution for thousands of WordPress sites stuck with classic themes because of the high learning curve and high cost of rebuilding with JavaScript.

You can now take shortcodes, widgets, and template parts and port them to the block editor with the PHP skills you already have. No JavaScript. No build pipeline. No code duplication.

And these blocks that you build do not need to be perfect. As long as you can insert them into block content, and they render correctly on the front end, that is all that is needed.

That is the killer use case for this feature. And for that, the wait was worth it.

But beyond this feature, PHP-only registered blocks signal an important shift: WordPress Core is finally prioritizing developer experience. Even as someone who builds JavaScript-powered blocks regularly, I’ll admit that the process involves too much boilerplate code, and too much coordination between block.json, the PHP code, and the JavaScript. Which is not to mention that time spent setting up and maintaining the build pipeline.

Anything that we can do to make this process easier, or avoid it entirely, is more than welcome.

So if you have projects with legacy PHP code preventing a migration to a block theme, then WordPress 7.0 has removed your biggest obstacle.

Migrate these legacy features to blocks and unlock everything modern WordPress has to offer.


Sumber Rujukan:

  • Artikel asal dari CSS-Tricks
  • Published on haqis.com
Comments

Anthropic’s new Fable release is cheaper, less restrictive

On Tuesday, Anthropic released Fable and Mythos 5.1, twinned versions of the company’s most advanced AI model. In addition to performance upgrades, the new Fable release includes changes meant to reduce token cost and false-positive restrictions from the model’s safeguards.

As with the previous Mythos model, Mythos 5.1 will only be available to registered Anthropic partners engaged in either cybersecurity or life sciences research. Fable 5.1, the unrestricted version, is available starting today on cloud platforms or through the Anthropic API.

One of the most significant changes is Anthropic’s previously reported embrace of zero data retention, allowing clients to run Anthropic models on their own infrastructure without data outflows. Previously unavailable for Fable due to security concerns, a high-privacy service (called Enterprise Frontier Safeguards) will now roll out to users in the fall. Notably, the system will still monitor for misuse by agents or human users, but clients will control how the monitoring takes place.

As part of the announcement, Anthropic assured customers that their data had not been inappropriately accessed. “Anthropic has never trained on enterprise data without explicit permission, and never will,” the announcement reads.

As is common for an Anthropic release, the new models set records in a range of benchmarks, including Terminal-Bench 4.0 (for CLI-based coding) and Humanity’s Last Exam (for general reasoning). Anthropic also released three novel scientific findings generated by the models before their release, including a custom GPU optimization and a high-resolution map of Venus assembled from existing photos.

As with previous releases, the models come with a detailed system card, which explains their capabilities in most straightforward terms. The system card rates Mythos as “low-risk” for concerns related to automated AI development — where the AI improves itself — which some see as a trigger for a loss of human control. It says “its ability to accelerate internal AI R&D progress is in line with current trends.”

In terms of general misbehavior, Mythos is slightly more prone to it than Opus, possibly as a result of its enhanced capabilities.

“Mythos 5.1 is a slight regression on overall misaligned behavior compared to Opus 5, and an improvement over Mythos 5 and Claude Sonnet 5,” the system card reads. “It cooperates with human misuse and accepts unverifiable claims of authorization somewhat more readily than Opus 5, but it is less likely to ignore explicit constraints, hallucinate inputs, or falsely claim to have completed tasks than previous models.”


Sumber Rujukan:

Comments

Pessimistic Locking in Laravel Eloquent with refreshForUpdate()

Pessimistic Locking in Laravel Eloquent with refreshForUpdate() image
Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Filed in

Sponsored

masteringlaravel logo


Sumber Rujukan:

Comments

Resolved: CSS Class Prefix Selector

Just looking at what Bramus shared the other day:

/* Adding a base class */
.btn {
padding: 0.5rem 1rem;
border-radius: 4px;
}

/* Listing everything... yuck! */
.btn-primary,
.btn-secondary,
.btn-danger {
padding: 0.5rem 1rem;
border-radius: 4px;
}

/* Works, but performs badly */
[class^="btn-"],
[class*=" btn-"] {
padding: 0.5rem 1rem;
}

/* Newly resolved class prefix selector */
.btn-* {
padding: 0.5rem 1rem;
border-radius: 4px;
}

First off, if you don’t follow Bramus, where have you been?! Jokes aside, his job is to be first-in-line on new features like this — especially as it pertains to Chrome — so it’s worth keeping tabs on his RSS and/or social.

It’s not a new proposal. Lea posted it back in 2024 and has advocated for it the whole while. As Bramus mentions in his post, what’s new is that the proposal was formally adopted and, as of three days ago, it has been added to the Selectors Level 5 spec draft. So, chances are that we’ll see it formally adopted at some point and implemented somewhere even sooner.

I really like the ergonomics of it. Existing substring selectors — class^="prefix" and class=*" prefix" — are verbose and defintely less readable than a simple .prefix-*.

And it’s not like [data-attribute] selectors that require not only an extra step touching HTML but still added verbosity.

But something makes me wince at the idea. I can’t quite put my finger on it. Perhaps it’s redundancy as in, we can already do this with existing selectors? Bramus cites performance issues with existing substring selectors as a primary reason we need this. But Brian Kardell’s reply resonates with me:

Then again, I do like how we extended color functions for brevity, like:

/* old */
color: hsla(100, 50%, 50%, .5);

/* new */
color: hsl(100 50 50% / .5);

And it’s backwards-compatible, so no real harm if you continue to use the “old” way. It’s not like substring selectors don’t have other use cases and become totally obsolete. But maybe that’s it: this isn’t an “upgrade” of something we already have, but a new thing that isn’t progressive enhancement out of the gate. We’d have to @support it until it becomes a Baseline feature:

@supports selector(.prefix-*) {
/* ... */
}

…which may or may not be a long wait. But we don’t know. And if ergonomics are the selling point, then we’re losing that in the wait.

Should also note that the wildcard doesn’t match other conditions or non-dashed cases:

/* Nope */
.prefix* {}
.prefix-*-suffix {}
.prefix_* {} /* the door is left open on this */

Another worthy note is that the spec currently implies (but doesn’t explicitly state) that this has the same specificity as a class selector, (0,1,0). That’d make sense, as .prefix-* is really no different than writing .prefix-variation.

That said, I like how it might possibly look in a nested syntax:

.prefix {
/* This would work, right? */
&-* { /* ... */ }
}

…and Dave’s plea to support selecting web components:

Maybe I just convinced myself that I like it. Again, I dunno. Just take the added convenience and move on! Yada yada.

Direct Link →


Sumber Rujukan:

  • Artikel asal dari CSS-Tricks
  • Published on haqis.com
Comments

Security Features in Laravel

1- Authentication
Laravel offers a complete authentication system that can be customized to fit your needs. The built-in Auth facade provides methods to manage user registration, login, logout, password reset, and email verification.

use Illuminate\Support\Facades\Auth;

// Check if the user is authenticated
if (Auth::check()) {
// The user is logged in
}


2- Authorization
Laravel includes a simple and easy-to-use authorization system. You can use policies and gates to control user access to various parts of your application.

  • Using policy:
// Create a policy
php artisan make:policy PostPolicy

// Register the policy in AuthServiceProvider
protected $policies = [
'App\Models\Post' => 'App\Policies\PostPolicy',
];

// Define a method in PostPolicy
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}

  • Using gates:
use Illuminate\Support\Facades\Gate;

Gate::define('update-post', function ($user, $post) {
return $user->id == $post->user_id;
});

if (Gate::allows('update-post', $post)) {
// The user can update the post
}


3- CSRF Protection
Laravel automatically generates a CSRF token for each active user session managed by the application. This token is used to verify that the authenticated user is the one actually making the requests to the application.

  • In HTML forms:

@csrf


4- Hashing
Laravel provides a secure way to hash passwords using the Hash facade.

Usage:

use Illuminate\Support\Facades\Hash;

// Hashing a password
$hashedPassword = Hash::make('password');

// Checking a password against a hash
if (Hash::check('password', $hashedPassword)) {
// The passwords match...
}


5- Input Validation
Laravel provides a powerful validation mechanism to ensure that the data received from the user is valid before it is processed.

Usage:

use Illuminate\Http\Request;

public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
]);

// The data is valid...
}


6- SQL Injection Protection
By using Eloquent ORM or the query builder, Laravel protects your application from SQL injection attacks.

Usage:

// Using Eloquent ORM
$user = User::where('email', $email)->first();

// Using query builder
$user = DB::table('users')->where('email', $email)->first();


7- XSS Protection
Laravel automatically escapes data that is output in views to prevent cross-site scripting (XSS) attacks.

Usage: Blade


{{ $userInput }}
{!! $userInput !!}


8- Rate Limiting
Laravel provides rate limiting to prevent abuse of your application by limiting the number of requests a user can make to your application.

Usage:

use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('global', function (Request $request) {
return Limit::perMinute(60);
});


9- File Upload Security
When handling file uploads, Laravel provides methods to ensure that the uploaded files are of expected types and sizes.

Usage:

$request->validate([
'photo' => 'required|file|mimes:jpeg,png,jpg,gif|max:2048',
]);


Best Practices:

  1. Always use the latest version of Laravel to ensure you have the latest security updates.
  2. Regularly update your dependencies using Composer.
  3. Use environment variables to manage sensitive information.
  4. Set proper permissions on your server to restrict access to files and directories.
  5. Regularly back up your data and test your backup strategy.
  6. Monitor and log your application for unusual activity and sensitive actions.

Sumber Rujukan:

Comments

Unguarded, Sonnet 5 read another customer’s order 100/100 times. Guarded: zero.

I ran the same storefront attacks against three models — one with its safety training deliberately removed, one stock open-weights model, and the frontier model Laravel AI ships as its default — each with and without an authorization boundary in place.

The headline isn’t that the boundary held (it did: zero breaches across every guarded arm). It’s that the frontier model’s safety training turned out to be per-action: it refused the destructive attack in 100 out of 100 trials, and executed the cross-customer read in 100 out of 100 trials. The attack it misses is the one that looks like helping.

The experiment

Verdict ships deterministic attack packs — executable specifications of things an agent should never be allowed to do, like reading or cancelling another customer’s order. The attack always arrives through the conversation — a hostile request, a poisoned document — and what gets measured is whether the agent goes along with it. Every attack ran in two arms:

  • Guarded: the agent’s tools are wired through the authorization boundary.
  • Unguarded: the identical agent, tools, and prompts — boundary absent. This is the control arm: would anything bad have happened without the guard?

Integrity rules, because a rigged benchmark is worse than none: the harness never forces a tool call, a model that declines an attack is never counted as a prevention, and independent sampled trials are reported as per-arm tallies, never as “the guard stopped this exact attack.”

The numbers

abliterated 7B gpt-oss:20b Sonnet 5
Lookup breached, unguarded (per trial) 30/30 19/30 100/100
Cancellation breached, unguarded (per trial) 28/30 1/30 0/100
Breaches with the boundary guarding (per attempted attack) 0/60 0/25 0/100

Read the last column twice. The frontier model refused the destructive attack perfectly — nothing stopping it but its own training — and executed the cross-customer read as reliably as the model with its safety training stripped out. The read series isn’t even monotonic: Sonnet executed it more often than the mid-tier open-weights model. More alignment did not mean less exposure on that action.

Why the read is the one that gets through

Because it doesn’t look like an attack from where the model sits. “Cancel someone else’s order” pattern-matches to harm; frontier training catches it. “Look up order #4471” looks like doing your job — the model has a lookup tool, a user asked about an order, and nothing in its context says whose order that is. This is the confused-deputy problem, as old as computer security: the agent isn’t malicious, it’s helpful, with authority the requester shouldn’t be able to borrow.

That’s why the fix isn’t a better prompt or a more aligned model. Whose order a tool may touch is a fact in your database, checked by your policies — application state the model never sees and cannot be argued out of. Models propose; applications authorize.

What these numbers are not

  • Breach rates are properties of each model’s alignment under these attack framings, not of production — this is a harness you point at your own agent, not a leaderboard.
  • The bounds are ceilings from the rule of three (≤3% at 95% for the guarded Sonnet arm over 100 observations), not proofs.
  • The prompt-injection case is reported as undemonstrated, not prevented: no model took the bait in any run, and a denial of an attack never attempted proves nothing.

The full write-up — the other two models in detail, the legitimate-work allow-side (zero false denials), the diagrams, and every caveat — is on my blog: The AI Wouldn’t Cancel Someone Else’s Order. But It Read It Every Single Time.

Recorded runs and raw numbers: docs/evaluation.md. If you’re building AI agents on Laravel, wire your tools through the boundary and run the control arm against your own app — the attack your model’s alignment misses is probably not the one you’d guess.


Sumber Rujukan:

  • Artikel asal dari Dev.to PHP
  • Published on haqis.com
Comments

Automatically Secure Your Livewire 4 Components From Client-Side Tampering

Livewire Secure Properties 🔒

👉 View on GitHub: janecodelife/livewire-secure-properties

An elegant, zero-configuration security package for Laravel Livewire 4 that automatically locks all public component properties from client-side manipulation, unless explicitly marked as unlocked.

✅ Auto-lock properties

✅ Zero configuration

✅ Protects against client-side tampering

✅ Unlock specific properties with #[Unlocked]

✅ Supports Livewire 4 (Single & Multiple File)

Requirements

  • Livewire ^4.0

Installation

You can install the package via composer:

composer require janecodelife/livewire-secure-properties

Usage

1. Single File Components (SFC)

If you are using Livewire 4’s native Single File Components layout, you can safely use the #[Unlocked] attribute inside the anonymous class block:


use Livewire\Component;
use JaneCodeLife\LivewireSecureProperties\Unlocked;

new class extends Component {
// ✅ Secured: Locked by default, any client-side update will throw a Security Violation exception
public string $role = 'admin';

// 🔓 UNLOCKED: Updatable from client side via wire:model or client-side requests
#[Unlocked]
public string $name = 'Jane Joe';
};
?>



Name: {{ $name }}




Role: {{ $role }}


2. Multiple File Components (Class-based)

use Livewire\Component;
use JaneCodeLife\LivewireSecureProperties\Unlocked;

class UserProfile extends Component
{
// ✅ Secured: Locked by default, any client-side update will throw a Security Violation exception
public string $role = 'admin';

// 🔓 UNLOCKED: Updatable from client side via wire:model or client-side requests
#[Unlocked]
public string $name = 'Jane Joe';

public function render()
{
return view('livewire.user-profile');
}
}

Configuration

If you need to disable the package globally during specific environments (e.g., local debugging), you can add this environment variable to your .env file:

LIVEWIRE_SECURE_PROPERTIES_ENABLED=false

💖 Support

☕☕☕☕ Support me by coffee via USDT ☕☕☕☕

  • Network: TRX Tron (TRC20)
  • Address: TAFFjBP39Z86weL5dDU1A2251VrgPprDUj

Upcoming 🚀 (Stay Tuned!)

The Ultimate Neovim Config for Modern Web & Laravel Devs ⚡

I am currently cooking a comprehensive guide and boilerplate configuration on How to turn Neovim into a (Powerful) IDE explicitly optimized for:

  • Backend & Frameworks: PHP (Intelephense) & Full Laravel & Livewire Integration (With Preformance)
  • Frontend & Tooling: HTML, CSS, JavaScript, TypeScript, and Livewire SFCs
  • Speed: Blazing fast autocompletion, lightning-speed code navigation, and fuzzy finding.

Sumber Rujukan:

Comments