⚠️ Important NoticeThis 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:
- Fork this repository to your own GitHub account or organization
- Customize the code according to your specific requirements (namespaces, directory structures, etc.)
- Follow the setup instructions below to configure it for your project
- 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.
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.
-
Fork this repository to your GitHub account/organization
-
Clone your fork locally:
git clone https://github.com/YOUR-USERNAME/YOUR-FORKED-REPO.git cd YOUR-FORKED-REPO -
Customize the generator for your needs:
- Adjust file paths and directory structures
- Update service client generation logic
- Customize validation rules and annotations
Update your main project's composer.json to include your forked generator:
{
"require-dev": {
"your-company/grpc-generator": "dev-main"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/YOUR-USERNAME/YOUR-FORKED-REPO"
}
]
}# Install your customized generator
composer install
# Download required protoc binary
composer download
# Create your console entry point if needed
# (See console setup instructions below)- 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
- 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
To ensure consistent and predictable code generation, follow these naming conventions for your proto files:
- Services: Use
PascalCasewith descriptive names ending inService
service UserService {
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}- Request messages: Use
PascalCaseending withRequest
message CreateUserRequest {
string email = 1;
string name = 2;
optional int32 age = 3;
}- Response messages: Use
PascalCaseending withResponse
message CreateUserResponse {
int64 user_id = 1;
string status = 2;
}- Data transfer objects: Use
PascalCasewith descriptive names
message UserProfile {
string email = 1;
string display_name = 2;
repeated string roles = 3;
}- Field names: Use
snake_casefor all message fields
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 types: Use
PascalCasewith descriptive names - Enum values: Use
SCREAMING_SNAKE_CASEwith enum name prefix
enum UserStatus {
USER_STATUS_UNKNOWN = 0; // ✅ Always include zero value
USER_STATUS_ACTIVE = 1;
USER_STATUS_INACTIVE = 2;
USER_STATUS_SUSPENDED = 3;
}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) |
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<T> |
PHP array of type T |
optional T |
T\|null |
Nullable PHP type |
Mark sensitive operations with security annotations:
// @Guarded - Requires authentication
// @Internal - Internal use only
service AdminUserService {
// @Guarded
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
// @Internal
rpc SyncUserData(SyncUserDataRequest) returns (SyncUserDataResponse);
}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
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
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
{
"name": "your-company/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
In your main project's composer.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"
}
]
}composer installcomposer downloadWhen proto files change in your service definitions:
# 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)# In your main project
composer update your-company/proto-filescomposer show your-company/proto-files
# Should show the new version number# 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!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
# See what files changed
git status
# Review the changes
git diff --name-only
git diff src/Command/
git diff src/Services/# 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 mainAfter generation, configure your environment variables:
# .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# 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\"<?php
use Internal\\Shared\\gRPC\\Services\\User\\v1\\UserServiceClient;
use Internal\\Shared\\gRPC\\Command\\User\\v1\\CreateUserCommand;
use Internal\\Shared\\gRPC\\RequestContext;
class UserController
{
public function __construct(
private UserServiceClient $userService,
private RequestContext $context
) {}
public function createUser(array $data): UserResponse
{
$command = new CreateUserCommand(
email: $data['email'],
name: $data['name'],
age: $data['age'] ?? null
);
return $this->userService->createUser($this->context, $command);
}
}<?php
use Spiral\Cqrs\CommandBusInterface;
use Internal\Shared\gRPC\Command\User\v1\CreateUserCommand;
class UserRegistrationService
{
public function __construct(
private CommandBusInterface $commandBus
) {}
public function registerUser(array $userData): void
{
$command = new CreateUserCommand(
email: $userData['email'],
name: $userData['name']
);
// Command handler will automatically use generated handler
$result = $this->commandBus->handle($command);
}
}The Generator component begins execution when the GeneratorCommand is invoked through the console interface:
php bin/console generateStep 1.1: Binary Verification
- The system checks for the existence of
protoc-gen-php-grpcbinary 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
.protofiles - 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 executionProtocCommandBuilder- Builds protoc commands with proper flagsCommandExecutor- Executes shell commands safely- Array of
GeneratorInterfaceimplementations for different output types
Step 2.1: Command Building
The ProtocCommandBuilder constructs a protoc command with specific parameters:
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.protoStep 2.2: File Filtering
- Only files ending with
.protoare 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
CompileExceptionwith 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
The Generator runs multiple specialized generators in a specific order to ensure dependencies are properly resolved:
Step 3.1: Config Generation (ConfigGenerator)
- Creates
GRPCServicesConfig.phpif 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.phpclass - Implements the original interface
- Adds
ServiceClientTraitfor 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
@Eventannotations to PHP attributes - Implements
ProtoEventinterface 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
MessageClassParserto analyze protobuf message classes - Extracts property information using reflection
- Parses docblock comments to understand field types and constraints
- Creates
PropertyTypeobjects with full metadata
Step 3.4b: Command Class Creation
- Generates DTO classes in the
Commandnamespace - Makes classes
finalandreadonlyfor immutability - Implements
CommandInterfacefor request messages - Adds
JsonSerializableinterface 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
__invokemethods with proper signatures - Adds
CommandHandlerattribute 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
@Guardedand@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
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
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
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.
User: Developer wants to integrate with a new user management service System Actions:
- Developer places UserService.proto file in the proto directory
- Developer runs
php console.php generatecommand - Generator reads the proto file and creates UserServiceClient.php
- Generator creates command classes for CreateUser, GetUser, UpdateUser requests
- Generator creates corresponding handler classes and mappers
- Developer can immediately use
$userClient->createUser($command)in their code
User: Business analyst needs to understand what happens when a service definition changes System Actions:
- Backend team updates the UserService.proto to add new fields
- Generator re-runs and updates all related PHP classes automatically
- New fields appear in command classes with proper validation
- Existing code continues to work due to backward compatibility
- New features can immediately use the additional fields
User: DevOps engineer deploying to different environments (dev, staging, production) System Actions:
- Generator creates environment variable templates like
USER_SERVICE_CLIENT_HOST= - DevOps sets appropriate values: dev=localhost:9000, staging=user-service.staging:9000
- Generated service clients automatically connect to correct endpoints
- No code changes needed between environments