Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"Testing\\BasicBidiStreaming\\": "tests/Unit/ProtoTests/BasicBidiStreaming/out/src",
"Testing\\BasicClientStreaming\\": "tests/Unit/ProtoTests/BasicClientStreaming/out/src",
"Testing\\BasicDiregapic\\": "tests/Unit/ProtoTests/BasicDiregapic/out/src",
"Testing\\BasicGrpcOnly\\": "tests/Unit/ProtoTests/BasicGrpcOnly/out/src",
"Testing\\Basicgrpconly\\": "tests/Unit/ProtoTests/BasicGrpcOnly/out/src",
"Testing\\BasicLro\\": "tests/Unit/ProtoTests/BasicLro/out/src",
"Testing\\BasicOneof\\": "tests/Unit/ProtoTests/BasicOneof/out/src",
"Testing\\BasicOneofNew\\": "tests/Unit/ProtoTests/BasicOneofNew/out/src",
Expand Down
17 changes: 15 additions & 2 deletions src/Ast/AST.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,11 @@ protected function clone(callable $fnOnClone)
/**
* Create a PHP file.
*
* @param ?PhpClass $class The class to be contained within this file.
* @param PhpClass|PhpInterface|null $class The class to be contained within this file.
*
* @return PhpFile
*/
public static function file(?PhpClass $class): PhpFile
public static function file(PhpClass|PhpInterface|null $class): PhpFile
{
return new PhpFile($class);
}
Expand All @@ -182,6 +182,19 @@ public static function class(
return new PhpClass($type, $extends, $final, $abstract);
}

/**
* Create an interface.
*
* @param Type $type The type of the class to create.
* @param ?ResolvedType $extends
*
* @return PhpInterface
*/
public static function interface(Type $type, ?ResolvedType $extends = null): PhpInterface
{
return new PhpInterface($type, $extends);
}

/**
* Create a class constant.
*
Expand Down
28 changes: 26 additions & 2 deletions src/Ast/PhpClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,16 @@ public function __construct(
public bool $abstract
) {
$this->traits = Set::new();
$this->interfaces = Set::new();
$this->members = Vector::new();
}

/** @var Set *Readonly* Set of ResolvedType; the traits used by this class. */
public Set $traits;

/** @var Set *Readonly* Set of ResolvedType; the interfaces implemented by this class. */
public Set $interfaces;

/** @var Vector *Readonly* Vector of PhpClassMember; all members of this class. */
public Vector $members;

Expand All @@ -70,6 +74,24 @@ public function withTrait(?ResolvedType $trait): PhpClass
}
return $this->clone(fn ($clone) => $clone->traits = $clone->traits->add($trait));
}
/**
* Create a class with an additional interface.
*
* @param ?ResolvedType $interface Interface to add. Must be a type which is an interface.
*
* @return PhpClass
*/
public function withInterface(?ResolvedType $interface): PhpClass
{
// No-op, just return the same class.
if (is_null($interface)) {
return $this;
}
if (!$interface->type->isClass()) {
throw new Exception('Only classes (interfaces) may be used as an interface.');
}
return $this->clone(fn ($clone) => $clone->interfaces = $clone->interfaces->add($interface));
}

/**
* Create a class with an additional member.
Expand Down Expand Up @@ -120,8 +142,10 @@ public function toCode(): string

return
$this->phpDocToCode() .
"{$class} {$this->type->name}{$extends}\n" .
"{\n" .
"{$class} {$this->type->name}{$extends}" .
(count($this->interfaces) >= 1 ? ' implements ' : '') .
$this->interfaces->toVector()->map(fn ($x) => "{$x->toCode()}")->join(', ') .
"\n{\n" .
$this->traits->toVector()->map(fn ($x) => "use {$x->toCode()};\n")->join() .
(count($this->traits) >= 1 ? "\n" : '') .
$this->members->map(fn ($x) => $x->toCode() . "\n")->join() .
Expand Down
6 changes: 3 additions & 3 deletions src/Ast/PhpFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@

final class PhpFile extends AST
{
public function __construct(?PhpClass $class)
public function __construct(PhpClass|PhpInterface|null $classOrInterface)
{
$this->class = $class;
$this->class = $classOrInterface;
$this->uses = Set::new();
$this->headerLines = Vector::new();
}

public ?PhpClass $class;
public PhpClass|PhpInterface|null $class;
private ?AST $block;
private Set $uses;
private Vector $headerLines;
Expand Down
89 changes: 89 additions & 0 deletions src/Ast/PhpInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
declare(strict_types=1);

namespace Google\Generator\Ast;

use Google\Generator\Collections\Vector;
use Google\Generator\Utils\ResolvedType;
use Google\Generator\Utils\Type;
use RuntimeException;

/** An interface definition. */
final class PhpInterface extends AST
{
use HasPhpDoc;

/**
* @param Type $type *Readonly* The type of this class.
* @param ?ResolvedType $extends
*/
public function __construct(
public Type $type,
private ?ResolvedType $extends
) {
$this->members = Vector::new();
}

/** @var Vector *Readonly* Vector of PhpClassMember; all members of this class. */
public Vector $members;

/**
* Create a class with an additional member.
*
* @param ?PhpClassMember $member The member to add. Ignored if null.
*
* @return PhpInterface
*/
public function withMember(?PhpClassMember $member): PhpInterface
{
return is_null($member) ? $this :
$this->clone(fn ($clone) => $clone->members = $clone->members->append($member));
}

/**
* Create a class with additional members.
*
* @param Vector $members Vector of PhpClassMember; the members to add.
*
* @return PhpInterface
*/
public function withMembers(Vector $members): PhpInterface
{
$members = $members->filter(fn ($x) => !is_null($x));

return count($members) === 0 ? $this :
$this->clone(fn ($clone) => $clone->members = $clone->members->concat($members));
}

/**
* Generates PHP code of the class.
*
* @throws RuntimeException When $abstract and $final both are set.
*/
public function toCode(): string
{
$extends = is_null($this->extends) ? '' : " extends {$this->extends->toCode()}";

return
$this->phpDocToCode() .
"interface {$this->type->name}{$extends}\n" .
"{\n" .
$this->members->map(fn ($x) => $x->toCode() . "\n")->join() .
"}\n";
}
}
15 changes: 12 additions & 3 deletions src/Ast/PhpMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public function __construct(string $name)
public string $name;
private Vector $params;
protected mixed $body;
private bool $declarationOnly = false;

