Skip to content

Synthesizers(合成器)

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

原生情况下,PHP 可以轻松地将大多数原始值序列化为 JSON。然而,为了让 Livewire 组件支持更复杂的属性类型(如模型、集合、Carbon 实例和 Stringable),需要一个更强大的系统。

因此,Livewire 提供了一个名为 "Synthesizers"(合成器)的扩展点,允许用户支持他们想要的任何自定义属性类型。

确保您首先理解水合

在使用 Synthesizers 之前,完全理解 Livewire 的水合系统很有帮助。您可以通过阅读水合文档了解更多信息。

理解 Synthesizers

在探索创建自定义 Synthesizers 之前,让我们首先看看 Livewire 用于支持 Laravel Stringables 的内部 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;
    }
}

要在您的应用程序中全局使用它,您可以使用 Livewire 的 propertySynthesizer 方法从服务提供者的 boot 方法中注册合成器:

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

支持数据绑定

使用上面的 UpdateProperty 示例,您可能希望支持直接绑定到 Address 对象属性的 wire:model。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;
    }
}