From a0780d888787898850ddc1451a63962793bed052 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 31 Aug 2026 12:35:11 +0200 Subject: [PATCH 1/5] Synchronize CI pipeline (cherry picked from commit d66b4bf4bb7f2db22bfcfd1942a1581d4f1371c5) --- .github/workflows/main.yml | 76 ++++++++++++-------------------------- 1 file changed, 23 insertions(+), 53 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c21c2be..33e5c87 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,11 +2,7 @@ name: "CI" on: push: - branches: - - "main" pull_request: - branches: - - "main" workflow_dispatch: permissions: @@ -24,11 +20,13 @@ jobs: steps: - name: "git checkout" uses: "actions/checkout@v7" + with: + persist-credentials: false - name: "Validate composer.json" run: "composer validate --strict" php: - name: "PHP ${{ matrix.php-version }}" + name: "PHP ${{ matrix.php-version }} (${{ matrix.dependencies }})" needs: "composer" runs-on: "ubuntu-latest" timeout-minutes: 15 @@ -39,62 +37,34 @@ jobs: - "8.1" - "8.2" - "8.3" + dependencies: + - "locked" + include: + - php-version: "8.1" + dependencies: "lowest" steps: - name: "git checkout" uses: "actions/checkout@v7" + with: + persist-credentials: false - name: "setup PHP" uses: "shivammathur/setup-php@v2" with: php-version: "${{ matrix.php-version }}" extensions: "json" - coverage: "xdebug" tools: "composer:v2" - cache: "composer" - - name: "check PHP version" - run: "php -v" + - name: "install composer dependencies (locked)" + if: "matrix.dependencies == 'locked'" + run: "composer install --prefer-dist --no-interaction --no-progress" + - name: "install composer dependencies (lowest)" + if: "matrix.dependencies == 'lowest'" + run: "composer update --prefer-lowest --prefer-dist --no-interaction --no-progress" + - name: "audit dependencies for known security vulnerabilities" + if: "matrix.php-version == '8.1'" + run: "composer audit" - name: "lint PHP files" run: "find src -type f -name '*.php' -print0 | xargs -0 -n1 php -l" - - name: "install composer dependencies" - run: "composer install --prefer-dist --no-interaction --no-progress" - - phpstan: - name: "PHPStan" - needs: "composer" - runs-on: "ubuntu-latest" - timeout-minutes: 15 - steps: - - name: "git checkout" - uses: "actions/checkout@v7" - - name: "setup PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "8.3" - extensions: "json" - tools: "composer:v2" - cache: "composer" - - name: "install composer dependencies" - run: "composer install --prefer-dist --no-interaction --no-progress" - - name: "PHPStan static analysis" - run: "vendor/bin/phpstan analyse --configuration=phpstan.neon" - - codesniffer: - name: "PHP CodeSniffer" - needs: "composer" - runs-on: "ubuntu-latest" - timeout-minutes: 15 - steps: - - name: "git checkout" - uses: "actions/checkout@v7" - - name: "setup PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "8.3" - extensions: "json" - tools: "composer:v2" - cache: "composer" - - name: "install composer dependencies" - run: "composer install --prefer-dist --no-interaction --no-progress" - - name: "check PHP_CodeSniffer version" - run: "vendor/bin/phpcs --version" - - name: "PHP CodeSniffer" - run: "vendor/bin/phpcs" + - name: "run PHPStan" + run: "php vendor/bin/phpstan analyse --no-progress" + - name: "run PHPCS" + run: "php vendor/bin/phpcs" From fc0062f31df9976c4e3220b0767b6467bae51c40 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 1 Sep 2026 11:33:48 +0200 Subject: [PATCH 2/5] synchronize phpstan floor version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3f444bb..c259d5d 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ ] }, "require-dev": { - "phpstan/phpstan": "^2.2", + "phpstan/phpstan": "^2.2.3", "squizlabs/php_codesniffer": "^4.0" } } From 96fed9736104498a5ad4efa9e5ede4dad0c98e4e Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 1 Sep 2026 11:36:29 +0200 Subject: [PATCH 3/5] synchronize AGENTS.md --- AGENTS.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 72863ef..fca6bc3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,28 @@ # AGENTS.md -## Project Purpose +## Project Overview Pure PHP interfaces library for [PHP/SAP](https://php-sap.github.io) — a contract-only package that consuming implementations must satisfy to call SAP RFC (Remote Function Call) functions. **No concrete classes exist here.** +## Ecosystem + +[PHP/SAP](https://php-sap.github.io) is split across five focused repositories that build on +each other instead of one monolithic package: + +| Repository | Role | Depends on (`composer.json`) | +|------------------------------|-------------------------------------------------------------------------------------------------------|-------------------------------------------------------| +| `php-sap/interfaces` | Contract-only interfaces (`IApi`, `IConfiguration`, `IFunction`, exceptions). No concrete classes. | — | +| `php-sap/datetime` | SAP date/time format support on top of native `DateTime`/`DateInterval`. | — | +| `php-sap/common` | Generic abstract classes, API/config value objects, and exceptions implementing `interfaces`. | `interfaces`, `datetime` | +| `php-sap/integration-tests` | Shared abstract PHPUnit test infrastructure and SAP module mocks reused by concrete connector packages. | `interfaces`, `common`, `datetime` | +| `php-sap/saprfc-kralik` | Concrete adapter for Gregor Kralik's `ext-sapnwrfc` extension. | `interfaces`, `common` (+ `integration-tests` for tests only) | + +**→ You are here: `php-sap/interfaces`** — the contracts every other package implements or consumes. + +This package only defines contracts; it has no dependencies of its own. Default/generic +implementations of these interfaces belong in `php-sap/common`, not here. + ## Architecture ``` @@ -31,7 +49,7 @@ src/ **Cross-cutting pattern:** every top-level interface (`IFunction`, `IApi`, `IApiElement`, `IConfiguration`, `IMember`) extends `IJsonSerializable` — all objects must be round-trippable through JSON. -## Namespace +### Namespace PSR-4 root `phpsap\interfaces` → `src/`. Subdirectory namespaces (`Api`, `Config`, `exceptions`, `Util`) map directly to directory names. Exception interfaces live in the lowercase `exceptions/` directory but use @@ -39,20 +57,51 @@ the mixed-case namespace `phpsap\interfaces\exceptions`. ## Developer Workflows +All commands run inside official PHP Docker images so the host machine does not need a +local PHP installation. Use PHP 8.1, 8.2, and 8.3 (matching the CI matrix in +`.github/workflows/main.yml`) for anything version-sensitive (PHPStan, PHP lint). +If you are behind a proxy, forward `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` into the +container whenever the command needs network access (e.g. `composer install`). + ```bash -# Full CI check (validate + lint + phpstan + phpcs) -composer ci - -# Individual tools (phpcs.xml already configures PSR12 for src/) -vendor/bin/phpstan analyse --configuration=phpstan.neon -vendor/bin/phpcbf # auto-fix first, uses phpcs.xml -vendor/bin/phpcs # inspect remaining issues, uses phpcs.xml -php -l src/ # syntax check only +# Install/update dependencies (needs network access -> forward proxy settings) +docker run --rm --init --interactive --tty \ + --user "$(id -u)":"$(id -g)" \ + --env HTTP_PROXY --env HTTPS_PROXY --env NO_PROXY \ + --volume "$(pwd)":/app --workdir /app \ + composer:2 install + +# Full CI check: validate + lint + phpstan + phpcs (no network access needed) +docker run --rm --init \ + --user "$(id -u)":"$(id -g)" \ + --volume "$(pwd)":/app --workdir /app \ + php:8.1-cli composer ci + +# Fix code style (run first, no network access needed) +docker run --rm --init \ + --user "$(id -u)":"$(id -g)" \ + --volume "$(pwd)":/app --workdir /app \ + php:8.1-cli php vendor/bin/phpcbf + +# Check remaining style issues (no network access needed) +docker run --rm --init \ + --user "$(id -u)":"$(id -g)" \ + --volume "$(pwd)":/app --workdir /app \ + php:8.1-cli php vendor/bin/phpcs + +# Run static analysis for every supported PHP version (no network access needed; +# --memory-limit=-1 works around the image's low default memory_limit) +for PHP_VERSION in 8.1 8.2 8.3; do + docker run --rm --init \ + --user "$(id -u)":"$(id -g)" \ + --volume "$(pwd)":/app --workdir /app \ + "php:${PHP_VERSION}-cli" php vendor/bin/phpstan analyse --configuration=phpstan.neon --memory-limit=-1 +done ``` PHPStan runs at **level 9** (strictest). All new code must pass without suppressions. -## Key Conventions +## Conventions - **Interfaces only** — never add concrete classes or traits to `src/`. - All type constants are defined as `string` constants directly on the interface @@ -64,3 +113,12 @@ PHPStan runs at **level 9** (strictest). All new code must pass without suppress - `.gitattributes` marks development-only files such as `phpcs.xml`, `phpstan.neon`, and `AGENTS.md` as `export-ignore`, so release archives are intentionally slimmer than the Git checkout. +## Safe Change Strategy for Agents + +- Never add a concrete class, trait, or default method body to `src/` — this package is + contracts only; defaults belong in `php-sap/common`. +- Before adding or renaming a constant/method on an interface, check `php-sap/common` for + every class implementing it — a breaking interface change ripples into every consumer repo. +- Keep new code PHPStan level 9 clean; do not add suppressions to work around this. +- Write documentation, comments, and new code in English to match the repository style. + From 2b8fbddedb71833202403eb27f6119b2afa634fa Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 1 Sep 2026 11:49:42 +0200 Subject: [PATCH 4/5] add Makefile --- .gitattributes | 3 +- Makefile | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 Makefile diff --git a/.gitattributes b/.gitattributes index 47f2df1..68b8773 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,7 +3,8 @@ .github export-ignore .gitignore export-ignore AGENTS.md export-ignore -phpcs.xml export-ignore composer.* export-ignore +Makefile export-ignore +phpcs.xml export-ignore phpstan.neon export-ignore README.md export-ignore diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..018c7ee --- /dev/null +++ b/Makefile @@ -0,0 +1,91 @@ +SHELL := /bin/sh +.DEFAULT_GOAL := help + +COMPOSER_IMAGE := composer:2 +COMPOSER_CACHE_DIR := $(HOME)/.cache/composer +DOCKER_USER := --user "$$(id -u)":"$$(id -g)" +DOCKER_MOUNT := --volume "$$(pwd)":/app --workdir /app + +# --prefer-lowest only has an effect on "composer update", not "composer +# install" (which just reproduces composer.lock) - switch commands so the +# flag actually does something, matching the CI "lowest" matrix job. +ifdef DEPENDENCIES_LOWEST +COMPOSER_INSTALL_CMD := update --prefer-lowest +else +COMPOSER_INSTALL_CMD := install +endif + +# Optional: set CA_CERT_FILE to a PEM file (e.g. a corporate proxy root CA) +# to make it trusted for HTTPS network access inside the containers used by +# "install" and "audit" (e.g. 'make install PHP_VERSION=8.1 CA_CERT_FILE=/path/to/ca.pem'). +ifdef CA_CERT_FILE +CA_MOUNT := --volume "$(CA_CERT_FILE)":/tmp/extra-ca.crt:ro +CA_TRUST_CMD := cat /etc/ssl/certs/ca-certificates.crt /tmp/extra-ca.crt > /tmp/ca-bundle.pem && export CURL_CA_BUNDLE=/tmp/ca-bundle.pem SSL_CERT_FILE=/tmp/ca-bundle.pem && +else +CA_MOUNT := +CA_TRUST_CMD := +endif + +.PHONY: help clean check-php-version install lint analyze beautify sniff audit validate + +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*## ' $(MAKEFILE_LIST) | sort | \ + awk 'BEGIN {FS = ":.*## "}; {printf " \033[36m%-10s\033[0m %s\n", $$1, $$2}' + +clean: ## Remove vendor/ and composer.lock (reset to a fresh checkout) + rm -rf vendor composer.lock + +check-php-version: + @if [ -z "$(PHP_VERSION)" ]; then \ + echo "Error: PHP_VERSION must be set, e.g. 'make $(MAKECMDGOALS) PHP_VERSION=8.1'." >&2; \ + exit 1; \ + fi + +install: check-php-version ## Install composer dependencies for PHP_VERSION (set DEPENDENCIES_LOWEST for --prefer-lowest, CA_CERT_FILE for a corporate proxy CA) + @mkdir -p "$(COMPOSER_CACHE_DIR)" + docker run --rm -t --init $(DOCKER_USER) \ + --env HTTP_PROXY --env HTTPS_PROXY --env NO_PROXY \ + --env COMPOSER_CACHE_DIR=/tmp/composer-cache \ + --volume "$(COMPOSER_CACHE_DIR)":/tmp/composer-cache \ + $(CA_MOUNT) \ + $(DOCKER_MOUNT) \ + $(COMPOSER_IMAGE) sh -c '\ + $(CA_TRUST_CMD) \ + composer config platform.php "$(PHP_VERSION)" && \ + composer $(COMPOSER_INSTALL_CMD) --prefer-dist --no-interaction --no-progress; \ + status=$$?; \ + composer config --unset platform.php; \ + composer config --unset platform 2>/dev/null; \ + composer config --unset config 2>/dev/null; \ + if [ $$status -eq 0 ]; then \ + composer update --lock --no-interaction --no-progress; \ + status=$$?; \ + fi; \ + exit $$status \ + ' + +lint: check-php-version ## Syntax-check every .php file in src/ for PHP_VERSION + docker run --rm --init $(DOCKER_USER) $(DOCKER_MOUNT) \ + "php:$(PHP_VERSION)-cli" sh -c "find src -type f -name '*.php' -print0 | xargs -0 -n1 php -l" + +analyze: check-php-version ## Run PHPStan for PHP_VERSION + docker run --rm -t --init $(DOCKER_USER) $(DOCKER_MOUNT) \ + "php:$(PHP_VERSION)-cli" php vendor/bin/phpstan analyse --memory-limit=-1 + +beautify: check-php-version ## Run PHPCBF (auto-fix code style) for PHP_VERSION + docker run --rm --init $(DOCKER_USER) $(DOCKER_MOUNT) \ + "php:$(PHP_VERSION)-cli" php vendor/bin/phpcbf + +sniff: check-php-version ## Run PHPCS (code style check) for PHP_VERSION + docker run --rm --init $(DOCKER_USER) $(DOCKER_MOUNT) \ + "php:$(PHP_VERSION)-cli" php vendor/bin/phpcs + +audit: ## Run composer audit (checks dependencies for known vulnerabilities; CA_CERT_FILE for a corporate proxy CA) + docker run --rm --init $(DOCKER_USER) \ + --env HTTP_PROXY --env HTTPS_PROXY --env NO_PROXY \ + $(CA_MOUNT) $(DOCKER_MOUNT) \ + $(COMPOSER_IMAGE) sh -c '$(CA_TRUST_CMD) composer audit' + +validate: ## Run composer validate --strict + docker run --rm --init $(DOCKER_USER) $(DOCKER_MOUNT) \ + $(COMPOSER_IMAGE) composer validate --strict From 16a46ffa8897f3d6d2821c8f43ed9a7c3b8b89f7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 1 Sep 2026 11:51:34 +0200 Subject: [PATCH 5/5] update README.md and AGENTS.md to use the Makefile --- AGENTS.md | 88 +++++++++++++++++++++++++++---------------------------- README.md | 12 ++++++++ 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fca6bc3..b0128e6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,13 +10,13 @@ implementations must satisfy to call SAP RFC (Remote Function Call) functions. * [PHP/SAP](https://php-sap.github.io) is split across five focused repositories that build on each other instead of one monolithic package: -| Repository | Role | Depends on (`composer.json`) | -|------------------------------|-------------------------------------------------------------------------------------------------------|-------------------------------------------------------| -| `php-sap/interfaces` | Contract-only interfaces (`IApi`, `IConfiguration`, `IFunction`, exceptions). No concrete classes. | — | -| `php-sap/datetime` | SAP date/time format support on top of native `DateTime`/`DateInterval`. | — | -| `php-sap/common` | Generic abstract classes, API/config value objects, and exceptions implementing `interfaces`. | `interfaces`, `datetime` | -| `php-sap/integration-tests` | Shared abstract PHPUnit test infrastructure and SAP module mocks reused by concrete connector packages. | `interfaces`, `common`, `datetime` | -| `php-sap/saprfc-kralik` | Concrete adapter for Gregor Kralik's `ext-sapnwrfc` extension. | `interfaces`, `common` (+ `integration-tests` for tests only) | +| Repository | Role | Depends on (`composer.json`) | +|-----------------------------|---------------------------------------------------------------------------------------------------------|---------------------------------------------------------------| +| `php-sap/interfaces` | Contract-only interfaces (`IApi`, `IConfiguration`, `IFunction`, exceptions). No concrete classes. | — | +| `php-sap/datetime` | SAP date/time format support on top of native `DateTime`/`DateInterval`. | — | +| `php-sap/common` | Generic abstract classes, API/config value objects, and exceptions implementing `interfaces`. | `interfaces`, `datetime` | +| `php-sap/integration-tests` | Shared abstract PHPUnit test infrastructure and SAP module mocks reused by concrete connector packages. | `interfaces`, `common`, `datetime` | +| `php-sap/saprfc-kralik` | Concrete adapter for Gregor Kralik's `ext-sapnwrfc` extension. | `interfaces`, `common` (+ `integration-tests` for tests only) | **→ You are here: `php-sap/interfaces`** — the contracts every other package implements or consumes. @@ -57,48 +57,43 @@ the mixed-case namespace `phpsap\interfaces\exceptions`. ## Developer Workflows -All commands run inside official PHP Docker images so the host machine does not need a -local PHP installation. Use PHP 8.1, 8.2, and 8.3 (matching the CI matrix in -`.github/workflows/main.yml`) for anything version-sensitive (PHPStan, PHP lint). -If you are behind a proxy, forward `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` into the -container whenever the command needs network access (e.g. `composer install`). +All commands run through the `Makefile` via Docker, so the host machine does not need a +local PHP installation. Run `make help` for the full target list. Use PHP 8.1, 8.2, and +8.3 (matching the CI matrix in `.github/workflows/main.yml`) for anything +version-sensitive (PHPStan, PHP lint). If you are behind a proxy, `install` and `audit` +already forward `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`; pass `CA_CERT_FILE=/path/to/ca.pem` +to trust a corporate proxy root CA inside the container. ```bash -# Install/update dependencies (needs network access -> forward proxy settings) -docker run --rm --init --interactive --tty \ - --user "$(id -u)":"$(id -g)" \ - --env HTTP_PROXY --env HTTPS_PROXY --env NO_PROXY \ - --volume "$(pwd)":/app --workdir /app \ - composer:2 install - -# Full CI check: validate + lint + phpstan + phpcs (no network access needed) -docker run --rm --init \ - --user "$(id -u)":"$(id -g)" \ - --volume "$(pwd)":/app --workdir /app \ - php:8.1-cli composer ci - -# Fix code style (run first, no network access needed) -docker run --rm --init \ - --user "$(id -u)":"$(id -g)" \ - --volume "$(pwd)":/app --workdir /app \ - php:8.1-cli php vendor/bin/phpcbf - -# Check remaining style issues (no network access needed) -docker run --rm --init \ - --user "$(id -u)":"$(id -g)" \ - --volume "$(pwd)":/app --workdir /app \ - php:8.1-cli php vendor/bin/phpcs - -# Run static analysis for every supported PHP version (no network access needed; -# --memory-limit=-1 works around the image's low default memory_limit) -for PHP_VERSION in 8.1 8.2 8.3; do - docker run --rm --init \ - --user "$(id -u)":"$(id -g)" \ - --volume "$(pwd)":/app --workdir /app \ - "php:${PHP_VERSION}-cli" php vendor/bin/phpstan analyse --configuration=phpstan.neon --memory-limit=-1 -done +# Install/update dependencies for a given PHP version (set DEPENDENCIES_LOWEST=1 for +# --prefer-lowest, matching the CI "lowest" matrix job) +make install PHP_VERSION=8.1 + +# Syntax-check every .php file in src/, matches CI +make lint PHP_VERSION=8.1 + +# Run PHPStan +make analyze PHP_VERSION=8.1 + +# Auto-fix code style (run this before "sniff") +make beautify PHP_VERSION=8.1 + +# Check code style (uses phpcs.xml) +make sniff PHP_VERSION=8.1 + +# Check dependencies for known vulnerabilities +make audit + +# Run composer validate --strict +make validate ``` +**Always use these Makefile targets instead of inventing ad-hoc `docker run`/`composer`/ +`php` commands.** If a task needs something the Makefile doesn't expose directly (e.g. +PHPCS/PHPStan on a single file), take the exact `docker run` invocation from the matching +Makefile target (image, `DOCKER_USER`, `DOCKER_MOUNT`, env forwarding) and only append the +extra arguments — don't build the command from scratch. + PHPStan runs at **level 9** (strictest). All new code must pass without suppressions. ## Conventions @@ -121,4 +116,7 @@ PHPStan runs at **level 9** (strictest). All new code must pass without suppress every class implementing it — a breaking interface change ripples into every consumer repo. - Keep new code PHPStan level 9 clean; do not add suppressions to work around this. - Write documentation, comments, and new code in English to match the repository style. +- Always run QA/build commands through the `Makefile` targets, not self-invented `docker run` + commands. For one-off variants (a single file), base the invocation on the relevant + Makefile target and only append the extra arguments. diff --git a/README.md b/README.md index 63de170..99c727f 100644 --- a/README.md +++ b/README.md @@ -4,5 +4,17 @@ This repository defines interfaces for implementing the [PHP/SAP][phpsap] API. +## Development + +All development commands (install, lint, analyze, beautify, sniff, audit, validate) +run via Docker through the `Makefile`, so no local PHP installation is needed. Run +`make help` to list all targets. Most targets require `PHP_VERSION`, e.g.: + +```sh +make install PHP_VERSION=8.1 +``` + +See `AGENTS.md` for details and proxy/CA options. + [phpsap]: https://php-sap.github.io [license-mit]: https://img.shields.io/badge/license-MIT-blue.svg