3 Commits

Author SHA1 Message Date
18a182f3cd feat: implement gRPC Greeter service with example proto and enhance makefile
Some checks failed
🧪✨ Tests Workflow / 🧪 ✨ Database Migrations (push) Failing after 18s
🧪✨ Tests Workflow / 🛡️ 🔒 Library Audit (push) Failing after 30s
🧪✨ Tests Workflow / 📝 ✨ Code Lint (push) Failing after 19s
🧪✨ Tests Workflow / 🐙 🔍 Code Sniffer (push) Failing after 13s
🧪✨ Tests Workflow / 🧪 ✅ Unit Tests (push) Failing after 3s
🧪✨ Tests Workflow / 🛡️ 🔒 License Check (push) Failing after 23s
2025-12-04 00:04:57 -05:00
373035d2cc feat: remove outdated PHP gRPC build instructions from makefile 2025-12-03 23:28:46 -05:00
92623941af feat: add gRPC server configuration and initial implementation with example proto 2025-12-03 23:25:41 -05:00
15 changed files with 557 additions and 6 deletions

View File

@@ -6,6 +6,20 @@ server:
rpc:
listen: tcp://127.0.0.1:6001
grpc:
listen: "tcp://0.0.0.0:9001"
pool:
command: "php grpc-worker.php"
num_workers: ${GRPC_WORKERS:-4}
allocate_timeout: 5s
reset_timeout: 5s
destroy_timeout: 5s
stream_timeout: 5s
reflection: ${GRPC_REFLECTION:-true}
health_check: ${GRPC_HEALTH_CHECK:-true}
proto:
- "protos/example.proto"
http:
pool:
allocate_timeout: 5s

View File

@@ -42,6 +42,7 @@ COPY --from=library /app/vendor /app/vendor
# Copy the RoadRunner configuration file and source
ADD src src/
ADD generated generated/
ADD server.php .
ADD .rr.yaml .
ADD config.php .

View File

