Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -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
76 changes: 23 additions & 53 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ name: "CI"

on:
push:
branches:
- "main"
pull_request:
branches:
- "main"
workflow_dispatch:

permissions:
Expand All @@ -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
Expand All @@ -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"
78 changes: 67 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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

```
Expand All @@ -31,28 +49,54 @@ 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
the mixed-case namespace `phpsap\interfaces\exceptions`.

## Developer Workflows

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
# 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 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.

## 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
Expand All @@ -64,3 +108,15 @@ 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.
- 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.

91 changes: 91 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
]
},
"require-dev": {
"phpstan/phpstan": "^2.2",
"phpstan/phpstan": "^2.2.3",
"squizlabs/php_codesniffer": "^4.0"
}
}