diff --git a/.rr.yaml b/.rr.yaml index 7155674..763504a 100644 --- a/.rr.yaml +++ b/.rr.yaml @@ -10,8 +10,15 @@ 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: - - "proto/helloworld.proto" + - "protos/example.proto" http: pool: diff --git a/generated/GRPC/GPBMetadata/Example.php b/generated/GRPC/GPBMetadata/Example.php new file mode 100644 index 0000000..09e16d5 --- /dev/null +++ b/generated/GRPC/GPBMetadata/Example.php @@ -0,0 +1,25 @@ +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; + } +} + diff --git a/generated/GRPC/Greeter/GreeterInterface.php b/generated/GRPC/Greeter/GreeterInterface.php new file mode 100644 index 0000000..ad49827 --- /dev/null +++ b/generated/GRPC/Greeter/GreeterInterface.php @@ -0,0 +1,22 @@ +helloworld.HelloReply + */ +class HelloReply extends \Google\Protobuf\Internal\Message +{ + /** + * Generated from protobuf field string message = 1; + */ + 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 string message = 1; + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Generated from protobuf field string message = 1; + * @param string $var + * @return $this + */ + public function setMessage($var) + { + GPBUtil::checkString($var, True); + $this->message = $var; + + return $this; + } + +} + diff --git a/generated/GRPC/Greeter/HelloRequest.php b/generated/GRPC/Greeter/HelloRequest.php new file mode 100644 index 0000000..87bd96f --- /dev/null +++ b/generated/GRPC/Greeter/HelloRequest.php @@ -0,0 +1,61 @@ +helloworld.HelloRequest + */ +class HelloRequest extends \Google\Protobuf\Internal\Message +{ + /** + * Generated from protobuf field string name = 1; + */ + 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 string name = 1; + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Generated from protobuf field string name = 1; + * @param string $var + * @return $this + */ + public function setName($var) + { + GPBUtil::checkString($var, True); + $this->name = $var; + + return $this; + } + +} + diff --git a/grpc-worker.php b/grpc-worker.php new file mode 100644 index 0000000..6f7ed2b --- /dev/null +++ b/grpc-worker.php @@ -0,0 +1,14 @@ +start(); +} catch (\Exception $e) { + echo $e->getMessage(); + + exit(1); +} diff --git a/makefile b/makefile index f8c9254..8322662 100644 --- a/makefile +++ b/makefile @@ -1,59 +1,120 @@ -help: +# 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:" - @echo " start - Start the development runtime container" - @echo " sh - Open a shell in the development runtime container" - @echo " run - Run the application server in the development runtime container" - @echo " stop - Stop and remove the development runtime container" - @echo " migrate - Run database migrations in the migration container" - @echo " composer-install - Install PHP dependencies in the composer runtime container" - @echo " composer-require - Require a PHP package in the composer runtime container (usage: make composer-require package=vendor/package)" - @echo " composer-require-dev - Require a PHP package as dev in the composer runtime container (usage: make composer-require-dev package=vendor/package)" - @echo " composer-update - Update PHP dependencies in the composer runtime container" - @echo " enable-debug - Enable Xdebug in the development runtime container" - @echo " enable-coverage - Enable PCOV code coverage in the composer runtime container" - @echo " protoc - Generate PHP gRPC code from .proto files" + @awk -F':|##' '/^[a-zA-Z0-9._-]+:.*##/ {printf " %-$(HELP_COL_WIDTH)s - %s\n", $$1, $$3}' $(MAKEFILE_LIST) | sort -composer-install: - docker compose exec composer-runtime sh -c "composer install --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-reqs" +start: ## Start the development runtime container + @printf "$(GREEN)Starting $(DEV_RUNTIME)$(RESET)\n" + $(DOCKER) up $(DEV_RUNTIME) -d --no-recreate -composer-require: - docker compose exec composer-runtime sh -c "composer require $(package) --ignore-platform-reqs" +sh: ## Open a shell in the development runtime container + @$(MAKE) start + $(DOCKER) exec $(DEV_RUNTIME) sh -composer-require-dev: - docker compose exec composer-runtime sh -c "composer require --dev $(package) --ignore-platform-reqs" +run: ## Run the application server in the development runtime container + @$(MAKE) start + $(DEV) "rr serve" -composer-update: - docker compose exec composer-runtime sh -c "composer update --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-reqs" +stop: ## Stop and remove the development runtime container + @printf "$(YELLOW)Stopping all containers$(RESET)\n" + $(DOCKER) down -migrate: - docker compose up migration-container +restart: ## Restart dev container (stop + start) + @$(MAKE) stop + @$(MAKE) start -enable-coverage: - ${MAKE} start - docker compose exec composer-runtime sh -c "bin/pcov.sh" +rebuild: ## Rebuild containers (useful after Dockerfile changes) + @printf "$(YELLOW)Rebuilding containers$(RESET)\n" + $(DOCKER) build + $(DOCKER) up --force-recreate --build -d -start: - docker compose up dev-runtime -d --no-recreate +ps: ## Show docker compose ps + $(DOCKER) ps -sh: - ${MAKE} start - docker compose exec dev-runtime sh +migrate: ## Run database migrations in the migration container + $(DOCKER) up $(MIGRATION_CONTAINER) -run: - ${MAKE} start - docker compose exec dev-runtime sh -c "rr serve" +# Composer helpers +composer-install: ## Install PHP dependencies in the composer runtime container + $(COMPOSER) "composer install --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-reqs" -enable-debug: - ${MAKE} start - docker compose exec dev-runtime sh -c "bin/xdebug.sh" +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" -stop: - docker compose down +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" -protoc: - protoc --plugin=protoc-gen-php-grpc \ - --php_out=./generated \ - --php-grpc_out=./generated \ - protos/example.proto +composer-update: ## Update PHP dependencies in the composer runtime container + $(COMPOSER) "composer update --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-reqs" -.PHONY: help enable-coverage start sh run enable-debug stop protoc \ No newline at end of file +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 \ No newline at end of file diff --git a/server.php b/server.php index dd031c6..74aabcf 100644 --- a/server.php +++ b/server.php @@ -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(); diff --git a/src/Grpc.php b/src/Grpc.php index af2be6a..9cc9d6f 100644 --- a/src/Grpc.php +++ b/src/Grpc.php @@ -4,11 +4,20 @@ 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 { /** @@ -19,14 +28,18 @@ class Grpc 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 Greeter()); - + $server->registerService(GreeterInterface::class, new GreeterHandler()); $server->serve(Worker::create()); return 0; diff --git a/src/GrpcHandlers/GreeterHandler.php b/src/GrpcHandlers/GreeterHandler.php new file mode 100644 index 0000000..72028d7 --- /dev/null +++ b/src/GrpcHandlers/GreeterHandler.php @@ -0,0 +1,21 @@ +setMessage('Hello ' . $in->getName()); + + return $reply; + } +}