Initial Version

This commit is contained in:
root
2025-12-21 10:09:54 -05:00
commit 4ae6befc7b
422 changed files with 47225 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace KTXF\Json;
interface JsonDeserializable {
public function jsonDeserialize(array|string $data): static;
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace KTXF\Json;
interface JsonSerializable extends \JsonSerializable {
public function jsonSerialize(): mixed;
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace KTXF\Json;
use KTXF\Json\JsonSerializable;
use KTXF\Json\JsonDeserializable;
use KTXF\Utile\Collection\CollectionAbstract;
abstract class JsonSerializableCollection extends CollectionAbstract implements JsonSerializable, JsonDeserializable {
protected array $primitiveTypes = [
self::TYPE_STRING,
self::TYPE_INT,
self::TYPE_FLOAT,
self::TYPE_BOOL,
self::TYPE_ARRAY,
self::TYPE_DATE,
];
public function jsonSerialize(): mixed {
return $this->getArrayCopy();
}
public function jsonDeserialize(array|string $data): static {
if (is_string($data)) {
$data = json_decode($data, true);
}
$this->exchangeArray([]);
if (in_array($this->typeValue, $this->primitiveTypes)) {
if ($this->associative) {
foreach ($data as $key => $value) {
$this[$key] = $value;
}
} else {
foreach ($data as $value) {
$this[] = $value;
}
}
}
if (!in_array($this->typeValue, $this->primitiveTypes) && class_exists($this->typeValue)) {
$reflection = new \ReflectionClass($this->typeValue);
if ($reflection->implementsInterface(JsonDeserializable::class)) {
if ($this->associative) {
foreach ($data as $key => $value) {
$instance = $reflection->newInstance();
/** @var JsonDeserializable $instance */
$this[$key] = $instance->jsonDeserialize($value);
}
} else {
foreach ($data as $value) {
$instance = $reflection->newInstance();
/** @var JsonDeserializable $instance */
$this[] = $instance->jsonDeserialize($value);
}
}
}
}
return $this;
}
}

View File

@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
namespace KTXF\Json;
use DateTimeInterface;
use DateTimeZone;
use DateInterval;
use KTXF\Json\JsonSerializable;
use KTXF\Json\JsonDeserializable;
abstract class JsonSerializableObject implements JsonSerializable, JsonDeserializable {
protected string $dateTimeFormat = 'c'; // ISO 8601 format by default
protected array $serializableProperties = []; // Empty array means serialize all properties
protected array $nonSerializableProperties = ['dateTimeFormat', 'serializableProperties', 'nonSerializableProperties']; // Properties to exclude from serialization
public function jsonSerialize(): mixed {
$vars = get_object_vars($this);
// Filter properties based on serializableProperties if specified
if (!empty($this->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;
}
}