209 lines
5.9 KiB
PHP
209 lines
5.9 KiB
PHP
|
<?php declare(strict_types=1);
|
||
|
|
||
|
/**
|
||
|
* @license Apache 2.0
|
||
|
*/
|
||
|
|
||
|
namespace OpenApi;
|
||
|
|
||
|
use OpenApi\Annotations as OA;
|
||
|
|
||
|
/**
|
||
|
* Allows to serialize/de-serialize annotations from/to JSON.
|
||
|
*
|
||
|
* @see https://github.com/zircote/swagger-php
|
||
|
*/
|
||
|
class Serializer
|
||
|
{
|
||
|
private static $VALID_ANNOTATIONS = [
|
||
|
OA\AdditionalProperties::class,
|
||
|
OA\Components::class,
|
||
|
OA\Contact::class,
|
||
|
OA\Delete::class,
|
||
|
OA\Discriminator::class,
|
||
|
OA\Examples::class,
|
||
|
OA\ExternalDocumentation::class,
|
||
|
OA\Flow::class,
|
||
|
OA\Get::class,
|
||
|
OA\Head::class,
|
||
|
OA\Header::class,
|
||
|
OA\Info::class,
|
||
|
OA\Items::class,
|
||
|
OA\JsonContent::class,
|
||
|
OA\License::class,
|
||
|
OA\Link::class,
|
||
|
OA\MediaType::class,
|
||
|
OA\OpenApi::class,
|
||
|
OA\Operation::class,
|
||
|
OA\Options::class,
|
||
|
OA\Parameter::class,
|
||
|
OA\Patch::class,
|
||
|
OA\PathItem::class,
|
||
|
OA\Post::class,
|
||
|
OA\Property::class,
|
||
|
OA\Put::class,
|
||
|
OA\RequestBody::class,
|
||
|
OA\Response::class,
|
||
|
OA\Schema::class,
|
||
|
OA\SecurityScheme::class,
|
||
|
OA\Server::class,
|
||
|
OA\ServerVariable::class,
|
||
|
OA\Tag::class,
|
||
|
OA\Trace::class,
|
||
|
OA\Xml::class,
|
||
|
OA\XmlContent::class,
|
||
|
];
|
||
|
|
||
|
public static function isValidAnnotationClass($className)
|
||
|
{
|
||
|
return in_array($className, static::$VALID_ANNOTATIONS);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Serialize.
|
||
|
*
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function serialize(OA\AbstractAnnotation $annotation)
|
||
|
{
|
||
|
return json_encode($annotation);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Deserialize a string.
|
||
|
*
|
||
|
* @return OA\AbstractAnnotation
|
||
|
*/
|
||
|
public function deserialize(string $jsonString, string $className)
|
||
|
{
|
||
|
if (!$this->isValidAnnotationClass($className)) {
|
||
|
throw new \Exception($className . ' is not defined in OpenApi PHP Annotations');
|
||
|
}
|
||
|
|
||
|
return $this->doDeserialize(json_decode($jsonString), $className);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Deserialize a file.
|
||
|
*
|
||
|
* @return OA\AbstractAnnotation
|
||
|
*/
|
||
|
public function deserializeFile(string $filename, string $className = OA\OpenApi::class)
|
||
|
{
|
||
|
if (!$this->isValidAnnotationClass($className)) {
|
||
|
throw new \Exception($className . ' is not defined in OpenApi PHP Annotations');
|
||
|
}
|
||
|
|
||
|
return $this->doDeserialize(json_decode(file_get_contents($filename)), $className);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Do deserialization.
|
||
|
*
|
||
|
* @return OA\AbstractAnnotation
|
||
|
*/
|
||
|
protected function doDeserialize(\stdClass $c, string $class)
|
||
|
{
|
||
|
$annotation = new $class([]);
|
||
|
foreach ((array) $c as $property => $value) {
|
||
|
if ($property === '$ref') {
|
||
|
$property = 'ref';
|
||
|
}
|
||
|
|
||
|
if (substr($property, 0, 2) === 'x-') {
|
||
|
if ($annotation->x === Generator::UNDEFINED) {
|
||
|
$annotation->x = [];
|
||
|
}
|
||
|
$custom = substr($property, 2);
|
||
|
$annotation->x[$custom] = $value;
|
||
|
} else {
|
||
|
$annotation->$property = $this->doDeserializeProperty($annotation, $property, $value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $annotation;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Deserialize the annotation's property.
|
||
|
*/
|
||
|
protected function doDeserializeProperty(OA\AbstractAnnotation $annotation, string $property, $value)
|
||
|
{
|
||
|
// property is primitive type
|
||
|
if (array_key_exists($property, $annotation::$_types)) {
|
||
|
return $this->doDeserializeBaseProperty($annotation::$_types[$property], $value);
|
||
|
}
|
||
|
|
||
|
// property is embedded annotation
|
||
|
// note: this does not support custom nested annotation classes
|
||
|
foreach ($annotation::$_nested as $nestedClass => $declaration) {
|
||
|
// property is an annotation
|
||
|
if (is_string($declaration) && $declaration === $property) {
|
||
|
if (is_object($value)) {
|
||
|
return $this->doDeserialize($value, $nestedClass);
|
||
|
} else {
|
||
|
return $value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// property is an annotation array
|
||
|
if (is_array($declaration) && count($declaration) === 1 && $declaration[0] === $property) {
|
||
|
$annotationArr = [];
|
||
|
foreach ($value as $v) {
|
||
|
$annotationArr[] = $this->doDeserialize($v, $nestedClass);
|
||
|
}
|
||
|
|
||
|
return $annotationArr;
|
||
|
}
|
||
|
|
||
|
// property is an annotation hash map
|
||
|
if (is_array($declaration) && count($declaration) === 2 && $declaration[0] === $property) {
|
||
|
$key = $declaration[1];
|
||
|
$annotationHash = [];
|
||
|
foreach ($value as $k => $v) {
|
||
|
$annotation = $this->doDeserialize($v, $nestedClass);
|
||
|
$annotation->$key = $k;
|
||
|
$annotationHash[$k] = $annotation;
|
||
|
}
|
||
|
|
||
|
return $annotationHash;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $value;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Deserialize base annotation property.
|
||
|
*
|
||
|
* @param array|string $type The property type
|
||
|
* @param mixed $value The value to deserialization
|
||
|
*
|
||
|
* @return array|OA\AbstractAnnotation
|
||
|
*/
|
||
|
protected function doDeserializeBaseProperty($type, $value)
|
||
|
{
|
||
|
$isAnnotationClass = is_string($type) && is_subclass_of(trim($type, '[]'), OA\AbstractAnnotation::class);
|
||
|
|
||
|
if ($isAnnotationClass) {
|
||
|
$isArray = strpos($type, '[') === 0 && substr($type, -1) === ']';
|
||
|
|
||
|
if ($isArray) {
|
||
|
$annotationArr = [];
|
||
|
$class = trim($type, '[]');
|
||
|
|
||
|
foreach ($value as $v) {
|
||
|
$annotationArr[] = $this->doDeserialize($v, $class);
|
||
|
}
|
||
|
|
||
|
return $annotationArr;
|
||
|
}
|
||
|
|
||
|
return $this->doDeserialize($value, $type);
|
||
|
}
|
||
|
|
||
|
return $value;
|
||
|
}
|
||
|
}
|