操作

您是视觉学习者吗?
使用我们深入的屏幕录制掌握 Livewire
立即观看

Livewire 操作是组件中的方法,可以通过前端交互(如单击按钮或提交表单)触发。它们为开发人员提供了直接从浏览器调用 PHP 方法的体验,让您专注于应用程序的逻辑,而无需陷入编写连接应用程序前端和后端的样板代码的困境。

我们来探索一个在 CreatePost 组件上调用 save 操作的基本示例

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class CreatePost extends Component
{
public $title = '';
 
public $content = '';
 
public function save()
{
Post::create([
'title' => $this->title,
'content' => $this->content,
]);
 
return redirect()->to('/posts');
}
 
public function render()
{
return view('livewire.create-post');
}
}
<form wire:submit="save">
<input type="text" wire:model="title">
 
<textarea wire:model="content"></textarea>
 
<button type="submit">Save</button>
</form>

在上面的示例中,当用户单击“保存”提交表单时,wire:submit 会拦截 submit 事件并在服务器上调用 save() 操作。

从本质上说,操作是一种将用户交互轻松映射到服务器端功能的方法,而无需手动提交和处理 AJAX 请求的麻烦。

刷新组件

有时您可能希望触发组件的简单“刷新”。例如,如果您有一个组件正在检查数据库中某项内容的状态,您可能希望向用户显示一个按钮,允许他们刷新显示的结果。

您可以在通常引用您自己的组件方法的任何地方使用 Livewire 的简单 $refresh 操作来执行此操作

<button type="button" wire:click="$refresh">...</button>

当触发 $refresh 操作时,Livewire 将进行服务器往返并重新渲染您的组件,而无需调用任何方法。

请注意,组件中的任何待定数据更新(例如 wire:model 绑定)将在组件刷新时应用到服务器上。

在内部,Livewire 使用名称“commit”来指代 Livewire 组件在服务器上更新的任何时间。如果您喜欢此术语,则可以使用 $commit 帮助器,而不是 $refresh。两者是相同的。

<button type="button" wire:click="$commit">...</button>

您还可以在 Livewire 组件中使用 AlpineJS 触发组件刷新

<button type="button" x-on:click="$wire.$refresh()">...</button>

通过阅读 在 Livewire 中使用 Alpine 的文档 了解更多信息。

确认操作

当允许用户执行危险操作(例如从数据库中删除帖子)时,您可能希望向他们显示一个确认警报,以验证他们是否希望执行该操作。

Livewire 通过提供一个名为 wire:confirm 的简单指令,让此操作变得简单

<button
type="button"
wire:click="delete"
wire:confirm="Are you sure you want to delete this post?"
>
Delete post
</button>

wire:confirm 添加到包含 Livewire 操作的元素时,当用户尝试触发该操作时,他们将看到一个包含所提供消息的确认对话框。他们可以按“确定”确认操作,也可以按“取消”或按 Escape 键。

有关更多信息,请访问 wire:confirm 文档页面

事件侦听器

Livewire 支持各种事件侦听器,允许您响应各种类型的用户交互

侦听器 描述
wire:click 当元素被点击时触发
wire:submit 当表单提交时触发
wire:keydown 当按下键时触发
wire:keyup 当释放键时触发
wire:mouseenter 当鼠标进入元素时触发
wire:* wire: 后面的任何文本都将用作侦听器的事件名称

由于 wire: 后面的事件名称可以是任何内容,因此 Livewire 支持您可能需要侦听的任何浏览器事件。例如,要侦听 transitionend,可以使用 wire:transitionend

侦听特定键

您可以使用 Livewire 的一个便捷别名将按键事件侦听器缩小到特定键或键组合。

例如,要在用户在搜索框中输入后按 Enter 时执行搜索,可以使用 wire:keydown.enter

<input wire:model="query" wire:keydown.enter="searchPosts">

您可以在第一个键别名之后链接更多键别名来监听键的组合。例如,如果您只想在按下 Enter 键时监听 Shift 键,您可以编写以下内容

<input wire:keydown.shift.enter="...">

以下是所有可用的键修饰符列表

修饰符
.shift Shift
.enter Enter
.space Space
.ctrl Ctrl
.cmd Cmd
.meta Mac 上的 Cmd,Windows 上的 Windows 键
.alt Alt
.up 向上箭头
.down 向下箭头
.left 向左箭头
.right 向右箭头
.escape Escape
.tab Tab
.caps-lock Caps Lock
.equal 等号,=
.period 句号,.
.slash 正斜杠,/

