diff --git a/manual/includes/_magic-dto.md b/manual/includes/_magic-dto.md
index e48d276d..7c2574ae 100644
--- a/manual/includes/_magic-dto.md
+++ b/manual/includes/_magic-dto.md
@@ -27,12 +27,30 @@ In modern applications, especially those that interact with third-party services
- The MagicDto class utilizes PHP annotations to clarify the purpose of each property. These annotations enhance code readability and provide useful metadata for serialization.
+4. **XML Support**
+ - MagicDto provides support for both XML input and output. This feature allows seamless integration with systems that utilize XML as their primary data format, making it easier to work with various data sources and services.
+
+ To parse XML string, use method `MagicTdo::xmlToObject(string $xmlString)`. This method takes an XML string as input and returning it as a stdClass object.
+
### Class Structure
The `MagicDto` class is designed with properties that have protected access levels, ensuring encapsulation while still allowing derived classes to access these properties. Each property is annotated with `@var`, which specifies its data type. This structured approach enhances type safety and improves code quality.
#### Key Annotations
+**Class Annotations**
+
+1. **@JSON**
+
+ The `@JSON` annotation controls whether the JSON format should be prettified. Using `@JSON(prettify=true)` will format the output in a more readable way, while `@JSON(prettify=false)` will minimize the format
+
+2. **@XML**
+
+ The `@XML` annotation controls whether the XML format should be prettified. Using `@XML(prettify=true)` will format the output in a more readable way, while `@XML(prettify=false)` will minimize the format
+
+
+**Property Annotations**
+
1. **@Source**
The `@Source` annotation indicates the source property that maps to a specific field in the incoming data. If this annotation is omitted, MagicDto will default to using the property name that matches the class property name. This allows for flexibility in cases where the external API may use different naming conventions.
@@ -48,7 +66,7 @@ protected $title;
2. **@JsonProperty**
The `@JsonProperty` annotation specifies the output property name when data is serialized to JSON. If this annotation is not provided, MagicDto will serialize the property using its class property name. This ensures that data sent to third-party applications adheres to their expected format.
-
+
```php
/**
* @JsonProperty("album_title")
@@ -72,6 +90,26 @@ In this example, `@Source("album_name")` indicates that the incoming data will u
To facilitate bidirectional communication, we need two different DTOs. The `@Source` annotation in the first DTO corresponds to the `@JsonProperty` annotation in the second DTO, while the `@JsonProperty` in the first DTO maps to the `@Source` in the second DTO.
+3. **@JsonFormat**
+
+ The @JsonFormat annotation specifies the output date-time format when data is serialized to JSON. The property type must be `DateTime`. It is written as `@JsonFormat(pattern="Y-m-d H:i:s")`. If this annotation is not provided, MagicDto will serialize the property using the default format Y-m-d H:i:s. This ensures that data sent to third-party applications adheres to their expected format.
+
+ Format the date and time according to the conventions used in the PHP programming language. This includes utilizing the built-in date and time functions, which allow for various formatting options to display dates and times in a way that is both readable and compatible with PHP's standards. Ensure that you adhere to formats such as 'Y-m-d H:i:s' for complete timestamps or 'd/m/Y' for more localized representations, depending on the specific requirements of your application.
+
+ MagicDto automatically parses input as both strings and integers. The integer is a unique timestamp, while the string date-time format must be one of the following:
+
+ - **'Y-m-d'**, // ISO 8601: 2024-10-24
+ - **'Y-m-d H:i:s'**, // ISO 8601: 2024-10-24 15:30:00
+ - **'Y-m-d\TH:i:s'**, // ISO 8601: 2024-10-24T15:30:00
+ - **'Y-m-d\TH:i:s\Z'**, // ISO 8601: 2024-10-24T15:30:00Z
+ - **'D, d M Y H:i:s O'**, // RFC 2822: Thu, 24 Oct 2024 15:30:00 +0000
+ - **'d/m/Y'**, // Local format: 24/10/2024
+ - **'d F Y'**, // Format with month name: 24 October 2024
+ - **'l, d F Y'** // Format with day of the week: Thursday, 24 October 2024
+
+
+
+
**Example:**
DTO on the Input Side
@@ -96,7 +134,8 @@ class AlbumDtoInput extends MagicDto
/**
* @Source("date_release")
* @JsonProperty("releaseDate")
- * @var string
+ * @JsonFormat(pattern="Y-m-d H:i:s")
+ * @var DateTime
*/
protected $release;
@@ -384,6 +423,8 @@ class AgencyDto extends MagicDto
**Usage**
+1. JSON Format
+
```php
$song = new Song(null, $database);
$song->find("1234");
@@ -393,6 +434,39 @@ header("Content-type: application/json");
echo $songDto;
```
+2. XML Format
+
+```php
+$song = new Song(null, $database);
+$song->find("1234");
+$songDto = new SongDto($song);
+
+header("Content-type: application/xml");
+echo $songDto->toXml("root");
+```
+
+3. Parse XML
+
+```php
+$albumDto = new AlbumDto();
+$obj = $albumDto->xmlToObject($xmlString);
+// $obj is stdClass
+header("Content-type: application/json");
+echo json_encode($obj);
+```
+
+3. Load from XML
+
+```php
+$albumDto = new AlbumDtoInput();
+$albumDto->loadXml($xmlString);
+header("Content-type: application/json");
+echo $albumDto;
+```
+
+`loadXml` method will load data from XML to `AlbumDtoInput`. `AlbumDtoInput` is the inverse of `AlbumDto`, where values of the `@JsonProperty` and `@Source` annotations are swapped. This inversion also applies to the objects contained within it.
+
+
#### Explanation
- **@Source**: This annotation specifies the path to the property within the nested object structure. In this case, `artist->agency->name` indicates that the `agencyName` will pull data from the `name` property of the `Agency` object linked to the `Artist`.
diff --git a/manual/index.html b/manual/index.html
index a49d713c..742f13a8 100644
--- a/manual/index.html
+++ b/manual/index.html
@@ -1353,10 +1353,29 @@
Features of MagicDto
The MagicDto class utilizes PHP annotations to clarify the purpose of each property. These annotations enhance code readability and provide useful metadata for serialization.
+
+XML Support
+
+- MagicDto provides support for both XML input and output. This feature allows seamless integration with systems that utilize XML as their primary data format, making it easier to work with various data sources and services.
+
+To parse XML string, use method MagicTdo::xmlToObject(string $xmlString)
. This method takes an XML string as input and returning it as a stdClass object.
+
Class Structure
The MagicDto
class is designed with properties that have protected access levels, ensuring encapsulation while still allowing derived classes to access these properties. Each property is annotated with @var
, which specifies its data type. This structured approach enhances type safety and improves code quality.
Key Annotations
+Class Annotations
+
+-
+
@JSON
+The @JSON
annotation controls whether the JSON format should be prettified. Using @JSON(prettify=true)
will format the output in a more readable way, while @JSON(prettify=false)
will minimize the format
+
+-
+
@XML
+The @XML
annotation controls whether the XML format should be prettified. Using @XML(prettify=true)
will format the output in a more readable way, while @XML(prettify=false)
will minimize the format
+
+
+Property Annotations
-
@Source
@@ -1388,6 +1407,24 @@ Key Annotations
protected $title;
In this example, @Source("album_name")
indicates that the incoming data will use album_name
, while @JsonProperty("album_title")
specifies that when the data is serialized, it will be output as album_title
.
To facilitate bidirectional communication, we need two different DTOs. The @Source
annotation in the first DTO corresponds to the @JsonProperty
annotation in the second DTO, while the @JsonProperty
in the first DTO maps to the @Source
in the second DTO.
+
+-
+
@JsonFormat
+The @JsonFormat annotation specifies the output date-time format when data is serialized to JSON. The property type must be DateTime
. It is written as @JsonFormat(pattern="Y-m-d H:i:s")
. If this annotation is not provided, MagicDto will serialize the property using the default format Y-m-d H:i:s. This ensures that data sent to third-party applications adheres to their expected format.
+Format the date and time according to the conventions used in the PHP programming language. This includes utilizing the built-in date and time functions, which allow for various formatting options to display dates and times in a way that is both readable and compatible with PHP's standards. Ensure that you adhere to formats such as 'Y-m-d H:i:s' for complete timestamps or 'd/m/Y' for more localized representations, depending on the specific requirements of your application.
+MagicDto automatically parses input as both strings and integers. The integer is a unique timestamp, while the string date-time format must be one of the following:
+
+- 'Y-m-d', // ISO 8601: 2024-10-24
+- 'Y-m-d H:i:s', // ISO 8601: 2024-10-24 15:30:00
+- 'Y-m-d\TH:i:s', // ISO 8601: 2024-10-24T15:30:00
+- 'Y-m-d\TH:i:s\Z', // ISO 8601: 2024-10-24T15:30:00Z
+- 'D, d M Y H:i:s O', // RFC 2822: Thu, 24 Oct 2024 15:30:00 +0000
+- 'd/m/Y', // Local format: 24/10/2024
+- 'd F Y', // Format with month name: 24 October 2024
+- 'l, d F Y' // Format with day of the week: Thursday, 24 October 2024
+
+
+
Example:
DTO on the Input Side
class AlbumDtoInput extends MagicDto
@@ -1409,7 +1446,8 @@ Key Annotations
/**
* @Source("date_release")
* @JsonProperty("releaseDate")
- * @var string
+ * @JsonFormat(pattern="Y-m-d H:i:s")
+ * @var DateTime
*/
protected $release;
@@ -1668,12 +1706,40 @@ Code Implementation
}
Usage
+
+- JSON Format
+
$song = new Song(null, $database);
$song->find("1234");
$songDto = new SongDto($song);
header("Content-type: application/json");
echo $songDto;
+
+- XML Format
+
+$song = new Song(null, $database);
+$song->find("1234");
+$songDto = new SongDto($song);
+
+header("Content-type: application/xml");
+echo $songDto->toXml("root");
+
+- Parse XML
+
+$albumDto = new AlbumDto();
+$obj = $albumDto->xmlToObject($xmlString);
+// $obj is stdClass
+header("Content-type: application/json");
+echo json_encode($obj);
+
+- Load from XML
+
+$albumDto = new AlbumDtoInput();
+$albumDto->loadXml($xmlString);
+header("Content-type: application/json");
+echo $albumDto;
+loadXml
method will load data from XML to AlbumDtoInput
. AlbumDtoInput
is the inverse of AlbumDto
, where values of the @JsonProperty
and @Source
annotations are swapped. This inversion also applies to the objects contained within it.
Explanation
-
diff --git a/src/MagicDto.php b/src/MagicDto.php
index dab24cd5..f69e8d47 100644
--- a/src/MagicDto.php
+++ b/src/MagicDto.php
@@ -2,13 +2,18 @@
namespace MagicObject;
+use DateTime;
+use DOMDocument;
+use InvalidArgumentException;
use MagicObject\Exceptions\InvalidAnnotationException;
use MagicObject\Exceptions\InvalidQueryInputException;
use MagicObject\Util\ClassUtil\PicoAnnotationParser;
use MagicObject\Util\ClassUtil\PicoObjectParser;
+use MagicObject\Util\PicoDateTimeUtil;
use MagicObject\Util\PicoGenericObject;
use ReflectionClass;
use ReflectionProperty;
+use SimpleXMLElement;
use stdClass;
/**
@@ -27,6 +32,7 @@ class MagicDto extends stdClass // NOSONAR
{
// Format constants
const JSON = 'JSON';
+ const XML = 'XML';
const PRETTIFY = 'prettify';
/**
@@ -107,6 +113,25 @@ public function loadData($data)
}
return $this;
}
+
+ /**
+ * Loads XML data into the object.
+ *
+ * This method accepts an XML string, converts it to an object representation,
+ * and then loads the resulting data into the internal data source of the object.
+ * It processes the XML input, ensuring that only non-scalar values are handled
+ * appropriately. This method is useful for integrating with external XML data sources.
+ *
+ * @param string $xmlString The XML string to load into the object.
+ * @return self Returns the current instance for method chaining.
+ * @throws InvalidArgumentException If the XML string is invalid or cannot be parsed.
+ */
+ public function loadXml($xmlString)
+ {
+ $data = $this->xmlToObject($xmlString);
+ $this->loadData($data);
+ return $this;
+ }
/**
* Get the object values
@@ -128,11 +153,15 @@ public function value()
$objectTest = class_exists($var) ? new $var() : null;
- if ($this->isSelfInstance($var, $objectTest)) {
+ if ($this->isSelfInstance($objectTest)) {
$returnValue->$propertyName = $this->handleSelfInstance($source, $var, $propertyName);
} elseif ($this->isMagicObjectInstance($objectTest)) {
$returnValue->$propertyName = $this->handleMagicObject($source, $propertyName);
- } else {
+ } elseif ($this->isDateTimeInstance($objectTest)) {
+ $returnValue->$propertyName = $this->formatDateTime($this->handleDateTimeObject($source, $propertyName), $this, $key);
+ } else if($this->_dataSource instanceof stdClass || is_object($this->_dataSource)) {
+ $returnValue->$propertyName = $this->handleStdClass($source, $key, $propertyName);
+ } else if(isset($this->_dataSource)) {
$returnValue->$propertyName = $this->handleDefaultCase($source, $key, $propertyName);
}
}
@@ -140,6 +169,26 @@ public function value()
return $returnValue;
}
+ private function formatDateTime($dateTime, $class, $property)
+ {
+ if(!isset($dateTime))
+ {
+ return null;
+ }
+ $reflexProp = new PicoAnnotationParser(get_class($class), $property, PicoAnnotationParser::PROPERTY);
+ $parameters = $reflexProp->getParameters();
+ if(isset($parameters['JsonFormat']))
+ {
+ $parsed = $reflexProp->parseKeyValueAsObject($parameters['JsonFormat']);
+ $format = isset($parsed->pattern) ? $parsed->pattern : 'Y-m-d H:i:s';
+ }
+ else
+ {
+ $format = 'Y-m-d H:i:s';
+ }
+ return $dateTime->format($format);
+ }
+
/**
* Retrieves the documentation comment for a specified property.
*
@@ -203,13 +252,12 @@ private function extractLabel($doc)
/**
* Checks if the given variable is a self-instance.
*
- * @param string $var The variable name.
* @param mixed $objectTest The object to test against.
* @return bool True if it's a self-instance, otherwise false.
*/
- private function isSelfInstance($var, $objectTest)
+ private function isSelfInstance($objectTest)
{
- return strtolower($var) != 'stdclass' && $objectTest instanceof self;
+ return $objectTest instanceof self;
}
/**
@@ -245,6 +293,17 @@ private function isMagicObjectInstance($objectTest)
$objectTest instanceof PicoGenericObject;
}
+ /**
+ * Checks if the given object is an instance of DateTime or its derivatives.
+ *
+ * @param mixed $objectTest The object to test.
+ * @return bool True if it is a MagicObject instance, otherwise false.
+ */
+ private function isDateTimeInstance($objectTest)
+ {
+ return $objectTest instanceof DateTime;
+ }
+
/**
* Handles the case where the property is an instance of MagicObject.
*
@@ -265,6 +324,23 @@ private function handleMagicObject($source, $propertyName)
}
}
+ /**
+ * Handles the case where the property is an instance of DateTime.
+ *
+ * @param string|null $source The source to extract the value from.
+ * @param string $propertyName The name of the property.
+ * @return DateTime|null The handled value for the MagicObject instance.
+ */
+ private function handleDateTimeObject($source, $propertyName)
+ {
+ if (strpos($source, "->") === false) {
+ $value = isset($source) ? $this->_dataSource->get($source) : $this->_dataSource->get($propertyName);
+ return PicoDateTimeUtil::parseDateTime($value);
+ } else {
+ return PicoDateTimeUtil::parseDateTime($this->getNestedValue($source));
+ }
+ }
+
/**
* Handles the default case when retrieving property values.
*
@@ -282,6 +358,23 @@ private function handleDefaultCase($source, $key, $propertyName)
}
}
+ /**
+ * Handles the stdClass when retrieving property values.
+ *
+ * @param string|null $source The source to extract the value from.
+ * @param string $key The key of the property.
+ * @param string $propertyName The name of the property.
+ * @return mixed The handled default value.
+ */
+ private function handleStdClass($source, $key, $propertyName)
+ {
+ if (strpos($source, "->") === false) {
+ return isset($source) && isset($this->_dataSource->{$source}) ? $this->_dataSource->{$source} : $this->_dataSource->{$key};
+ } else {
+ return $this->getNestedValue($source);
+ }
+ }
+
/**
* Retrieves nested values from the data source based on a specified source string.
*
@@ -360,7 +453,7 @@ public function valueArrayUpperCamel()
*
* @return bool True if JSON output is set to be prettified; otherwise, false
*/
- protected function _pretty()
+ protected function _prettyJson()
{
return isset($this->_classParams[self::JSON])
&& isset($this->_classParams[self::JSON][self::PRETTIFY])
@@ -368,6 +461,19 @@ protected function _pretty()
;
}
+ /**
+ * Check if the XML output should be prettified
+ *
+ * @return bool True if XML output is set to be prettified; otherwise, false
+ */
+ protected function _prettyXml()
+ {
+ return isset($this->_classParams[self::XML])
+ && isset($this->_classParams[self::XML][self::PRETTIFY])
+ && strcasecmp($this->_classParams[self::XML][self::PRETTIFY], 'true') == 0
+ ;
+ }
+
/**
* Get a list of properties
*
@@ -438,19 +544,48 @@ private function stringifyObject($value)
return $value->value();
}
+ /**
+ * Convert XML to an object.
+ *
+ * This function takes an XML string as input and returning it as a stdClass object.
+ *
+ * @param string $xmlString The XML string to be converted.
+ * @return stdClass An object representation of the XML data.
+ * @throws InvalidArgumentException If the XML is invalid or cannot be parsed.
+ */
+ public function xmlToObject($xmlString) {
+ // Suppress errors to handle them manually
+ libxml_use_internal_errors(true);
+
+ // Convert the XML string to a SimpleXMLElement
+ $xmlObject = simplexml_load_string($xmlString);
+
+ // Check for errors in XML parsing
+ if ($xmlObject === false) {
+ $errors = libxml_get_errors();
+ libxml_clear_errors();
+ throw new InvalidArgumentException('Invalid XML provided: ' . implode(', ', array_map(function($error) {
+ return $error->message;
+ }, $errors)));
+ }
+
+ // Convert SimpleXMLElement to stdClass
+ return json_decode(json_encode($xmlObject));
+ }
+
/**
* Magic method to convert the object to a JSON string representation.
*
- * This method recursively converts the object's properties into a JSON format.
- * If any property is an instance of the same class, it will be stringified
- * as well. The output can be formatted for readability based on the
- * `_pretty()` method's return value.
+ * This method recursively converts the object's properties into JSON format.
+ * If any property is an instance of the same class, it will also be stringified.
+ * The output can be formatted for readability based on the JSON annotation
+ * of the class.
*
- * @return string A JSON representation of the object, possibly pretty-printed.
+ * @return string A JSON representation of the object, potentially formatted for readability.
*/
public function __toString()
{
- $pretty = $this->_pretty();
+ $pretty = $this->_prettyJson();
$flag = $pretty ? JSON_PRETTY_PRINT : 0;
$obj = clone $this;
foreach($obj as $key=>$value)
@@ -465,13 +600,14 @@ public function __toString()
}
/**
- * Convert the object to a string.
+ * Magic method to convert the object to a JSON string representation.
*
- * This method returns the string representation of the object by calling
- * the magic `__toString()` method. It's useful for obtaining the
- * JSON representation directly as a string.
+ * This method recursively converts the object's properties into JSON format.
+ * If any property is an instance of the same class, it will also be stringified.
+ * The output can be formatted for readability based on the JSON annotation
+ * of the class.
*
- * @return string The string representation of the object.
+ * @return string A JSON representation of the object, potentially formatted for readability.
*/
public function toString()
{
@@ -507,4 +643,71 @@ public function toArray()
{
return json_decode((string) $this, true);
}
+
+
+ /**
+ * Convert the object's properties to XML format.
+ *
+ * This method generates an XML representation of the object based on its properties.
+ * The XML structure is built from the object's properties, and the output can
+ * be formatted for readability based on the XML annotation of the class.
+ *
+ * @param string $root The name of the root element in the XML structure.
+ * @return string XML representation of the object's properties, potentially formatted for readability.
+ * @throws InvalidArgumentException If the JSON representation of the object is invalid.
+ */
+ public function toXml($root = "root") {
+ // Decode the JSON string into an associative array
+ $dataArray = $this->toArray();
+
+ // Check if JSON was valid
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ throw new InvalidArgumentException('Invalid JSON provided.');
+ }
+
+ // Create the XML structure
+ $xml = new SimpleXMLElement("<$root/>");
+
+ // Recursive function to convert array to XML
+ $this->arrayToXml($dataArray, $xml);
+
+ $pretty = $this->_prettyXml();
+
+ if($pretty)
+ {
+ // Convert SimpleXMLElement to DOMDocument for prettifying
+ $dom = new DOMDocument('1.0', 'UTF-8');
+ $dom->preserveWhiteSpace = false;
+ $dom->formatOutput = true;
+ $dom->loadXML($xml->asXML());
+ return $dom->saveXML();
+ }
+ else
+ {
+ return $xml->asXML();
+ }
+ }
+
+ /**
+ * Helper function to convert an array to XML
+ *
+ * @param array $dataArray The array to convert
+ * @param SimpleXMLElement $xml XML element to append to
+ */
+ function arrayToXml($dataArray, $xml) {
+ foreach ($dataArray as $key => $value) {
+ // Replace spaces and special characters in key names
+ $key = preg_replace('/[^a-z0-9_]/i', '_', $key);
+
+ // If the value is an array, call this function recursively
+ if (is_array($value)) {
+ $subNode = $xml->addChild($key);
+ $this->arrayToXml($value, $subNode);
+ } else {
+ // If the value is not an array, just add it as a child
+ $xml->addChild($key, htmlspecialchars($value));
+ }
+ }
+ }
+
}
diff --git a/src/Util/PicoDateTimeUtil.php b/src/Util/PicoDateTimeUtil.php
new file mode 100644
index 00000000..3e3fed8f
--- /dev/null
+++ b/src/Util/PicoDateTimeUtil.php
@@ -0,0 +1,60 @@
+setTimestamp((int)$dateString);
+ }
+
+ // List of formats to parse
+ $formats = [
+ 'Y-m-d', // ISO 8601: 2024-10-24
+ 'Y-m-d H:i:s', // ISO 8601: 2024-10-24 15:30:00
+ 'Y-m-d\TH:i:s', // ISO 8601: 2024-10-24T15:30:00
+ 'Y-m-d\TH:i:s\Z', // ISO 8601: 2024-10-24T15:30:00Z
+ 'D, d M Y H:i:s O', // RFC 2822: Thu, 24 Oct 2024 15:30:00 +0000
+ 'd/m/Y', // Local format: 24/10/2024
+ 'd F Y', // Format with month name: 24 October 2024
+ 'l, d F Y' // Format with day of the week: Thursday, 24 October 2024
+ ];
+
+ // Iterate over each format and attempt to create a DateTime object
+ foreach ($formats as $format) {
+ $dateTime = DateTime::createFromFormat($format, $dateString);
+ if ($dateTime !== false) {
+ return $dateTime; // Return the DateTime object if successful
+ }
+ }
+
+ return null; // Return null if no formats matched
+ }
+}
diff --git a/tests/date.php b/tests/date.php
new file mode 100644
index 00000000..1f50ef9b
--- /dev/null
+++ b/tests/date.php
@@ -0,0 +1,26 @@
+format('Y-m-d H:i:s') . " => from $dateString\n";
+ } else {
+ echo "Could not parse: $dateString\n";
+ }
+}
\ No newline at end of file
diff --git a/tests/dto.php b/tests/dto.php
index 44c24b34..0f679fb9 100644
--- a/tests/dto.php
+++ b/tests/dto.php
@@ -191,6 +191,8 @@ class EntityAlbum extends MagicObject
*/
protected $asDraft;
+
+
}
/**
@@ -625,6 +627,7 @@ class Artist extends MagicObject
/**
* @JSON(prettify=true)
+ * @XML(prettify=true)
*/
class AlbumDto extends MagicDto
{
@@ -671,6 +674,17 @@ class AlbumDto extends MagicDto
* @var string
*/
protected $kotaDomisili;
+
+ /**
+ * Release Date
+ *
+ * @JsonProperty("releaseDate")
+ * @JsonFormat(pattern="Y-m-d H:i:s")
+ * @Source("releaseDate")
+ * @var DateTime
+ */
+ protected $releaseDate;
+
}
class ProducerDto extends MagicDto
@@ -678,7 +692,7 @@ class ProducerDto extends MagicDto
/**
* Producer ID
*
- * @Source("producerId")
+ * @Source("id_producer")
* @JsonProperty("id_producer")
* @var string
*/
@@ -702,8 +716,36 @@ class ProducerDto extends MagicDto
$album->getProducer()->setName("Kamshory");
$album->getProducer()->setCity($city);
$album->getProducer()->getCity()->setNamaKota("Jakarta");
+$album->setReleaseDate("2024-10-29 01:06:12");
$albumDto = new AlbumDto($album);
-echo $albumDto;
\ No newline at end of file
+echo "JSON:\r\n";
+echo $albumDto."\r\n\r\n";
+echo "XML:\r\n";
+echo $albumDto->toXml();
+
+$obj2 = $albumDto->xmlToObject($albumDto->toXml());
+
+echo "JSON 2:\r\n";
+echo json_encode($obj2, JSON_PRETTY_PRINT)."\r\n\r\n";
+
+
+$xml = '
+
+ 1234
+ Album Pertama
+
+ 5678
+ Kamshory
+
+ Kamshory
+ Jakarta
+ 2024-10-29 01:06:12
+';
+
+$album2 = new AlbumDto();
+$album2->loadXml($xml);
+
+echo $album2;
\ No newline at end of file
diff --git a/tutorial.md b/tutorial.md
index d7c25c0a..8ff4bd48 100644
--- a/tutorial.md
+++ b/tutorial.md
@@ -1553,12 +1553,30 @@ In modern applications, especially those that interact with third-party services
- The MagicDto class utilizes PHP annotations to clarify the purpose of each property. These annotations enhance code readability and provide useful metadata for serialization.
+4. **XML Support**
+ - MagicDto provides support for both XML input and output. This feature allows seamless integration with systems that utilize XML as their primary data format, making it easier to work with various data sources and services.
+
+ To parse XML string, use method `MagicTdo::xmlToObject(string $xmlString)`. This method takes an XML string as input and returning it as a stdClass object.
+
### Class Structure
The `MagicDto` class is designed with properties that have protected access levels, ensuring encapsulation while still allowing derived classes to access these properties. Each property is annotated with `@var`, which specifies its data type. This structured approach enhances type safety and improves code quality.
#### Key Annotations
+**Class Annotations**
+
+1. **@JSON**
+
+ The `@JSON` annotation controls whether the JSON format should be prettified. Using `@JSON(prettify=true)` will format the output in a more readable way, while `@JSON(prettify=false)` will minimize the format
+
+2. **@XML**
+
+ The `@XML` annotation controls whether the XML format should be prettified. Using `@XML(prettify=true)` will format the output in a more readable way, while `@XML(prettify=false)` will minimize the format
+
+
+**Property Annotations**
+
1. **@Source**
The `@Source` annotation indicates the source property that maps to a specific field in the incoming data. If this annotation is omitted, MagicDto will default to using the property name that matches the class property name. This allows for flexibility in cases where the external API may use different naming conventions.
@@ -1574,7 +1592,7 @@ protected $title;
2. **@JsonProperty**
The `@JsonProperty` annotation specifies the output property name when data is serialized to JSON. If this annotation is not provided, MagicDto will serialize the property using its class property name. This ensures that data sent to third-party applications adheres to their expected format.
-
+
```php
/**
* @JsonProperty("album_title")
@@ -1598,6 +1616,26 @@ In this example, `@Source("album_name")` indicates that the incoming data will u
To facilitate bidirectional communication, we need two different DTOs. The `@Source` annotation in the first DTO corresponds to the `@JsonProperty` annotation in the second DTO, while the `@JsonProperty` in the first DTO maps to the `@Source` in the second DTO.
+3. **@JsonFormat**
+
+ The @JsonFormat annotation specifies the output date-time format when data is serialized to JSON. The property type must be `DateTime`. It is written as `@JsonFormat(pattern="Y-m-d H:i:s")`. If this annotation is not provided, MagicDto will serialize the property using the default format Y-m-d H:i:s. This ensures that data sent to third-party applications adheres to their expected format.
+
+ Format the date and time according to the conventions used in the PHP programming language. This includes utilizing the built-in date and time functions, which allow for various formatting options to display dates and times in a way that is both readable and compatible with PHP's standards. Ensure that you adhere to formats such as 'Y-m-d H:i:s' for complete timestamps or 'd/m/Y' for more localized representations, depending on the specific requirements of your application.
+
+ MagicDto automatically parses input as both strings and integers. The integer is a unique timestamp, while the string date-time format must be one of the following:
+
+ - **'Y-m-d'**, // ISO 8601: 2024-10-24
+ - **'Y-m-d H:i:s'**, // ISO 8601: 2024-10-24 15:30:00
+ - **'Y-m-d\TH:i:s'**, // ISO 8601: 2024-10-24T15:30:00
+ - **'Y-m-d\TH:i:s\Z'**, // ISO 8601: 2024-10-24T15:30:00Z
+ - **'D, d M Y H:i:s O'**, // RFC 2822: Thu, 24 Oct 2024 15:30:00 +0000
+ - **'d/m/Y'**, // Local format: 24/10/2024
+ - **'d F Y'**, // Format with month name: 24 October 2024
+ - **'l, d F Y'** // Format with day of the week: Thursday, 24 October 2024
+
+
+
+
**Example:**
DTO on the Input Side
@@ -1622,7 +1660,8 @@ class AlbumDtoInput extends MagicDto
/**
* @Source("date_release")
* @JsonProperty("releaseDate")
- * @var string
+ * @JsonFormat(pattern="Y-m-d H:i:s")
+ * @var DateTime
*/
protected $release;
@@ -1910,6 +1949,8 @@ class AgencyDto extends MagicDto
**Usage**
+1. JSON Format
+
```php
$song = new Song(null, $database);
$song->find("1234");
@@ -1919,6 +1960,39 @@ header("Content-type: application/json");
echo $songDto;
```
+2. XML Format
+
+```php
+$song = new Song(null, $database);
+$song->find("1234");
+$songDto = new SongDto($song);
+
+header("Content-type: application/xml");
+echo $songDto->toXml("root");
+```
+
+3. Parse XML
+
+```php
+$albumDto = new AlbumDto();
+$obj = $albumDto->xmlToObject($xmlString);
+// $obj is stdClass
+header("Content-type: application/json");
+echo json_encode($obj);
+```
+
+3. Load from XML
+
+```php
+$albumDto = new AlbumDtoInput();
+$albumDto->loadXml($xmlString);
+header("Content-type: application/json");
+echo $albumDto;
+```
+
+`loadXml` method will load data from XML to `AlbumDtoInput`. `AlbumDtoInput` is the inverse of `AlbumDto`, where values of the `@JsonProperty` and `@Source` annotations are swapped. This inversion also applies to the objects contained within it.
+
+
#### Explanation
- **@Source**: This annotation specifies the path to the property within the nested object structure. In this case, `artist->agency->name` indicates that the `agencyName` will pull data from the `name` property of the `Agency` object linked to the `Artist`.