Last active
March 4, 2025 13:46
-
-
Save ManukMinasyan/d2b367028baab8f5d2c5c6ea80f4c8e8 to your computer and use it in GitHub Desktop.
Spatie Laravel Data - AbstractDataCollectionCast
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace DataLens\Casts; | |
| use Illuminate\Contracts\Support\Arrayable; | |
| use Illuminate\Support\Facades\Crypt; | |
| use Spatie\LaravelData\Casts\Cast; | |
| use Spatie\LaravelData\Casts\Uncastable; | |
| use Spatie\LaravelData\Contracts\BaseData; | |
| use Spatie\LaravelData\Contracts\BaseDataCollectable; | |
| use Spatie\LaravelData\Contracts\TransformableData; | |
| use Spatie\LaravelData\DataCollection; | |
| use Spatie\LaravelData\Support\Creation\CreationContext; | |
| use Spatie\LaravelData\Support\DataProperty; | |
| use Spatie\LaravelData\Support\DataConfig; | |
| use Spatie\LaravelData\Transformers\Transformer; | |
| use Spatie\LaravelData\Support\Transformation\TransformationContext; | |
| class AbstractDataCollectionCast implements Cast, Transformer | |
| { | |
| protected DataConfig $dataConfig; | |
| public function __construct( | |
| protected string $dataClass, | |
| protected string $dataCollectionClass = DataCollection::class, | |
| protected array $arguments = [] | |
| ) { | |
| $this->dataConfig = app(DataConfig::class); | |
| } | |
| public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): mixed | |
| { | |
| if ($value === null) { | |
| return null; | |
| } | |
| // Handle encrypted values | |
| if (is_string($value) && in_array('encrypted', $this->arguments)) { | |
| try { | |
| $value = Crypt::decryptString($value); | |
| } catch (\Exception $e) { | |
| return Uncastable::create(); | |
| } | |
| } | |
| // Default empty collection for null values | |
| if ($value === null && in_array('default', $this->arguments)) { | |
| $value = '[]'; | |
| } | |
| // Already a properly typed DataCollection | |
| if ($value instanceof $this->dataCollectionClass && | |
| $value->getDataClass() === $this->dataClass) { | |
| return $value; | |
| } | |
| try { | |
| // Parse string value | |
| if (is_string($value)) { | |
| $data = json_decode($value, true, flags: JSON_THROW_ON_ERROR); | |
| if (!is_array($data)) { | |
| return Uncastable::create(); | |
| } | |
| } elseif (is_array($value)) { | |
| $data = $value; | |
| } elseif ($value instanceof Arrayable) { | |
| $data = $value->toArray(); | |
| } else { | |
| return Uncastable::create(); | |
| } | |
| $dataClass = $this->dataConfig->getDataClass($this->dataClass); | |
| $items = []; | |
| // Process each item in the collection | |
| foreach ($data as $item) { | |
| $processed = $this->castCollectionItem($dataClass, $item, $context); | |
| if ($processed instanceof Uncastable) { | |
| return $processed; | |
| } | |
| $items[] = $processed; | |
| } | |
| return new ($this->dataCollectionClass)($this->dataClass, $items); | |
| } catch (\Exception $e) { | |
| return Uncastable::create(); | |
| } | |
| } | |
| /** | |
| * Cast an individual item within the collection | |
| * @throws \Exception | |
| */ | |
| protected function castCollectionItem($dataClass, $item, CreationContext $context): mixed | |
| { | |
| $actualDataClass = $this->dataClass; // Default class | |
| // Handle polymorphic abstract data classes | |
| if ($dataClass->isAbstract) { | |
| if (is_array($item) && isset($item['type'])) { | |
| // Polymorphic serialized format with type/data | |
| $morphedClass = $this->dataConfig->morphMap->getMorphedDataClass($item['type']) ?? $item['type']; | |
| if (!class_exists($morphedClass)) { | |
| return Uncastable::create(); | |
| } | |
| $actualDataClass = $morphedClass; | |
| } elseif (is_array($item) && isset($item['class'])) { | |
| // Alternative format with class/attributes | |
| $class = $item['class']; | |
| if (!class_exists($class)) { | |
| return Uncastable::create(); | |
| } | |
| $actualDataClass = $class; | |
| $item = $item['attributes'] ?? []; | |
| } elseif ($item instanceof BaseData) { | |
| // Already a data object, just return it | |
| return $item; | |
| } | |
| } | |
| // Use the from method on the data class with the current context | |
| if (is_array($item)) { | |
| return $actualDataClass::from($item); | |
| } | |
| return Uncastable::create(); | |
| } | |
| /** | |
| * Transform method for serializing the DataCollection back | |
| */ | |
| public function transform(DataProperty $property, mixed $value, TransformationContext $context): mixed | |
| { | |
| if ($value === null) { | |
| return null; | |
| } | |
| // Handle different types of collections | |
| if ($value instanceof BaseDataCollectable && $value instanceof TransformableData) { | |
| $items = $value->all(); | |
| } elseif ($value instanceof Arrayable) { | |
| $items = $value->toArray(); | |
| } elseif (is_array($value)) { | |
| $items = $value; | |
| } else { | |
| return $value; // Can't transform | |
| } | |
| $dataClass = $this->dataConfig->getDataClass($this->dataClass); | |
| $result = []; | |
| // Transform each item in the collection | |
| foreach ($items as $item) { | |
| if ($dataClass->isAbstract && $item instanceof BaseData) { | |
| $class = get_class($item); | |
| $alias = $this->dataConfig->morphMap->getDataClassAlias($class) ?? $class; | |
| // Store with type information for abstract classes | |
| $result[] = [ | |
| 'type' => $alias, | |
| 'data' => $item->toArray(), | |
| ]; | |
| } elseif ($item instanceof BaseData) { | |
| $result[] = $item->toArray(); | |
| } elseif (is_array($item)) { | |
| $result[] = $item; | |
| } else { | |
| $result[] = (string) $item; | |
| } | |
| } | |
| // Handle encryption if needed | |
| if (in_array('encrypted', $this->arguments)) { | |
| return Crypt::encryptString(json_encode($result)); | |
| } | |
| return $result; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment