An open API service indexing awesome lists of open source software.

https://github.com/spiral-packages/grpc-shared

gRPC shared package for PHP services
https://github.com/spiral-packages/grpc-shared

grpc php shared

Last synced: 7 months ago
JSON representation

gRPC shared package for PHP services

Awesome Lists containing this project

README

          

# gRPC Shared package template

> **โš ๏ธ Important Notice**
>
> This repository is an **example implementation** of a PHP gRPC code generation tool. It is **not intended to be used
directly as a Composer package**.
>
> **To use this generator:**
> 1. **Fork this repository** to your own GitHub account or organization
> 2. **Customize the code** according to your specific requirements (namespaces, directory structures, etc.)
> 3. **Follow the setup instructions** below to configure it for your project
> 4. **Maintain your own version** with your specific business logic and requirements
>
> This example demonstrates the concepts and patterns for building gRPC service generators, but you'll need to adapt it
> to your own infrastructure and conventions.

## ๐Ÿงญ Purpose of the Component

The Generator component is a powerful PHP code generation tool that automatically creates client-side code from Protocol
Buffer (protobuf) service definitions. It transforms gRPC service interfaces into fully functional PHP classes,
commands, handlers, mappers, and configuration files. This component eliminates the need for manual boilerplate code
creation and ensures consistency across all generated services.

## ๐Ÿš€ Getting Started with Your Fork

### Step 1: Fork and Customize

1. **Fork this repository** to your GitHub account/organization
2. **Clone your fork** locally:
```bash
git clone https://github.com/YOUR-USERNAME/YOUR-FORKED-REPO.git
cd YOUR-FORKED-REPO
```

3. **Customize the generator** for your needs:

- Adjust file paths and directory structures
- Update service client generation logic
- Customize validation rules and annotations

### Step 2: Set Up Your Project Structure

Update your main project's `composer.json` to include your forked generator:

```json
{
"require-dev": {
"your-company/grpc-generator": "dev-main"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/YOUR-USERNAME/YOUR-FORKED-REPO"
}
]
}
```

### Step 3: Install and Configure

```bash
# Install your customized generator
composer install

# Download required protoc binary
composer download

# Create your console entry point if needed
# (See console setup instructions below)
```

## ๐Ÿง List of Actors

### System Actors

- **Proto Files** - Source definitions containing service interfaces and message structures
- **gRPC Services** - Remote services that the generated code will communicate with
- **PHP Application** - The host application that uses the generated service clients
- **Protoc Compiler** - The underlying Protocol Buffer compiler that processes .proto files
- **File System** - Stores generated PHP classes and configuration files

### ๐Ÿ“š Domain Ubiquitous Terminology

- **Generator Command** - The main CLI command that orchestrates the entire code generation process
- **Service Client** - Generated PHP class that provides methods to call remote gRPC service methods
- **Command Class** - Generated data transfer objects that represent request/response messages
- **Handler Class** - Generated classes that process commands and delegate to service clients
- **Mapper Class** - Generated classes that transform between protobuf messages and PHP objects
- **Bootloader** - Generated configuration class that sets up service dependencies and connections
- **Protoc Binary** - The Protocol Buffer compiler executable used to process .proto files
- **Message Fixer** - Post-processing component that enhances generated protobuf message classes
- **Annotations Parser** - Component that extracts metadata from protobuf comments and converts to PHP attributes

## ๐Ÿ“‹ Proto Messages Naming Conventions

To ensure consistent and predictable code generation, follow these naming conventions for your proto files:

### Service Naming

- **Services**: Use `PascalCase` with descriptive names ending in `Service`

```protobuf
service UserService {
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
```

### Message Naming

- **Request messages**: Use `PascalCase` ending with `Request`

```protobuf
message CreateUserRequest {
string email = 1;
string name = 2;
optional int32 age = 3;
}
```

- **Response messages**: Use `PascalCase` ending with `Response`

```protobuf
message CreateUserResponse {
int64 user_id = 1;
string status = 2;
}
```

- **Data transfer objects**: Use `PascalCase` with descriptive names

```protobuf
message UserProfile {
string email = 1;
string display_name = 2;
repeated string roles = 3;
}
```

### Field Naming

- **Field names**: Use `snake_case` for all message fields

```protobuf
message UserProfile {
string first_name = 1; // โœ… Correct
string last_name = 2; // โœ… Correct
string display_name = 3; // โœ… Correct
// string firstName = 4; // โŒ Avoid camelCase
// string DisplayName = 5; // โŒ Avoid PascalCase
}
```

### Enum Naming

- **Enum types**: Use `PascalCase` with descriptive names
- **Enum values**: Use `SCREAMING_SNAKE_CASE` with enum name prefix

```protobuf
enum UserStatus {
USER_STATUS_UNKNOWN = 0; // โœ… Always include zero value
USER_STATUS_ACTIVE = 1;
USER_STATUS_INACTIVE = 2;
USER_STATUS_SUSPENDED = 3;
}
```

### Generated PHP Class Mapping

The generator transforms proto names to PHP classes following these patterns:

| Proto Element | Example Proto | Generated PHP Class |
|------------------|----------------------|---------------------------------------------------------|
| Service | `UserService` | `UserServiceClient` (implements `UserServiceInterface`) |
| Request Message | `CreateUserRequest` | `CreateUserCommand` (in `Command\\` namespace) |
| Response Message | `CreateUserResponse` | `CreateUserResponse` (in `Command\\` namespace) |
| Data Message | `UserProfile` | `UserProfile` (in `Command\\` namespace) |
| Enum | `UserStatus` | `UserStatus` (PHP 8.1+ backed enum) |

### Field Type Conversions

Proto field types are converted to appropriate PHP types:

| Proto Type | PHP Type | Notes |
|-----------------------------|-----------------------|-----------------------|
| `string` | `string` | |
| `int32`, `int64` | `int` | |
| `float`, `double` | `float` | |
| `bool` | `bool` | |
| `bytes` | `string` | Binary data as string |
| `google.protobuf.Timestamp` | `\\DateTimeInterface` | Automatic conversion |
| `google.protobuf.Duration` | `\\DateInterval` | Automatic conversion |
| `repeated T` | `array` | PHP array of type T |
| `optional T` | `T\\|null` | Nullable PHP type |

### Security Annotations

Mark sensitive operations with security annotations:

```protobuf
// @Guarded - Requires authentication
// @Internal - Internal use only
service AdminUserService {
// @Guarded
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);

// @Internal
rpc SyncUserData(SyncUserDataRequest) returns (SyncUserDataResponse);
}
```

### File Organization Best Practices

Organize proto files in a clear hierarchy:

```
proto/
โ”œโ”€โ”€ user/v1/
โ”‚ โ”œโ”€โ”€ user_service.proto # Service definitions
โ”‚ โ”œโ”€โ”€ user_messages.proto # Request/response messages
โ”‚ โ””โ”€โ”€ user_types.proto # Common data types
โ”œโ”€โ”€ payment/v1/
โ”‚ โ”œโ”€โ”€ payment_service.proto
โ”‚ โ””โ”€โ”€ payment_messages.proto
โ””โ”€โ”€ common/
โ”œโ”€โ”€ errors.proto # Common error types
โ””โ”€โ”€ pagination.proto # Shared pagination types
```

## ๐Ÿš€ Quick Start Workflow

Before starting, ensure you have:

- PHP 8.3 or higher
- Composer installed
- Your forked and customized version of this generator
- Access to your proto files repository

### 1.1 Create Proto Files Repository

Create a dedicated Composer package containing only your proto files:

```
proto-files-package/
โ”œโ”€โ”€ composer.json
โ”œโ”€โ”€ README.md
โ””โ”€โ”€ proto/
โ”œโ”€โ”€ user/
โ”‚ โ””โ”€โ”€ v1/
โ”‚ โ”œโ”€โ”€ user_service.proto
โ”‚ โ””โ”€โ”€ user_messages.proto
โ”œโ”€โ”€ payment/
โ”‚ โ””โ”€โ”€ v1/
โ”‚ โ”œโ”€โ”€ payment_service.proto
โ”‚ โ””โ”€โ”€ payment_messages.proto
โ””โ”€โ”€ shared/
โ””โ”€โ”€ common.proto
```

### 1.2 Configure composer.json for Proto Package

```json
{
"name": "your-company/proto-files"
//...
}
```

### 1.3 Version Your Proto Files

Use semantic versioning for your proto files:

- **Major version** (2.0.0): Breaking changes to service interfaces
- **Minor version** (1.1.0): New services or non-breaking additions
- **Patch version** (1.0.1): Bug fixes or documentation updates

### 2.1 Add Dependencies

In your main project's `composer.json`:

```json
{
"require": {
"your-company/proto-files": "^1.0",
"spiral/framework": "^3.0",
"grpc/grpc": "^1.50"
},
"require-dev": {
"your-company/grpc-generator": "dev-main"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/your-company/proto-files"
},
{
"type": "vcs",
"url": "https://github.com/your-company/your-forked-generator"
}
]
}
```

