Skip to content

Commit b0c0b60

Browse files
committed
Add UnserializeCast
Data objects can now restore values that were stored with serialize(), such as serialized database columns or cache entries, through #[WithCast(UnserializeCast::class)]. The cast can fail silently, leaving the value to the built-in conversion, and allowedClasses limits which classes unserialize() may create. Port of spatie/laravel-data#1220 at main a161f325d5. Hypervel adaptations: - allowedClasses defaults to false, so no objects are restored unless their classes are listed. Spatie allows every class by default, which runs __wakeup() and __destruct() for any class named in the value. Pass true to allow every class. - failSilently no longer turns coroutine cancellation into a failed cast. The restore hooks of allowed classes can suspend, such as SerializesModels querying the database. - The docs cover the allowed classes, including nested ones, and warn against unserializing untrusted data. Their failSilently example lists the class it restores. All upstream UnserializeCast tests are ported, with coverage for the default, a data object restoring only allowed classes, and cancellation during a restore hook. The cast tests, analysis and formatting pass.
1 parent d99d3fe commit b0c0b60

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

‎data-objects.md‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- [Abstract Data Objects](#abstract-data-objects)
2323
- [Backed Enums](#backed-enums)
2424
- [Casts and Transformers](#casts-and-transformers)
25+
- [Unserializing Values](#unserializing-values)
2526
- [Custom Casts](#custom-casts)
2627
- [Custom Transformers](#custom-transformers)
2728
- [Global Casts and Transformers](#global-casts-and-transformers)
@@ -898,6 +899,38 @@ class InvoiceData extends Data
898899
}
899900
```
900901

902+
<a name="unserializing-values"></a>
903+
### Unserializing Values
904+
905+
The `UnserializeCast` runs PHP's `unserialize` function on a value. This is useful when you store serialized values yourself, for example in a database column or a cache:
906+
907+
```php
908+
use Hypervel\Data\Attributes\WithCast;
909+
use Hypervel\Data\Casts\UnserializeCast;
910+
911+
class SongData extends Data
912+
{
913+
public function __construct(
914+
public string $title,
915+
#[WithCast(UnserializeCast::class, allowedClasses: [SongMetadata::class])]
916+
public SongMetadata $metadata,
917+
) {
918+
}
919+
}
920+
```
921+
922+
The `allowedClasses` argument lists the classes that may be created while unserializing. Any other class becomes a `__PHP_Incomplete_Class` object, so a property typed as that class fails with a `TypeError`. This also applies to nested objects: when `SongMetadata` holds a `CarbonImmutable` date, `CarbonImmutable` must be listed as well. By default, no classes are allowed, so only scalar values, arrays, and enum cases are restored. You may pass `true` to allow every class.
923+
924+
> [!WARNING]
925+
> Only unserialize trusted data. Class restrictions do not make user-controlled serialized data safe. For user input, use JSON and validate the decoded values.
926+
927+
When the value cannot be unserialized, an exception is thrown. You may make the cast fail silently instead, which leaves the value to the built-in conversion:
928+
929+
```php
930+
#[WithCast(UnserializeCast::class, failSilently: true, allowedClasses: [SongMetadata::class])]
931+
public SongMetadata $metadata;
932+
```
933+
901934
<a name="custom-casts"></a>
902935
### Custom Casts
903936

‎porting-from-laravel.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,7 @@ Laravel's deprecated `InvokableRule` contract is not available. Change rules tha
720720

721721
When porting `spatie/laravel-data`, replace its namespace with `Hypervel\Data` and review the [Data Objects documentation](/docs/{{version}}/data-objects). The familiar `Data`, `Dto`, `Resource`, `Optional`, mapping, casting, validation, lazy-value, collection, resource, and Eloquent APIs are all available.
722722

723-
Replace Spatie's `From*` attributes with Hypervel contextual constructor attributes. `UnserializeCast` is not included; write a custom cast that passes `allowed_classes` to `unserialize()` for trusted values. Livewire and TypeScript integrations are also not included.
723+
Replace Spatie's `From*` attributes with Hypervel contextual constructor attributes. Livewire and TypeScript integrations are not included.
724724

725725
In `config/data.php`, the `casts`, `transformers`, `normalizers`, and `rule_inferrers` options only hold your own extensions; remove Spatie's built-in entries, since Hypervel's built-in handling is fixed. If you enabled Spatie's optional `FormRequestNormalizer`, keep it as `Hypervel\Data\Normalizers\FormRequestNormalizer`. Typed iterable items are always cast and transformed, and an array given to a collection property becomes that collection.
726726

@@ -734,6 +734,7 @@ Review these behavior differences in ported code:
734734
- Custom `pipeline()` overrides and `DataPipe` classes are not supported. Rebuild them with named factories, `prepareForPipeline()`, or factory hooks.
735735
- A value that a union property already accepts is kept, such as a string for `string|SongData` or an array for `array|Collection`, and validation applies the rules of the declared type that holds it. Declare `Collection` alone when the property should always hold a collection. See [type conversion](/docs/{{version}}/data-objects#type-conversion).
736736
- A model attribute holding `null` is passed as `null` instead of falling back to the property's default or `Optional`. Columns that were not selected still count as missing.
737+
- `UnserializeCast` restores no objects unless their classes are listed in `allowedClasses`. List the classes your stored values contain, including nested ones. See [unserializing values](/docs/{{version}}/data-objects#unserializing-values).
737738
- A custom cast's `$properties` contains only declared property values keyed by PHP property name, without undeclared input or raw input names.
738739
- `CreationContext::$dataClass` is the class the creation started with, even while nested objects are created, and the context has no `from()`, `collect()`, or `currentPath`. Replace `$context->from()` with `TargetData::factory($context)->from()`, which copies the context's options but not its hooks. A cast can read `$property->className` for the class that declares the property.
739740
- An array or collection cannot be collected into a paginator target or given to a paginator property. Pass a Hypervel paginator so its pagination details are kept.

0 commit comments

Comments
 (0)