You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I tried to use #[Hidden] on properties that contains sensitive data from being sent to Livewire when having synths enabled. This does not work, throws exception.
And as I understand Lazy on plain properties is not really possible or recommended for Livewire?
So I tried to create a custom Attribute and it caused similar issues, and the method that finally worked was to implement a custom Synth that basically duplicates all code in LivewireDataSynth but just adds some additional features.
It works - but I wonder if there is some simpler approach that would do the same thing. Basically just add a attribute that will prevent data from being leaked over the wire to public.
My current implementation:
Add to AppServiceProvider to use my custom Synth
Livewire::propertySynthesizer(DataSynth::class);
My custom Synth:
<?php
declare(strict_types=1);
namespace App\Synths;
use Livewire\Mechanisms\HandleComponents\ComponentContext;
use Livewire\Mechanisms\HandleComponents\Synthesizers\Synth;
use ReflectionClass;
use ReflectionProperty;
use Spatie\LaravelData\Contracts\BaseData;
use Spatie\LaravelData\Contracts\ContextableData;
use Spatie\LaravelData\Contracts\TransformableData;
use Spatie\LaravelData\Support\Creation\CreationContextFactory;
use Spatie\LaravelData\Support\DataConfig;
use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Support\Lazy\LivewireLostLazy;
use Spatie\LaravelData\Support\Transformation\TransformationContextFactory;
class DataSynth extends Synth
{
public static string $key = 'ldo';
protected DataConfig $dataConfig;
public function __construct(ComponentContext $context, $path)
{
$this->dataConfig = app(DataConfig::class);
parent::__construct($context, $path);
}
public static function match($target): bool
{
return $target instanceof BaseData && $target instanceof TransformableData;
}
public function get(&$target, $key): mixed
{
return $target->{$key};
}
public function set(&$target, $key, $value): void
{
$target->{$key} = $value;
}
/**
* @param callable(array-key, mixed):mixed $dehydrateChild
*/
public function dehydrate(
BaseData&TransformableData&ContextableData $target,
callable $dehydrateChild,
): array {
$morph = $this->dataConfig->morphMap->getDataClassAlias($target::class) ?? $target::class;
$payload = $target->transform(
TransformationContextFactory::create()
->withPropertyNameMapping(false)
->withoutWrapping()
->withoutPropertyNameMapping()
->withoutValueTransformation(),
);
// Remove properties marked with HiddenFromLivewire
$reflection = new ReflectionClass($target);
foreach ($reflection->getProperties() as $property) {
if ($this->hasHiddenFromLivewireAttributeReflection($property)) {
unset($payload[$property->getName()]);
}
}
// Dehydrate each child value
foreach ($payload as $key => $value) {
$payload[$key] = $dehydrateChild($key, $value);
}
return [
$payload,
['morph' => $morph, 'context' => encrypt($target->getDataContext())],
];
}
/**
* @param callable(array-key, mixed):mixed $hydrateChild
*/
public function hydrate(
array $value,
array $meta,
callable $hydrateChild,
): BaseData {
$morph = $meta['morph'];
$context = decrypt($meta['context']);
$dataClass = $this->dataConfig->morphMap->getMorphedDataClass($morph) ?? $morph;
$payload = [];
foreach ($this->dataConfig->getDataClass($dataClass)->properties as $name => $property) {
if (array_key_exists($name, $value) === false && $property->type->lazyType) {
$payload[$name] = new LivewireLostLazy($dataClass, $name);
continue;
}
// Skip properties marked with HiddenFromLivewire if they're not in the value array
if ($this->hasHiddenFromLivewireAttributeData($property) && ! array_key_exists($name, $value)) {
continue;
}
$payload[$name] = $hydrateChild($name, $value[$name] ?? null);
}
/** @var CreationContextFactory $factory */
$factory = $dataClass::factory();
$data = $factory
->withPropertyNameMapping(false)
->ignoreMagicalMethod('fromLivewire')
->withoutValidation()
->from($payload);
$data->setDataContext($context);
return $data;
}
/**
* Check if a ReflectionProperty has the HiddenFromLivewire attribute.
*/
protected function hasHiddenFromLivewireAttributeReflection(ReflectionProperty $property): bool
{
foreach ($property->getAttributes() as $attribute) {
if ($attribute->getName() === \App\Attributes\HiddenFromLivewire::class) {
return true;
}
}
return false;
}
/**
* Check if a DataProperty has the HiddenFromLivewire attribute.
*/
protected function hasHiddenFromLivewireAttributeData(DataProperty $property): bool
{
foreach ($property->attributes as $attribute) {
if ($attribute->getName() === \App\Attributes\HiddenFromLivewire::class) {
return true;
}
}
return false;
}
}
My custom Attribute:
<?php
declare(strict_types=1);
namespace App\Attributes;
use Attribute;
/**
* Attribute to mark properties that should be hidden when serialized for Livewire
* but remain available for internal PHP usage.
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
class HiddenFromLivewire {}
And then finally use the attribute in my Data object:
#[HiddenFromLivewire]
public ?string $paymentProviderPrivateReference = null,
So this works - but I am afraid that this code might break as synths is still considered experimental and the interface and implementation might change?
What do you guys say - is this overkill? Is there some simpler way to achieve the same thing?
Or does anyone have some idea that is nice enough to contribute to the project?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello!
I tried to use #[Hidden] on properties that contains sensitive data from being sent to Livewire when having synths enabled. This does not work, throws exception.
And as I understand Lazy on plain properties is not really possible or recommended for Livewire?
So I tried to create a custom Attribute and it caused similar issues, and the method that finally worked was to implement a custom Synth that basically duplicates all code in LivewireDataSynth but just adds some additional features.
It works - but I wonder if there is some simpler approach that would do the same thing. Basically just add a attribute that will prevent data from being leaked over the wire to public.
My current implementation:
Add to AppServiceProvider to use my custom Synth
My custom Synth:
My custom Attribute:
And then finally use the attribute in my Data object:
So this works - but I am afraid that this code might break as synths is still considered experimental and the interface and implementation might change?
What do you guys say - is this overkill? Is there some simpler way to achieve the same thing?
Or does anyone have some idea that is nice enough to contribute to the project?
Beta Was this translation helpful? Give feedback.
All reactions