class = $class; $this->includeDoc = $includeDoc; } /** * @throws SettingsBuilderException * @return array */ public function load(): array { $schemas = []; $obsolete = []; try { $class = new ReflectionClass( $this->class ); foreach ( $class->getReflectionConstants() as $const ) { if ( !$const->isPublic() ) { continue; } $name = $const->getName(); $schema = $const->getValue(); if ( !is_array( $schema ) ) { continue; } if ( isset( $schema['obsolete'] ) ) { $obsolete[ $name ] = $schema['obsolete']; continue; } if ( $this->includeDoc ) { $doc = $const->getDocComment(); if ( $doc ) { $schema['description'] = $this->normalizeComment( $doc ); } } if ( isset( $schema['dynamicDefault'] ) ) { $schema['dynamicDefault'] = $this->normalizeDynamicDefault( $name, $schema['dynamicDefault'] ); } $schema['default'] ??= null; $schema = self::normalizeJsonSchema( $schema ); $schemas[ $name ] = $schema; } } catch ( ReflectionException $e ) { throw new SettingsBuilderException( 'Failed to load schema from class {class}', [ 'class' => $this->class ], 0, $e ); } return [ 'config-schema' => $schemas, 'obsolete-config' => $obsolete ]; } /** * Returns this file source as a string. * * @return string */ public function __toString(): string { return 'class ' . $this->class; } private function normalizeComment( string $doc ) { $doc = preg_replace( '/^\s*\/\*+\s*|\s*\*+\/\s*$/', '', $doc ); $doc = preg_replace( '/^\s*\**$/m', " ", $doc ); $doc = preg_replace( '/^\s*\**[ \t]?/m', '', $doc ); return $doc; } private function normalizeDynamicDefault( string $name, $spec ) { if ( $spec === true ) { $spec = [ 'callback' => [ $this->class, "getDefault{$name}" ] ]; } if ( is_string( $spec ) ) { $spec = [ 'callback' => $spec ]; } if ( !isset( $spec['callback'] ) ) { $spec['callback'] = [ $this->class, "getDefault{$name}" ]; } // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset per fallback above. if ( $spec['callback'] instanceof Closure ) { throw new SettingsBuilderException( "dynamicDefaults callback for $name must be JSON serializable. " . "Closures are not supported." ); } if ( !is_callable( $spec['callback'] ) ) { $pretty = var_export( $spec['callback'], true ); $pretty = preg_replace( '/\s+/', ' ', $pretty ); throw new SettingsBuilderException( "dynamicDefaults callback for $name is not callable: " . $pretty ); } return $spec; } }