事件处理程序修饰符

Livewire 还包括一些有用的修饰符,可以轻松完成常见的事件处理任务。

例如,如果您需要从事件侦听器内部调用 event.preventDefault(),则可以在事件名称后加上 .prevent

<input wire:keydown.prevent="...">

以下是所有可用的事件侦听器修饰符及其功能的完整列表

修饰符
.prevent 相当于调用 .preventDefault()
.stop 相当于调用 .stopPropagation()
.window window 对象上侦听事件
.outside 仅侦听元素“外部”的点击
.document document 对象上侦听事件
.once 确保侦听器仅被调用一次
.debounce 默认情况下以 250 毫秒为单位对处理程序进行防抖
.debounce.100ms 在特定时间内对处理程序进行防抖
.throttle 将处理程序限制为至少每 250 毫秒调用一次
.throttle.100ms 以自定义持续时间限制处理程序
.self 仅在事件源自该元素(而不是子元素)时调用侦听器
.camel 将事件名称转换为驼峰式大小写(wire:custom-event -> "customEvent")
.dot 将事件名称转换为点表示法(wire:custom-event -> "custom.event")
.passive wire:touchstart.passive 不会阻止滚动性能
.capture 在“捕获”阶段侦听事件

由于 wire: 在幕后使用 Alpinex-on 指令,因此这些修饰符由 Alpine 提供给您。有关何时应使用这些修饰符的更多背景信息,请参阅 Alpine 事件文档

处理第三方事件

Livewire 还支持监听第三方库触发的自定义事件。

例如,假设你在项目中使用了 Trix 富文本编辑器,并且希望监听 trix-change 事件以捕获编辑器内容。你可以使用 wire:trix-change 指令来实现此目的

<form wire:submit="save">
<!-- ... -->
 
<trix-editor
wire:trix-change="setPostContent($event.target.value)"
></trix-editor>
 
<!-- ... -->
</form>

在此示例中,每当触发 trix-change 事件时,都会调用 setPostContent 操作,使用 Trix 编辑器的当前值更新 Livewire 组件中的 content 属性。

你可以使用 $event 访问事件对象

在 Livewire 事件处理程序中,你可以通过 $event 访问事件对象。这对于引用事件中的信息很有用。例如,你可以通过 $event.target 访问触发事件的元素。

上面的 Trix 演示代码不完整,仅可用作事件侦听器的演示。如果逐字使用,则每次击键都会触发网络请求。性能更高的实现方式是

<trix-editor
x-on:trix-change="$wire.content = $event.target.value"
></trix-editor>

监听分发的自定义事件

如果你的应用程序从 Alpine 分发自定义事件,你也可以使用 Livewire 监听这些事件

<div wire:custom-event="...">
 
<!-- Deeply nested within this component: -->
<button x-on:click="$dispatch('custom-event')">...</button>
 
</div>

在上述示例中,当单击按钮时,会分发 custom-event 事件并冒泡到 Livewire 组件的根部,wire:custom-event 会捕获该事件并调用给定的操作。

如果你想监听应用程序其他位置分发的事件,则需要等待事件冒泡到 window 对象,然后在那里监听它。幸运的是,Livewire 通过允许你向任何事件侦听器添加一个简单的 .window 修饰符,从而使此操作变得容易

<div wire:custom-event.window="...">
<!-- ... -->
</div>
 
<!-- Dispatched somewhere on the page outside the component: -->
<button x-on:click="$dispatch('custom-event')">...</button>

在提交表单时禁用输入

考虑我们之前讨论的 CreatePost 示例

<form wire:submit="save">
<input wire:model="title">
 
<textarea wire:model="content"></textarea>
 
<button type="submit">Save</button>
</form>

当用户单击“保存”时,会向服务器发送网络请求以调用 Livewire 组件上的 save() 操作。

但是,假设用户在网速较慢的连接上填写此表单。用户单击“保存”,但最初什么也没有发生,因为网络请求花费的时间比平时长。他们可能会怀疑提交是否失败,并在第一个请求仍在处理时再次尝试单击“保存”按钮。

在这种情况下,将同时处理针对同一操作的两个请求。

为了防止这种情况,Livewire 在处理 wire:submit 操作时会自动禁用 <form> 元素内的提交按钮和所有表单输入。这可确保不会意外地提交表单两次。

