public function __invoke(Analysis $analysis) { $paths = []; // Merge @SWG\Paths with the same path. foreach ($analysis->swagger->paths as $annotation) { if (empty($annotation->path)) { Logger::notice($annotation->identity() . ' is missing required property "path" in ' . $annotation->_context); } elseif (isset($paths[$annotation->path])) { $paths[$annotation->path]->mergeProperties($annotation); $analysis->annotations->detach($annotation); } else { $paths[$annotation->path] = $annotation; } } // Merge @SWG\Operations into existing @SWG\Paths or create a new one. $operations = $analysis->unmerged()->getAnnotationsOfType('\\Swagger\\Annotations\\Operation'); foreach ($operations as $operation) { if ($operation->path) { if (empty($paths[$operation->path])) { $paths[$operation->path] = new Path(['path' => $operation->path, '_context' => new Context(['generated' => true], $operation->_context)]); $analysis->annotations->attach($paths[$operation->path]); } if ($paths[$operation->path]->merge([$operation])) { Logger::notice('Unable to merge ' . $operation->identity() . ' in ' . $operation->_context); } } } $analysis->swagger->paths = array_values($paths); }
/** * {@inheritdoc} */ public function process($annotation, $context) { if ($annotation instanceof Api === false) { return; } if ($annotation->hasPartialId() === false) { $resource = $context->resource; if ($resource) { $resource->apis[] = $annotation; } else { Logger::notice('Unexpected "' . $annotation->identity() . '", should be inside or after @SWG\\Resource() in ' . $context); } } if ($context->is('method')) { $resource = $context->resource; if ($annotation->path === null && $resource && $resource->resourcePath) { // No path given? // Assume method (without Action suffix) on top the resourcePath $annotation->path = $resource->resourcePath . '/' . preg_replace('/Action$/i', '', $context->method); } if ($annotation->description === null) { $annotation->description = $context->extractDescription(); } foreach ($annotation->operations as $i => $operation) { if ($operation->nickname === null) { $operation->nickname = $context->method; if (count($annotation->operations) > 1) { $operation->nickname .= '_' . $i; } } } } }
public function __construct(array $values = array()) { parent::__construct($values); if ($this->paramType && !in_array($this->paramType, array('path', 'query', 'body', 'header', 'form'))) { Logger::warning('Unexpected paramType "' . $this->paramType . '", expecting "path", "query", "body", "header" or "form" in ' . $this->_context); } }
protected function prepareSwagger() { $errorTypes = [E_ERROR => 'ERROR', E_WARNING => 'WARNING', E_PARSE => 'PARSE', E_NOTICE => 'NOTICE', E_CORE_ERROR => 'CORE_ERROR', E_CORE_WARNING => 'CORE_WARNING', E_COMPILE_ERROR => 'COMPILE_ERROR', E_COMPILE_WARNING => 'COMPILE_WARNING', E_USER_ERROR => 'ERROR', E_USER_WARNING => 'WARNING', E_USER_NOTICE => 'NOTICE', E_STRICT => 'STRICT', E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR', E_DEPRECATED => 'DEPRECATED', E_USER_DEPRECATED => 'DEPRECATED']; set_error_handler(function ($errno, $errstr, $file, $line) use($errorTypes) { if (!(error_reporting() & $errno)) { return; // This error code is not included in error_reporting } $type = @$errorTypes[$errno] ?: 'ERROR'; error_log('[' . $type . '] ' . $errstr . ' in ' . $file . ' on line ' . $line); if ($type === 'ERROR') { exit($errno); } }); set_exception_handler(function ($exception) { error_log('[EXCEPTION] ' . $exception->getMessage() . ' in ' . $exception->getFile() . ' on line ' . $exception->getLine()); exit($exception->getCode() ?: 1); }); Swagger\Logger::getInstance()->log = function ($entry, $type) { $type = $type === E_USER_NOTICE ? 'INFO' : 'WARN'; if ($entry instanceof Exception) { $entry = $entry->getMessage(); } error_log('[' . $type . '] ' . $entry . PHP_EOL); }; }
/** @inheritdoc */ public function validate($parents = [], $skip = []) { if (in_array($this, $skip, true)) { return true; } $valid = parent::validate($parents, $skip); if (!$this->ref) { if ($this->type === 'array' && $this->items === null) { Logger::notice('@SWG\\Items() is required when ' . $this->identity() . ' has type "array" in ' . $this->_context); $valid = false; } $parent = end($parents); if (is_object($parent) && ($parent instanceof Parameter && $parent->in !== 'body' || $parent instanceof Header)) { // This is a "Items Object" https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#items-object // A limited subset of JSON-Schema's items object. $allowedTypes = ['string', 'number', 'integer', 'boolean', 'array']; if (in_array($this->type, $allowedTypes) === false) { Logger::notice('@SWG\\Items()->type="' . $this->type . '" not allowed inside a ' . $parent->_identity([]) . ' must be "' . implode('", "', $allowedTypes) . '" in ' . $this->_context); $valid = false; } } } return $valid; // @todo Additional validation when used inside a Header or Parameter context. }
public function validate() { if (empty($this->scope)) { Logger::warning('Required field "scope" is missing for "' . $this->identity() . '" in ' . $this->_context); return false; } return true; }
public function validate() { foreach (array('title', 'description') as $required) { if (empty($this->{$required})) { Logger::notice('Required field "' . $required . '" is missing for "' . $this->identity() . '" in ' . $this->_context); return false; } } return true; }
public function validate() { if (in_array($this->type, array('basicAuth', 'apiKey', 'oauth2')) === false) { Logger::warning('Unexpected ' . $this->identity() . '->type "' . $this->type . '", expection "basicAuth", "apiKey" or "oauth2" in ' . $this->_context); return false; } if ($this->type === 'apiKey' && (empty($this->passAs) || empty($this->keyname))) { Logger::notice('Fields "passAs" and "keyname" are required for ' . $this->identity() . '->type "apiKey" in ' . $this->_context); } return true; }
/** * Add routes to the app that generate swagger documentation based on your annotations * * @param Application $app */ public function boot(Application $app) { AnnotationRegistry::registerAutoloadNamespace("Swagger\\Annotations", $app["swagger.srcDir"]); if ($app["logger"]) { $logger = Logger::getInstance(); $originalLog = $logger->log; $logger->log = function ($entry, $type) use($app, $originalLog) { $app["logger"]->notice($entry); $originalLog($entry, $type); }; } $app->get($app["swagger.apiDocPath"], new ResourceListController()); $app->get("{$app["swagger.apiDocPath"]}/{service}", new ResourceDefinitionController()); }
/** * Add routes to the app that generate swagger documentation based on your annotations * * @param Application $app */ public function boot(Application $app) { // Attach logger if ($app['logger']) { $logger = Logger::getInstance(); $originalLog = $logger->log; $logger->log = function ($entry, $type) use($app, $originalLog) { $app['logger']->notice($entry); $originalLog($entry, $type); }; } // Register route for docs $app->get('/api-docs', new SwaggerController()); }
public function validate() { $operations = array(); foreach ($this->operations as $operation) { if ($operation->validate()) { $operations[] = $operation; } } $this->operations = $operations; if (count($this->operations) === 0 && count($this->_partials) === 0) { Logger::notice('Api "' . $this->path . '" doesn\'t have any valid operations'); return false; } return true; }
/** * {@inheritdoc} */ public function process($annotation, $context) { if ($annotation instanceof AbstractAnnotation && $annotation->hasPartialId() === false) { $whitelist = array('Swagger\\Annotations\\Resource', 'Swagger\\Annotations\\Api', 'Swagger\\Annotations\\Model', 'Swagger\\Annotations\\Property', 'Swagger\\Annotations\\Info', 'Swagger\\Annotations\\Authorization'); if (in_array(get_class($annotation), $whitelist)) { return; } $nesting = array('Swagger\\Annotations\\Operations' => '@SWG\\Api()', 'Swagger\\Annotations\\Operation' => '@SWG\\Api() or @SWG\\Operations()', 'Swagger\\Annotations\\Parameters' => '@SWG\\Operation()', 'Swagger\\Annotations\\Parameter' => '@SWG\\Operation()'); $parent = @$nesting[get_class($annotation)]; if (!$parent) { $parent = 'annotation'; } Logger::notice($annotation->identity() . ' is not placed inside an ' . $parent . ' in ' . $context); } }
/** * {@inheritdoc} */ public function process($annotation, $context) { if ($annotation instanceof Property === false) { return; } if (!$annotation->hasPartialId()) { if ($context->model) { $context->model->properties[] = $annotation; } else { Logger::notice('Unexpected "' . $annotation->identity() . '", should be inside or after @SWG\\Model() in ' . $context); } } if ($context->is('property')) { if ($annotation->name === null) { $annotation->name = $context->property; } if ($annotation->type === null) { if (preg_match('/@var\\s+(\\w+)(\\[\\])?/i', $context->comment, $matches)) { $type = $matches[1]; $isArray = isset($matches[2]); $map = array('array' => 'array', 'byte' => array('string', 'byte'), 'boolean' => 'boolean', 'bool' => 'boolean', 'int' => 'integer', 'integer' => 'integer', 'long' => array('integer', 'long'), 'float' => array('number', 'float'), 'double' => array('number', 'double'), 'string' => 'string', 'date' => array('string', 'date'), 'datetime' => array('string', 'date-time'), '\\datetime' => array('string', 'date-time'), 'byte' => array('string', 'byte'), 'number' => 'number', 'object' => 'object'); if (array_key_exists(strtolower($type), $map)) { $type = $map[strtolower($type)]; if (is_array($type)) { if ($annotation->format === null) { $annotation->format = $type[1]; } $type = $type[0]; } } if ($isArray) { $annotation->type = 'array'; if ($annotation->items === null) { $annotation->items = new Items(array('value' => $type)); } } else { $annotation->type = $type; } } } if ($annotation->description === null) { $annotation->description = $context->extractDescription(); } } }
/** * * @param Property|Parameter|Operation $annotation * @return bool */ public static function validateContainer($annotation) { // Interpret `items="$ref:Model"` as `@SWG\Items(type="Model")` if (is_string($annotation->items) && preg_match('/\\$ref:[\\s]*(.+)[\\s]*$/', $annotation->items, $matches)) { $annotation->items = new Items(); $annotation->items->type = array_pop($matches); } // Validate if items are inside a container type. if ($annotation->items !== null) { if ($annotation->type !== 'array') { Logger::warning('Unexcepted items for type "' . $annotation->type . '" in ' . $annotation->identity() . ', expecting "array"'); $annotation->items = null; } else { Swagger::checkDataType($annotation->items->type, $annotation->items->_context); } } return true; }
/** * * @param Context $context * @return AbstractAnnotation[] */ protected function parseContext($context) { try { self::$context = $context; $annotations = $this->docParser->parse($context->comment, $context); self::$context = null; } catch (\Exception $e) { self::$context = null; if (preg_match('/^(.+) at position ([0-9]+) in ' . preg_quote($context, '/') . '\\.$/', $e->getMessage(), $matches)) { $errorMessage = $matches[1]; $errorPos = $matches[2]; $atPos = strpos($context->comment, '@'); $context->line += substr_count($context->comment, "\n", 0, $atPos + $errorPos); $lines = explode("\n", substr($context->comment, $atPos, $errorPos)); $context->character = strlen(array_pop($lines)) + 1; // position starts at 0 character starts at 1 Logger::warning(new \Exception($errorMessage . ' in ' . $context, $e->getCode(), $e)); } else { Logger::warning($e); } return array(); } foreach ($annotations as $annotation) { foreach ($this->processors as $processor) { $processor->process($annotation, $context); } if ($annotation instanceof AbstractAnnotation) { if ($annotation->hasPartialId()) { if ($this->hasPartial($annotation->_partialId)) { Logger::notice('partial="' . $annotation->_partialId . '" is not unique. another was found in ' . $annotation->_context); } $this->setPartial($annotation->_partialId, $annotation); } elseif ($annotation instanceof Resource) { $this->resources[] = $annotation; } elseif ($annotation instanceof Model) { $this->models[] = $annotation; } } } return $annotations; }
public function validate() { if (empty($this->resourcePath)) { Logger::warning('@SWG\\Resource() is missing "resourcePath" in ' . $this->_context); return false; } if ($this->swaggerVersion) { if (version_compare($this->swaggerVersion, '1.2', '<')) { Logger::warning('swaggerVersion: ' . $this->swaggerVersion . ' is no longer supported. Use 1.2 or higher'); $this->swaggerVersion = null; } } $validApis = array(); foreach ($this->apis as $api) { $append = true; foreach ($validApis as $validApi) { if ($api->path === $validApi->path) { // The same api path? $append = false; // merge operations foreach ($api->operations as $operation) { $validApi->operations[] = $operation; } // merge description if ($validApi->description === null) { $validApi->description = $api->description; } elseif ($api->description !== null && $api->description !== $validApi->description) { Logger::notice('Competing description for ' . $validApi->identity() . ' in ' . $validApi->_context . ' and ' . $api->_context); } break; } } if ($api->validate() && $append) { $validApis[] = $api; } } if (count($validApis) === 0 && count($this->_partials) === 0) { Logger::warning($this->identity() . ' doesn\'t have any valid api calls'); return false; } $this->apis = $validApis; Produces::validateContainer($this); Consumes::validateContainer($this); return true; }
/** * Log a notice when the type doesn't exactly match the Swagger spec. * @link https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#43-data-types * * @param string $type * @param Context $context * @return void */ public static function checkDataType($type, $context) { $map = array('integer' => 'integer', 'number' => 'number', 'string' => 'string', 'boolean' => 'boolean', 'object' => 'object', 'array' => 'array', 'int' => 'integer', 'long' => 'integer', 'bool' => 'boolean', 'date' => 'string', 'datetime' => 'string', 'byte' => 'string'); $mapFormats = array('long' => 'int64', 'byte' => 'byte', 'date' => 'date', 'datetime' => 'date-format'); if (array_key_exists(strtolower($type), $map) && array_search($type, $map) === false) { // Invalid notation for a primitive? if (array_key_exists(strtolower($type), $mapFormats)) { // Logger::notice('Invalid `type="' . $type . '"` use `type="' . $map[strtolower($type)] . '",format="' . $mapFormats[strtolower($type)] . '"` in ' . $context); } else { // Don't automaticly correct the type, this creates the incentive to use consistent naming in the doc comments. Logger::notice('Invalid `type="' . $type . '"` use `type="' . $map[strtolower($type)] . '"` in ' . $context); } } }
/** * Validate annotation tree, and log notices & warnings. * @param array $parents The path of annotations above this annotation in the tree. * @param array $skip (prevent stack overflow, when traversing an infinite dependency graph) * @return boolean * @throws Exception */ public function validate($parents = [], $skip = []) { if (in_array($this, $skip, true)) { return true; } $valid = true; // Report orphaned annotations foreach ($this->_unmerged as $annotation) { if (!is_object($annotation)) { Logger::notice('Unexpected type: "' . gettype($annotation) . '" in ' . $this->identity() . '->_unmerged, expecting a Annotation object'); break; } $class = get_class($annotation); if (isset(static::$_nested[$class])) { $property = static::$_nested[$class]; Logger::notice('Only one @' . str_replace('Swagger\\Annotations\\', 'SWG\\', get_class($annotation)) . '() allowed for ' . $this->identity() . " multiple found in:\n Using: " . $this->{$property}->_context . "\n Skipped: " . $annotation->_context); } elseif ($annotation instanceof AbstractAnnotation) { $message = 'Unexpected ' . $annotation->identity(); if (count($class::$_parents)) { $shortNotations = []; foreach ($class::$_parents as $_class) { $shortNotations[] = '@' . str_replace('Swagger\\Annotations\\', 'SWG\\', $_class); } $message .= ', expected to be inside ' . implode(', ', $shortNotations); } Logger::notice($message . ' in ' . $annotation->_context); } $valid = false; } // Report conflicting key foreach (static::$_nested as $nested) { if (is_string($nested) || count($nested) === 1) { continue; } $property = $nested[0]; if ($this->{$property} === null) { continue; } $keys = []; $keyField = $nested[1]; foreach ($this->{$property} as $item) { if (empty($item->{$keyField})) { Logger::notice($item->identity() . ' is missing key-field: "' . $keyField . '" in ' . $item->_context); } elseif (isset($keys[$item->{$keyField}])) { Logger::notice('Multiple ' . $item->_identity([]) . ' with the same ' . $keyField . '="' . $item->{$keyField} . "\":\n " . $item->_context . "\n " . $keys[$item->{$keyField}]->_context); } else { $keys[$item->{$keyField}] = $item; } } } if (empty($this->ref)) { // Report missing required fields (when not a $ref) foreach (static::$_required as $property) { if ($this->{$property} === null || $this->{$property} === UNDEFINED) { $message = 'Missing required field "' . $property . '" for ' . $this->identity() . ' in ' . $this->_context; foreach (static::$_nested as $class => $nested) { $nestedProperty = is_array($nested) ? $nested[0] : $nested; if ($property === $nestedProperty) { if ($this instanceof Swagger) { $message = 'Required @' . str_replace('Swagger\\Annotations\\', 'SWG\\', $class) . '() not found'; } elseif (is_array($nested)) { $message = $this->identity() . ' requires at least one @' . str_replace('Swagger\\Annotations\\', 'SWG\\', $class) . '() in ' . $this->_context; } else { $message = $this->identity() . ' requires a @' . str_replace('Swagger\\Annotations\\', 'SWG\\', $class) . '() in ' . $this->_context; } break; } } Logger::notice($message); } } } // Report invalid types foreach (static::$_types as $property => $type) { $value = $this->{$property}; if ($value === null || $value === UNDEFINED) { continue; } if (is_string($type)) { if ($this->validateType($type, $value) === false) { $valid = false; Logger::notice($this->identity() . '->' . $property . ' is a "' . gettype($value) . '", expecting a "' . $type . '" in ' . $this->_context); } } elseif (is_array($type)) { // enum? if (in_array($value, $type) === false) { Logger::notice($this->identity() . '->' . $property . ' "' . $value . '" is invalid, expecting "' . implode('", "', $type) . '" in ' . $this->_context); } } else { throw new Exception('Invalid ' . get_class($this) . '::$_types[' . $property . ']'); } } $parents[] = $this; return self::_validate($this, $parents, $skip) ? $valid : false; }
/** @inheritdoc */ public function validate($parents = [], $skip = []) { if (in_array($this, $skip, true)) { return true; } $valid = parent::validate($parents, $skip); if (empty($this->ref)) { if ($this->in === 'body') { if ($this->schema === null) { Logger::notice('Field "schema" is required when ' . $this->identity() . ' is in "' . $this->in . '" in ' . $this->_context); $valid = false; } } else { $validTypes = ['string', 'number', 'integer', 'boolean', 'array', 'file']; if ($this->type === null) { Logger::notice($this->identity() . '->type is required when ' . $this->_identity([]) . '->in == "' . $this->in . '" in ' . $this->_context); $valid = false; } elseif ($this->type === 'array' && $this->items === null) { Logger::notice($this->identity() . '->items required when ' . $this->_identity([]) . '->type == "array" in ' . $this->_context); $valid = false; } elseif (in_array($this->type, $validTypes) === false) { $valid = false; Logger::notice($this->identity() . '->type must be "' . implode('", "', $validTypes) . '" when ' . $this->_identity([]) . '->in != "body" in ' . $this->_context); } } } return $valid; }
<?php try { $project_root = realpath(__DIR__ . '/../../'); require_once $project_root . '/vendor/autoload.php'; $outputPath = realpath(__DIR__ . '/json/'); $projectPaths = array($project_root . '/server/library/Cms/Request/', $project_root . '/server/library/Cms/Response/', $project_root . '/server/application/controllers/'); $excludePaths = array(); // Possible options and their default values. $options = array('url' => null, 'default-base-path' => null, 'default-api-version' => null, 'default-swagger-version' => '1.2', 'version' => false, 'suffix' => '.{format}', 'help' => false, 'debug' => false); \Swagger\Logger::getInstance()->log = function ($entry, $type) { $type = $type === E_USER_NOTICE ? 'INFO' : 'WARN'; if ($entry instanceof Exception) { $entry = $entry->getMessage(); } echo '[', $type, '] ', $entry, PHP_EOL; }; $swagger = new \Swagger\Swagger($projectPaths, $excludePaths); $resourceListOptions = array('output' => 'json', 'suffix' => $options['suffix'], 'basePath' => $options['url'], 'apiVersion' => $options['default-api-version'], 'swaggerVersion' => $options['default-swagger-version']); $resourceOptions = array('output' => 'json', 'defaultBasePath' => $options['default-base-path'], 'defaultApiVersion' => $options['default-api-version'], 'defaultSwaggerVersion' => $options['default-swagger-version']); $resourceName = false; $output = array(); foreach ($swagger->getResourceNames() as $resourceName) { $json = $swagger->getResource($resourceName, $resourceOptions); $resourceName = str_replace(DIRECTORY_SEPARATOR, '-', ltrim($resourceName, DIRECTORY_SEPARATOR)); $output[$resourceName] = $json; } if ($output) { if (file_exists($outputPath) && !is_dir($outputPath)) { throw new RuntimeException(sprintf('[%s] is not a directory', $outputPath)); } else {
/** * Example: @Annotation("hello", 124) would call setNestedValues with array("hello", 123) * @param array $values */ protected function setNestedValue($value) { Logger::notice('Unexpected value "' . $value . '", direct values not supported for ' . get_class($this) . ' in ' . $this->_context); }
public function validate($skip = []) { if (in_array($this, $skip, true)) { return true; } $valid = parent::validate($skip); if (!$this->ref && $this->type === 'array' && $this->items === null) { Logger::notice('@SWG\\Items() is required when ' . $this->identity() . ' has type "array" in ' . $this->_context); $valid = false; } return $valid; }
public function validate() { if (count($this->_partials) !== 0) { return true; } if (empty($this->nickname)) { Logger::notice('Required field "nickname" is missing for "' . $this->identity() . '" in ' . $this->_context); } Swagger::checkDataType($this->type, $this->_context); foreach ($this->parameters as $parameter) { if ($parameter->validate() == false) { return false; } } Items::validateContainer($this); Produces::validateContainer($this); Consumes::validateContainer($this); return true; }
/** * Use doctrine to parse the comment block and return the detected annotations. * * @param string $comment a T_DOC_COMMENT. * @param Context $context * @return array Annotations */ public function fromComment($comment, $context = null) { if ($context === null) { $context = new Context(['comment' => $comment]); } else { $context->comment = $comment; } try { self::$context = $context; if ($context->is('annotations') === false) { $context->annotations = []; } $comment = preg_replace_callback('/^[\\t ]*\\*[\\t ]+/m', function ($match) { // Replace leading tabs with spaces. // Workaround for http://www.doctrine-project.org/jira/browse/DCOM-255 return str_replace("\t", ' ', $match[0]); }, $comment); $annotations = $this->docParser->parse($comment, $context); self::$context = null; return $annotations; } catch (Exception $e) { self::$context = null; if (preg_match('/^(.+) at position ([0-9]+) in ' . preg_quote($context, '/') . '\\.$/', $e->getMessage(), $matches)) { $errorMessage = $matches[1]; $errorPos = $matches[2]; $atPos = strpos($comment, '@'); $context->line += substr_count($comment, "\n", 0, $atPos + $errorPos); $lines = explode("\n", substr($comment, $atPos, $errorPos)); $context->character = strlen(array_pop($lines)) + 1; // position starts at 0 character starts at 1 Logger::warning(new Exception($errorMessage . ' in ' . $context, $e->getCode(), $e)); } else { Logger::warning($e); } return []; } }
protected function tearDown() { $this->assertCount(0, $this->expectedLogMessages, count($this->expectedLogMessages) . ' Swagger\\Logger messages were not triggered'); Logger::getInstance()->log = $this->originalLogger; parent::tearDown(); }
public function validate() { if (is_string($this->required)) { $required = filter_var($this->required, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($required === null) { Logger::notice('Invalid `required="' . $this->required . '"` for ' . $this->identity() . ' expecting `required=true` or `required=false` in ' . $this->_context); } else { Logger::notice('Expecting a boolean, got a string `required="' . $this->required . '"` instead of `required=' . ($required ? 'true' : 'false') . '` for ' . $this->identity() . ' in ' . $this->_context); $this->required = $required; } } Items::validateContainer($this); return true; }
public function validate() { if ($this->swagger) { return $this->swagger->validate(); } Logger::notice('No swagger target set. Run the MergeIntoSwagger processor before validate()'); return false; }
public function validate($parents = [], $skip = []) { if (in_array($this, $skip, true)) { return true; } $valid = parent::validate($parents, $skip); if ($this->responses !== null) { foreach ($this->responses as $response) { if ($response->response !== 'default' && preg_match('/^[12345]{1}[0-9]{2}$/', $response->response) === 0) { Logger::notice('Invalid value "' . $response->response . '" for ' . $response->_identity([]) . '->response, expecting "default" or a HTTP Status Code in ' . $response->_context); } } } return $valid; }