You've already forked Php-Template
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
e971d32f9d
|
|||
|
2a060fb972
|
|||
|
b53a95ebcf
|
|||
|
de0c95db2a
|
|||
|
cae1de6ef3
|
|||
|
84c3b392ba
|
|||
|
f59dcb2dcc
|
|||
|
8252ae4e53
|
|||
|
68ab2dcdd7
|
@@ -5,6 +5,12 @@ volumes:
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
traefik:
|
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
|
image: traefik:latest
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -15,15 +21,20 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
|
- "9001:9001"
|
||||||
volumes:
|
volumes:
|
||||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||||
|
- "./ssl:/etc/ssl"
|
||||||
restart: always
|
restart: always
|
||||||
command:
|
command:
|
||||||
- "--providers.docker=true"
|
- "--providers.docker=true"
|
||||||
|
- "--api.insecure=true"
|
||||||
- "--ping"
|
- "--ping"
|
||||||
|
- "--providers.file.filename=/etc/ssl/traefik.yml"
|
||||||
- "--providers.docker.exposedByDefault=false"
|
- "--providers.docker.exposedByDefault=false"
|
||||||
- "--entrypoints.web.address=:80"
|
- "--entrypoints.web.address=:80"
|
||||||
- "--entrypoints.web-secure.address=:443"
|
- "--entrypoints.web-secure.address=:443"
|
||||||
|
- "--entrypoints.grpc.address=:9001"
|
||||||
- "--accesslog=true"
|
- "--accesslog=true"
|
||||||
- "--entrypoints.web.http.redirections.entryPoint.to=web-secure"
|
- "--entrypoints.web.http.redirections.entryPoint.to=web-secure"
|
||||||
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
|
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
|
||||||
@@ -31,7 +42,7 @@ services:
|
|||||||
|
|
||||||
composer-runtime:
|
composer-runtime:
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- ..:/app
|
||||||
image: siteworxpro/composer
|
image: siteworxpro/composer
|
||||||
entrypoint: "/bin/sh -c 'while true; do sleep 30; done;'"
|
entrypoint: "/bin/sh -c 'while true; do sleep 30; done;'"
|
||||||
environment:
|
environment:
|
||||||
@@ -53,8 +64,8 @@ services:
|
|||||||
|
|
||||||
migration-container:
|
migration-container:
|
||||||
volumes:
|
volumes:
|
||||||
- ./db/migrations:/app/db/migrations
|
- ../db/migrations:/app/db/migrations
|
||||||
- ./bin:/app/bin
|
- ../bin:/app/bin
|
||||||
image: siteworxpro/migrate:v4.18.3
|
image: siteworxpro/migrate:v4.18.3
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
# entrypoint: "/bin/sh -c 'while true; do sleep 30; done;'"
|
# entrypoint: "/bin/sh -c 'while true; do sleep 30; done;'"
|
||||||
@@ -79,12 +90,21 @@ services:
|
|||||||
- "traefik.http.services.api.loadbalancer.healthcheck.path=/healthz"
|
- "traefik.http.services.api.loadbalancer.healthcheck.path=/healthz"
|
||||||
- "traefik.http.services.api.loadbalancer.healthcheck.interval=5s"
|
- "traefik.http.services.api.loadbalancer.healthcheck.interval=5s"
|
||||||
- "traefik.http.services.api.loadbalancer.healthcheck.timeout=60s"
|
- "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:
|
volumes:
|
||||||
- .:/app
|
- ..:/app
|
||||||
build:
|
build:
|
||||||
args:
|
args:
|
||||||
KAFKA_ENABLED: "1"
|
KAFKA_ENABLED: "0"
|
||||||
context: .
|
UID: 0
|
||||||
|
USER: root
|
||||||
|
context: ..
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
entrypoint: "/bin/sh -c 'while true; do sleep 30; done;'"
|
entrypoint: "/bin/sh -c 'while true; do sleep 30; done;'"
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -103,6 +123,7 @@ services:
|
|||||||
QUEUE_BROKER: redis
|
QUEUE_BROKER: redis
|
||||||
PHP_IDE_CONFIG: serverName=localhost
|
PHP_IDE_CONFIG: serverName=localhost
|
||||||
WORKERS: 1
|
WORKERS: 1
|
||||||
|
GRPC_WORKERS: 1
|
||||||
DEBUG: 1
|
DEBUG: 1
|
||||||
REDIS_HOST: redis
|
REDIS_HOST: redis
|
||||||
DB_HOST: postgres
|
DB_HOST: postgres
|
||||||
83
.dev/ssl/localhost.crt
Normal file
83
.dev/ssl/localhost.crt
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEFzCCA52gAwIBAgIURfvF11Q9R3Ue38Tr0BzIoUe0TKQwCgYIKoZIzj0EAwMw
|
||||||
|
MDEuMCwGA1UEAxMlU2l0ZXdvcnggSW50ZXJtZWRpYXRlIEVDMzg0IEF1dGhvcml0
|
||||||
|
eTAeFw0yNTEyMDQxNjM1NTFaFw0yNjEyMDQxNjM2MjFaMHExCzAJBgNVBAYTAlVT
|
||||||
|
MREwDwYDVQQIEwhWaXJnaW5pYTEVMBMGA1UEBxMMUHVyY2VsbHZpbGxlMSQwIgYD
|
||||||
|
VQQKExtTaXRld29yeCBQcm9mZXNzaW9uYWxzLCBMTEMxEjAQBgNVBAMTCWxvY2Fs
|
||||||
|
aG9zdDB2MBAGByqGSM49AgEGBSuBBAAiA2IABM+jXangYCOi01IMblAXJ6iFZE4v
|
||||||
|
SBBOZKNQCwGz8kKi5jyXtVwz6U26DMlBSK+InhhOFQlCRcP9ow8LtlQdaY2XnGKr
|
||||||
|
3X3zxdUZJVhLi/wog+I4igU3+xuyn1E/BgEZx6OCAjUwggIxMA4GA1UdDwEB/wQE
|
||||||
|
AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW
|
||||||
|
BBRAtanjiWMYAdpCCz0rEkyqf691bzAfBgNVHSMEGDAWgBQYBC15lPoGGGxbmwqY
|
||||||
|
MWL7jjI6azBJBggrBgEFBQcBAQQ9MDswOQYIKwYBBQUHMAGGLWh0dHBzOi8vdmF1
|
||||||
|
bHQuc2l0ZXdvcnhwcm8uY29tL3YxL3N3eF9pbnQvb2NzcDAaBgNVHREEEzARggls
|
||||||
|
b2NhbGhvc3SHBH8AAAEwHAYDVR0gBBUwEzAIBgZngQwBAgIwBwYFZ4EMAQEwggE1
|
||||||
|
BgNVHR8EggEsMIIBKDBioGCgXoZcaHR0cHM6Ly92YXVsdC5zaXRld29yeHByby5j
|
||||||
|
b20vdjEvc3d4X2ludC9pc3N1ZXIvMjVmMWRiNTAtZDQxOS1kZWQ3LTZiZjktZWNh
|
||||||
|
Y2E4NGEwMmY0L2NybC9wZW0wXqBcoFqGWGh0dHBzOi8vdmF1bHQuc2l0ZXdvcnhw
|
||||||
|
cm8uY29tL3YxL3N3eF9pbnQvaXNzdWVyLzI1ZjFkYjUwLWQ0MTktZGVkNy02YmY5
|
||||||
|
LWVjYWNhODRhMDJmNC9jcmwwYqBgoF6GXGh0dHBzOi8vdmF1bHQuc2l0ZXdvcnhw
|
||||||
|
cm8uY29tL3YxL3N3eF9pbnQvaXNzdWVyLzI1ZjFkYjUwLWQ0MTktZGVkNy02YmY5
|
||||||
|
LWVjYWNhODRhMDJmNC9jcmwvZGVyMAoGCCqGSM49BAMDA2gAMGUCMGxgZmKITQFu
|
||||||
|
H6j3j/t9MOTxhVsfOuoD0q3pMlp9d1u4Lg0THKUOzN06BVuXwC1eagIxAL2I/2a1
|
||||||
|
MMJmhky2EavzOsYt37Ae+1KGyELiwcWe5f/lActlw97pqRajpmqEmdo7PA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEETCCAfmgAwIBAgIUIRpRFzFBITweYJETytgbPBgwbWgwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwgZ0xCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhWaXJnaW5pYTEVMBMGA1UEBwwM
|
||||||
|
UHVyY2VsbHZpbGxlMSQwIgYDVQQKDBtTaXRld29yeCBQcm9mZXNzaW9uYWxzLCBM
|
||||||
|
TEMxFDASBgNVBAMMC1NXWCBSb290IENBMSgwJgYJKoZIhvcNAQkBFhl3ZWJtYXN0
|
||||||
|
ZXJAc2l0ZXdvcnhwcm8uY29tMB4XDTIzMDMyMTE2MzAxNVoXDTMzMDMxODE2MzA0
|
||||||
|
NVowMDEuMCwGA1UEAxMlU2l0ZXdvcnggSW50ZXJtZWRpYXRlIEVDMzg0IEF1dGhv
|
||||||
|
cml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIhlP1W1O1WjoDFGFi5XbE0zVy90
|
||||||
|
76pQQ8VmSYtaZI9Jz5pAZTOQ073t/QkTWge8uhDaJ2J2uBhjQJGr5BPttvBcLJFI
|
||||||
|
52X7hJuck4oL0aukXiHYA5gZbC5LhKVvCyZcWqNjMGEwDgYDVR0PAQH/BAQDAgEG
|
||||||
|
MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBgELXmU+gYYbFubCpgxYvuOMjpr
|
||||||
|
MB8GA1UdIwQYMBaAFHWCysIFrdsWYZJSjBO1pSQPETkTMA0GCSqGSIb3DQEBCwUA
|
||||||
|
A4ICAQBbw5roegt0tUc+gu0IcHDt56cUoqChmIZXzla8gTgg820ww/+Wm+vNAl8W
|
||||||
|
r3Y67LzK19CygoujD2o7M25syaByRiw9JdIfNGvBzklOOM+sus9DDmwSUBMCuljS
|
||||||
|
KLBhWzIrXDZwemzklGEbj+RL4o2ZiL01nx8xygDF55eaudNS0VzRzd2Hv0C+rm2i
|
||||||
|
nnwRNoKsL14YXc41rFBWwb5ViRuD2Wp0c9CivEOd4UNKgOnGyNxcNhjzNlY05t3c
|
||||||
|
NEeskEXiz21sj0vnrwM7olKyXPXDFUCCKGb21Sn9sWKldicumU1i1HdDGA1w50uh
|
||||||
|
NS4G4wqGQ8iZCq3h6JkpBMGPJPG3Dq6yuzrh8fmh56IqtKY4MxdKHb91MtFHnkw5
|
||||||
|
jCrxqpTKShRyqcBSx8QmXRXpec5FEB88NQ3aKhtFlNqXYphNRAI9bLIyGkdxUF/r
|
||||||
|
PCkZkKBhbsRvXT8Ii/K1PQHzliQqJxXhrrJEsIg2jiSQItBg52ZySzuw+Y6++h11
|
||||||
|
73XMKJ53oOeLcxvp2qJRwMkNTwVfNxDmKC0tIRdI+KoJYbYeN0Ev/pEdPdYl+hjY
|
||||||
|
uQhKMt1KtpUyYwPzTGPKGMnklKj/T3Qu7fmpsWxtAOuK7yLLMayBwXBlVBD23md+
|
||||||
|
UAfPR3FfVX+aRqqsvT7WI+SnlycJuYXs41ZPxBjLq2aB7fhAwQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIGIjCCBAqgAwIBAgIJAKU+Idu5bncNMA0GCSqGSIb3DQEBCwUAMIGdMQswCQYD
|
||||||
|
VQQGEwJVUzERMA8GA1UECAwIVmlyZ2luaWExFTATBgNVBAcMDFB1cmNlbGx2aWxs
|
||||||
|
ZTEkMCIGA1UECgwbU2l0ZXdvcnggUHJvZmVzc2lvbmFscywgTExDMRQwEgYDVQQD
|
||||||
|
DAtTV1ggUm9vdCBDQTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQHNpdGV3b3J4
|
||||||
|
cHJvLmNvbTAeFw0yMDA5MDgxMjU3NTJaFw00MDA5MDMxMjU3NTJaMIGdMQswCQYD
|
||||||
|
VQQGEwJVUzERMA8GA1UECAwIVmlyZ2luaWExFTATBgNVBAcMDFB1cmNlbGx2aWxs
|
||||||
|
ZTEkMCIGA1UECgwbU2l0ZXdvcnggUHJvZmVzc2lvbmFscywgTExDMRQwEgYDVQQD
|
||||||
|
DAtTV1ggUm9vdCBDQTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQHNpdGV3b3J4
|
||||||
|
cHJvLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAN9JNyWot7VX
|
||||||
|
ODvru8S5/o6gdFuynA1l5T0uXSzWhROMYHndmY+n7pwQCwf1R8iLL3aat9sDRxqM
|
||||||
|
RRScD3nNW6UzC5xNz12wiuemf2KT82cTjmUBU3CvtjstbgkrQ/SrpR/Arvu2YwUe
|
||||||
|
tmL9ft/xaoGvZXx8LKpyRMrHA1FlS2st+RFWBC0yXTU/nL4/7YQKVEcbc3YZvgCT
|
||||||
|
P4/8pxH9u8W7kgnufQHHKEIZR9lxIUhQ7yvc61B3zMntbJsZV1N+0c7j5DXY5cfT
|
||||||
|
6zXlfG2hSX1dbhM56y8O8KiCFaWaDRZ9mwkfZGM0W58gkhXUPXOrIOwewLmvl2Z4
|
||||||
|
Vu43UkLfKhtQApxk6zodHRq1e2rNWSpBCGznT9XyoeO/spJ7yggNkleTa+SnnlmV
|
||||||
|
rHJS/YUp3/jAvJY2bCHQKFu/mguMY3Ub2X6eEBsVZOmUqDMbya1TPP6GCVqh4gUu
|
||||||
|
yip6qS9UksaTF6IN3IcrGhwtTyvp8BFqwVA0tMhgraf1rv6ZoXjY/NDuGjE1xXJg
|
||||||
|
Hg+gg2pIIRcXjcsG1tXFXTgxDqoh127ADg/gtq9cIyarMx4LdNTjnR+CnhjqvRkT
|
||||||
|
uiUBB1bwDc9pbX0ulfnR+VuIZtQ6PSuWwChnMdNBKmCgQT1J1AHWpQqnFTjg42NV
|
||||||
|
5QAdFOQxAnsq2DxkurVFEz2J3euZx1ZdAgMBAAGjYzBhMB0GA1UdDgQWBBR1gsrC
|
||||||
|
Ba3bFmGSUowTtaUkDxE5EzAfBgNVHSMEGDAWgBR1gsrCBa3bFmGSUowTtaUkDxE5
|
||||||
|
EzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
|
||||||
|
AAOCAgEAZDjIlaAoMfRGdb3/i40s6nN18iWN2Chttd8dLXgV+/SZ8GrAU89JNJrK
|
||||||
|
ODaLZT1wHeWVz0LP3miByuvfrnH4qzPEOI2L6zEy/FJr8SCivjm7aUExyb5kTSXp
|
||||||
|
LkwVcOI9UfQb6lCy9Gs/rUEcWQjs5KS3dy6ZwBMaywq6sRj7MeXmhqXhj7aAyWFA
|
||||||
|
psnQsuP2XweWa9OX6Z+u78sebfoiJlOEUvV9VRNHQYpLUd75p6sti1Dm9blWkZEO
|
||||||
|
hyssi3kOJMH+g5pc9xNbD9gS+/pFUWxEVAhHOc0xdEIcHfV5oiiOUDD5EOIPi3xv
|
||||||
|
/NYTV7o7pv2/QlH09vO2PHdsy07lhsg7NoM3U+zYq609Ox78/b4PNd+TkdtYKebO
|
||||||
|
VumZ0xXab0lWbTVuno52k473ODQRA/v9YWHtuovW0Lzf5fDcBhVXTDeW21SmMJIx
|
||||||
|
B+dgJDh7ql7ruZqjMj+kePjM9Mm+M5pDZ6vrEtgiR2yQj/IE+LoQh/bxFHpFkIK8
|
||||||
|
I6AWoxABAvLZB+KHl1ufR5yOauJG2+SQRuzHNZvkAcdjmwpgfxcsB2mY7o0RbGmZ
|
||||||
|
VWm97P4P9iJhje/W4C0cGwVY5wRAMAg6SI1BpcW7YghB14UrKaxpEzHCdZIeeT94
|
||||||
|
GYzN2XNSSGW3s1anFedd5PQyRM7PlJIcloLYrqyWW6M7OwWnMXA=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
6
.dev/ssl/localhost.key
Normal file
6
.dev/ssl/localhost.key
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MIGkAgEBBDBrpJYaCMqgu490fpZoIphGVspE33v3JwyD9B55HwSX/jykySs9NTOv
|
||||||
|
68YndzE9LNCgBwYFK4EEACKhZANiAATPo12p4GAjotNSDG5QFyeohWROL0gQTmSj
|
||||||
|
UAsBs/JCouY8l7VcM+lNugzJQUiviJ4YThUJQkXD/aMPC7ZUHWmNl5xiq91988XV
|
||||||
|
GSVYS4v8KIPiOIoFN/sbsp9RPwYBGcc=
|
||||||
|
-----END EC PRIVATE KEY-----
|
||||||
14
.dev/ssl/traefik.yml
Normal file
14
.dev/ssl/traefik.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
tls:
|
||||||
|
stores:
|
||||||
|
default:
|
||||||
|
defaultCertificate:
|
||||||
|
certFile: /etc/ssl/localhost.crt
|
||||||
|
keyFile: /etc/ssl/localhost.key
|
||||||
|
|
||||||
|
options:
|
||||||
|
default:
|
||||||
|
minVersion: VersionTLS13
|
||||||
|
preferServerCipherSuites: true
|
||||||
|
|
||||||
|
mintls13:
|
||||||
|
minVersion: VersionTLS13
|
||||||
11
.rr.yaml
11
.rr.yaml
@@ -6,6 +6,17 @@ server:
|
|||||||
rpc:
|
rpc:
|
||||||
listen: tcp://127.0.0.1:6001
|
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:
|
http:
|
||||||
pool:
|
pool:
|
||||||
allocate_timeout: 5s
|
allocate_timeout: 5s
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name=" Compose Deployment" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
<configuration default="false" name=".dev: Compose Deployment" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
||||||
<deployment type="docker-compose.yml">
|
<deployment type="docker-compose.yml">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="sourceFilePath" value="docker-compose.yml" />
|
<option name="sourceFilePath" value=".dev/docker-compose.yml" />
|
||||||
</settings>
|
</settings>
|
||||||
</deployment>
|
</deployment>
|
||||||
<method v="2" />
|
<method v="2" />
|
||||||
|
|||||||
25
Dockerfile
25
Dockerfile
@@ -1,5 +1,5 @@
|
|||||||
# Use the RoadRunner image as a base for the first stage
|
# Use the RoadRunner image as a base for the first stage
|
||||||
FROM ghcr.io/roadrunner-server/roadrunner:2025.1.4 AS roadrunner
|
FROM ghcr.io/roadrunner-server/roadrunner:2025.1.6 AS roadrunner
|
||||||
|
|
||||||
# Use the official Composer image as the base for the library stage
|
# Use the official Composer image as the base for the library stage
|
||||||
FROM siteworxpro/composer AS library
|
FROM siteworxpro/composer AS library
|
||||||
@@ -12,9 +12,11 @@ RUN composer install --optimize-autoloader --ignore-platform-reqs --no-dev
|
|||||||
|
|
||||||
|
|
||||||
# Use the official PHP CLI image with Alpine Linux for the second stage
|
# Use the official PHP CLI image with Alpine Linux for the second stage
|
||||||
FROM siteworxpro/php:8.5.0-cli-alpine AS php
|
FROM siteworxpro/php:8.5.1-cli-alpine AS php
|
||||||
|
|
||||||
ARG KAFKA_ENABLED=0
|
ARG KAFKA_ENABLED=0
|
||||||
|
ARG USER=appuser
|
||||||
|
ARG UID=1000
|
||||||
|
|
||||||
# Move the production PHP configuration file to the default location
|
# Move the production PHP configuration file to the default location
|
||||||
RUN mv /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini \
|
RUN mv /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini \
|
||||||
@@ -26,6 +28,7 @@ RUN if [ "$KAFKA_ENABLED" -eq 1 ] ; then \
|
|||||||
echo "Kafka support enabled" ; \
|
echo "Kafka support enabled" ; \
|
||||||
apk add autoconf g++ librdkafka-dev make --no-cache ; \
|
apk add autoconf g++ librdkafka-dev make --no-cache ; \
|
||||||
pecl install rdkafka && docker-php-ext-enable rdkafka ; \
|
pecl install rdkafka && docker-php-ext-enable rdkafka ; \
|
||||||
|
apk del autoconf g++ make ; \
|
||||||
else \
|
else \
|
||||||
echo "Kafka support disabled" ; \
|
echo "Kafka support disabled" ; \
|
||||||
exit 0 ; \
|
exit 0 ; \
|
||||||
@@ -42,11 +45,19 @@ COPY --from=library /app/vendor /app/vendor
|
|||||||
|
|
||||||
# Copy the RoadRunner configuration file and source
|
# Copy the RoadRunner configuration file and source
|
||||||
ADD src src/
|
ADD src src/
|
||||||
ADD server.php .
|
ADD generated generated/
|
||||||
ADD .rr.yaml .
|
ADD protos protos/
|
||||||
ADD config.php .
|
ADD server.php cli.php grpc-worker.php .rr.yaml config.php ./
|
||||||
|
|
||||||
EXPOSE 9501
|
EXPOSE 9501 9001
|
||||||
|
|
||||||
|
# Create a non-root user and set ownership of the /app directory
|
||||||
|
RUN if [ ! $UID -eq 0 ] ; then addgroup -g $UID $USER && adduser -D -u $UID -G $USER $USER && chown -R $USER:$USER /app ; fi
|
||||||
|
USER $USER
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
|
||||||
|
CMD curl -f http://localhost:9501/healthz || exit 1
|
||||||
|
|
||||||
# Entrypoint command to run the RoadRunner server with the specified configuration
|
# Entrypoint command to run the RoadRunner server with the specified configuration
|
||||||
ENTRYPOINT ["rr", "serve", "-c", ".rr.yaml", "-s"]
|
ENTRYPOINT ["rr", "serve"]
|
||||||
|
CMD ["-c", ".rr.yaml", "-s"]
|
||||||
139
README.md
139
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
|
## Dev Environment
|
||||||
|
|
||||||
### Prerequisites
|
This project uses Docker Compose and Make to manage the development environment. The `makefile` provides convenient
|
||||||
- Docker
|
commands for common development tasks.
|
||||||
- Docker Compose
|
|
||||||
|
|
||||||
### migrations
|
## Prerequisites
|
||||||
|
|
||||||
create a new migration
|
- Docker and Docker Compose
|
||||||
```shell
|
- Make
|
||||||
docker run --rm -v $(PWD):/app siteworxpro/migrate:v4.18.3 create -ext sql -dir /app/db/migrations -seq create_users_table
|
- 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
|
## Available Commands
|
||||||
postgres://siteworxpro:password@localhost:5432/siteworxpro?sslmode=disable
|
|
||||||
|
### 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
|
### Adding a New Package
|
||||||
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
|
|
||||||
|
```bash
|
||||||
|
make composer-require package=vendor/package-name
|
||||||
```
|
```
|
||||||
|
|
||||||
### Starting the Runtime
|
### Running Tests
|
||||||
```shell
|
|
||||||
docker-compose up -d
|
```bash
|
||||||
```
|
make test
|
||||||
### Start the server
|
|
||||||
```shell
|
|
||||||
docker exec -it template-dev-runtime-1 rr serve
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can access the api at `http://localhost:9501/`
|
### Code Quality Check
|
||||||
|
|
||||||
### Xdebug
|
```bash
|
||||||
|
make lint
|
||||||
xdebug needs to be built into the container before it will work
|
make fmt
|
||||||
```shell
|
|
||||||
docker exec -it php-template-composer-runtime-1 bin/xdebug.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install the dependencies
|
### Debugging
|
||||||
```shell
|
|
||||||
docker run --rm -v $(PWD):/app siteworxpro/composer install --ignore-platform-reqs
|
```bash
|
||||||
|
make enable-debug
|
||||||
|
make run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running all tests
|
## Notes
|
||||||
```shell
|
|
||||||
docker run --rm -v $(PWD):/app siteworxpro/composer run tests:all
|
|
||||||
```
|
|
||||||
|
|
||||||
|
- 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
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Siteworxpro\\App\\": "src/",
|
"Siteworxpro\\App\\": "src/",
|
||||||
"Siteworxpro\\Tests\\": "tests/"
|
"Siteworxpro\\Tests\\": "tests/",
|
||||||
|
"GRPC\\": "generated/GRPC"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -25,7 +26,9 @@
|
|||||||
"react/promise": "^3",
|
"react/promise": "^3",
|
||||||
"react/async": "^4",
|
"react/async": "^4",
|
||||||
"guzzlehttp/guzzle": "^7.10",
|
"guzzlehttp/guzzle": "^7.10",
|
||||||
"zircote/swagger-php": "^5.7"
|
"zircote/swagger-php": "^5.7",
|
||||||
|
"spiral/roadrunner-grpc": "^3.5",
|
||||||
|
"league/tactician": "^1.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^12.4",
|
"phpunit/phpunit": "^12.4",
|
||||||
|
|||||||
197
composer.lock
generated
197
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f12aaf0dae6930c226e719a5705e3f91",
|
"content-hash": "d027bee8e875c5542f7ff9612bfac4e2",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "adhocore/cli",
|
"name": "adhocore/cli",
|
||||||
@@ -298,6 +298,65 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-08-10T19:31:58+00:00"
|
"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",
|
"name": "google/protobuf",
|
||||||
"version": "v4.33.1",
|
"version": "v4.33.1",
|
||||||
@@ -1301,6 +1360,61 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-11-25T08:10:15+00:00"
|
"time": "2024-11-25T08:10:15+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "league/tactician",
|
||||||
|
"version": "v1.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/thephpleague/tactician.git",
|
||||||
|
"reference": "e79f763170f3d5922ec29e85cffca0bac5cd8975"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/thephpleague/tactician/zipball/e79f763170f3d5922ec29e85cffca0bac5cd8975",
|
||||||
|
"reference": "e79f763170f3d5922ec29e85cffca0bac5cd8975",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.3",
|
||||||
|
"phpunit/phpunit": "^7.5.20 || ^9.3.8",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5.8"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"League\\Tactician\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ross Tuck",
|
||||||
|
"homepage": "http://tactician.thephpleague.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A small, flexible command bus. Handy for building service layers.",
|
||||||
|
"keywords": [
|
||||||
|
"command",
|
||||||
|
"command bus",
|
||||||
|
"service layer"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/thephpleague/tactician/issues",
|
||||||
|
"source": "https://github.com/thephpleague/tactician/tree/v1.1.0"
|
||||||
|
},
|
||||||
|
"time": "2021-02-14T15:29:04+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "monolog/monolog",
|
"name": "monolog/monolog",
|
||||||
"version": "3.9.0",
|
"version": "3.9.0",
|
||||||
@@ -2943,6 +3057,87 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-11-13T17:24:29+00:00"
|
"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",
|
"name": "spiral/roadrunner-http",
|
||||||
"version": "v3.6.0",
|
"version": "v3.6.0",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ return [
|
|||||||
*/
|
*/
|
||||||
'server' => [
|
'server' => [
|
||||||
'port' => Env::get('HTTP_PORT', 9501, 'int'),
|
'port' => Env::get('HTTP_PORT', 9501, 'int'),
|
||||||
'dev_mode' => Env::get('DEV_MODE', false, 'bool'),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
25
generated/GRPC/GPBMetadata/Example.php
Normal file
25
generated/GRPC/GPBMetadata/Example.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
22
generated/GRPC/Greeter/GreeterInterface.php
Normal file
22
generated/GRPC/Greeter/GreeterInterface.php
Normal 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;
|
||||||
|
}
|
||||||
61
generated/GRPC/Greeter/HelloReply.php
Normal file
61
generated/GRPC/Greeter/HelloReply.php
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
61
generated/GRPC/Greeter/HelloRequest.php
Normal file
61
generated/GRPC/Greeter/HelloRequest.php
Normal 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
3
generated/README.md
Normal 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
14
grpc-worker.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Siteworxpro\App\Grpc;
|
||||||
|
|
||||||
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$server = new Grpc();
|
||||||
|
exit($server->start());
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo $e->getMessage();
|
||||||
|
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
187
makefile
Normal file
187
makefile
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
# Makefile (enhanced)
|
||||||
|
SHELL := /bin/sh
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
# Docker Compose file
|
||||||
|
COMPOSE_FILE := -f .dev/docker-compose.yml
|
||||||
|
|
||||||
|
# Reusable vars
|
||||||
|
DOCKER := docker compose $(COMPOSE_FILE)
|
||||||
|
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
|
||||||
|
|
||||||
|
DEV := $(DOCKER) exec $(DEV_RUNTIME) sh -c
|
||||||
|
COMPOSER := $(DOCKER) exec $(COMPOSER_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
|
||||||
|
|
||||||
|
docker-build: ## Build Docker image
|
||||||
|
@push_flag=""; \
|
||||||
|
if [ -n "$(push)" ]; then \
|
||||||
|
push="--push"; \
|
||||||
|
fi
|
||||||
|
@if [ -z "$(image)" ]; then \
|
||||||
|
echo "image variable is required: make docker-build image=your-image-name tag=your"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@if [ -z "$(tag)" ]; then \
|
||||||
|
echo "tag variable is required: make docker-build image=your-image-name tag=your"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@platform_flag=""; \
|
||||||
|
if [ -n "$(platform)" ]; then \
|
||||||
|
platform_flag="--platform=$(platform)"; \
|
||||||
|
fi; \
|
||||||
|
printf "$(YELLOW)$(SPARK) Building Docker image: $(image):$(tag)$(RESET)\n"; \
|
||||||
|
docker buildx build $$platform_flag --provenance=true --sbom=true $$push_flag --tag $(image):$(tag) .
|
||||||
|
|
||||||
|
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-install-no-dev: ## Install PHP dependencies without dev packages in the composer runtime container
|
||||||
|
@printf "$(COMPOSE) $(GREEN)Installing PHP dependencies (no-dev) in $(COMPOSER_RUNTIME)$(RESET)\n"
|
||||||
|
@$(DOCKER) up $(COMPOSER_RUNTIME) -d --no-recreate
|
||||||
|
$(COMPOSER) "composer install --no-dev --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 migrate license-check lint test ## CI-like local flow
|
||||||
|
down: stop ## Alias for stop
|
||||||
|
up: start ## Alias for start
|
||||||
|
|
||||||
|
.PHONY: help start sh run stop docker-build 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
|
||||||
23
protos/example.proto
Normal file
23
protos/example.proto
Normal 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;
|
||||||
|
}
|
||||||
@@ -5,10 +5,7 @@ use Siteworxpro\App\Api;
|
|||||||
require __DIR__ . '/vendor/autoload.php';
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Instantiate the ExternalServer class
|
|
||||||
$server = new Api();
|
$server = new Api();
|
||||||
|
|
||||||
// Start the server
|
|
||||||
$server->startServer();
|
$server->startServer();
|
||||||
} catch (JsonException $e) {
|
} catch (JsonException $e) {
|
||||||
echo $e->getMessage();
|
echo $e->getMessage();
|
||||||
|
|||||||
22
src/Attributes/CommandBus/HandlesCommand.php
Normal file
22
src/Attributes/CommandBus/HandlesCommand.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Attributes\CommandBus;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class HandlesCommand
|
||||||
|
* @package Siteworxpro\App\Attributes\CommandBus
|
||||||
|
*/
|
||||||
|
#[Attribute(Attribute::TARGET_CLASS)]
|
||||||
|
readonly class HandlesCommand
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param class-string $commandClass
|
||||||
|
*/
|
||||||
|
public function __construct(public string $commandClass)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace Siteworxpro\App\Cli\Commands;
|
namespace Siteworxpro\App\Cli\Commands;
|
||||||
|
|
||||||
use Ahc\Cli\Input\Command;
|
use Ahc\Cli\Input\Command;
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\ExampleCommand;
|
||||||
|
use Siteworxpro\App\Services\Facades\CommandBus;
|
||||||
|
|
||||||
class DemoCommand extends Command implements CommandInterface
|
class DemoCommand extends Command implements CommandInterface
|
||||||
{
|
{
|
||||||
@@ -28,18 +30,14 @@ class DemoCommand extends Command implements CommandInterface
|
|||||||
$pb->finish();
|
$pb->finish();
|
||||||
|
|
||||||
$this->writer()->boldBlue("Demo Command Executed!\n");
|
$this->writer()->boldBlue("Demo Command Executed!\n");
|
||||||
|
|
||||||
if ($this->values()['name']) {
|
|
||||||
$name = $this->values()['name'];
|
$name = $this->values()['name'];
|
||||||
$greet = $this->values()['greet'] ?? false;
|
$greet = $this->values()['greet'] ?? false;
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($greet) {
|
if ($greet) {
|
||||||
$this->writer()->green("Hello, $name! Welcome to the CLI demo.\n");
|
$this->writer()->green("Hello, $name! Welcome to the CLI demo.\n");
|
||||||
} else {
|
} else {
|
||||||
$this->writer()->yellow("Name provided: {$name}\n");
|
$exampleCommand = new ExampleCommand($name);
|
||||||
|
$this->writer()->yellow(CommandBus::handle($exampleCommand));
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
49
src/CommandBus/AttributeLocator.php
Normal file
49
src/CommandBus/AttributeLocator.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\CommandBus;
|
||||||
|
|
||||||
|
use League\Tactician\Exception\CanNotInvokeHandlerException;
|
||||||
|
use League\Tactician\Handler\Locator\HandlerLocator;
|
||||||
|
use Siteworxpro\App\Attributes\CommandBus\HandlesCommand;
|
||||||
|
|
||||||
|
class AttributeLocator implements HandlerLocator
|
||||||
|
{
|
||||||
|
private const string HANDLER_NAMESPACE = 'Siteworxpro\\App\\CommandBus\\Handlers\\';
|
||||||
|
|
||||||
|
private array $handlers;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$directory = __DIR__ . '/Handlers';
|
||||||
|
$files = scandir($directory);
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if (pathinfo($file, PATHINFO_EXTENSION) === 'php') {
|
||||||
|
$className = pathinfo($file, PATHINFO_FILENAME);
|
||||||
|
$fullClassName = self::HANDLER_NAMESPACE . $className;
|
||||||
|
|
||||||
|
if (class_exists($fullClassName)) {
|
||||||
|
$reflectionClass = new \ReflectionClass($fullClassName);
|
||||||
|
$attributes = $reflectionClass->getAttributes(HandlesCommand::class);
|
||||||
|
|
||||||
|
foreach ($attributes as $attribute) {
|
||||||
|
$instance = $attribute->newInstance();
|
||||||
|
$commandClass = $instance->commandClass;
|
||||||
|
$this->handlers[$commandClass] = $fullClassName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHandlerForCommand($commandName)
|
||||||
|
{
|
||||||
|
if (isset($this->handlers[$commandName])) {
|
||||||
|
$handlerClass = $this->handlers[$commandName];
|
||||||
|
return new $handlerClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CanNotInvokeHandlerException("No handler found for command: " . $commandName);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/CommandBus/Commands/Command.php
Normal file
9
src/CommandBus/Commands/Command.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\CommandBus\Commands;
|
||||||
|
|
||||||
|
readonly abstract class Command
|
||||||
|
{
|
||||||
|
}
|
||||||
18
src/CommandBus/Commands/ExampleCommand.php
Normal file
18
src/CommandBus/Commands/ExampleCommand.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\CommandBus\Commands;
|
||||||
|
|
||||||
|
readonly class ExampleCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $name
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/CommandBus/Handlers/CommandHandler.php
Normal file
9
src/CommandBus/Handlers/CommandHandler.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\CommandBus\Handlers;
|
||||||
|
|
||||||
|
abstract class CommandHandler implements CommandHandlerInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
12
src/CommandBus/Handlers/CommandHandlerInterface.php
Normal file
12
src/CommandBus/Handlers/CommandHandlerInterface.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\CommandBus\Handlers;
|
||||||
|
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\Command;
|
||||||
|
|
||||||
|
interface CommandHandlerInterface
|
||||||
|
{
|
||||||
|
public function __invoke(Command $command): mixed;
|
||||||
|
}
|
||||||
30
src/CommandBus/Handlers/ExampleHandler.php
Normal file
30
src/CommandBus/Handlers/ExampleHandler.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\CommandBus\Handlers;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Attributes\CommandBus\HandlesCommand;
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\Command;
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\ExampleCommand;
|
||||||
|
use Siteworxpro\App\Services\Facades\Logger;
|
||||||
|
|
||||||
|
#[HandlesCommand(ExampleCommand::class)]
|
||||||
|
class ExampleHandler extends CommandHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Command|ExampleCommand $command
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __invoke(Command|ExampleCommand $command): string
|
||||||
|
{
|
||||||
|
if (!method_exists($command, 'getName')) {
|
||||||
|
throw new \TypeError('Invalid command type provided to ExampleHandler.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $command->getName();
|
||||||
|
Logger::info('Handling ExampleCommand for name: ' . $name);
|
||||||
|
|
||||||
|
return 'Hello, ' . $name . '!';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,9 @@ use Nyholm\Psr7\ServerRequest;
|
|||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||||
use Siteworxpro\App\Http\Responses\GenericResponse;
|
use Siteworxpro\App\Http\Responses\GenericResponse;
|
||||||
|
use Siteworxpro\App\Http\Responses\ServerErrorResponse;
|
||||||
use Siteworxpro\App\Models\Model;
|
use Siteworxpro\App\Models\Model;
|
||||||
|
use Siteworxpro\App\Services\Facades\Logger;
|
||||||
use Siteworxpro\App\Services\Facades\Redis;
|
use Siteworxpro\App\Services\Facades\Redis;
|
||||||
use Siteworxpro\HttpStatus\CodesEnum;
|
use Siteworxpro\HttpStatus\CodesEnum;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
@@ -43,18 +45,19 @@ class HealthcheckController extends Controller
|
|||||||
throw new \Exception('Redis ping failed');
|
throw new \Exception('Redis ping failed');
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
Logger::emergency(
|
||||||
|
'Healthcheck failed: ' . $e->getMessage(),
|
||||||
|
['exception' => $e]
|
||||||
|
);
|
||||||
|
|
||||||
return JsonResponseFactory::createJsonResponse(
|
return JsonResponseFactory::createJsonResponse(
|
||||||
[
|
new ServerErrorResponse($e),
|
||||||
'status_code' => CodesEnum::SERVICE_UNAVAILABLE->value,
|
|
||||||
'message' => 'Healthcheck Failed',
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
],
|
|
||||||
CodesEnum::SERVICE_UNAVAILABLE
|
CodesEnum::SERVICE_UNAVAILABLE
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return JsonResponseFactory::createJsonResponse(
|
return JsonResponseFactory::createJsonResponse(
|
||||||
new GenericResponse('Healthcheck OK', CodesEnum::OK->value)
|
new GenericResponse('Healthcheck OK')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ namespace Siteworxpro\App\Controllers;
|
|||||||
use Nyholm\Psr7\ServerRequest;
|
use Nyholm\Psr7\ServerRequest;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Siteworxpro\App\Attributes\Guards;
|
use Siteworxpro\App\Attributes\Guards;
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\ExampleCommand;
|
||||||
use Siteworxpro\App\Docs\TokenSecurity;
|
use Siteworxpro\App\Docs\TokenSecurity;
|
||||||
use Siteworxpro\App\Docs\UnauthorizedResponse;
|
use Siteworxpro\App\Docs\UnauthorizedResponse;
|
||||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Siteworxpro\App\Http\Responses\GenericResponse;
|
use Siteworxpro\App\Http\Responses\GenericResponse;
|
||||||
|
use Siteworxpro\App\Services\Facades\CommandBus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class IndexController
|
* Class IndexController
|
||||||
@@ -37,7 +39,10 @@ class IndexController extends Controller
|
|||||||
#[UnauthorizedResponse]
|
#[UnauthorizedResponse]
|
||||||
public function get(ServerRequest $request): ResponseInterface
|
public function get(ServerRequest $request): ResponseInterface
|
||||||
{
|
{
|
||||||
return JsonResponseFactory::createJsonResponse(new GenericResponse('Server is running'));
|
$command = new ExampleCommand($request->getQueryParams()['name'] ?? 'Guest');
|
||||||
|
$greeting = CommandBus::handle($command);
|
||||||
|
|
||||||
|
return JsonResponseFactory::createJsonResponse(new GenericResponse('Server is running. ' . $greeting));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ class UnauthorizedResponse extends OA\Response
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
properties: [
|
properties: [
|
||||||
new OA\Property(property: 'status_code', type: 'integer', example: 401),
|
|
||||||
new OA\Property(property: 'message', type: 'string', example: 'Unauthorized'),
|
new OA\Property(property: 'message', type: 'string', example: 'Unauthorized'),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
54
src/Grpc.php
Normal file
54
src/Grpc.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?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
|
||||||
|
{
|
||||||
|
private const array SERVICES = [
|
||||||
|
GreeterInterface::class => GreeterHandler::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \ReflectionException
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
Kernel::boot();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the gRPC server
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function start(): int
|
||||||
|
{
|
||||||
|
$server = new Server(new Invoker(), [
|
||||||
|
'debug' => Config::get('app.dev_mode'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach (self::SERVICES as $interface => $handler) {
|
||||||
|
$server->registerService($interface, new $handler());
|
||||||
|
}
|
||||||
|
|
||||||
|
$server->serve(Worker::create());
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/GrpcHandlers/GreeterHandler.php
Normal file
25
src/GrpcHandlers/GreeterHandler.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\GrpcHandlers;
|
||||||
|
|
||||||
|
use GRPC\Greeter\GreeterInterface;
|
||||||
|
use GRPC\Greeter\HelloReply;
|
||||||
|
use GRPC\Greeter\HelloRequest;
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\ExampleCommand;
|
||||||
|
use Siteworxpro\App\Services\Facades\CommandBus;
|
||||||
|
use Spiral\RoadRunner\GRPC;
|
||||||
|
|
||||||
|
class GreeterHandler implements GreeterInterface
|
||||||
|
{
|
||||||
|
public function SayHello(GRPC\ContextInterface $ctx, HelloRequest $in): HelloReply // phpcs:ignore
|
||||||
|
{
|
||||||
|
$command = new ExampleCommand($in->getName());
|
||||||
|
|
||||||
|
$reply = new HelloReply();
|
||||||
|
$reply->setMessage(CommandBus::handle($command));
|
||||||
|
|
||||||
|
return $reply;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,14 +11,12 @@ use OpenApi\Attributes as OA;
|
|||||||
schema: 'GenericResponse',
|
schema: 'GenericResponse',
|
||||||
properties: [
|
properties: [
|
||||||
new OA\Property(property: 'message', type: 'string', example: 'Operation completed successfully.'),
|
new OA\Property(property: 'message', type: 'string', example: 'Operation completed successfully.'),
|
||||||
new OA\Property(property: 'status_code', type: 'integer', example: 200),
|
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
readonly class GenericResponse implements Arrayable
|
readonly class GenericResponse implements Arrayable
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private string $message = '',
|
private string $message = '',
|
||||||
private int $statusCode = 200
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +24,6 @@ readonly class GenericResponse implements Arrayable
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'message' => $this->message,
|
'message' => $this->message,
|
||||||
'status_code' => $this->statusCode,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use OpenApi\Attributes as OA;
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
example: 'The requested resource /api/resource was not found.'
|
example: 'The requested resource /api/resource was not found.'
|
||||||
),
|
),
|
||||||
new OA\Property(property: 'status_code', type: 'integer', example: 404),
|
|
||||||
new OA\Property(
|
new OA\Property(
|
||||||
property: 'context',
|
property: 'context',
|
||||||
description: 'Additional context about the not found error.',
|
description: 'Additional context about the not found error.',
|
||||||
@@ -32,7 +31,6 @@ readonly class NotFoundResponse implements Arrayable
|
|||||||
public function toArray(): array
|
public function toArray(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'status_code' => CodesEnum::NOT_FOUND->value,
|
|
||||||
'message' => 'The requested resource ' . $this->uri . ' was not found.',
|
'message' => 'The requested resource ' . $this->uri . ' was not found.',
|
||||||
'context' => $this->context,
|
'context' => $this->context,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use OpenApi\Attributes as OA;
|
|||||||
schema: 'ServerErrorResponse',
|
schema: 'ServerErrorResponse',
|
||||||
properties: array(
|
properties: array(
|
||||||
new OA\Property(property: 'message', type: 'string', example: 'An internal server error occurred.'),
|
new OA\Property(property: 'message', type: 'string', example: 'An internal server error occurred.'),
|
||||||
new OA\Property(property: 'status_code', type: 'integer', example: 500),
|
new OA\Property(property: 'code', type: 'integer', example: 500),
|
||||||
new OA\Property(
|
new OA\Property(
|
||||||
property: 'file',
|
property: 'file',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@@ -35,7 +35,7 @@ readonly class ServerErrorResponse implements Arrayable
|
|||||||
{
|
{
|
||||||
if (Config::get('app.dev_mode')) {
|
if (Config::get('app.dev_mode')) {
|
||||||
return [
|
return [
|
||||||
'status_code' => $this->e->getCode() != 0 ?
|
'code' => $this->e->getCode() != 0 ?
|
||||||
$this->e->getCode() :
|
$this->e->getCode() :
|
||||||
CodesEnum::INTERNAL_SERVER_ERROR->value,
|
CodesEnum::INTERNAL_SERVER_ERROR->value,
|
||||||
'message' => $this->e->getMessage(),
|
'message' => $this->e->getMessage(),
|
||||||
@@ -47,7 +47,7 @@ readonly class ServerErrorResponse implements Arrayable
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'status_code' => $this->e->getCode() != 0 ?
|
'code' => $this->e->getCode() != 0 ?
|
||||||
$this->e->getCode() :
|
$this->e->getCode() :
|
||||||
CodesEnum::INTERNAL_SERVER_ERROR->value,
|
CodesEnum::INTERNAL_SERVER_ERROR->value,
|
||||||
'message' => 'An internal server error occurred.',
|
'message' => 'An internal server error occurred.',
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use Siteworxpro\App\Services\Facade;
|
|||||||
use Siteworxpro\App\Services\Facades\Config;
|
use Siteworxpro\App\Services\Facades\Config;
|
||||||
use Siteworxpro\App\Services\Facades\Dispatcher;
|
use Siteworxpro\App\Services\Facades\Dispatcher;
|
||||||
use Siteworxpro\App\Services\ServiceProviders\BrokerServiceProvider;
|
use Siteworxpro\App\Services\ServiceProviders\BrokerServiceProvider;
|
||||||
|
use Siteworxpro\App\Services\ServiceProviders\CommandBusProvider;
|
||||||
use Siteworxpro\App\Services\ServiceProviders\DispatcherServiceProvider;
|
use Siteworxpro\App\Services\ServiceProviders\DispatcherServiceProvider;
|
||||||
use Siteworxpro\App\Services\ServiceProviders\LoggerServiceProvider;
|
use Siteworxpro\App\Services\ServiceProviders\LoggerServiceProvider;
|
||||||
use Siteworxpro\App\Services\ServiceProviders\RedisServiceProvider;
|
use Siteworxpro\App\Services\ServiceProviders\RedisServiceProvider;
|
||||||
@@ -33,7 +34,8 @@ class Kernel
|
|||||||
LoggerServiceProvider::class,
|
LoggerServiceProvider::class,
|
||||||
RedisServiceProvider::class,
|
RedisServiceProvider::class,
|
||||||
DispatcherServiceProvider::class,
|
DispatcherServiceProvider::class,
|
||||||
BrokerServiceProvider::class
|
BrokerServiceProvider::class,
|
||||||
|
CommandBusProvider::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -55,24 +55,6 @@ class Facade
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert the facade into a Mockery spy.
|
|
||||||
*
|
|
||||||
* @return HigherOrderTapProxy | MockInterface
|
|
||||||
*/
|
|
||||||
public static function spy(): HigherOrderTapProxy | MockInterface
|
|
||||||
{
|
|
||||||
if (! static::isMock()) {
|
|
||||||
$class = static::getMockableClass();
|
|
||||||
|
|
||||||
return tap($class ? Mockery::spy($class) : Mockery::spy(), function ($spy) {
|
|
||||||
static::swap($spy);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new \RuntimeException('Cannot spy on an existing mock instance.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiate a partial mock on the facade.
|
* Initiate a partial mock on the facade.
|
||||||
*
|
*
|
||||||
@@ -189,19 +171,6 @@ class Facade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether a "fake" has been set as the facade instance.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public static function isFake(): bool
|
|
||||||
{
|
|
||||||
$name = static::getFacadeAccessor();
|
|
||||||
|
|
||||||
return isset(static::$resolvedInstance[$name]) &&
|
|
||||||
static::$resolvedInstance[$name] instanceof Fake;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the root object behind the facade.
|
* Get the root object behind the facade.
|
||||||
*
|
*
|
||||||
|
|||||||
27
src/Services/Facades/CommandBus.php
Normal file
27
src/Services/Facades/CommandBus.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Services\Facades;
|
||||||
|
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\Command;
|
||||||
|
use Siteworxpro\App\Services\Facade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broker Facade
|
||||||
|
*
|
||||||
|
* @package Siteworxpro\App\Services\Facades
|
||||||
|
* @method static mixed handle(Command $command)
|
||||||
|
*/
|
||||||
|
class CommandBus extends Facade
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the registered name of the component.
|
||||||
|
*
|
||||||
|
* @return string The name of the component.
|
||||||
|
*/
|
||||||
|
protected static function getFacadeAccessor(): string
|
||||||
|
{
|
||||||
|
return \League\Tactician\CommandBus::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,10 @@ namespace Siteworxpro\App\Services\ServiceProviders;
|
|||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Siteworxpro\App\Async\Brokers\Broker;
|
use Siteworxpro\App\Async\Brokers\Broker;
|
||||||
|
use Siteworxpro\App\Async\Brokers\Kafka;
|
||||||
|
use Siteworxpro\App\Async\Brokers\RabbitMQ;
|
||||||
|
use Siteworxpro\App\Async\Brokers\Redis;
|
||||||
|
use Siteworxpro\App\Async\Brokers\Sqs;
|
||||||
use Siteworxpro\App\Services\Facades\Config;
|
use Siteworxpro\App\Services\Facades\Config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,6 +22,11 @@ use Siteworxpro\App\Services\Facades\Config;
|
|||||||
*/
|
*/
|
||||||
class BrokerServiceProvider extends ServiceProvider
|
class BrokerServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
|
public function provides(): array
|
||||||
|
{
|
||||||
|
return [Kafka::class, RabbitMQ::class, Redis::class, Sqs::class];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register services.
|
* Register services.
|
||||||
*
|
*
|
||||||
|
|||||||
38
src/Services/ServiceProviders/CommandBusProvider.php
Normal file
38
src/Services/ServiceProviders/CommandBusProvider.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Services\ServiceProviders;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use League\Tactician\CommandBus;
|
||||||
|
use League\Tactician\Handler\CommandHandlerMiddleware;
|
||||||
|
use League\Tactician\Handler\CommandNameExtractor\ClassNameExtractor;
|
||||||
|
use League\Tactician\Handler\MethodNameInflector\InvokeInflector;
|
||||||
|
use Siteworxpro\App\CommandBus\AttributeLocator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CommandBusProvider
|
||||||
|
*
|
||||||
|
* @package Siteworxpro\App\Services\ServiceProviders
|
||||||
|
*/
|
||||||
|
class CommandBusProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function provides(): array
|
||||||
|
{
|
||||||
|
return [CommandBus::class];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->app->singleton(CommandBus::class, function () {
|
||||||
|
return new CommandBus([
|
||||||
|
new CommandHandlerMiddleware(
|
||||||
|
new ClassNameExtractor(),
|
||||||
|
new AttributeLocator(),
|
||||||
|
new InvokeInflector()
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,11 @@ use Siteworxpro\App\Events\Dispatcher;
|
|||||||
*/
|
*/
|
||||||
class DispatcherServiceProvider extends ServiceProvider
|
class DispatcherServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
|
public function provides(): array
|
||||||
|
{
|
||||||
|
return [Dispatcher::class];
|
||||||
|
}
|
||||||
|
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
$this->app->singleton(Dispatcher::class, function () {
|
$this->app->singleton(Dispatcher::class, function () {
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ use Siteworxpro\App\Services\Facades\Config;
|
|||||||
*/
|
*/
|
||||||
class LoggerServiceProvider extends ServiceProvider
|
class LoggerServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
|
public function provides(): array
|
||||||
|
{
|
||||||
|
return [Logger::class];
|
||||||
|
}
|
||||||
|
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
$this->app->singleton(Logger::class, function () {
|
$this->app->singleton(Logger::class, function () {
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ use Siteworxpro\App\Services\Facades\Config;
|
|||||||
*/
|
*/
|
||||||
class RedisServiceProvider extends ServiceProvider
|
class RedisServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
|
public function provides(): array
|
||||||
|
{
|
||||||
|
return [Client::class];
|
||||||
|
}
|
||||||
|
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
$this->app->singleton(Client::class, function () {
|
$this->app->singleton(Client::class, function () {
|
||||||
|
|||||||
36
tests/CommandBus/AttributeLocatorTest.php
Normal file
36
tests/CommandBus/AttributeLocatorTest.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\Tests\CommandBus;
|
||||||
|
|
||||||
|
use League\Tactician\Exception\CanNotInvokeHandlerException;
|
||||||
|
use Siteworxpro\App\CommandBus\AttributeLocator;
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\ExampleCommand;
|
||||||
|
use Siteworxpro\App\CommandBus\Handlers\ExampleHandler;
|
||||||
|
use Siteworxpro\Tests\Unit;
|
||||||
|
|
||||||
|
class AttributeLocatorTest extends Unit
|
||||||
|
{
|
||||||
|
private const array HANDLERS = [
|
||||||
|
ExampleCommand::class => ExampleHandler::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
public function testResolvesFiles(): void
|
||||||
|
{
|
||||||
|
$attributeLocator = new AttributeLocator();
|
||||||
|
|
||||||
|
foreach (self::HANDLERS as $command => $handler) {
|
||||||
|
$class = $attributeLocator->getHandlerForCommand($command);
|
||||||
|
$this->assertInstanceOf($handler, $class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testThrowsOnCannotResolve(): void
|
||||||
|
{
|
||||||
|
$attributeLocator = new AttributeLocator();
|
||||||
|
|
||||||
|
$this->expectException(CanNotInvokeHandlerException::class);
|
||||||
|
$attributeLocator->getHandlerForCommand('NonExistentCommand');
|
||||||
|
}
|
||||||
|
}
|
||||||
32
tests/CommandBus/Handlers/ExampleHandlerTest.php
Normal file
32
tests/CommandBus/Handlers/ExampleHandlerTest.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Siteworxpro\Tests\CommandBus\Handlers;
|
||||||
|
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\Command;
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\ExampleCommand;
|
||||||
|
use Siteworxpro\App\CommandBus\Handlers\ExampleHandler;
|
||||||
|
use Siteworxpro\Tests\Unit;
|
||||||
|
|
||||||
|
class ExampleHandlerTest extends Unit
|
||||||
|
{
|
||||||
|
public function testExampleCommand(): void
|
||||||
|
{
|
||||||
|
$command = new ExampleCommand('test payload');
|
||||||
|
$this->assertEquals('test payload', $command->getName());
|
||||||
|
|
||||||
|
$handler = new ExampleHandler();
|
||||||
|
$result = $handler($command);
|
||||||
|
$this->assertEquals('Hello, test payload!', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testThrowsException(): void
|
||||||
|
{
|
||||||
|
$class = new readonly class extends Command
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->expectException(\TypeError::class);
|
||||||
|
$handler = new ExampleHandler();
|
||||||
|
$handler($class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,23 +4,31 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Siteworxpro\Tests\Controllers;
|
namespace Siteworxpro\Tests\Controllers;
|
||||||
|
|
||||||
|
use League\Tactician\CommandBus;
|
||||||
use Siteworxpro\App\Controllers\IndexController;
|
use Siteworxpro\App\Controllers\IndexController;
|
||||||
|
|
||||||
class IndexControllerTest extends AbstractController
|
class IndexControllerTest extends AbstractController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @throws \JsonException
|
* @throws \JsonException|\ReflectionException
|
||||||
*/
|
*/
|
||||||
public function testGet(): void
|
public function testGet(): void
|
||||||
{
|
{
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
|
|
||||||
|
$this->getContainer()->bind(CommandBus::class, function () {
|
||||||
|
return \Mockery::mock(CommandBus::class)
|
||||||
|
->shouldReceive('handle')
|
||||||
|
->andReturn('Hello World')
|
||||||
|
->getMock();
|
||||||
|
});
|
||||||
|
|
||||||
$controller = new IndexController();
|
$controller = new IndexController();
|
||||||
|
|
||||||
$response = $controller->get($this->getMockRequest());
|
$response = $controller->get($this->getMockRequest());
|
||||||
|
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
$this->assertEquals('{"message":"Server is running","status_code":200}', (string)$response->getBody());
|
$this->assertEquals('{"message":"Server is running. Hello World"}', (string)$response->getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,6 +43,6 @@ class IndexControllerTest extends AbstractController
|
|||||||
$response = $controller->post($this->getMockRequest());
|
$response = $controller->post($this->getMockRequest());
|
||||||
|
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
$this->assertEquals('{"message":"POST request received","status_code":200}', (string)$response->getBody());
|
$this->assertEquals('{"message":"POST request received"}', (string)$response->getBody());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
tests/GrpcHandlers/GreeterHandlerTest.php
Normal file
36
tests/GrpcHandlers/GreeterHandlerTest.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\Tests\GrpcHandlers;
|
||||||
|
|
||||||
|
use GRPC\Greeter\HelloRequest;
|
||||||
|
use League\Tactician\CommandBus;
|
||||||
|
use Siteworxpro\App\GrpcHandlers\GreeterHandler;
|
||||||
|
use Siteworxpro\Tests\Unit;
|
||||||
|
use Spiral\RoadRunner\GRPC\ContextInterface;
|
||||||
|
|
||||||
|
class GreeterHandlerTest extends Unit
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws \ReflectionException
|
||||||
|
*/
|
||||||
|
public function testSayHello(): void
|
||||||
|
{
|
||||||
|
$this->getContainer()->bind(CommandBus::class, function () {
|
||||||
|
return \Mockery::mock(CommandBus::class)
|
||||||
|
->shouldReceive('handle')
|
||||||
|
->andReturn('Hello World')
|
||||||
|
->getMock();
|
||||||
|
});
|
||||||
|
|
||||||
|
$request = new HelloRequest();
|
||||||
|
$request->setName('World');
|
||||||
|
|
||||||
|
$context = \Mockery::mock(ContextInterface::class);
|
||||||
|
|
||||||
|
$handler = new GreeterHandler();
|
||||||
|
$response = $handler->SayHello($context, $request);
|
||||||
|
$this->assertEquals('Hello World', $response->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ class NotFoundResponseTest extends Unit
|
|||||||
$response = new NotFoundResponse('/api/resource', ['key' => 'value']);
|
$response = new NotFoundResponse('/api/resource', ['key' => 'value']);
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
'status_code' => 404,
|
|
||||||
'message' => 'The requested resource /api/resource was not found.',
|
'message' => 'The requested resource /api/resource was not found.',
|
||||||
'context' => ['key' => 'value'],
|
'context' => ['key' => 'value'],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class ServerErrorResponseTest extends Unit
|
|||||||
$response = new ServerErrorResponse($e, ['operation' => 'data_processing']);
|
$response = new ServerErrorResponse($e, ['operation' => 'data_processing']);
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
'status_code' => 500,
|
'code' => 500,
|
||||||
'message' => 'A Test Error occurred.',
|
'message' => 'A Test Error occurred.',
|
||||||
'context' => [
|
'context' => [
|
||||||
'operation' => 'data_processing'
|
'operation' => 'data_processing'
|
||||||
@@ -35,13 +35,15 @@ class ServerErrorResponseTest extends Unit
|
|||||||
|
|
||||||
public function testToArrayNotInDevMode(): void
|
public function testToArrayNotInDevMode(): void
|
||||||
{
|
{
|
||||||
|
Config::set('app.dev_mode', false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
throw new \Exception('A Test Error occurred.');
|
throw new \Exception('A Test Error occurred.');
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
$response = new ServerErrorResponse($exception);
|
$response = new ServerErrorResponse($exception);
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
'status_code' => 500,
|
'code' => 500,
|
||||||
'message' => 'An internal server error occurred.',
|
'message' => 'An internal server error occurred.',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -51,13 +53,15 @@ class ServerErrorResponseTest extends Unit
|
|||||||
|
|
||||||
public function testToArrayIfCodeIsSet(): void
|
public function testToArrayIfCodeIsSet(): void
|
||||||
{
|
{
|
||||||
|
Config::set('app.dev_mode', false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
throw new \Exception('A Test Error occurred.', 1234);
|
throw new \Exception('A Test Error occurred.', 1234);
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
$response = new ServerErrorResponse($exception);
|
$response = new ServerErrorResponse($exception);
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
'status_code' => 1234,
|
'code' => 1234,
|
||||||
'message' => 'An internal server error occurred.',
|
'message' => 'An internal server error occurred.',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -75,7 +79,7 @@ class ServerErrorResponseTest extends Unit
|
|||||||
$response = new ServerErrorResponse($exception);
|
$response = new ServerErrorResponse($exception);
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
'status_code' => 1234,
|
'code' => 1234,
|
||||||
'message' => 'A Test Error occurred.',
|
'message' => 'A Test Error occurred.',
|
||||||
'file' => $exception->getFile(),
|
'file' => $exception->getFile(),
|
||||||
'line' => $exception->getLine(),
|
'line' => $exception->getLine(),
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ use Psr\Container\ContainerExceptionInterface;
|
|||||||
use Psr\Container\NotFoundExceptionInterface;
|
use Psr\Container\NotFoundExceptionInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
|
use RoadRunner\Logger\Logger as RRLogger;
|
||||||
use Siteworxpro\App\Log\Logger;
|
use Siteworxpro\App\Log\Logger;
|
||||||
|
use Siteworxpro\App\Services\Facades\Logger as LoggerFacade;
|
||||||
use Siteworxpro\Tests\Unit;
|
use Siteworxpro\Tests\Unit;
|
||||||
|
|
||||||
class LoggerRpcTest extends Unit
|
class LoggerRpcTest extends Unit
|
||||||
@@ -36,8 +38,8 @@ class LoggerRpcTest extends Unit
|
|||||||
->with('message', ['key' => 'value'])
|
->with('message', ['key' => 'value'])
|
||||||
->times(1);
|
->times(1);
|
||||||
|
|
||||||
\Siteworxpro\App\Services\Facades\Logger::getFacadeContainer()
|
LoggerFacade::getFacadeContainer()
|
||||||
->bind(\RoadRunner\Logger\Logger::class, function () use ($mock) {
|
->bind(RRLogger::class, function () use ($mock) {
|
||||||
return $mock;
|
return $mock;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,8 +65,8 @@ class LoggerRpcTest extends Unit
|
|||||||
->with('message', ['key' => 'value'])
|
->with('message', ['key' => 'value'])
|
||||||
->times(2);
|
->times(2);
|
||||||
|
|
||||||
\Siteworxpro\App\Services\Facades\Logger::getFacadeContainer()
|
LoggerFacade::getFacadeContainer()
|
||||||
->bind(\RoadRunner\Logger\Logger::class, function () use ($mock) {
|
->bind(RRLogger::class, function () use ($mock) {
|
||||||
return $mock;
|
return $mock;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,8 +93,8 @@ class LoggerRpcTest extends Unit
|
|||||||
->with('message', ['key' => 'value'])
|
->with('message', ['key' => 'value'])
|
||||||
->times(1);
|
->times(1);
|
||||||
|
|
||||||
\Siteworxpro\App\Services\Facades\Logger::getFacadeContainer()
|
LoggerFacade::getFacadeContainer()
|
||||||
->bind(\RoadRunner\Logger\Logger::class, function () use ($mock) {
|
->bind(RRLogger::class, function () use ($mock) {
|
||||||
return $mock;
|
return $mock;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,8 +120,8 @@ class LoggerRpcTest extends Unit
|
|||||||
->with('message', ['key' => 'value'])
|
->with('message', ['key' => 'value'])
|
||||||
->times(4);
|
->times(4);
|
||||||
|
|
||||||
\Siteworxpro\App\Services\Facades\Logger::getFacadeContainer()
|
LoggerFacade::getFacadeContainer()
|
||||||
->bind(\RoadRunner\Logger\Logger::class, function () use ($mock) {
|
->bind(RRLogger::class, function () use ($mock) {
|
||||||
return $mock;
|
return $mock;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -147,8 +149,8 @@ class LoggerRpcTest extends Unit
|
|||||||
$mock->expects('log')
|
$mock->expects('log')
|
||||||
->with('notaloglevel', 'message', ['key' => 'value']);
|
->with('notaloglevel', 'message', ['key' => 'value']);
|
||||||
|
|
||||||
\Siteworxpro\App\Services\Facades\Logger::getFacadeContainer()
|
LoggerFacade::getFacadeContainer()
|
||||||
->bind(\RoadRunner\Logger\Logger::class, function () use ($mock) {
|
->bind(RRLogger::class, function () use ($mock) {
|
||||||
return $mock;
|
return $mock;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ abstract class AbstractServiceProvider extends Unit
|
|||||||
$this->assertInstanceOf($providerClass, $provider);
|
$this->assertInstanceOf($providerClass, $provider);
|
||||||
$provider->register();
|
$provider->register();
|
||||||
|
|
||||||
$bindings = $provider->bindings;
|
$abstract = $provider->provides()[0];
|
||||||
foreach ($bindings as $abstract => $concrete) {
|
$concrete = get_class($container->make($abstract));
|
||||||
|
|
||||||
$this->assertTrue($container->bound($abstract), "The $abstract is not bound in the container.");
|
$this->assertTrue($container->bound($abstract), "The $abstract is not bound in the container.");
|
||||||
$this->assertNotNull($container->make($abstract), "The $abstract could not be resolved.");
|
$this->assertNotNull($container->make($abstract), "The $abstract could not be resolved.");
|
||||||
|
|
||||||
@@ -40,4 +41,3 @@ abstract class AbstractServiceProvider extends Unit
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
15
tests/ServiceProviders/CommandBusServiceProviderTest.php
Normal file
15
tests/ServiceProviders/CommandBusServiceProviderTest.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\Tests\ServiceProviders;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Services\ServiceProviders\CommandBusProvider;
|
||||||
|
|
||||||
|
class CommandBusServiceProviderTest extends AbstractServiceProvider
|
||||||
|
{
|
||||||
|
protected function getProviderClass(): string
|
||||||
|
{
|
||||||
|
return CommandBusProvider::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ use Illuminate\Container\Container;
|
|||||||
use Mockery;
|
use Mockery;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Siteworx\Config\Config as SWConfig;
|
use Siteworx\Config\Config as SWConfig;
|
||||||
|
use Siteworxpro\App\Kernel;
|
||||||
use Siteworxpro\App\Services\Facade;
|
use Siteworxpro\App\Services\Facade;
|
||||||
use Siteworxpro\App\Services\Facades\Config;
|
use Siteworxpro\App\Services\Facades\Config;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user