/** @var string The return type of the function. */
private ?ResolvedType $returnType = null;
Expand Down Expand Up @@ -75,6 +76,11 @@ public function withReturnType(ResolvedType $returnType): PhpMethod
return $this->clone(fn ($clone) => $clone->returnType = $returnType);
}

public function declarationOnly(): PhpMethod
{
return $this->clone(fn ($clone) => $clone->declarationOnly = true);
}

public function getName(): string
{
return $this->name;
Expand All @@ -90,8 +96,11 @@ public function toCode(): string
$this->phpDocToCode() .
$this->accessToCode() .
$fnSignatureDeclaration .
'{' . PHP_EOL .
static::toPhp($this->body) .
PHP_EOL . '}' . PHP_EOL;
(
$this->declarationOnly ? ';' :
'{' . PHP_EOL .
static::toPhp($this->body) .
PHP_EOL . '}' . PHP_EOL
);
}
}
10 changes: 6 additions & 4 deletions src/CodeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,12 @@ private static function generateServices(
// [Start V2 GAPIC surface generation]
if ($migrationMode != MigrationMode::PRE_MIGRATION_SURFACE_ONLY) {
$ctx = new SourceFileContext($service->gapicClientType->getNamespace(), $licenseYear);
$file = GapicClientV2Generator::generate($ctx, $service, $generateSnippets);
$code = $file->toCode();
$code = Formatter::format($code);
yield ["src/{$version}Client/{$service->gapicClientV2Type->name}.php", $code];
[$classFile, $interfaceFile] = GapicClientV2Generator::generate($ctx, $service, $generateSnippets);
foreach ([$classFile, $interfaceFile] as $file) {
$code = $file->toCode();
$code = Formatter::format($code);
yield ["src/{$version}Client/{$file->class->type->name}.php", $code];
}

// Unit tests.
$ctx = new SourceFileContext($service->unitTestsV2Type->getNamespace(), $licenseYear);
Expand Down
64 changes: 60 additions & 4 deletions src/Generation/GapicClientV2Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
use Google\Generator\Ast\PhpClassMember;
use Google\Generator\Ast\PhpDoc;
use Google\Generator\Ast\PhpFile;
use Google\Generator\Ast\PhpInterface;
use Google\Generator\Ast\PhpMethod;
use Google\Generator\Collections\Map;
use Google\Generator\Collections\Vector;
use Google\Generator\Utils\Helpers;
Expand All @@ -50,7 +52,10 @@ class GapicClientV2Generator
{
private const CALL_OPTIONS_VAR = 'callOptions';

public static function generate(SourceFileContext $ctx, ServiceDetails $serviceDetails, bool $generateSnippets): PhpFile
/**
* @return array<PhpFile>
*/
public static function generate(SourceFileContext $ctx, ServiceDetails $serviceDetails, bool $generateSnippets): array
{
return (new GapicClientV2Generator($ctx, $serviceDetails, $generateSnippets))->generateImpl();
}
Expand All @@ -67,7 +72,10 @@ private function __construct(SourceFileContext $ctx, ServiceDetails $serviceDeta
$this->generateSnippets = $generateSnippets;
}

private function generateImpl(): PhpFile
/**
* @return array<PhpFile>
*/
private function generateImpl(): array
{
// TODO(vNext): Remove the forced addition of these `use` clauses.
$this->ctx->type(Type::fromName(\Google\ApiCore\PathTemplate::class));
Expand All @@ -92,15 +100,27 @@ private function generateImpl(): PhpFile
}
}
// Generate file content
$file = AST::file($this->generateClass())
$classFile = AST::file($class = $this->generateClass())
->withApacheLicense($this->ctx->licenseYear)
// TODO(vNext): Consider if this header is sensible, as it ties this generator to Google cloud.
->withGeneratedFromProtoCodeWarning(
$this->serviceDetails->filePath,
$this->serviceDetails->isGa()
);
// Generate service interface
$interfaceFile = AST::file($this->generateInterface($class))
->withApacheLicense($this->ctx->licenseYear)
// TODO(vNext): Consider if this header is sensible, as it ties this generator to Google cloud.
->withGeneratedFromProtoCodeWarning(
$this->serviceDetails->filePath,
$this->serviceDetails->isGa()
);

// Finalize as required by the source-context; e.g. add top-level 'use' statements.
return $this->ctx->finalize($file);
return [
$this->ctx->finalize($classFile),
$this->ctx->finalize($interfaceFile),
];
}

private function generateClass(): PhpClass
Expand Down Expand Up @@ -142,6 +162,7 @@ private function generateClass(): PhpClass
->withTrait(
$this->serviceDetails->hasResources ? $this->ctx->type(Type::fromName(\Google\ApiCore\ResourceHelperTrait::class)): null
)
->withInterface($this->ctx->type($this->serviceDetails->gapicClientInterfaceType))
->withMember($this->serviceName())
->withMember($this->serviceAddress())
->withMember($this->hasServiceAddressTemplate() ? $this->serviceAddressTemplate() : null)
Expand All @@ -161,6 +182,41 @@ private function generateClass(): PhpClass
->withMember(EmulatorSupportGenerator::generateEmulatorSupport($this->serviceDetails, $this->ctx));
}

