vendor/zircote/swagger-php/src/Annotations/AbstractAnnotation.php line 402

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. /**
  3.  * @license Apache 2.0
  4.  */
  5. namespace OpenApi\Annotations;
  6. use OpenApi\Context;
  7. use OpenApi\Generator;
  8. use OpenApi\Annotations as OA;
  9. use OpenApi\Util;
  10. use Symfony\Component\Yaml\Yaml;
  11. /**
  12.  * The openapi annotation base class.
  13.  */
  14. abstract class AbstractAnnotation implements \JsonSerializable
  15. {
  16.     /**
  17.      * While the OpenAPI Specification tries to accommodate most use cases, additional data can be added to extend the specification at certain points.
  18.      * For further details see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specificationExtensions
  19.      * The keys inside the array will be prefixed with `x-`.
  20.      *
  21.      * @var array<string,mixed>
  22.      */
  23.     public $x Generator::UNDEFINED;
  24.     /**
  25.      * Arbitrary attachables for this annotation.
  26.      * These will be ignored but can be used for custom processing.
  27.      *
  28.      * @var array
  29.      */
  30.     public $attachables Generator::UNDEFINED;
  31.     /**
  32.      * @var Context|null
  33.      */
  34.     public $_context null;
  35.     /**
  36.      * Annotations that couldn't be merged by mapping or postprocessing.
  37.      *
  38.      * @var array
  39.      */
  40.     public $_unmerged = [];
  41.     /**
  42.      * The properties which are required by [the spec](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md).
  43.      *
  44.      * @var array
  45.      */
  46.     public static $_required = [];
  47.     /**
  48.      * Specify the type of the property.
  49.      *
  50.      * Examples:
  51.      *   'name' => 'string' // a string
  52.      *   'required' => 'boolean', // true or false
  53.      *   'tags' => '[string]', // array containing strings
  54.      *   'in' => ["query", "header", "path", "formData", "body"] // must be one on these
  55.      *   'oneOf' => [Schema::class] // array of schema objects.
  56.      *
  57.      * @var array<string,string|array<string>>
  58.      */
  59.     public static $_types = [];
  60.     /**
  61.      * Declarative mapping of Annotation types to properties.
  62.      * Examples:
  63.      *   Info::clas => 'info', // Set @OA\Info annotation as the info property.
  64.      *   Parameter::clas => ['parameters'],  // Append @OA\Parameter annotations the parameters array.
  65.      *   PathItem::clas => ['paths', 'path'],  // Append @OA\PathItem annotations the paths array and use path as key.
  66.      *
  67.      * @var array<class-string<AbstractAnnotation>,string|array<string>>
  68.      */
  69.     public static $_nested = [];
  70.     /**
  71.      * Reverse mapping of $_nested with the allowed parent annotations.
  72.      *
  73.      * @var array<class-string<AbstractAnnotation>>
  74.      */
  75.     public static $_parents = [];
  76.     /**
  77.      * List of properties are blacklisted from the JSON output.
  78.      *
  79.      * @var array<string>
  80.      */
  81.     public static $_blacklist = ['_context''_unmerged''_analysis''attachables'];
  82.     public function __construct(array $properties)
  83.     {
  84.         if (isset($properties['_context'])) {
  85.             $this->_context $properties['_context'];
  86.             unset($properties['_context']);
  87.         } elseif (Generator::$context) {
  88.             $this->_context Generator::$context;
  89.         } else {
  90.             $this->_context Context::detect(1);
  91.         }
  92.         if ($this->_context->is('annotations') === false) {
  93.             $this->_context->annotations = [];
  94.         }
  95.         $this->_context->annotations[] = $this;
  96.         $nestedContext = new Context(['nested' => $this], $this->_context);
  97.         foreach ($properties as $property => $value) {
  98.             if (property_exists($this$property)) {
  99.                 $this->{$property} = $value;
  100.                 if (is_array($value)) {
  101.                     foreach ($value as $key => $annotation) {
  102.                         if ($annotation instanceof AbstractAnnotation) {
  103.                             $this->{$property}[$key] = $this->nested($annotation$nestedContext);
  104.                         }
  105.                     }
  106.                 }
  107.             } elseif ($property !== 'value') {
  108.                 $this->{$property} = $value;
  109.             } elseif (is_array($value)) {
  110.                 $annotations = [];
  111.                 foreach ($value as $annotation) {
  112.                     if ($annotation instanceof AbstractAnnotation) {
  113.                         $annotations[] = $annotation;
  114.                     } else {
  115.                         $this->_context->logger->warning('Unexpected field in ' $this->identity() . ' in ' $this->_context);
  116.                     }
  117.                 }
  118.                 $this->merge($annotations);
  119.             } elseif (is_object($value)) {
  120.                 $this->merge([$value]);
  121.             } else {
  122.                 if (!Generator::isDefault($value)) {
  123.                     $this->_context->logger->warning('Unexpected parameter "' $property '" in ' $this->identity());
  124.                 }
  125.             }
  126.         }
  127.         if ($this instanceof OpenApi) {
  128.             if ($this->_context->root()->version) {
  129.                 // override via `Generator::setVersion()`
  130.                 $this->openapi $this->_context->root()->version;
  131.             } else {
  132.                 $this->_context->root()->version $this->openapi;
  133.             }
  134.         }
  135.     }
  136.     public function __get(string $property)
  137.     {
  138.         $properties get_object_vars($this);
  139.         $this->_context->logger->warning('Property "' $property '" doesn\'t exist in a ' $this->identity() . ', existing properties: "' implode('", "'array_keys($properties)) . '" in ' $this->_context);
  140.     }
  141.     /**
  142.      * @param mixed $value
  143.      */
  144.     public function __set(string $property$value): void
  145.     {
  146.         $fields get_object_vars($this);
  147.         foreach (static::$_blacklist as $_property) {
  148.             unset($fields[$_property]);
  149.         }
  150.         $this->_context->logger->warning('Unexpected field "' $property '" for ' $this->identity() . ', expecting "' implode('", "'array_keys($fields)) . '" in ' $this->_context);
  151.         $this->{$property} = $value;
  152.     }
  153.     /**
  154.      * Merge given annotations to their mapped properties configured in static::$_nested.
  155.      *
  156.      * Annotations that couldn't be merged are added to the _unmerged array.
  157.      *
  158.      * @param AbstractAnnotation[] $annotations
  159.      * @param bool                 $ignore      Ignore unmerged annotations
  160.      *
  161.      * @return AbstractAnnotation[] The unmerged annotations
  162.      */
  163.     public function merge(array $annotationsbool $ignore false): array
  164.     {
  165.         $unmerged = [];
  166.         $nestedContext = new Context(['nested' => $this], $this->_context);
  167.         foreach ($annotations as $annotation) {
  168.             $mapped false;
  169.             if ($details $this->matchNested($annotation)) {
  170.                 $property $details->value;
  171.                 if (is_array($property)) {
  172.                     $property $property[0];
  173.                     if (Generator::isDefault($this->{$property})) {
  174.                         $this->{$property} = [];
  175.                     }
  176.                     $this->{$property}[] = $this->nested($annotation$nestedContext);
  177.                     $mapped true;
  178.                 } elseif (Generator::isDefault($this->{$property})) {
  179.                     // ignore duplicate nested if only one expected
  180.                     $this->{$property} = $this->nested($annotation$nestedContext);
  181.                     $mapped true;
  182.                 }
  183.             }
  184.             if (!$mapped) {
  185.                 $unmerged[] = $annotation;
  186.             }
  187.         }
  188.         if (!$ignore) {
  189.             foreach ($unmerged as $annotation) {
  190.                 $this->_unmerged[] = $this->nested($annotation$nestedContext);
  191.             }
  192.         }
  193.         return $unmerged;
  194.     }
  195.     /**
  196.      * Merge the properties from the given object into this annotation.
  197.      * Prevents overwriting properties that are already configured.
  198.      *
  199.      * @param object $object
  200.      */
  201.     public function mergeProperties($object): void
  202.     {
  203.         $defaultValues get_class_vars(get_class($this));
  204.         $currentValues get_object_vars($this);
  205.         foreach ($object as $property => $value) {
  206.             if ($property === '_context') {
  207.                 continue;
  208.             }
  209.             if ($currentValues[$property] === $defaultValues[$property]) { // Overwrite default values
  210.                 $this->{$property} = $value;
  211.                 continue;
  212.             }
  213.             if ($property === '_unmerged') {
  214.                 $this->_unmerged array_merge($this->_unmerged$value);
  215.                 continue;
  216.             }
  217.             if ($currentValues[$property] !== $value) { // New value is not the same?
  218.                 if ($defaultValues[$property] === $value) { // but is the same as the default?
  219.                     continue; // Keep current, no notice
  220.                 }
  221.                 $identity method_exists($object'identity') ? $object->identity() : get_class($object);
  222.                 $context1 $this->_context;
  223.                 $context2 property_exists($object'_context') ? $object->_context 'unknown';
  224.                 if (is_object($this->{$property}) && $this->{$property} instanceof AbstractAnnotation) {
  225.                     $context1 $this->{$property}->_context;
  226.                 }
  227.                 $this->_context->logger->error('Multiple definitions for ' $identity '->' $property "\n     Using: " $context1 "\n  Skipping: " $context2);
  228.             }
  229.         }
  230.     }
  231.     /**
  232.      * Generate the documentation in YAML format.
  233.      */
  234.     public function toYaml(?int $flags null): string
  235.     {
  236.         if ($flags === null) {
  237.             $flags Yaml::DUMP_OBJECT_AS_MAP Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE;
  238.         }
  239.         return Yaml::dump(json_decode($this->toJson(JSON_INVALID_UTF8_IGNORE)), 102$flags);
  240.     }
  241.     /**
  242.      * Generate the documentation in JSON format.
  243.      */
  244.     public function toJson(?int $flags null): string
  245.     {
  246.         if ($flags === null) {
  247.             $flags JSON_PRETTY_PRINT JSON_UNESCAPED_SLASHES JSON_UNESCAPED_UNICODE JSON_INVALID_UTF8_IGNORE;
  248.         }
  249.         return json_encode($this$flags);
  250.     }
  251.     public function __debugInfo()
  252.     {
  253.         $properties = [];
  254.         foreach (get_object_vars($this) as $property => $value) {
  255.             if (!Generator::isDefault($value)) {
  256.                 $properties[$property] = $value;
  257.             }
  258.         }
  259.         return $properties;
  260.     }
  261.     /**
  262.      * @return mixed
  263.      */
  264.     #[\ReturnTypeWillChange]
  265.     public function jsonSerialize()
  266.     {
  267.         $data = new \stdClass();
  268.         // Strip undefined values.
  269.         foreach (get_object_vars($this) as $property => $value) {
  270.             if (!Generator::isDefault($value)) {
  271.                 $data->{$property} = $value;
  272.             }
  273.         }
  274.         // Strip properties that are for internal (swagger-php) use.
  275.         foreach (static::$_blacklist as $property) {
  276.             unset($data->{$property});
  277.         }
  278.         // Correct empty array to empty objects.
  279.         foreach (static::$_types as $property => $type) {
  280.             if ($type === 'object' && is_array($data->{$property}) && empty($data->{$property})) {
  281.                 $data->{$property} = new \stdClass();
  282.             }
  283.         }
  284.         // Inject vendor properties.
  285.         unset($data->x);
  286.         if (is_array($this->x)) {
  287.             foreach ($this->as $property => $value) {
  288.                 $prefixed 'x-' $property;
  289.                 $data->{$prefixed} = $value;
  290.             }
  291.         }
  292.         // Map nested keys
  293.         foreach (static::$_nested as $nested) {
  294.             if (is_string($nested) || count($nested) === 1) {
  295.                 continue;
  296.             }
  297.             $property $nested[0];
  298.             if (Generator::isDefault($this->{$property})) {
  299.                 continue;
  300.             }
  301.             $keyField $nested[1];
  302.             $object = new \stdClass();
  303.             foreach ($this->{$property} as $key => $item) {
  304.                 if (is_numeric($key) === false && is_array($item)) {
  305.                     $object->{$key} = $item;
  306.                 } else {
  307.                     $key $item->{$keyField};
  308.                     if (!Generator::isDefault($key) && empty($object->{$key})) {
  309.                         if ($item instanceof \JsonSerializable) {
  310.                             $object->{$key} = $item->jsonSerialize();
  311.                         } else {
  312.                             $object->{$key} = $item;
  313.                         }
  314.                         unset($object->{$key}->{$keyField});
  315.                     }
  316.                 }
  317.             }
  318.             $data->{$property} = $object;
  319.         }
  320.         // $ref
  321.         if (isset($data->ref)) {
  322.             // Only specific https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.1.0.md#reference-object
  323.             $ref = ['$ref' => $data->ref];
  324.             $defaultValues get_class_vars(get_class($this));
  325.             if ($this->_context->version === OpenApi::VERSION_3_1_0) {
  326.                 foreach (['summary''description'] as $prop) {
  327.                     if (property_exists($this$prop)) {
  328.                         if ($this->{$prop} !== $defaultValues[$prop]) {
  329.                             $ref[$prop] = $data->{$prop};
  330.                         }
  331.                     }
  332.                 }
  333.             }
  334.             if (property_exists($this'nullable') && $this->nullable === true) {
  335.                 $ref = ['oneOf' => [$ref]];
  336.                 if ($this->_context->version == OpenApi::VERSION_3_1_0) {
  337.                     $ref['oneOf'][] = ['type' => 'null'];
  338.                 } else {
  339.                     $ref['nullable'] = $data->nullable;
  340.                 }
  341.                 unset($data->nullable);
  342.             }
  343.             $data = (object) $ref;
  344.         }
  345.         if ($this->_context->version === OpenApi::VERSION_3_1_0) {
  346.             if (isset($data->nullable)) {
  347.                 if (true === $data->nullable) {
  348.                     $data->type = (array) $data->type;
  349.                     $data->type[] = 'null';
  350.                 }
  351.                 unset($data->nullable);
  352.             }
  353.         }
  354.         return $data;
  355.     }
  356.     /**
  357.      * Validate annotation tree, and log notices & warnings.
  358.      *
  359.      * @param array  $stack   the path of annotations above this annotation in the tree
  360.      * @param array  $skip    (prevent stack overflow, when traversing an infinite dependency graph)
  361.      * @param string $ref     Current ref path?
  362.      * @param object $context a free-form context contains
  363.      */
  364.     public function validate(array $stack = [], array $skip = [], string $ref ''$context null): bool
  365.     {
  366.         if (in_array($this$skiptrue)) {
  367.             return true;
  368.         }
  369.         $valid true;
  370.         // Report orphaned annotations
  371.         foreach ($this->_unmerged as $annotation) {
  372.             if (!is_object($annotation)) {
  373.                 $this->_context->logger->warning('Unexpected type: "' gettype($annotation) . '" in ' $this->identity() . '->_unmerged, expecting a Annotation object');
  374.                 break;
  375.             }
  376.             /** @var class-string<AbstractAnnotation> $class */
  377.             $class get_class($annotation);
  378.             if ($details $this->matchNested($annotation)) {
  379.                 $property $details->value;
  380.                 if (is_array($property)) {
  381.                     $this->_context->logger->warning('Only one ' Util::shorten(get_class($annotation)) . '() allowed for ' $this->identity() . ' multiple found, skipped: ' $annotation->_context);
  382.                 } else {
  383.                     $this->_context->logger->warning('Only one ' Util::shorten(get_class($annotation)) . '() allowed for ' $this->identity() . " multiple found in:\n    Using: " $this->{$property}->_context "\n  Skipped: " $annotation->_context);
  384.                 }
  385.             } elseif ($annotation instanceof AbstractAnnotation) {
  386.                 $message 'Unexpected ' $annotation->identity();
  387.                 if ($class::$_parents) {
  388.                     $message .= ', expected to be inside ' implode(', 'Util::shorten($class::$_parents));
  389.                 }
  390.                 $this->_context->logger->warning($message ' in ' $annotation->_context);
  391.             }
  392.             $valid false;
  393.         }
  394.         // Report conflicting key
  395.         foreach (static::$_nested as $annotationClass => $nested) {
  396.             if (is_string($nested) || count($nested) === 1) {
  397.                 continue;
  398.             }
  399.             $property $nested[0];
  400.             if (Generator::isDefault($this->{$property})) {
  401.                 continue;
  402.             }
  403.             $keys = [];
  404.             $keyField $nested[1];
  405.             foreach ($this->{$property} as $key => $item) {
  406.                 if (is_array($item) && is_numeric($key) === false) {
  407.                     $this->_context->logger->warning($this->identity() . '->' $property ' is an object literal, use nested ' Util::shorten($annotationClass) . '() annotation(s) in ' $this->_context);
  408.                     $keys[$key] = $item;
  409.                 } elseif (Generator::isDefault($item->{$keyField})) {
  410.                     $this->_context->logger->error($item->identity() . ' is missing key-field: "' $keyField '" in ' $item->_context);
  411.                 } elseif (isset($keys[$item->{$keyField}])) {
  412.                     $this->_context->logger->error('Multiple ' $item->_identity([]) . ' with the same ' $keyField '="' $item->{$keyField} . "\":\n  " $item->_context "\n  " $keys[$item->{$keyField}]->_context);
  413.                 } else {
  414.                     $keys[$item->{$keyField}] = $item;
  415.                 }
  416.             }
  417.         }
  418.         if (property_exists($this'ref') && !Generator::isDefault($this->ref) && is_string($this->ref)) {
  419.             if (substr($this->ref02) === '#/' && count($stack) > && $stack[0] instanceof OpenApi) {
  420.                 // Internal reference
  421.                 try {
  422.                     $stack[0]->ref($this->ref);
  423.                 } catch (\Exception $e) {
  424.                     $this->_context->logger->warning($e->getMessage() . ' for ' $this->identity() . ' in ' $this->_context, ['exception' => $e]);
  425.                 }
  426.             }
  427.         } else {
  428.             // Report missing required fields (when not a $ref)
  429.             foreach (static::$_required as $property) {
  430.                 if (Generator::isDefault($this->{$property})) {
  431.                     $message 'Missing required field "' $property '" for ' $this->identity() . ' in ' $this->_context;
  432.                     foreach (static::$_nested as $class => $nested) {
  433.                         $nestedProperty is_array($nested) ? $nested[0] : $nested;
  434.                         if ($property === $nestedProperty) {
  435.                             if ($this instanceof OpenApi) {
  436.                                 $message 'Required ' Util::shorten($class) . '() not found';
  437.                             } elseif (is_array($nested)) {
  438.                                 $message $this->identity() . ' requires at least one ' Util::shorten($class) . '() in ' $this->_context;
  439.                             } else {
  440.                                 $message $this->identity() . ' requires a ' Util::shorten($class) . '() in ' $this->_context;
  441.                             }
  442.                             break;
  443.                         }
  444.                     }
  445.                     $this->_context->logger->warning($message);
  446.                 }
  447.             }
  448.         }
  449.         // Report invalid types
  450.         foreach (static::$_types as $property => $type) {
  451.             $value $this->{$property};
  452.             if (Generator::isDefault($value) || $value === null) {
  453.                 continue;
  454.             }
  455.             if (is_string($type)) {
  456.                 if ($this->validateType($type$value) === false) {
  457.                     $valid false;
  458.                     $this->_context->logger->warning($this->identity() . '->' $property ' is a "' gettype($value) . '", expecting a "' $type '" in ' $this->_context);
  459.                 }
  460.             } elseif (is_array($type)) { // enum?
  461.                 if (in_array($value$type) === false) {
  462.                     $this->_context->logger->warning($this->identity() . '->' $property ' "' $value '" is invalid, expecting "' implode('", "'$type) . '" in ' $this->_context);
  463.                 }
  464.             } else {
  465.                 throw new \Exception('Invalid ' get_class($this) . '::$_types[' $property ']');
  466.             }
  467.         }
  468.         $stack[] = $this;
  469.         return self::_validate($this$stack$skip$ref$context) ? $valid false;
  470.     }
  471.     /**
  472.      * Recursively validate all annotation properties.
  473.      *
  474.      * @param array|object $fields
  475.      */
  476.     private static function _validate($fields, array $stack, array $skipstring $baseRef, ?object $context): bool
  477.     {
  478.         $valid true;
  479.         $blacklist = [];
  480.         if (is_object($fields)) {
  481.             if (in_array($fields$skiptrue)) {
  482.                 return true;
  483.             }
  484.             $skip[] = $fields;
  485.             $blacklist property_exists($fields'_blacklist') ? $fields::$_blacklist : [];
  486.         }
  487.         foreach ($fields as $field => $value) {
  488.             if ($value === null || is_scalar($value) || in_array($field$blacklist)) {
  489.                 continue;
  490.             }
  491.             $ref $baseRef !== '' $baseRef '/' urlencode((string) $field) : urlencode((string) $field);
  492.             if (is_object($value)) {
  493.                 if (method_exists($value'validate')) {
  494.                     if (!$value->validate($stack$skip$ref$context)) {
  495.                         $valid false;
  496.                     }
  497.                 } elseif (!self::_validate($value$stack$skip$ref$context)) {
  498.                     $valid false;
  499.                 }
  500.             } elseif (is_array($value) && !self::_validate($value$stack$skip$ref$context)) {
  501.                 $valid false;
  502.             }
  503.         }
  504.         return $valid;
  505.     }
  506.     /**
  507.      * Return a identity for easy debugging.
  508.      * Example: "@OA\Get(path="/pets")".
  509.      */
  510.     public function identity(): string
  511.     {
  512.         $class get_class($this);
  513.         $properties = [];
  514.         /** @var class-string<AbstractAnnotation> $parent */
  515.         foreach (static::$_parents as $parent) {
  516.             foreach ($parent::$_nested as $annotationClass => $entry) {
  517.                 if ($annotationClass === $class && is_array($entry) && !Generator::isDefault($this->{$entry[1]})) {
  518.                     $properties[] = $entry[1];
  519.                     break 2;
  520.                 }
  521.             }
  522.         }
  523.         return $this->_identity($properties);
  524.     }
  525.     /**
  526.      * Check if `$other` can be nested and if so return details about where/how.
  527.      *
  528.      * @param AbstractAnnotation $other the other annotation
  529.      *
  530.      * @return null|object key/value object or `null`
  531.      */
  532.     public function matchNested($other)
  533.     {
  534.         if ($other instanceof AbstractAnnotation && array_key_exists($root $other->getRoot(), static::$_nested)) {
  535.             return (object) ['key' => $root'value' => static::$_nested[$root]];
  536.         }
  537.         return null;
  538.     }
  539.     /**
  540.      * Get the root annotation.
  541.      *
  542.      * This is used for resolving type equality and nesting rules to allow those rules to also work for custom,
  543.      * derived annotation classes.
  544.      *
  545.      * @return class-string the root annotation class in the `OpenApi\\Annotations` namespace
  546.      */
  547.     public function getRoot(): string
  548.     {
  549.         $class get_class($this);
  550.         do {
  551.             if (=== strpos($class'OpenApi\\Annotations\\')) {
  552.                 break;
  553.             }
  554.         } while ($class get_parent_class($class));
  555.         return $class;
  556.     }
  557.     /**
  558.      * Match the annotation root.
  559.      *
  560.      * @param class-string $rootClass the root class to match
  561.      */
  562.     public function isRoot(string $rootClass): bool
  563.     {
  564.         return $this->getRoot() == $rootClass;
  565.     }
  566.     /**
  567.      * Helper for generating the identity().
  568.      */
  569.     protected function _identity(array $properties): string
  570.     {
  571.         $fields = [];
  572.         foreach ($properties as $property) {
  573.             $value $this->{$property};
  574.             if ($value !== null && !Generator::isDefault($value)) {
  575.                 $fields[] = $property '=' . (is_string($value) ? '"' $value '"' $value);
  576.             }
  577.         }
  578.         return Util::shorten(get_class($this)) . '(' implode(','$fields) . ')';
  579.     }
  580.     /**
  581.      * Validates the matching of the property value to a annotation type.
  582.      *
  583.      * @param string $type  The annotations property type
  584.      * @param mixed  $value The property value
  585.      */
  586.     private function validateType(string $type$value): bool
  587.     {
  588.         if (substr($type01) === '[' && substr($type, -1) === ']') { // Array of a specified type?
  589.             if ($this->validateType('array'$value) === false) {
  590.                 return false;
  591.             }
  592.             $itemType substr($type1, -1);
  593.             foreach ($value as $i => $item) {
  594.                 if ($this->validateType($itemType$item) === false) {
  595.                     return false;
  596.                 }
  597.             }
  598.             return true;
  599.         }
  600.         if (is_subclass_of($typeAbstractAnnotation::class)) {
  601.             $type 'object';
  602.         }
  603.         return $this->validateDefaultTypes($type$value);
  604.     }
  605.     /**
  606.      * Validates default Open Api types.
  607.      *
  608.      * @param string $type  The property type
  609.      * @param mixed  $value The value to validate
  610.      */
  611.     private function validateDefaultTypes(string $type$value): bool
  612.     {
  613.         switch ($type) {
  614.             case 'string':
  615.                 return is_string($value);
  616.             case 'boolean':
  617.                 return is_bool($value);
  618.             case 'integer':
  619.                 return is_int($value);
  620.             case 'number':
  621.                 return is_numeric($value);
  622.             case 'object':
  623.                 return is_object($value);
  624.             case 'array':
  625.                 return $this->validateArrayType($value);
  626.             case 'scheme':
  627.                 return in_array($value, ['http''https''ws''wss'], true);
  628.             default:
  629.                 throw new \Exception('Invalid type "' $type '"');
  630.         }
  631.     }
  632.     /**
  633.      * Validate array type.
  634.      *
  635.      * @param mixed $value
  636.      */
  637.     private function validateArrayType($value): bool
  638.     {
  639.         if (is_array($value) === false) {
  640.             return false;
  641.         }
  642.         $count 0;
  643.         foreach ($value as $i => $item) {
  644.             // not a array, but a hash/map
  645.             if ($count !== $i) {
  646.                 return false;
  647.             }
  648.             $count++;
  649.         }
  650.         return true;
  651.     }
  652.     /**
  653.      * Wrap the context with a reference to the annotation it is nested in.
  654.      *
  655.      * @param AbstractAnnotation $annotation
  656.      *
  657.      * @return AbstractAnnotation
  658.      */
  659.     protected function nested(AbstractAnnotation $annotationContext $nestedContext)
  660.     {
  661.         if (property_exists($annotation'_context') && $annotation->_context === $this->_context) {
  662.             $annotation->_context $nestedContext;
  663.         }
  664.         return $annotation;
  665.     }
  666.     protected function combine(...$args): array
  667.     {
  668.         $combined = [];
  669.         foreach ($args as $arg) {
  670.             if (is_array($arg)) {
  671.                 $combined array_merge($combined$arg);
  672.             } else {
  673.                 $combined[] = $arg;
  674.             }
  675.         }
  676.         return array_filter($combined, function ($value) {
  677.             return !Generator::isDefault($value) && $value !== null;
  678.         });
  679.     }
  680. }