为了进一步减少连接速度较慢的用户遇到的困惑,通常有帮助的做法是显示一些加载指示器,例如微妙的背景颜色更改或 SVG 动画。

Livewire 提供了一个 wire:loading 指令,可轻松地在页面上的任何位置显示和隐藏加载指示器。以下是一个使用 wire:loading 在“保存”按钮下方显示加载消息的简短示例

<form wire:submit="save">
<textarea wire:model="content"></textarea>
 
<button type="submit">Save</button>
 
<span wire:loading>Saving...</span>
</form>

wire:loading 是一个强大的功能,具有各种更强大的功能。 查看完整的加载文档以获取更多信息

传递参数

Livewire 允许您将参数从 Blade 模板传递到组件中的操作,从而让您有机会在调用操作时从前端提供操作附加数据或状态。

例如,假设您有一个 ShowPosts 组件,允许用户删除帖子。您可以将帖子的 ID 作为参数传递给 Livewire 组件中的 delete() 操作。然后,该操作可以获取相关帖子并将其从数据库中删除

<?php
 
namespace App\Livewire;
 
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Post;
 
class ShowPosts extends Component
{
public function delete($id)
{
$post = Post::findOrFail($id);
 
$this->authorize('delete', $post);
 
$post->delete();
}
 
public function render()
{
return view('livewire.show-posts', [
'posts' => Auth::user()->posts,
]);
}
}
<div>
@foreach ($posts as $post)
<div wire:key="{{ $post->id }}">
<h1>{{ $post->title }}</h1>
<span>{{ $post->content }}</span>
 
<button wire:click="delete({{ $post->id }})">Delete</button>
</div>
@endforeach
</div>

对于 ID 为 2 的帖子,Blade 模板中上面的“删除”按钮将在浏览器中呈现为

<button wire:click="delete(2)">Delete</button>

单击此按钮时,将调用 delete() 方法,并且 $id 将传递,其值为“2”。

不要信任操作参数

操作参数应像 HTTP 请求输入一样对待,这意味着不应信任操作参数值。在数据库中更新实体之前,您应始终授权其所有权。

有关更多信息,请参阅我们关于 安全问题和最佳实践 的文档。

作为一种额外的便利,您可以通过作为参数提供给操作的相应模型 ID 自动解析 Eloquent 模型。这与 路由模型绑定 非常相似。要开始,请使用模型类提示操作参数的类型,然后将自动从数据库中检索适当的模型并将其传递给操作,而不是 ID

<?php
 
namespace App\Livewire;
 
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Post;
 
class ShowPosts extends Component
{
public function delete(Post $post)
{
$this->authorize('delete', $post);
 
$post->delete();
}
 
public function render()
{
return view('livewire.show-posts', [
'posts' => Auth::user()->posts,
]);
}
}

依赖注入

您可以通过在操作签名中提示参数类型来利用 Laravel 的依赖注入 系统。Livewire 和 Laravel 将自动从容器中解析操作的依赖项

<?php
 
namespace App\Livewire;
 
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Repositories\PostRepository;
 
class ShowPosts extends Component
{
public function delete(PostRepository $posts, $postId)
{
$posts->deletePost($postId);
}
 
public function render()
{
return view('livewire.show-posts', [
'posts' => Auth::user()->posts,
]);
}
}
<div>
@foreach ($posts as $post)
<div wire:key="{{ $post->id }}">
<h1>{{ $post->title }}</h1>
<span>{{ $post->content }}</span>
 
<button wire:click="delete({{ $post->id }})">Delete</button>
</div>
@endforeach
</div>

在此示例中,delete() 方法在接收提供的 $postId 参数之前,接收通过 Laravel 服务容器 解析的 PostRepository 实例。

从 Alpine 调用操作

Livewire 与 Alpine 无缝集成。事实上,在幕后,每个 Livewire 组件也是一个 Alpine 组件。这意味着你可以在组件中充分利用 Alpine,以添加由 JavaScript 驱动的客户端交互性。

为了让这种配对更加强大,Livewire 向 Alpine 公开了一个神奇的 $wire 对象,该对象可以被视为 PHP 组件的 JavaScript 表示。除了 通过 $wire 访问和更改公共属性 之外,你还可以调用操作。当在 $wire 对象上调用操作时,将在后端 Livewire 组件上调用相应 PHP 方法

<button x-on:click="$wire.save()">Save Post</button>

或者,为了说明一个更复杂的示例,你可以使用 Alpine 的 x-intersect 实用程序,当给定元素在页面上可见时触发 incrementViewCount() Livewire 操作