### 2.2 Install Dependencies

```bash
composer install
```

### 2.3 Download Proto Generator Binary

```bash
composer download
```

### 3.1 Update Proto Files Package

When proto files change in your service definitions:

```bash
# In your proto-files repository
git pull origin main
./scripts/release.sh # Run your release script
# Enter new version when prompted (e.g., v1.2.0)
```

### 3.2 Update Your Main Project

```bash
# In your main project
composer update your-company/proto-files
```

### 3.3 Check Updated Dependencies

```bash
composer show your-company/proto-files
# Should show the new version number
```

### 4.1 Execute Generator

```bash
# Single proto directory
php bin/console generate --proto-path /path/to/proto/files

# Multiple proto directories
php bin/console generate --proto-path /path/to/proto1 --proto-path /path/to/proto2

# Short option
php bin/console generate -p /path/to/proto/files

# Example output:
# Compiling `user/v1`:
# โ€ข src/Services/User/v1/UserServiceInterface.php
# โ€ข src/Services/User/v1/CreateUserRequest.php
# โ€ข src/Services/User/v1/GetUserResponse.php
# Running `Generator\\Generators\\ConfigGenerator`:
# Running `Generator\\Generators\\ServiceClientGenerator`:
# Running `Generator\\Generators\\CommandClassGenerator`:
# Done!
```

### 4.2 Verify Generated Files

Check that files were created in the expected locations:

```
src/
โ”œโ”€โ”€ Services/
โ”‚ โ”œโ”€โ”€ User/v1/
โ”‚ โ”‚ โ”œโ”€โ”€ UserServiceClient.php
โ”‚ โ”‚ โ”œโ”€โ”€ UserServiceInterface.php
โ”‚ โ”‚ โ””โ”€โ”€ Messages/...
โ”‚ โ””โ”€โ”€ Payment/v1/
โ”‚ โ”œโ”€โ”€ PaymentServiceClient.php
โ”‚ โ””โ”€โ”€ PaymentServiceInterface.php
โ”œโ”€โ”€ Command/
โ”‚ โ”œโ”€โ”€ User/v1/
โ”‚ โ”‚ โ”œโ”€โ”€ CreateUserCommand.php
โ”‚ โ”‚ โ””โ”€โ”€ GetUserCommand.php
โ”‚ โ””โ”€โ”€ Payment/v1/
โ”œโ”€โ”€ Handler/
โ”œโ”€โ”€ Mapper/
โ”œโ”€โ”€ Bootloader/
โ”‚ โ””โ”€โ”€ ServiceBootloader.php
โ””โ”€โ”€ Config/
โ””โ”€โ”€ GRPCServicesConfig.php
```

### 5.1 Review Generated Files

```bash
# See what files changed
git status

# Review the changes
git diff --name-only
git diff src/Command/
git diff src/Services/
```

### 5.2 Commit with Descriptive Messages

```bash
# Stage generated files
git add src/

# Commit with clear message linking to proto version
git commit -m "Generate service clients for proto-files v1.2.0

- Added new CreateUserV2 command with validation
- Updated PaymentService with refund support
- Regenerated all handlers and mappers

Proto files version: your-company/proto-files@v1.2.0\"

# Push changes
git push origin main
```

### 6.1 Configure Service Endpoints

After generation, configure your environment variables:

```bash
# .env.local
USER_SERVICE_CLIENT_HOST=user-service.internal:9000
PAYMENT_SERVICE_CLIENT_HOST=payment-service.internal:9001

# For development
USER_SERVICE_CLIENT_HOST=localhost:9000
PAYMENT_SERVICE_CLIENT_HOST=localhost:9001
```

### 6.2 Update Docker Compose (if applicable)

```yaml
# docker-compose.yml
services:
app:
environment:
- USER_SERVICE_CLIENT_HOST=user-service:9000
- PAYMENT_SERVICE_CLIENT_HOST=payment-service:9001
depends_on:
- user-service
- payment-service

user-service:
image: your-company/user-service:latest
ports:
- \"9000:9000\"

payment-service:
image: your-company/payment-service:latest
ports:
- \"9001:9001\"
```

### 7.1 Basic Usage Example

```php
userService->createUser($this->context, $command);
}
}
```

### 7.2 Using with Command Handlers

```php
commandBus->handle($command);
}
}
```

## ๐Ÿ”ง How It Works - Detailed Technical Process

### Phase 1: Initialization and Setup

The Generator component begins execution when the `GeneratorCommand` is invoked through the console interface:

```php
php bin/console generate
```

**Step 1.1: Binary Verification**