@@ -4,7 +4,8 @@
"autoload": {
"psr-4": {
"Siteworxpro\\App\\": "src/",
"Siteworxpro\\Tests\\": "tests/"
"Siteworxpro\\Tests\\": "tests/",
"GRPC\\": "generated/GRPC"
}
},
"require": {
@@ -25,7 +26,8 @@
"react/promise": "^3",
"react/async": "^4",
"guzzlehttp/guzzle": "^7.10",
"zircote/swagger-php": "^5.7"
"zircote/swagger-php": "^5.7",
"spiral/roadrunner-grpc": "^3.5"
},
"require-dev": {
"phpunit/phpunit": "^12.4",

142
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "f12aaf0dae6930c226e719a5705e3f91",
"content-hash": "977f74570c671e4d59fd70d5e732c3d2",
"packages": [
{
"name": "adhocore/cli",
@@ -298,6 +298,65 @@
],
"time": "2025-08-10T19:31:58+00:00"
},
{
"name": "google/common-protos",
"version": "4.12.4",
"source": {
"type": "git",
"url": "https://github.com/googleapis/common-protos-php.git",
"reference": "0127156899af0df2681bd42024c60bd5360d64e3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/googleapis/common-protos-php/zipball/0127156899af0df2681bd42024c60bd5360d64e3",
"reference": "0127156899af0df2681bd42024c60bd5360d64e3",
"shasum": ""
},
"require": {
"google/protobuf": "^4.31",
"php": "^8.1"
},
"require-dev": {
"phpunit/phpunit": "^9.6"
},
"type": "library",
"extra": {
"component": {
"id": "common-protos",
"path": "CommonProtos",
"entry": "README.md",
"target": "googleapis/common-protos-php.git"
}
},
"autoload": {
"psr-4": {
"Google\\Api\\": "src/Api",
"Google\\Iam\\": "src/Iam",
"Google\\Rpc\\": "src/Rpc",
"Google\\Type\\": "src/Type",
"Google\\Cloud\\": "src/Cloud",
"GPBMetadata\\Google\\Api\\": "metadata/Api",
"GPBMetadata\\Google\\Iam\\": "metadata/Iam",
"GPBMetadata\\Google\\Rpc\\": "metadata/Rpc",
"GPBMetadata\\Google\\Type\\": "metadata/Type",
"GPBMetadata\\Google\\Cloud\\": "metadata/Cloud",
"GPBMetadata\\Google\\Logging\\": "metadata/Logging"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"description": "Google API Common Protos for PHP",
"homepage": "https://github.com/googleapis/common-protos-php",
"keywords": [
"google"
],
"support": {
"source": "https://github.com/googleapis/common-protos-php/tree/v4.12.4"
},
"time": "2025-09-20T01:29:44+00:00"
},
{
"name": "google/protobuf",
"version": "v4.33.1",
@@ -2943,6 +3002,87 @@
],
"time": "2025-11-13T17:24:29+00:00"
},
{
"name": "spiral/roadrunner-grpc",
"version": "v3.5.2",
"source": {
"type": "git",
"url": "https://github.com/roadrunner-php/grpc.git",
"reference": "916c061de160d6b2f3efc82dcffac0360d84fab8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/roadrunner-php/grpc/zipball/916c061de160d6b2f3efc82dcffac0360d84fab8",
"reference": "916c061de160d6b2f3efc82dcffac0360d84fab8",
"shasum": ""
},
"require": {
"ext-json": "*",
"google/common-protos": "^3.1|^4.0",
"google/protobuf": "^3.7 || ^4.0",
"php": ">=8.1",
"spiral/goridge": "^4.0",
"spiral/roadrunner": "^2024.3 || ^2025.1",
"spiral/roadrunner-worker": "^3.0",
"symfony/polyfill-php83": "*"
},
"require-dev": {
"jetbrains/phpstorm-attributes": "^1.0",
"mockery/mockery": "^1.4",
"phpunit/phpunit": "^10.0",
"spiral/code-style": "^2.2",
"spiral/dumper": "^3.3",
"vimeo/psalm": ">=5.8"
},
"type": "library",
"autoload": {
"psr-4": {
"Spiral\\RoadRunner\\GRPC\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anton Titov (wolfy-j)",
"email": "wolfy-j@spiralscout.com"
},
{
"name": "Pavel Buchnev (butschster)",
"email": "pavel.buchnev@spiralscout.com"
},
{
"name": "Aleksei Gagarin (roxblnfk)",
"email": "alexey.gagarin@spiralscout.com"
},
{
"name": "Maksim Smakouz (msmakouz)",
"email": "maksim.smakouz@spiralscout.com"
},
{
"name": "RoadRunner Community",
"homepage": "https://github.com/spiral/roadrunner/graphs/contributors"
}
],
"description": "High-Performance GRPC server for PHP applications",
"homepage": "https://roadrunner.dev/",
"support": {
"chat": "https://discord.gg/V6EK4he",
"docs": "https://docs.roadrunner.dev",
"forum": "https://forum.roadrunner.dev/",
"issues": "https://github.com/roadrunner-server/roadrunner/issues",
"source": "https://github.com/roadrunner-php/grpc/tree/v3.5.2"
},
"funding": [
{
"url": "https://github.com/sponsors/roadrunner-server",
"type": "github"
}
],
"time": "2025-05-18T13:54:33+00:00"
},
{
"name": "spiral/roadrunner-http",
"version": "v3.6.0",

View File

@@ -0,0 +1,25 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: protos/example.proto
namespace GRPC\GPBMetadata;
class Example
{
public static $is_initialized = false;
public static function initOnce() {
$pool = \Google\Protobuf\Internal\DescriptorPool::getGeneratedPool();
if (static::$is_initialized == true) {
return;
}
$pool->internalAddGeneratedFile(
"\x0A\xE5\x01\x0A\x14protos/example.proto\x12\x0Ahelloworld\"\x1C\x0A\x0CHelloRequest\x12\x0C\x0A\x04name\x18\x01 \x01(\x09\"\x1D\x0A\x0AHelloReply\x12\x0F\x0A\x07message\x18\x01 \x01(\x092I\x0A\x07Greeter\x12>\x0A\x08SayHello\x12\x18.helloworld.HelloRequest\x1A\x16.helloworld.HelloReply\"\x00B1Z\x0Dproto/greeter\xCA\x02\x0CGRPC\\Greeter\xE2\x02\x10GRPC\\GPBMetadatab\x06proto3"
, true);
static::$is_initialized = true;
}
}

View File

@@ -0,0 +1,22 @@
<?php
# Generated by the protocol buffer compiler (roadrunner-server/grpc). DO NOT EDIT!
# source: protos/example.proto
namespace GRPC\Greeter;
use Spiral\RoadRunner\GRPC;
interface GreeterInterface extends GRPC\ServiceInterface
{
// GRPC specific service name.
public const NAME = "helloworld.Greeter";
/**
* @param GRPC\ContextInterface $ctx
* @param HelloRequest $in
* @return HelloReply
*
* @throws GRPC\Exception\InvokeException
*/
public function SayHello(GRPC\ContextInterface $ctx, HelloRequest $in): HelloReply;
}

View File

@@ -0,0 +1,61 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: protos/example.proto
namespace GRPC\Greeter;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\GPBUtil;
use Google\Protobuf\RepeatedField;
/**
* The response message containing the greetings
*
* Generated from protobuf message <code>helloworld.HelloReply</code>
*/
class HelloReply extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>string message = 1;</code>
*/
protected $message = '';
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type string $message
* }
*/
public function __construct($data = NULL) {
\GRPC\GPBMetadata\Example::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>string message = 1;</code>
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* Generated from protobuf field <code>string message = 1;</code>
* @param string $var
* @return $this
*/
public function setMessage($var)
{
GPBUtil::checkString($var, True);
$this->message = $var;
return $this;
}
}

View File

@@ -0,0 +1,61 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: protos/example.proto
namespace GRPC\Greeter;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\GPBUtil;
use Google\Protobuf\RepeatedField;
/**
* The request message containing the user's name.
*
* Generated from protobuf message <code>helloworld.HelloRequest</code>
*/
class HelloRequest extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>string name = 1;</code>
*/
protected $name = '';
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type string $name
* }
*/
public function __construct($data = NULL) {
\GRPC\GPBMetadata\Example::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>string name = 1;</code>
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Generated from protobuf field <code>string name = 1;</code>
* @param string $var
* @return $this
*/
public function setName($var)
{
GPBUtil::checkString($var, True);
$this->name = $var;
return $this;
}
}

3
generated/README.md Normal file
View File

@@ -0,0 +1,3 @@
### Note to Developers
Only generated files are allowed in this directory.
Please do not add any other files here manually.

14
grpc-worker.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
use Siteworxpro\App\Grpc;
require __DIR__ . '/vendor/autoload.php';
try {
$server = new Grpc();
$server->start();
} catch (\Exception $e) {
echo $e->getMessage();
exit(1);
}

120
makefile Normal file
View File

@@ -0,0 +1,120 @@
# Makefile (enhanced)
SHELL := /bin/sh
.DEFAULT_GOAL := help
# Reusable vars
DOCKER := docker compose
COMPOSER_RUNTIME := composer-runtime
DEV_RUNTIME := dev-runtime
MIGRATION_CONTAINER := migration-container
PROTOC_GEN_DIR := ./protoc-gen-php-grpc-2025.1.5-darwin-arm64
PROTOC_GEN := $(PROTOC_GEN_DIR)/protoc-gen-php-grpc
PROTOC_URL := https://github.com/roadrunner-server/roadrunner/releases/download/v2025.1.5/protoc-gen-php-grpc-2025.1.5-darwin-arm64.tar.gz
COMPOSER := $(DOCKER) exec $(COMPOSER_RUNTIME) sh -c
DEV := $(DOCKER) exec $(DEV_RUNTIME) sh -c
# Colors
GREEN := \033[32m
YELLOW := \033[33m
RESET := \033[0m
# Align width for help display
HELP_COL_WIDTH := 26
# Help: auto-generate from targets with "##" comments
help: ## Show this help
@echo "Available commands:"
@awk -F':|##' '/^[a-zA-Z0-9._-]+:.*##/ {printf " %-$(HELP_COL_WIDTH)s - %s\n", $$1, $$3}' $(MAKEFILE_LIST) | sort
start: ## Start the development runtime container
@printf "$(GREEN)Starting $(DEV_RUNTIME)$(RESET)\n"
$(DOCKER) up $(DEV_RUNTIME) -d --no-recreate
sh: ## Open a shell in the development runtime container
@$(MAKE) start
$(DOCKER) exec $(DEV_RUNTIME) sh
run: ## Run the application server in the development runtime container
@$(MAKE) start
$(DEV) "rr serve"
stop: ## Stop and remove the development runtime container
@printf "$(YELLOW)Stopping all containers$(RESET)\n"
$(DOCKER) down
restart: ## Restart dev container (stop + start)
@$(MAKE) stop
@$(MAKE) start
rebuild: ## Rebuild containers (useful after Dockerfile changes)
@printf "$(YELLOW)Rebuilding containers$(RESET)\n"
$(DOCKER) build
$(DOCKER) up --force-recreate --build -d
ps: ## Show docker compose ps
$(DOCKER) ps
migrate: ## Run database migrations in the migration container
$(DOCKER) up $(MIGRATION_CONTAINER)
# Composer helpers
composer-install: ## Install PHP dependencies in the composer runtime container
$(COMPOSER) "composer install --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-reqs"
composer-require: ## Require a PHP package in the composer runtime container (usage: make composer-require package=vendor/package)
ifndef package
$(error package variable is required: make composer-require package=vendor/package)
endif
$(COMPOSER) "composer require $(package) --ignore-platform-reqs"
composer-require-dev: ## Require a PHP package as dev in the composer runtime container (usage: make composer-require-dev package=vendor/package)
ifndef package
$(error package variable is required: make composer-require-dev package=vendor/package)
endif
$(COMPOSER) "composer require --dev $(package) --ignore-platform-reqs"
composer-update: ## Update PHP dependencies in the composer runtime container
$(COMPOSER) "composer update --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-reqs"
enable-debug: ## Enable Xdebug in the development runtime container
@$(MAKE) start
$(DEV) "bin/xdebug.sh"
enable-coverage: ## Enable PCOV code coverage in the composer runtime container
@$(MAKE) start
$(COMPOSER) "bin/pcov.sh"
protoc: ## Generate PHP gRPC code from .proto files
@printf "$(GREEN)Setting up protoc-gen-php-grpc plugin$(RESET)\n"
@curl -LOs $(PROTOC_URL)
@tar -xzf protoc-gen-php-grpc-2025.1.5-darwin-arm64.tar.gz
@printf "$(GREEN)Generating PHP gRPC code from .proto files$(RESET)\n"
@protoc --plugin=./protoc-gen-php-grpc-2025.1.5-darwin-arm64/protoc-gen-php-grpc \
--php_out=./generated \
--php-grpc_out=./generated \
protos/example.proto
@printf "$(GREEN)Cleaning up protoc-gen-php-grpc plugin files$(RESET)\n"
@rm -rf $(PROTOC_GEN_DIR) protoc-gen-php-grpc-2025.1.5-darwin-arm64.tar.gz
# Developer tasks
lint: ## Run linting (phpcs/phpstan) in composer runtime
@$(DOCKER) up $(COMPOSER_RUNTIME) -d --no-recreate
$(COMPOSER) "composer run-script tests:lint || true"
$(COMPOSER) "composer run-script tests:phpstan || true"
fmt: ## Format code (php-cs-fixer)
@$(DOCKER) up $(COMPOSER_RUNTIME) -d --no-recreate
$(COMPOSER) "composer run-script tests:lint:fix"
test: ## Run test suite (phpunit)
$(COMPOSER) "composer run-script tests:unit || true"
# Convenience aliases
dev: run ## Alias for start
ci: composer-install test ## CI-like local flow
down: stop ## Alias for stop
up: start ## Alias for start
.PHONY: help start sh run stop restart rebuild ps logs migrate composer-install composer-require composer-require-dev composer-update enable-debug enable-coverage protoc lint fmt test check-lock dev ci

23
protos/example.proto Normal file
View File

@@ -0,0 +1,23 @@
syntax = "proto3";
option go_package = "proto/greeter";
option php_namespace = "GRPC\\Greeter";
option php_metadata_namespace = "GRPC\\GPBMetadata";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}

View File

@@ -5,10 +5,7 @@ use Siteworxpro\App\Api;
require __DIR__ . '/vendor/autoload.php';
try {
// Instantiate the ExternalServer class
$server = new Api();
// Start the server
$server->startServer();
} catch (JsonException $e) {
echo $e->getMessage();

47
src/Grpc.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\App;
use GRPC\Greeter\GreeterInterface;
use Siteworxpro\App\GrpcHandlers\GreeterHandler;
use Siteworxpro\App\Services\Facades\Config;
use Spiral\RoadRunner\GRPC\Invoker;
use Spiral\RoadRunner\GRPC\Server;
use Spiral\RoadRunner\Worker;
/**
* Class Grpc
*
* starts a gRPC server using RoadRunner
*
* @package Siteworxpro\App
*/
class Grpc
{
/**
* @throws \ReflectionException
*/
public function __construct()
{
Kernel::boot();
}
/**
* Starts the gRPC server
*
* @return int
*/
public function start(): int
{
$server = new Server(new Invoker(), [
'debug' => (bool) Config::get('app.debug'),
]);
$server->registerService(GreeterInterface::class, new GreeterHandler());
$server->serve(Worker::create());
return 0;
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\App\GrpcHandlers;
use GRPC\Greeter\GreeterInterface;
use GRPC\Greeter\HelloReply;
use GRPC\Greeter\HelloRequest;
use Spiral\RoadRunner\GRPC;
class GreeterHandler implements GreeterInterface
{
public function SayHello(GRPC\ContextInterface $ctx, HelloRequest $in): HelloReply // phpcs:ignore
{
$reply = new HelloReply();
$reply->setMessage('Hello ' . $in->getName());
return $reply;
}
}