<div x-intersect="$wire.incrementViewCount()">...</div>

传递参数

你传递给 $wire 方法的任何参数也将传递给 PHP 类方法。例如,考虑以下 Livewire 操作

public function addTodo($todo)
{
$this->todos[] = $todo;
}

在组件的 Blade 模板中,你可以通过 Alpine 调用此操作,提供应提供给操作的参数

<div x-data="{ todo: '' }">
<input type="text" x-model="todo">
 
<button x-on:click="$wire.addTodo(todo)">Add Todo</button>
</div>

如果用户在文本输入框中输入“倒垃圾”,然后按下“添加待办事项”按钮,则 addTodo() 方法将被触发,$todo 参数值为“倒垃圾”。

接收返回值

为了获得更大的功能,在网络请求处理期间,调用的 $wire 操作会返回一个 Promise。当收到服务器响应时,Promise 会使用后端操作返回的值进行解析。

例如,考虑一个具有以下操作的 Livewire 组件

use App\Models\Post;
 
public function getPostCount()
{
return Post::count();
}

使用 $wire,可以调用操作并解析其返回值

<span x-init="$el.innerHTML = await $wire.getPostCount()"></span>

在此示例中,如果 getPostCount() 方法返回“10”,则 <span> 标记也将包含“10”。

使用 Livewire 时不需要 Alpine 知识;但是,它是一个非常强大的工具,了解 Alpine 将增强你的 Livewire 体验和生产力。

Livewire 的“混合”JavaScript 函数

有时,组件中的操作不需要与服务器通信,并且可以使用仅 JavaScript 更有效地编写。

在这些情况下,您可以让组件操作返回 JavaScript 函数作为字符串,而不是在 Blade 模板或其他文件中编写操作。如果操作标记了 #[Js] 属性,则可以从应用程序的前端调用它

例如

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Js;
use Livewire\Component;
use App\Models\Post;
 
class SearchPosts extends Component
{
public $query = '';
 
#[Js]
public function resetQuery()
{
return <<<'JS'
$wire.query = '';
JS;
}
 
public function render()
{
return view('livewire.search-posts', [
'posts' => Post::whereTitle($this->query)->get(),
]);
}
}
<div>
<input wire:model.live="query">
 
<button wire:click="resetQuery">Reset Search</button>
 
@foreach ($posts as $post)
<!-- ... -->
@endforeach
</div>

在上面的示例中,当按下“重置搜索”按钮时,文本输入将被清除,而不会向服务器发送任何请求。

求值一次性 JavaScript 表达式

除了指定要在 JavaScript 中求值的方法外,您还可以使用 js() 方法求值更小的单个表达式。

这通常在执行服务器端操作后执行某种客户端后续操作时很有用。

例如,以下是一个 CreatePost 组件的示例,该组件在将帖子保存到数据库后触发客户端警报对话框

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
public $title = '';
 
public function save()
{
// ...
 
$this->js("alert('Post saved!')");
}
}

JavaScript 表达式 alert('Post saved!') 现在将在服务器上将帖子保存到数据库后在客户端执行。

就像 #[Js] 方法一样,您可以在表达式中访问当前组件的 $wire 对象。

神奇操作

Livewire 提供了一组“神奇”操作,允许您在组件中执行常见任务,而无需定义自定义方法。这些神奇操作可以在 Blade 模板中定义的事件侦听器中使用。

$parent

$parent 神奇变量允许您从子组件访问父组件属性并调用父组件操作

<button wire:click="$parent.removePost({{ $post->id }})">Remove</button>

在上面的示例中,如果父组件有 removePost() 操作,则子组件可以使用 $parent.removePost() 直接从其 Blade 模板中调用它。

$set

$set 神奇操作允许您直接从 Blade 模板更新 Livewire 组件中的属性。要使用 $set,请提供您要更新的属性和新值作为参数

<button wire:click="$set('query', '')">Reset Search</button>

在此示例中,当单击按钮时,将分派一个网络请求,将组件中的 $query 属性设置为 ''

$refresh

$refresh 操作触发 Livewire 组件的重新渲染。这在不更改任何属性值的情况下更新组件视图时很有用

<button wire:click="$refresh">Refresh</button>

单击按钮时,组件将重新渲染,让你看到视图中的最新更改。

$toggle

$toggle 操作用于切换 Livewire 组件中布尔属性的值

