* @license http://www.apache.org/licenses/LICENSE-2.0 * @link http://phpsx.org */ abstract class LanguageAbstract implements GeneratorInterface { /** @var string */ protected $baseUrl; /** @var string */ protected $namespace; /** @var Environment */ protected $engine; /** * @param string $baseUrl * @param string $namespace */ public function __construct(string $baseUrl, ?string $namespace = null) { $this->baseUrl = $baseUrl; $this->namespace = $namespace; $this->engine = $this->newTemplateEngine(); } /** * @inheritDoc */ public function generate(SpecificationInterface $specification) { $collection = $specification->getResourceCollection(); $definitions = $specification->getDefinitions(); $resources = []; $chunks = new Generator\Code\Chunks(); foreach ($collection as $path => $resource) { $this->generateResource($resource, $definitions, $chunks, $resources); } $this->generateSchema($definitions, $chunks); $this->generateClient($resources, $chunks); return $chunks; } /** * @param Resource $resource * @param DefinitionsInterface $definitions * @param Generator\Code\Chunks $chunks * @param array $resources * @return void */ public function generateResource( Resource $resource, DefinitionsInterface $definitions, Generator\Code\Chunks $chunks, array &$resources, ): void { $className = $this->getClassName($resource->getPath()); if (empty($className)) { return; } $imports = []; $properties = $this->getPathParameterTypes($resource, $definitions); $urlParts = $this->getUrlParts($resource, $properties ?? []); $resources[$className] = [ 'description' => 'Endpoint: ' . $resource->getPath(), 'methodName' => 'get' . substr($className, 0, -8), 'path' => $resource->getPath(), 'properties' => $properties ]; $methods = []; foreach ($resource->getMethods() as $method) { $methodName = $this->getMethodName($method->getOperationId() ?: strtolower($method->getName())); $args = []; $docs = []; // query parameters if ($method->hasQueryParameters()) { $query = TypeFactory::getReference($method->getQueryParameters()); $args['query'] = $this->getType($query); $docs['query'] = $this->getDocType($query); $this->resolveImport($query, $imports); } // request $request = $method->getRequest(); if (!empty($request) && !in_array($method->getName(), ['GET', 'DELETE'])) { $type = $this->resolveType($request, $definitions); $args['data'] = $this->getType($type); $docs['data'] = $this->getDocType($type); $this->resolveImport($type, $imports); } // response $response = $this->getSuccessfulResponse($method); if (!empty($response)) { $type = $this->resolveType($response, $definitions); $return = $this->getType($type); $returnDoc = $this->getDocType($type); $this->resolveImport($type, $imports); } else { $return = null; $returnDoc = null; } $methods[$methodName] = [ 'httpMethod' => $method->getName(), 'description' => $method->getDescription(), 'secure' => $method->hasSecurity(), 'args' => $args, 'docs' => $docs, 'return' => $return, 'returnDoc' => $returnDoc, ]; } $code = $this->engine->render($this->getTemplate(), [ 'baseUrl' => $this->baseUrl, 'namespace' => $this->namespace, 'className' => $className, 'urlParts' => $urlParts, 'resource' => $resource, 'properties' => $properties, 'methods' => $methods, 'imports' => $imports, ]); $chunks->append($this->getFileName($className), $this->getFileContent($code, $className)); } /** * @param \PSX\Api\Resource $resource * @param array $args * @return array */ protected function getUrlParts(Resource $resource, array $args): array { $result = []; reset($args); $parts = explode('/', $resource->getPath()); foreach ($parts as $part) { if (isset($part[0]) && ($part[0] == ':' || $part[0] == '$')) { $pathName = key($args); if ($pathName === null) { throw new \RuntimeException('Missing ' . $part . ' as path parameter'); } $result[] = [ 'type' => 'variable', 'value' => $pathName, ]; next($args); } elseif (!empty($part)) { $result[] = [ 'type' => 'string', 'value' => $part, ]; } } return $result; } private function getPathParameterTypes(Resource $resource, DefinitionsInterface $definitions): ?array { if (!$resource->hasPathParameters()) { return null; } if (!$definitions->hasType($resource->getPathParameters())) { return null; } $type = $definitions->getType($resource->getPathParameters()); if (!$type instanceof StructType) { return null; } $args = []; $properties = $type->getProperties(); foreach ($properties as $name => $property) { $args[$name] = $this->getType($property); } return $args; } /** * @param \PSX\Api\Resource\MethodAbstract $method * @return \PSX\Schema\SchemaInterface|null */ protected function getSuccessfulResponse(Resource\MethodAbstract $method) { $responses = $method->getResponses(); $codes = [200, 201]; foreach ($codes as $code) { if (isset($responses[$code])) { return $responses[$code]; } } return null; } /** * @param DefinitionsInterface $definitions * @param Generator\Code\Chunks $chunks */ protected function generateSchema(DefinitionsInterface $definitions, Generator\Code\Chunks $chunks) { $schema = new Schema(TypeFactory::getAny(), $definitions); $result = $this->getGenerator()->generate($schema); if ($result instanceof Generator\Code\Chunks) { foreach ($result->getChunks() as $identifier => $code) { $chunks->append($this->getFileName($identifier), $this->getFileContent($code, $identifier)); } } else { $chunks->append($this->getFileName('RootSchema'), $result); } } /** * @param array $resources * @param Generator\Code\Chunks $chunks * @throws \Twig\Error\LoaderError * @throws \Twig\Error\RuntimeError * @throws \Twig\Error\SyntaxError */ protected function generateClient(array $resources, Generator\Code\Chunks $chunks) { $identifier = 'Client'; $code = $this->engine->render($this->getClientTemplate(), [ 'baseUrl' => $this->baseUrl, 'namespace' => $this->namespace, 'resources' => $resources, ]); $chunks->append($this->getFileName($identifier), $this->getFileContent($code, $identifier)); } /** * Returns the type of the provided property for the specific language * * @param \PSX\Schema\TypeInterface $property * @return string */ protected function getType(TypeInterface $property): string { $generator = $this->getGenerator(); if ($generator instanceof Generator\TypeAwareInterface) { return $generator->getType($property); } else { return ''; } } /** * Returns a type which is used in the documentation * * @param \PSX\Schema\TypeInterface $property * @return string */ protected function getDocType(TypeInterface $property): string { $generator = $this->getGenerator(); if ($generator instanceof Generator\TypeAwareInterface) { return $generator->getDocType($property); } else { return ''; } } /** * @param string $path * @return string */ protected function getClassName(string $path): string { $parts = explode('/', $path); $result = []; $i = 0; foreach ($parts as $part) { if (substr($part, 0, 1) === ':') { $part = ($i === 0 ? 'By' : 'And') . ucfirst(substr($part, 1)); $i++; } elseif (substr($part, 0, 1) === '$') { $part = ($i === 0 ? 'By' : 'And') . ucfirst(substr($part, 1, strpos($part, '<'))); $i++; } $result[] = ucfirst(preg_replace('/[^A-Za-z0-9_]+/', '', $part)); } $className = implode('', $result); $className = str_replace(' ', '', ucwords(str_replace('_', ' ', $className))); return $className . 'Resource'; } /** * @param string $code * @param string $identifier * @return string */ protected function getFileContent(string $code, string $identifier): string { return $code; } /** * @return string */ abstract protected function getTemplate(): string; /** * @return string */ abstract protected function getClientTemplate(): string; /** * @return \PSX\Schema\GeneratorInterface */ abstract protected function getGenerator(): SchemaGeneratorInterface; /** * @param string $identifier * @return string */ abstract protected function getFileName(string $identifier): string; /** * @param string $methodName * @return string */ private function getMethodName(string $methodName): string { $parts = explode('_', str_replace(['.', ' '], '_', $methodName)); $parts = array_map(function($part){ return ucfirst(preg_replace('/[^A-Za-z0-9_]+/', '', $part)); }, $parts); return lcfirst(implode('', $parts)); } /** * Resolves a reference type in case it points to a non struct or map type * * @param string $name * @param DefinitionsInterface $definitions * @return ReferenceType|TypeInterface */ private function resolveType(string $name, DefinitionsInterface $definitions): TypeInterface { $resolved = $definitions->getType($name); if (!$resolved instanceof StructType && !$resolved instanceof MapType) { return $resolved; } return TypeFactory::getReference($name); } private function resolveImport(TypeInterface $type, array &$imports) { if ($type instanceof ReferenceType) { $imports[$type->getRef()] = $type->getRef(); if ($type->getTemplate()) { foreach ($type->getTemplate() as $t) { $imports[$t] = $t; } } } elseif ($type instanceof MapType && $type->getAdditionalProperties() instanceof TypeInterface) { $this->resolveImport($type->getAdditionalProperties(), $imports); } elseif ($type instanceof ArrayType && $type->getItems() instanceof TypeInterface) { $this->resolveImport($type->getItems(), $imports); } elseif ($type instanceof UnionType && $type->getOneOf()) { foreach ($type->getOneOf() as $item) { $this->resolveImport($item, $imports); } } elseif ($type instanceof IntersectionType) { foreach ($type->getAllOf() as $item) { $this->resolveImport($item, $imports); } } } /** * @return Environment */ private function newTemplateEngine(): Environment { $loader = new FilesystemLoader([__DIR__ . '/Language']); $engine = new Environment($loader); return $engine; } } __halt_compiler();----SIGNATURE:----MNSf/lnwwoZeCiOB2S/3rUjm8j24/wcVJsqYyAY4e6a+JhwmYBgJ6+sA//ZYuLyTE8WqYVu9UZ95c3Sx3+pkFowD8jNrwIGm0B+vvb4P/kxaeJaSZdHe3bZA4JgYWUQhTZOimuMlApS9KPDdaRzUUE1SHwzQHCUJ0SOYhsi/M9PX2j1xUjHx2gGM9qhXgrtQbAeMkiMZvheOPVeX/fKFRBcRZuA1BIizdYWo1cS/JaJLumc1rvJtK/s/Es4QkbutiEB8GNzqsi59+S9H4b4g0ZZTXKBtOKKGBst9sNLh2ngOTeVuRwAVrJdHMR4rHfxqRDPofJ498MC6/HC3lBYrt7ABvC/1T2DthhrTQlXSdd+G2MtiehxgX2s+oLvjZ8BVgDyTiFpdMH5ByVhXWMHaY35TZaRirgalVa/GdiHR8vRVMHhW67dULZKrKQJtZ57bU71zjLMDUteLEZQ/7buW2+d/cf3JOPvpnGjPV+/65TvM/qr1uDqwOhuxODIArQFBjH/MFwbs2kUEFiRpAwtPJYymlcZl22EFr8mYowbly67D5ExMreG02yiMK5kg0OQ3G4PWgLib97wUTXsuO+JpA7fcLfwPbWaJ+xaVYAKics32A1Ye8zlgAcjPe9SCSpNaxtVpUwx2Vs96a8S+QD2Mr9xAkp/DGiFheFPFX22zgu8=----ATTACHMENT:----OTA3MDY3NDYzMjUxMzgyMCAxOTE3MTkwNDE3MDgzNjI5IDkzNjc4MTY0NTY5MjQ3NjM=