- The system checks for the existence of `protoc-gen-php-grpc` binary in the root directory
- This binary is essential for generating PHP gRPC client code from proto files
- If missing, the process terminates with instructions to run `composer download`

**Step 1.2: Directory Discovery**

- The command scans configured proto file directories (`$protoFileDirs`)
- Each directory is validated to ensure it exists and contains `.proto` files
- Non-existent directories are logged as errors but don't stop the process

**Step 1.3: Component Initialization**

- Creates instances of all generator components:
- `ProtoCompiler` - Handles protoc command execution
- `ProtocCommandBuilder` - Builds protoc commands with proper flags
- `CommandExecutor` - Executes shell commands safely
- Array of `GeneratorInterface` implementations for different output types

### Phase 2: Proto File Compilation

**Step 2.1: Command Building**
The `ProtocCommandBuilder` constructs a protoc command with specific parameters:

```bash
protoc --plugin=/path/to/protoc-gen-php-grpc \
--php_out=/tmp/generated \
--php-grpc_out=/tmp/generated \
-I=/vendor/proto-files \
-I=/current/proto/dir \
service.proto message.proto
```

**Step 2.2: File Filtering**

- Only files ending with `.proto` are included
- Google's standard proto files are excluded to avoid conflicts
- Each proto directory is processed independently

**Step 2.3: Temporary Directory Management**

- Creates unique temporary directory using `spl_object_hash($this)`
- Executes protoc command with output directed to temp directory
- Captures both stdout and stderr for error handling

**Step 2.4: Error Handling**

- Monitors protoc exit codes (0 = success, non-zero = error)
- Throws `CompileException` with detailed error output if compilation fails
- Cleans up temporary directories even on failure

**Step 2.5: File Relocation**

- Moves generated files from temp directory to final destination
- Preserves namespace-based directory structure
- Removes temporary files and directories

### Phase 3: PHP Class Generation Pipeline

The Generator runs multiple specialized generators in a specific order to ensure dependencies are properly resolved:

**Step 3.1: Config Generation (`ConfigGenerator`)**

- Creates `GRPCServicesConfig.php` if it doesn't exist
- Defines configuration structure for service connections
- Sets up default credential handling and interceptor configuration
- Only runs once to avoid overwriting custom configurations

**Step 3.2: Service Client Generation (`ServiceClientGenerator`)**
For each `*Interface.php` file found:

- Parses the interface to extract method signatures
- Creates corresponding `*ServiceClient.php` class
- Implements the original interface
- Adds `ServiceClientTrait` for common functionality
- Generates method bodies that delegate to `callAction()`
- Creates test files with mock data for each service method

**Step 3.3: Message Processing (`GeneratedMessagesFixer`)**

- Scans all generated protobuf message classes
- Parses docblock comments for annotations
- Converts `@Event` annotations to PHP attributes
- Implements `ProtoEvent` interface where applicable
- Fixes comment formatting and adds proper PHPDoc

**Step 3.4: Command Class Generation (`CommandClassGenerator`)**
This is the most complex phase, involving multiple sub-processes:

**Step 3.4a: Message Parsing**

- Uses `MessageClassParser` to analyze protobuf message classes
- Extracts property information using reflection
- Parses docblock comments to understand field types and constraints
- Creates `PropertyType` objects with full metadata

**Step 3.4b: Command Class Creation**

- Generates DTO classes in the `Command` namespace
- Makes classes `final` and `readonly` for immutability
- Implements `CommandInterface` for request messages
- Adds `JsonSerializable` interface with proper serialization logic

**Step 3.4c: Property Generation**

- Creates constructor with promoted parameters
- Applies proper PHP type hints based on protobuf types
- Handles optional fields with default values
- Converts protobuf enums to PHP enum classes
- Adds validation attributes from protobuf annotations

**Step 3.4d: Handler Generation**

- Creates handler classes for each service method
- Generates `__invoke` methods with proper signatures
- Adds `CommandHandler` attribute for CQRS integration
- Implements delegation to service clients with context passing

**Step 3.4e: Mapper Generation**

- Creates mapper classes extending `AbstractMapper`
- Implements `fromMessage()` method for protobuf to DTO conversion
- Uses Valinor for type-safe object creation
- Handles complex type transformations (timestamps, enums, arrays)

**Step 3.5: Enum Class Generation (`EnumClassGenerator`)**

- Extracts enum definitions from protobuf descriptors
- Creates PHP 8.1+ backed enum classes with integer values
- Handles enum case naming conventions
- Returns default enum value for property initialization

**Step 3.6: Service Interface Enhancement (`ServiceInterfaceAttributesGenerator`)**