<button wire:click="$toggle('sortAsc')">
Sort {{ $sortAsc ? 'Descending' : 'Ascending' }}
</button>

在此示例中,单击按钮时,组件中的 $sortAsc 属性将在 truefalse 之间切换。

$dispatch

$dispatch 操作允许你在浏览器中直接分派 Livewire 事件。以下是单击时将分派 post-deleted 事件的按钮示例

<button type="submit" wire:click="$dispatch('post-deleted')">Delete Post</button>

$event

$event 操作可以在事件侦听器(如 wire:click)中使用。此操作允许你访问触发的实际 JavaScript 事件,让你可以引用触发元素和其他相关信息

<input type="text" wire:keydown.enter="search($event.target.value)">

当用户在上述输入框中键入时按下回车键,输入框的内容将作为参数传递给 search() 操作。

使用 Alpine 中的魔术操作

你还可以使用 $wire 对象从 Alpine 中调用魔术操作。例如,你可以使用 $wire 对象调用 $refresh 魔术操作

<button x-on:click="$wire.$refresh()">Refresh</button>

跳过重新渲染

有时,你的组件中可能存在一个操作,该操作没有任何副作用,在调用该操作时会更改渲染的 Blade 模板。如果是这样,你可以通过在操作方法上方添加 #[Renderless] 属性来跳过 Livewire 生命周期中的 render 部分。

为了演示,在下面的 ShowPost 组件中,当用户滚动到帖子的底部时,会记录“查看次数”

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Renderless;
use Livewire\Component;
use App\Models\Post;
 
class ShowPost extends Component
{
public Post $post;
 
public function mount(Post $post)
{
$this->post = $post;
}
 
#[Renderless]
public function incrementViewCount()
{
$this->post->incrementViewCount();
}
 
public function render()
{
return view('livewire.show-post');
}
}
<div>
<h1>{{ $post->title }}</h1>
<p>{{ $post->content }}</p>
 
<div x-intersect="$wire.incrementViewCount()"></div>
</div>

上面的示例使用了 x-intersect,这是一个 Alpine 实用程序,当元素进入视口时调用表达式(通常用于检测用户何时滚动到页面下方的元素)。

如您所见,当用户滚动到帖子的底部时,将调用 incrementViewCount()。由于 #[Renderless] 已添加到操作,因此视图已记录,但模板不会重新渲染,并且页面的任何部分都不会受到影响。

如果您不想使用 method 属性或需要有条件地跳过渲染,则可以在组件操作中调用 skipRender() 方法

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class ShowPost extends Component
{
public Post $post;
 
public function mount(Post $post)
{
$this->post = $post;
}
 
public function incrementViewCount()
{
$this->post->incrementViewCount();
 
$this->skipRender();
}
 
public function render()
{
return view('livewire.show-post');
}
}

安全问题

请记住,即使没有关联的 wire:click 处理器调用它,也可以从客户端调用 Livewire 组件中的任何公共方法。在这些情况下,用户仍然可以从浏览器的 DevTools 触发操作。

以下是 Livewire 组件中三个容易被忽视的漏洞示例。每个示例都会首先显示易受攻击的组件,然后显示安全的组件。作为练习,请尝试在查看解决方案之前发现第一个示例中的漏洞。

如果您难以发现漏洞,并且这会让您担心自己保护应用程序安全的能力,请记住所有这些漏洞都适用于使用请求和控制器的标准 Web 应用程序。如果您将组件方法用作控制器方法的代理,并将其参数用作请求输入的代理,则应该能够将现有的应用程序安全知识应用到 Livewire 代码中。

始终授权操作参数

就像控制器请求输入一样,授权操作参数非常重要,因为它们是任意的用户输入。

下面是一个 ShowPosts 组件,用户可以在其中在一个页面上查看其所有帖子。他们可以使用帖子的“删除”按钮删除任何他们喜欢的帖子。

以下是组件的易受攻击版本

<?php
 
namespace App\Livewire;
 
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Post;
 
class ShowPosts extends Component
{
public function delete($id)
{
$post = Post::find($id);
 
$post->delete();
}
 
public function render()
{
return view('livewire.show-posts', [
'posts' => Auth::user()->posts,
]);
}
}
<div>
@foreach ($posts as $post)
<div wire:key="{{ $post->id }}">
<h1>{{ $post->title }}</h1>
<span>{{ $post->content }}</span>
 
<button wire:click="delete({{ $post->id }})">Delete</button>
</div>
@endforeach
</div>