private function generateInterface(PhpClass $class): PhpInterface
{
return AST::interface($this->serviceDetails->gapicClientInterfaceType)
->withPhpDoc(PhpDoc::block(
PhpDoc::preFormattedText(
$this->serviceDetails->docLines->skip(1)
->prepend(
'Service Description: ' . ($this->serviceDetails->docLines->firstOrNull() ?? '')
)
),
PhpDoc::text(
'This interface defines the methods available for calling ' .
(
is_null($this->serviceDetails->apiVersion)
? 'the backing service API'
: $this->serviceDetails->shortName . ' version ' . $this->serviceDetails->apiVersion
) . '.'
),
$this->serviceDetails->isGa() ? null : PhpDoc::experimental(),
!$this->serviceDetails->isDeprecated ? null : PhpDoc::deprecated(ServiceDetails::DEPRECATED_MSG),
$this->serviceDetails->streamingOnly ? null : $this->magicAsyncDocs(),
))
->withMembers(
$class->members
// omit constants and properties from the interface
->filter(fn ($x) => $x instanceof PhpMethod)
// omit private, protected, and static methods
->filter(fn ($x) => $x->access->join() === Access::PUBLIC)
// omit "__call" and "__construct"
->filter(fn ($x) => 0 !== strpos($x->name, '__'))
// ensure the members are rendered without their method body
->map(fn ($x) => $x->declarationOnly())
);
}

private function serviceName(): PhpClassMember
{
return AST::constant('SERVICE_NAME')
Expand Down
4 changes: 4 additions & 0 deletions src/Generation/ServiceDetails.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ class ServiceDetails
/** @var Type *Readonly* The type of the service client V2 class. */
public Type $gapicClientV2Type;

/** @var Type *Readonly* The type of the service client interface. */
public Type $gapicClientInterfaceType;

/** @var Type *Readonly* The type of the empty client class. */
public Type $emptyClientType;

Expand Down Expand Up @@ -179,6 +182,7 @@ public function __construct(
$this->gapicClientType = Type::fromName("{$namespace}\\Gapic\\{$desc->getName()}GapicClient");
$this->emptyClientType = Type::fromName("{$namespace}\\{$desc->getName()}Client");
$this->gapicClientV2Type = Type::fromName("{$namespace}\\Client\\{$desc->getName()}Client");
$this->gapicClientInterfaceType = Type::fromName("{$namespace}\\Client\\{$desc->getName()}ClientInterface");
$this->grpcClientType = Type::fromName("{$namespace}\\{$desc->getName()}GrpcClient");
$nsVersionAndSuffix = Helpers::nsVersionAndSuffixPath($namespace);
$unitTestNs = $nsVersionAndSuffix === '' ?
Expand Down
2 changes: 1 addition & 1 deletion src/Utils/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ private function __construct(?Vector $namespaceParts, string $name)
public string $name;

/**
* Does this Type represent a class?
* Does this Type represent a class, trait, or interface?
*
* @return bool
*/
Expand Down
Loading
Loading