- Adds PHP attributes to existing interface methods
- Processes annotations like `@Guarded` and `@Internal`
- Adds proper parameter documentation for `RequestContext`
- Maintains original interface structure while adding metadata

**Step 3.7: Bootloader Generation (`BootloaderGenerator`)**

- Creates or updates `ServiceBootloader.php`
- Generates service configuration with environment variable mapping
- Creates service binding logic for dependency injection
- Adds interceptor chain configuration
- Handles incremental updates without losing custom code

**Step 3.8: Service Provider Generation (`ServiceProviderGenerator`)**

- Creates Laravel-compatible service provider
- Generates singleton bindings for service clients
- Configures gRPC client cores with proper credentials
- Sets up exception mapping and interceptor chains

**Step 3.9: Environment Template Generation (`EnvTemplateGenerator`)**

- Outputs environment variable templates to console
- Creates variables like `USER_SERVICE_CLIENT_HOST=`
- Uses service names to generate consistent variable names
- Provides copy-paste ready configuration templates

### Phase 4: Type System and Transformation

**Step 4.1: Type Factory Processing**
The `TypeFactory` handles complex type transformations:

- Converts protobuf types to PHP types (e.g., `Timestamp` โ†’ `DateTimeInterface`)
- Handles repeated fields as PHP arrays
- Processes nested message types as class references
- Manages special Google types (`Any`, `FieldMask`, `Duration`)

**Step 4.2: Property Type Analysis**
Each field goes through comprehensive analysis:

- Determines if field is optional, required, or repeated
- Extracts default values from annotations
- Applies validation constraints from protobuf options
- Handles enum types with proper case conversion

**Step 4.3: Class Transformation**
The `ClassTransformer` manages namespace operations:

- Converts between different namespace contexts
- Handles path generation for file output
- Manages class name transformations (Interface โ†’ Client)
- Ensures PSR-4 compliance

### Phase 5: Output Generation and File Management

**Step 5.1: File Declaration System**
Uses Spiral's Reactor library for PHP code generation:

- Creates AST-like structures for classes
- Manages imports and namespace declarations
- Handles method generation with proper signatures
- Generates formatted PHP code with proper indentation

**Step 5.2: Persistence Strategy**

- Checks if files exist before overwriting
- Preserves custom modifications where possible
- Uses special markers for generated sections
- Maintains file permissions and structure

**Step 5.3: Error Recovery**

- Continues processing even if individual files fail
- Logs specific errors with file names and reasons
- Provides detailed output in verbose mode
- Cleans up partial outputs on critical failures

### Phase 6: Integration and Validation

**Step 6.1: Cross-Reference Resolution**

- Links command classes to their corresponding handlers
- Connects service clients to their interfaces
- Resolves mapper dependencies
- Updates bootloader with new service registrations

**Step 6.2: Dependency Injection Setup**

- Configures service container bindings
- Sets up interceptor chains
- Handles credential management
- Creates proper service scoping (singleton vs transient)

**Step 6.3: Final Validation**

- Verifies all generated files are syntactically valid
- Checks class loading and autoloading compatibility
- Validates that all required dependencies are available
- Reports generation summary with file counts and any issues

This entire process typically completes in seconds for small to medium service definitions, but can take longer for
large proto files with many services and complex message types.

## ๐Ÿงช Simple Use Cases

### Use Case 1: Adding New Service Integration

**User**: Developer wants to integrate with a new user management service
**System Actions**:

1. Developer places UserService.proto file in the proto directory
2. Developer runs `php console.php generate` command
3. Generator reads the proto file and creates UserServiceClient.php
4. Generator creates command classes for CreateUser, GetUser, UpdateUser requests
5. Generator creates corresponding handler classes and mappers
6. Developer can immediately use `$userClient->createUser($command)` in their code

### Use Case 2: Updating Existing Service

**User**: Business analyst needs to understand what happens when a service definition changes
**System Actions**:

1. Backend team updates the UserService.proto to add new fields
2. Generator re-runs and updates all related PHP classes automatically
3. New fields appear in command classes with proper validation
4. Existing code continues to work due to backward compatibility
5. New features can immediately use the additional fields

### Use Case 3: Environment Configuration

**User**: DevOps engineer deploying to different environments (dev, staging, production)
**System Actions**:

1. Generator creates environment variable templates like `USER_SERVICE_CLIENT_HOST=`
2. DevOps sets appropriate values: dev=localhost:9000, staging=user-service.staging:9000
3. Generated service clients automatically connect to correct endpoints
4. No code changes needed between environments