diff --git a/.rr.yaml b/.rr.yaml
index 186b19e..180c970 100644
--- a/.rr.yaml
+++ b/.rr.yaml
@@ -6,6 +6,17 @@ 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}
+ debug: ${DEBUG:-false}
+ reflection: ${GRPC_REFLECTION:-true}
+ destroy_timeout: 5s
+ proto:
+ - "protos/example.proto"
+
http:
pool:
allocate_timeout: 5s
diff --git a/Dockerfile b/Dockerfile
index 1fbd4b6..885b972 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -42,11 +42,14 @@ COPY --from=library /app/vendor /app/vendor
# Copy the RoadRunner configuration file and source
ADD src src/
+ADD generated generated/
+ADD protos protos/
ADD server.php .
ADD .rr.yaml .
ADD config.php .
EXPOSE 9501
+EXPOSE 9001
# Entrypoint command to run the RoadRunner server with the specified configuration
ENTRYPOINT ["rr", "serve", "-c", ".rr.yaml", "-s"]
\ No newline at end of file
diff --git a/README.md b/README.md
index c60c60e..072f710 100644
--- a/README.md
+++ b/README.md
@@ -2,55 +2,132 @@

+## Overview
+
+This is a PHP project template that provides a structured development environment using Docker Compose and Make.
+It includes tools for code quality, testing, dependency management, and gRPC support.
+
## Dev Environment
-### Prerequisites
-- Docker
-- Docker Compose
+This project uses Docker Compose and Make to manage the development environment. The `makefile` provides convenient
+commands for common development tasks.
-### migrations
+## Prerequisites
-create a new migration
-```shell
-docker run --rm -v $(PWD):/app siteworxpro/migrate:v4.18.3 create -ext sql -dir /app/db/migrations -seq create_users_table
+- Docker and Docker Compose
+- Make
+- protoc (Protocol Buffers compiler) - for gRPC code generation
+
+## Quick Start
+
+```bash
+# Install PHP dependencies
+make composer-install
+
+# Start the development container
+make start
+
+# Run the application server
+make run
```
-```text
-postgres://siteworxpro:password@localhost:5432/siteworxpro?sslmode=disable
+## Available Commands
+
+### Container Management
+
+- `make start` - Start the development runtime container
+- `make stop` - Stop and remove all containers
+- `make restart` - Restart the development container
+- `make rebuild` - Rebuild containers (use after Dockerfile changes)
+- `make sh` - Open a shell in the development container
+- `make ps` - Show running containers
+
+### Application
+
+- `make run` - Run the application server (RoadRunner)
+- `make migrate` - Run database migrations
+
+### Composer & Dependencies
+
+- `make composer-install` - Install PHP dependencies
+- `make composer-update` - Update PHP dependencies
+- `make composer-require package=vendor/package` - Add a new dependency
+- `make composer-require-dev package=vendor/package` - Add a new dev dependency
+
+### Code Quality
+
+- `make lint` - Run linters (phpcs and phpstan)
+- `make fmt` - Format code with php-cs-fixer
+- `make test` - Run the test suite (phpunit)
+- `make license-check` - Check license headers in source files
+
+### Debugging & Coverage
+
+- `make enable-debug` - Enable Xdebug for debugging
+- `make enable-coverage` - Enable PCOV for code coverage
+
+### gRPC
+
+- `make protoc` - Generate PHP gRPC code from `.proto` files
+
+### CI Workflow
+
+- `make ci` - Run the full CI pipeline locally (install, license check, lint, test)
+
+### Help
+
+- `make help` - Show all available commands with descriptions
+
+## Common Workflows
+
+### Starting Development
+
+```bash
+make composer-install
+make start
+make run
```
-```shell
-docker run --rm -v $(PWD):/app siteworxpro/migrate:v4.18.3 -database "postgres://siteworxpro:password@localhost:5432/siteworxpro?sslmode=disable" -path /app/db/migrations up
+### Adding a New Package
+
+```bash
+make composer-require package=vendor/package-name
```
-### Starting the Runtime
-```shell
-docker-compose up -d
-```
-### Start the server
-```shell
-docker exec -it template-dev-runtime-1 rr serve
+### Running Tests
+
+```bash
+make test
```
-You can access the api at `http://localhost:9501/`
+### Code Quality Check
-### Xdebug
-
-xdebug needs to be built into the container before it will work
-```shell
- docker exec -it php-template-composer-runtime-1 bin/xdebug.sh
+```bash
+make lint
+make fmt
```
-### Install the dependencies
-```shell
-docker run --rm -v $(PWD):/app siteworxpro/composer install --ignore-platform-reqs
+### Debugging
+
+```bash
+make enable-debug
+make run
```
-### Running all tests
-```shell
-docker run --rm -v $(PWD):/app siteworxpro/composer run tests:all
-```
+## Notes
+- All commands run inside Docker containers, ensuring a consistent environment
+- The development runtime uses RoadRunner as the application server
+- Composer commands run in a separate `composer-runtime` container
+- Database migrations run in a dedicated `migration-container`
+
+## Additional Information
+
+### Accessing Services
+
+- You can access the api at [https://localhost](https://localhost)
+- Traefik dashboard is at [https://127.0.0.1/dashboard/](https://127.0.0.1/dashboard/)
+- the grpc server is at [tcp://localhost:9001](tcp://localhost:9001)
## License
diff --git a/composer.json b/composer.json
index 2ca465d..53136ba 100644
--- a/composer.json
+++ b/composer.json
@@ -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",
diff --git a/composer.lock b/composer.lock
index eb3ac31..9d3dad5 100644
--- a/composer.lock
+++ b/composer.lock
@@ -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",
diff --git a/config.php b/config.php
index 48dd820..4b00213 100644
--- a/config.php
+++ b/config.php
@@ -14,7 +14,6 @@ return [
*/
'server' => [
'port' => Env::get('HTTP_PORT', 9501, 'int'),
- 'dev_mode' => Env::get('DEV_MODE', false, 'bool'),
],
/**
diff --git a/docker-compose.yml b/docker-compose.yml
index a7c1f4c..aad53f4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,6 +5,12 @@ volumes:
services:
traefik:
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.traefik.entrypoints=web-secure"
+ - "traefik.http.routers.traefik.rule=Host(`127.0.0.1`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))"
+ - "traefik.http.routers.traefik.tls=true"
+ - "traefik.http.routers.traefik.service=api@internal"
image: traefik:latest
container_name: traefik
healthcheck:
@@ -15,15 +21,18 @@ services:
ports:
- "80:80"
- "443:443"
+ - "9001:9001"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
restart: always
command:
- "--providers.docker=true"
+ - "--api.insecure=true"
- "--ping"
- "--providers.docker.exposedByDefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.web-secure.address=:443"
+ - "--entrypoints.grpc.address=:9001"
- "--accesslog=true"
- "--entrypoints.web.http.redirections.entryPoint.to=web-secure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
@@ -79,6 +88,13 @@ services:
- "traefik.http.services.api.loadbalancer.healthcheck.path=/healthz"
- "traefik.http.services.api.loadbalancer.healthcheck.interval=5s"
- "traefik.http.services.api.loadbalancer.healthcheck.timeout=60s"
+ - "traefik.tcp.services.api.loadbalancer.server.port=9001"
+ - "traefik.http.services.api.loadbalancer.server.port=9501"
+ - "traefik.tcp.routers.grpc.entrypoints=grpc"
+ - "traefik.tcp.routers.grpc.rule=HostSNI(`localhost`) || HostSNI(`127.0.0.1`)"
+ - "traefik.tcp.routers.grpc.tls=true"
+ - "traefik.tcp.routers.grpc.service=api"
+ container_name: dev-runtime
volumes:
- .:/app
build:
@@ -103,6 +119,7 @@ services:
QUEUE_BROKER: redis
PHP_IDE_CONFIG: serverName=localhost
WORKERS: 1
+ GRPC_WORKERS: 1
DEBUG: 1
REDIS_HOST: redis
DB_HOST: postgres
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/generated/README.md b/generated/README.md
new file mode 100644
index 0000000..aa2bde0
--- /dev/null
+++ b/generated/README.md
@@ -0,0 +1,3 @@
+### Note to Developers
+Only generated files are allowed in this directory.
+Please do not add any other files here manually.
\ No newline at end of file
diff --git a/grpc-worker.php b/grpc-worker.php
new file mode 100644
index 0000000..0dbc05c
--- /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
new file mode 100644
index 0000000..e4d4d80
--- /dev/null
+++ b/makefile
@@ -0,0 +1,159 @@
+# 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
+
+# Fancy emoji
+SPARK := โจ
+ROCKET := ๐
+WARN := โ ๏ธ
+MAGNIFY := ๐
+BUG := ๐
+COMPOSE := ๐ณ
+TRASH := ๐งน
+PROTO := ๐งฉ
+CHECK := โ
+CROSS := โ
+
+# Align width for help display
+HELP_COL_WIDTH := 26
+
+# Help: auto-generate from targets with "##" comments
+help: ## Show this help
+ @echo "$(SPARK) 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)$(ROCKET) 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)$(WARN) 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)$(SPARK) Rebuilding containers$(RESET)\n"
+ @$(MAKE) stop
+ @printf "$(YELLOW)$(TRASH) Deleting all Docker resources$(RESET)\n"
+ docker system prune --all --volumes --force
+ @$(MAKE) start
+
+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
+ @printf "$(COMPOSE) $(GREEN)Installing PHP dependencies in $(COMPOSER_RUNTIME)$(RESET)\n"
+ @$(DOCKER) up $(COMPOSER_RUNTIME) -d --no-recreate
+ $(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
+ @printf "$(COMPOSE) $(MAGNIFY) Requiring package $(package) in $(COMPOSER_RUNTIME)$(RESET)\n"
+ @$(DOCKER) up $(COMPOSER_RUNTIME) -d --no-recreate
+ $(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
+ @printf "$(COMPOSE) $(MAGNIFY) Requiring dev package $(package) in $(COMPOSER_RUNTIME)$(RESET)\n"
+ @$(DOCKER) up $(COMPOSER_RUNTIME) -d --no-recreate
+ $(COMPOSER) "composer require --dev $(package) --ignore-platform-reqs"
+
+composer-update: ## Update PHP dependencies in the composer runtime container
+ @printf "$(COMPOSE) $(MAGNIFY) Updating PHP dependencies$(RESET)\n"
+ @$(DOCKER) up $(COMPOSER_RUNTIME) -d --no-recreate
+ $(COMPOSER) "composer update --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-reqs"
+
+enable-debug: ## Enable Xdebug in the development runtime container
+ @$(DOCKER) up $(DEV_RUNTIME) -d --no-recreate
+ @printf "$(GREEN)$(BUG) Enabling Xdebug in $(DEV_RUNTIME)$(RESET)\n"
+ $(DEV) "bin/xdebug.sh"
+
+enable-coverage: ## Enable PCOV code coverage in the composer runtime container
+ @printf "$(GREEN)$(MAGNIFY) Enabling PCOV in $(COMPOSER_RUNTIME)$(RESET)\n"
+ @$(DOCKER) up $(COMPOSER_RUNTIME) -d --no-recreate
+ $(COMPOSER) "bin/pcov.sh"
+
+protoc: ## Generate PHP gRPC code from .proto files
+ @printf "$(PROTO) $(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 "$(PROTO) $(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 "$(TRASH) $(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
+
+license-check: ## Check license headers in source files
+ @printf "$(MAGNIFY) $(GREEN)Checking license headers$(RESET)\n"
+ @$(DOCKER) up $(COMPOSER_RUNTIME) -d --no-recreate
+ $(COMPOSER) "composer run-script tests:license || true"
+
+# Developer tasks
+lint: ## Run linting (phpcs/phpstan) in composer runtime
+ @printf "$(MAGNIFY) $(GREEN)Running linters$(RESET)\n"
+ @$(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)
+ @printf "$(MAGNIFY) $(GREEN)Formatting code$(RESET)\n"
+ @$(DOCKER) up $(COMPOSER_RUNTIME) -d --no-recreate
+ $(COMPOSER) "composer run-script tests:lint:fix"
+
+test: ## Run test suite (phpunit)
+ @printf "$(CHECK) $(GREEN)Running unit tests$(RESET)\n"
+ @$(DOCKER) up $(COMPOSER_RUNTIME) -d
+ $(COMPOSER) "composer run-script tests:unit || true"
+
+test-coverage: ## Run test suite with coverage report
+ @printf "$(CHECK) $(GREEN)Running unit tests with coverage report$(RESET)\n"
+ @$(DOCKER) up $(COMPOSER_RUNTIME) -d
+ @$(MAKE) enable-coverage
+ $(COMPOSER) "composer run-script tests:unit:coverage || true"
+
+# Convenience aliases
+dev: run ## Alias for start
+ci: composer-install license-check lint test ## CI-like local flow
+down: stop ## Alias for stop
+up: start ## Alias for start
+
+.PONY: help start sh run stop restart rebuild ps migrate composer-install composer-require composer-require-dev composer-update enable-debug enable-coverage protoc license-check lint fmt test test-coverage dev ci down up
\ No newline at end of file
diff --git a/protos/example.proto b/protos/example.proto
new file mode 100644
index 0000000..78ab89a
--- /dev/null
+++ b/protos/example.proto
@@ -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;
+}
\ 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
new file mode 100644
index 0000000..d8c2779
--- /dev/null
+++ b/src/Grpc.php
@@ -0,0 +1,47 @@
+ Config::get('app.dev_mode'),
+ ]);
+
+ $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;
+ }
+}