请记住,恶意用户可以直接从 JavaScript 控制台调用 delete(),将他们希望的操作传递给任何参数。这意味着查看其一篇帖子的用户可以通过将未拥有的帖子 ID 传递给 delete() 来删除另一位用户的帖子。

为了保护这一点,我们需要授权用户拥有即将被删除的帖子

<?php
 
namespace App\Livewire;
 
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Post;
 
class ShowPosts extends Component
{
public function delete($id)
{
$post = Post::find($id);
 
$this->authorize('delete', $post);
 
$post->delete();
}
 
public function render()
{
return view('livewire.show-posts', [
'posts' => Auth::user()->posts,
]);
}
}

始终授权服务器端

与标准 Laravel 控制器一样,即使 UI 中没有调用操作的承受力,任何用户也可以调用 Livewire 操作。

考虑以下 BrowsePosts 组件,其中任何用户都可以查看应用程序中的所有帖子,但只有管理员可以删除帖子

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class BrowsePosts extends Component
{
public function deletePost($id)
{
$post = Post::find($id);
 
$post->delete();
}
 
public function render()
{
return view('livewire.browse-posts', [
'posts' => Post::all(),
]);
}
}
<div>
@foreach ($posts as $post)
<div wire:key="{{ $post->id }}">
<h1>{{ $post->title }}</h1>
<span>{{ $post->content }}</span>
 
@if (Auth::user()->isAdmin())
<button wire:click="deletePost({{ $post->id }})">Delete</button>
@endif
</div>
@endforeach
</div>

如您所见,只有管理员才能看到“删除”按钮;但是,任何用户都可以从浏览器的 DevTools 在组件上调用 deletePost()

要修补此漏洞,我们需要像这样在服务器上授权操作

<?php
 
namespace App\Livewire;
 
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Post;
 
class BrowsePosts extends Component
{
public function deletePost($id)
{
if (! Auth::user()->isAdmin) {
abort(403);
}
 
$post = Post::find($id);
 
$post->delete();
}
 
public function render()
{
return view('livewire.browse-posts', [
'posts' => Post::all(),
]);
}
}

通过此更改,只有管理员才能从此组件中删除帖子。

保持危险方法受保护或私有

Livewire 组件中的每个公共方法都可以从客户端调用。即使您没有在 wire:click 处理程序中引用的方法。为了防止用户调用不打算在客户端调用的方法,您应该将它们标记为 protectedprivate。通过这样做,您可以将该敏感方法的可见性限制为组件的类及其子类,确保它们不能从客户端调用。

考虑我们之前讨论过的 BrowsePosts 示例,其中用户可以在您的应用程序中查看所有帖子,但只有管理员可以删除帖子。在 始终授权服务器端 部分中,我们通过添加服务器端授权来确保操作安全。现在想象一下,我们将帖子的实际删除重构为一个专用方法,就像您可能为了简化代码而做的那样

// Warning: This snippet demonstrates what NOT to do...
<?php
 
namespace App\Livewire;
 
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Post;
 
class BrowsePosts extends Component
{
public function deletePost($id)
{
if (! Auth::user()->isAdmin) {
abort(403);
}
 
$this->delete($id);
}
 
public function delete($postId)
{
$post = Post::find($postId);
 
$post->delete();
}
 
public function render()
{
return view('livewire.browse-posts', [
'posts' => Post::all(),
]);
}
}
<div>
@foreach ($posts as $post)
<div wire:key="{{ $post->id }}">
<h1>{{ $post->title }}</h1>
<span>{{ $post->content }}</span>
 
<button wire:click="deletePost({{ $post->id }})">Delete</button>
</div>
@endforeach
</div>

如您所见,我们将帖子删除逻辑重构为一个名为 delete() 的专用方法。即使此方法在我们的模板中没有任何引用,但如果用户了解它的存在,他们将能够从浏览器的 DevTools 中调用它,因为它为 public

为了解决这个问题,我们可以将该方法标记为 protectedprivate。一旦该方法被标记为 protectedprivate,如果用户尝试调用它,将抛出一个错误

<?php
 
namespace App\Livewire;
 
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Post;
 
class BrowsePosts extends Component
{
public function deletePost($id)
{
if (! Auth::user()->isAdmin) {
abort(403);
}
 
$this->delete($id);
}
 
protected function delete($postId)
{
$post = Post::find($postId);
 
$post->delete();
}
 
public function render()
{
return view('livewire.browse-posts', [
'posts' => Post::all(),
]);
}
}