Synthesizers
因为 Livewire 组件在请求之间会被脱水(序列化)为 JSON,再水合(反序列化)回 PHP 组件,所以它们的属性需要是可 JSON 序列化的。
原生地,PHP 很容易将大多数原始值序列化为 JSON。但为了让 Livewire 组件支持更复杂的属性类型(如模型、集合、Carbon 实例和 Stringable),需要一个更稳健的系统。
因此,Livewire 提供了名为「Synthesizers」的扩展点,允许用户支持任意自定义属性类型。
理解 Synthesizers
在探索创建自定义 Synthesizer 之前,我们先看看 Livewire 用于支持 Laravel Stringable 的内部 Synthesizer。
假设你的应用包含以下 CreatePost 组件:
class CreatePost extends Component
{
public $title = '';
}在请求之间,Livewire 可能将该组件的状态序列化为如下 JSON 对象:
state: { title: '' },现在,考虑一个更高级的示例,其中 $title 属性值是 Stringable 而非普通字符串:
class CreatePost extends Component
{
public $title = '';
public function mount()
{
$this->title = str($this->title);
}
}表示该组件状态的脱水 JSON 现在包含一个 元数据元组,而不是普通的空字符串:
state: { title: ['', { s: 'str' }] },Livewire 现在可以使用该元组,在下一次请求时将 $title 属性水合回 Stringable。
既然你已经从外到内看到了 Synthesizers 的效果,下面是 Livewire 内部 Stringable synth 的实际源码:
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 属性:
public static $key = 'str';每个 synth 必须包含一个静态 $key 属性,Livewire 用它将像 ['', { s: 'str' }] 这样的 元数据元组 转换回 Stringable。如你所见,每个元数据元组都有一个引用此键的 s 键。
反过来,当 Livewire 脱水属性时,会使用 synth 的静态 match() 函数来判断该 Synthesizer 是否适合脱水当前属性($target 为属性的当前值):
public static function match($target)
{
return $target instanceof Stringable;
}若 match() 返回 true,将使用 dehydrate() 方法,以属性的 PHP 值作为输入,返回可 JSON 化的 元数据 元组:
public function dehydrate($target)
{
return [$target->__toString(), []];
}现在,在下一次请求开始时,该 Synthesizer 被元组中的 { s: 'str' } 键匹配后,会调用 hydrate() 方法,并传入属性的原始 JSON 表示,期望它返回完整的、可赋给属性的 PHP 兼容值。
public function hydrate($value)
{
return str($value);
}注册自定义 Synthesizer
为演示如何编写自己的 Synthesizer 以支持自定义属性,我们将以下面的 UpdateProperty 组件为例:
class UpdateProperty extends Component
{
public Address $address;
public function mount()
{
$this->address = new Address();
}
}下面是 Address 类的源码:
namespace App\Dtos\Address;
class Address
{
public $street = '';
public $city = '';
public $state = '';
public $zip = '';
}要支持 Address 类型的属性,可以使用以下 Synthesizer:
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:
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Livewire::propertySynthesizer(AddressSynth::class);
}
}支持数据绑定
使用上面的 UpdateProperty 示例,你很可能希望支持直接将 wire:model 绑定到 Address 对象的属性。Synthesizers 允许你通过 get() 和 set() 方法支持这一点:
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 接口:
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 元数据应用于非数组更新。