Skip to content
全部文档

Synthesizers

因为 Livewire 组件在请求之间会被脱水(序列化)为 JSON,再水合(反序列化)回 PHP 组件,所以它们的属性需要是可 JSON 序列化的。

原生地,PHP 很容易将大多数原始值序列化为 JSON。但为了让 Livewire 组件支持更复杂的属性类型(如模型、集合、Carbon 实例和 Stringable),需要一个更稳健的系统。

因此,Livewire 提供了名为「Synthesizers」的扩展点,允许用户支持任意自定义属性类型。

TIP

请先确保理解 hydration

使用 Synthesizers 之前,最好先充分理解 Livewire 的 hydration 系统。可通过阅读 hydration 文档 了解更多。

理解 Synthesizers

在探索创建自定义 Synthesizer 之前,我们先看看 Livewire 用于支持 Laravel Stringable 的内部 Synthesizer。

假设你的应用包含以下 CreatePost 组件:

php
class CreatePost extends Component
{
    public $title = '';
}

在请求之间,Livewire 可能将该组件的状态序列化为如下 JSON 对象:

js
state: { title: '' },

现在,考虑一个更高级的示例,其中 $title 属性值是 Stringable 而非普通字符串:

php
class CreatePost extends Component
{
    public $title = '';

    public function mount()
    {
        $this->title = str($this->title);
    }
}

表示该组件状态的脱水 JSON 现在包含一个 元数据元组,而不是普通的空字符串:

js
state: { title: ['', { s: 'str' }] },

Livewire 现在可以使用该元组,在下一次请求时将 $title 属性水合回 Stringable。

既然你已经从外到内看到了 Synthesizers 的效果,下面是 Livewire 内部 Stringable synth 的实际源码:

php
use Illuminate\Support\Stringable;

class StringableSynth extends Synth
{
    public static $key = 'str';

    public static function match($target)
    {
        return $target instanceof Stringable;
    }

    public function dehydrate($target)
    {
        return [$target->__toString(), []];
    }

    public function hydrate($value)
    {
        return str($value);
    }
}

让我们逐段拆解。

首先是 $key 属性:

php
public static $key = 'str';

每个 synth 必须包含一个静态 $key 属性,Livewire 用它将像 ['', { s: 'str' }] 这样的 元数据元组 转换回 Stringable。如你所见,每个元数据元组都有一个引用此键的 s 键。

反过来,当 Livewire 脱水属性时,会使用 synth 的静态 match() 函数来判断该 Synthesizer 是否适合脱水当前属性($target 为属性的当前值):

php
public static function match($target)
{
    return $target instanceof Stringable;
}

match() 返回 true,将使用 dehydrate() 方法,以属性的 PHP 值作为输入,返回可 JSON 化的 元数据 元组:

php
public function dehydrate($target)
{
    return [$target->__toString(), []];
}

现在,在下一次请求开始时,该 Synthesizer 被元组中的 { s: 'str' } 键匹配后,会调用 hydrate() 方法,并传入属性的原始 JSON 表示,期望它返回完整的、可赋给属性的 PHP 兼容值。

php
public function hydrate($value)
{
    return str($value);
}

注册自定义 Synthesizer

为演示如何编写自己的 Synthesizer 以支持自定义属性,我们将以下面的 UpdateProperty 组件为例:

php
class UpdateProperty extends Component
{
    public Address $address;

    public function mount()
    {
        $this->address = new Address();
    }
}

下面是 Address 类的源码:

php
namespace App\Dtos\Address;

class Address
{
    public $street = '';
    public $city = '';
    public $state = '';
    public $zip = '';
}

要支持 Address 类型的属性,可以使用以下 Synthesizer:

php
use App\Dtos\Address;

class AddressSynth extends Synth
{
    public static $key = 'address';

    public static function match($target)
    {
        return $target instanceof Address;
    }

    public function dehydrate($target)
    {
        return [[
            'street' => $target->street,
            'city' => $target->city,
            'state' => $target->state,
            'zip' => $target->zip,
        ], []];
    }

    public function hydrate($value)
    {
        $instance = new Address;

        $instance->street = $value['street'];
        $instance->city = $value['city'];
        $instance->state = $value['state'];
        $instance->zip = $value['zip'];

        return $instance;
    }
}

要使其在整个应用中全局可用,可以在服务提供者的 boot 方法中使用 Livewire 的 propertySynthesizer 方法注册该 synthesizer:

php
class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Livewire::propertySynthesizer(AddressSynth::class);
    }
}

支持数据绑定

使用上面的 UpdateProperty 示例,你很可能希望支持直接将 wire:model 绑定到 Address 对象的属性。Synthesizers 允许你通过 get()set() 方法支持这一点:

php
use App\Dtos\Address;

class AddressSynth extends Synth
{
    public static $key = 'address';

    public static function match($target)
    {
        return $target instanceof Address;
    }

    public function dehydrate($target)
    {
        return [[
            'street' => $target->street,
            'city' => $target->city,
            'state' => $target->state,
            'zip' => $target->zip,
        ], []];
    }

    public function hydrate($value)
    {
        $instance = new Address;

        $instance->street = $value['street'];
        $instance->city = $value['city'];
        $instance->state = $value['state'];
        $instance->zip = $value['zip'];

        return $instance;
    }

    public function get(&$target, $key) // [tl! highlight:8]
    {
        return $target->{$key};
    }

    public function set(&$target, $key, $value)
    {
        $target->{$key} = $value;
    }
}

数组形态的 synthesizers

在属性更新期间,Livewire 可能复用先前快照中的 synthesizer 元数据来水合嵌套值。若你的 synthesizer 始终将值序列化为数组,请实现 ArrayShapedSynth 接口:

php
use Livewire\Mechanisms\HandleComponents\Synthesizers\ArrayShapedSynth;
use Livewire\Mechanisms\HandleComponents\Synthesizers\Synth;

class AddressSynth extends Synth implements ArrayShapedSynth
{
    // ...
}

这告诉 Livewire:当顶层或嵌套更新用任意非数组值替换先前值时,不要运行你的 synthesizer。相反,Livewire 将传入值视为替换值。数组更新仍会照常通过你的 synthesizer。

仅当你的 synthesizer 的 hydrate() 方法需要数组时才实现此接口。水合标量值(如日期、枚举、整数、浮点数或字符串)的 Synthesizers 不应实现它。没有该接口时,为保持向后兼容,Livewire 会继续将先前的 synthesizer 元数据应用于非数组更新。