serializableProperties)) { $vars = array_filter($vars, function($key) { return in_array($key, $this->serializableProperties); }, ARRAY_FILTER_USE_KEY); } // Process each property for special types foreach ($vars as $key => $value) { // Skip internal control properties if (in_array($key, $this->nonSerializableProperties)) { unset($vars[$key]); continue; } // Handle DateTimeInterface (DateTime/DateTimeImmutable) if ($value instanceof DateTimeInterface) { $vars[$key] = $value->format($this->dateTimeFormat); } // Handle DateTimeZone elseif ($value instanceof DateTimeZone) { $vars[$key] = $value->getName(); } // Handle DateInterval elseif ($value instanceof DateInterval) { $vars[$key] = $this->fromDateInterval($value); } // Handle backed enums elseif ($value instanceof \BackedEnum) { $vars[$key] = $value->value; } // Handle JsonSerializable objects elseif ($value instanceof JsonSerializable) { $vars[$key] = $value->jsonSerialize(); } } return $vars; } public function jsonDeserialize(array|string $data): static { if (is_string($data)) { $data = json_decode($data, true); } foreach ($data as $key => $value) { if (property_exists($this, $key)) { // Skip internal control properties if (in_array($key, $this->nonSerializableProperties)) { continue; } // Check if property should be deserialized (if serializableProperties is set) if (!empty($this->serializableProperties) && !in_array($key, $this->serializableProperties)) { continue; } $type = gettype($this->$key); // Handle JsonDeserializable objects if ($type === 'object' && $this->$key instanceof JsonDeserializable) { $this->$key = $this->$key->jsonDeserialize($value); } // Handle DateTimeInterface (DateTime/DateTimeImmutable) elseif ($type === 'object' && $this->$key instanceof DateTimeInterface) { $this->$key = new \DateTimeImmutable($value); } // Handle DateTimeZone elseif ($type === 'object' && $this->$key instanceof DateTimeZone) { $this->$key = new DateTimeZone($value); } // Handle DateInterval elseif ($type === 'object' && $this->$key instanceof DateInterval) { $this->$key = $this->toDateInterval($value); } // Handle backed enums elseif ($type === 'object' && $this->$key instanceof \BackedEnum) { $enumClass = get_class($this->$key); $this->$key = $enumClass::from($value); } // Handle regular values else { $this->$key = $value; } } } return $this; } protected function fromDateInterval(DateInterval $interval): string { $spec = ''; // Handle negative intervals if ($interval->invert === 1) { $spec = '-'; } $spec .= 'P'; if ($interval->y > 0) $spec .= $interval->y . 'Y'; if ($interval->m > 0) $spec .= $interval->m . 'M'; if ($interval->d > 0) $spec .= $interval->d . 'D'; $timePart = ''; if ($interval->h > 0) $timePart .= $interval->h . 'H'; if ($interval->i > 0) $timePart .= $interval->i . 'M'; if ($interval->s > 0) $timePart .= $interval->s . 'S'; if (!empty($timePart)) { $spec .= 'T' . $timePart; } // Handle edge case of zero duration if ($spec === 'P' || $spec === '-P') { $spec = 'PT0S'; } return $spec; } protected function toDateInterval(string $value): DateInterval { $isNegative = false; // Check for negative interval if (str_starts_with($value, '-')) { $isNegative = true; $value = substr($value, 1); } // Create the interval $interval = new DateInterval($value); // Set invert property for negative intervals if ($isNegative) { $interval->invert = 1; } return $interval; } }