From d36caecc4d63f659a34bffc0cff074491035af09 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 02:30:51 +0000 Subject: [PATCH 01/17] Add Docker (Nginx + PHP-FPM) hosting; move secrets to %env() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the Windows/IIS hosting with a Linux container stack and inject secrets from the environment / Docker secrets via the framework's %env(...)% config resolver. Docker: - Dockerfile: multi-stage (base -> vendor/dev/prod) PHP 8.3-FPM with ext-mongodb/sodium/zip; `prod` is the default target. - docker-compose.yml: php + nginx, optional mongodb behind the `dev` profile; CORS origins and PHP-FPM host from the environment. - docker/nginx/default.conf.template: nginx translation of the five IIS web.config behaviors — scheme from X-Forwarded-Proto (TLS at the edge), trailing-slash strip, static pass-through (theme/, favicon.ico), front controller to index.php (REQUEST_URI preserved), and CORS with an origin allowlist + preflight. Rendered by the stock nginx image's envsubst. - .env.example (+ .dockerignore); .gitignore now tracks composer.json / composer.lock and ignores .env, .env.local, /secrets/. Config -> %env(): - environment-local.json: dev-safe %env(default:...)% fallbacks (e.g. MONGO_URI defaults to mongodb://mongodb:27017); phpPath emptied. - environment-prod.json: hard %env(VAR)% references (no defaults) so a missing secret fails loudly, naming the variable. File-based secret alternative (%env(trim:file:VAR_FILE)%) documented in DOCKER.md. - app.json: SMTPUsername/SMTPPassword via %env(default::...)%. - Non-secret identity/URL fields keep their {app_*}/{prod_app_*} gf setup tokens. Removals (Windows/IIS): www/web-*.config, app/cli/*.bat, srv/app.* php.ini trees, scripts/*.ps1, update-production.ps1, composer-local.json, composer-prod.json. Other: - www/index.php: __DIR__-relative autoload; drop the removed constructor arg (framework::__construct() takes none). - composer.json: committed; php >=8.3; gcgov/framework bumped to ^v6.2 (the release that must carry the %env() resolver — final tag left to the owner). - composer-ci.json + ci.yml: point the framework path-repo/checkout at the Docker-support branch until it is tagged/merged; `rm -f composer.lock` before path-repo install; new nginx-config-lint and (best-effort) prod docker-build CI jobs. - README.md/DOCKER.md: Docker quick start, gf setup/env flow, and a guide to setting environment variables securely (Docker/Swarm/Kubernetes secrets, TLS at the edge). Note: composer.lock is not committed here because the framework release tag and the sibling plugin repos are unavailable in this environment; generate and commit it once the framework %env() release is tagged. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JWacdhEDN9upUvYeagNKru --- .dockerignore | 16 + .env.example | 36 + .github/workflows/ci.yml | 48 +- .gitignore | 9 +- DOCKER.md | 158 +++ Dockerfile | 52 + README.md | 113 +- app/cli/local-debug.bat | 1 - app/cli/local.bat | 1 - app/cli/prod.bat | 1 - app/config/app.json | 4 +- app/config/environment-local.json | 14 +- app/config/environment-prod.json | 12 +- composer-ci.json | 2 +- composer-local.json | 31 - composer-prod.json | 21 - composer.json | 6 +- docker-compose.yml | 50 + docker/nginx/default.conf.template | 101 ++ scripts/create-jwt-keys.ps1 | 2 - scripts/setup.ps1 | 2 - srv/app.local-cli/php.ini | 1968 --------------------------- srv/app.local/php.ini | 1970 ---------------------------- srv/app.prod-cli/php.ini | 1923 --------------------------- srv/app.prod/php.ini | 1923 --------------------------- update-production.ps1 | 66 - www/index.php | 7 +- www/web-local.config | 111 -- www/web-prod.config | 87 -- 29 files changed, 529 insertions(+), 8206 deletions(-) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 DOCKER.md create mode 100644 Dockerfile delete mode 100644 app/cli/local-debug.bat delete mode 100644 app/cli/local.bat delete mode 100644 app/cli/prod.bat delete mode 100644 composer-local.json delete mode 100644 composer-prod.json create mode 100644 docker-compose.yml create mode 100644 docker/nginx/default.conf.template delete mode 100644 scripts/create-jwt-keys.ps1 delete mode 100644 scripts/setup.ps1 delete mode 100644 srv/app.local-cli/php.ini delete mode 100644 srv/app.local/php.ini delete mode 100644 srv/app.prod-cli/php.ini delete mode 100644 srv/app.prod/php.ini delete mode 100644 update-production.ps1 delete mode 100644 www/web-local.config delete mode 100644 www/web-prod.config diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..44aa3c6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +.git +.github +vendor +node_modules +.env +.env.local +secrets +*.md +.idea +.phpunit.cache +.phpunit.result.cache +.phpstan-cache +srv/tmp/**/* +srv/profile/* +logs/* +!**/.gitignore diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c8c4432 --- /dev/null +++ b/.env.example @@ -0,0 +1,36 @@ +# Copy this file to .env and fill in real values. NEVER commit .env. +# These variables are read by the framework's %env(...)% config resolver +# (see the framework's readme/environment-variables.md) and by docker-compose. +# +# Precedence, highest wins: real process environment > .env.local > .env + +# ---- HTTP / container ports (docker-compose) ---- +HTTP_PORT=8080 +MONGO_PORT=27017 +PHP_FPM_HOST=php:9000 + +# ---- CORS allowlist (nginx). Keep all three distinct and non-empty. ---- +CORS_ORIGIN_APP=http://localhost:8080 +CORS_ORIGIN_FRONTEND=http://localhost:5173 +CORS_ORIGIN_SWAGGER=http://localhost:8081 + +# ---- MongoDB ---- +# In the dev profile the compose service is reachable at mongodb:27017. +MONGO_URI=mongodb://mongodb:27017 +MONGO_DATABASE=app + +# ---- Microsoft OAuth (leave blank if unused) ---- +MICROSOFT_CLIENT_SECRET= + +# ---- SMTP (leave blank if unused) ---- +SMTP_USERNAME= +SMTP_PASSWORD= + +# ---- PayJunction (leave blank if unused) ---- +PAYJUNCTION_PASSWORD= +PAYJUNCTION_API_KEY= + +# ---- Production: file-based secrets (Docker/Swarm/Kubernetes) ---- +# Prefer mounting secrets as files and referencing them in environment-prod.json +# with %env(trim:file:MONGO_URI_FILE)%. Example: +# MONGO_URI_FILE=/run/secrets/mongo_uri diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 387f3f7..2a3459f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,8 +21,10 @@ jobs: - name: Check out sibling repositories required by composer-ci.json uses: actions/checkout@v4 with: + # Framework points at the Docker-support branch until it is tagged/merged + # to main. Revert this ref to `main` (or the release tag) afterwards. repository: gcgov/framework - ref: main + ref: claude/docker-gcgov-framework-tphme2 path: ../framework - uses: actions/checkout@v4 with: @@ -51,7 +53,11 @@ jobs: tools: composer:v2 coverage: none - name: Swap composer.json for CI variant - run: cp composer-ci.json composer.json + run: | + cp composer-ci.json composer.json + # The committed composer.lock is for Packagist/Docker builds; path-repo + # resolution against the sibling checkouts must not reuse it. + rm -f composer.lock - name: Install dependencies run: composer install --no-interaction --no-progress --prefer-dist - name: Run PHPStan @@ -68,8 +74,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/checkout@v4 with: + # Framework points at the Docker-support branch until it is tagged/merged + # to main. Revert this ref to `main` (or the release tag) afterwards. repository: gcgov/framework - ref: main + ref: claude/docker-gcgov-framework-tphme2 path: ../framework - uses: actions/checkout@v4 with: @@ -98,8 +106,40 @@ jobs: tools: composer:v2 coverage: none - name: Swap composer.json for CI variant - run: cp composer-ci.json composer.json + run: | + cp composer-ci.json composer.json + # The committed composer.lock is for Packagist/Docker builds; path-repo + # resolution against the sibling checkouts must not reuse it. + rm -f composer.lock - name: Install dependencies run: composer install --no-interaction --no-progress --prefer-dist - name: Run PHPUnit run: composer test + + nginx: + name: Nginx config lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Render the template and validate nginx config + run: | + docker run --rm \ + -e PHP_FPM_HOST=php:9000 \ + -e CORS_ORIGIN_APP=http://localhost:8080 \ + -e CORS_ORIGIN_FRONTEND=http://localhost:5173 \ + -e CORS_ORIGIN_SWAGGER=http://localhost:8081 \ + -e NGINX_ENVSUBST_FILTER='^(CORS_ORIGIN_|PHP_FPM_HOST)' \ + -v "$PWD/docker/nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro" \ + nginx:1.27-alpine \ + sh -c 'set -e; /docker-entrypoint.sh nginx -t' + + docker-build: + name: Docker build (prod target) + runs-on: ubuntu-latest + # Best effort: requires the framework release carrying the %env() resolver to + # be published to Packagist (see composer.json). Allowed to fail until then. + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - name: Build the production image + run: docker build --target prod -t framework-app-template:ci . diff --git a/.gitignore b/.gitignore index c1db786..8771399 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,14 @@ composer.phar /vendor/ -composer.lock -composer.json /.idea -www/web.config version.json app/config/environment.json + +# Local secrets — never commit real values (see DOCKER.md) +.env +.env.local +/secrets/ + .phpunit.cache/ .phpunit.result.cache .phpstan-cache/ diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..716a4e1 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,158 @@ +# Running this app in Docker + +This template ships a Linux container stack — **Nginx + PHP-FPM** (plus an optional MongoDB +for development) — that replaces the old Windows/IIS hosting. Secrets are injected from the +environment or Docker secrets via the framework's `%env(...)%` config resolver, so nothing +sensitive lives in the config files. + +> **Prerequisite:** the images install `gcgov/framework` from Packagist. The release that ships +> the `%env()` config resolver must be tagged and referenced by `composer.json` before +> `composer install` (and therefore `docker build`) can succeed. See `composer.json`. + +--- + +## Quick start (local development) + +```bash +cp .env.example .env # fill in any real values you have; blanks are fine for dev +docker compose --profile dev up --build +# → API on http://localhost:8080 +``` + +The `dev` profile also starts a throwaway MongoDB at `mongodb:27017`, which the default +`environment-local.json` points at (`%env(default:mongodb://mongodb:27017:MONGO_URI)%`). + +Run framework CLI routes and tooling inside the PHP container: + +```bash +docker compose exec php vendor/bin/gf cli:list +docker compose exec php vendor/bin/gf cli /your/cli/route +docker compose exec php composer ci +``` + +Before first run you still scaffold the identity/URL placeholders with `gf setup` (it replaces +the `{app_*}` / `{prod_app_*}` tokens in the config, nginx, and compose files). Environment +selection is unchanged: `gf env local` / `gf env prod` copy the matching +`environment-{name}.json` into place. + +--- + +## Securely setting environment variables in Docker + +The framework reads secrets through `%env(...)%`, so **how** you supply those variables is what +keeps them safe. In order of preference: + +### 1. Prefer file-based secrets (Docker / Swarm / Kubernetes secrets) + +A secret mounted as a file never appears in the process environment, so it is **not** exposed by +`docker inspect` and does not leak into child processes. Mount it and read it with the `file` +processor (the leading `trim:` strips the trailing newline): + +```jsonc +// environment-prod.json +"uri": "%env(trim:file:MONGO_URI_FILE)%" +``` + +```yaml +# compose / swarm +services: + php: + environment: + MONGO_URI_FILE: /run/secrets/mongo_uri + secrets: + - mongo_uri +secrets: + mongo_uri: + external: true # `docker secret create mongo_uri ./mongo_uri` +``` + +Kubernetes — mount the secret as a file and point the `*_FILE` variable at it: + +```yaml +env: + - name: MONGO_URI_FILE + value: /run/secrets/mongo_uri +volumeMounts: + - name: mongo-uri + mountPath: /run/secrets + readOnly: true +volumes: + - name: mongo-uri + secret: + secretName: mongo-uri +``` + +### 2. Process environment variables (acceptable, less private) + +Fine for non-secret config and local development; readable via `docker inspect` and the +container's `/proc`, so avoid for high-value secrets. + +```bash +docker run --env-file .env … # a gitignored env file +``` + +```yaml +services: + php: + env_file: [.env] # what this template's compose uses +``` + +Kubernetes can inject individual values from a Secret without a file: + +```yaml +env: + - name: MICROSOFT_CLIENT_SECRET + valueFrom: + secretKeyRef: { name: app-secrets, key: microsoft-client-secret } +``` + +### Rules — do not break these + +- **Keep `.env` out of git.** It is gitignored here; commit only `.env.example` with blank/dummy + values. +- **Never bake secrets into an image.** `ENV`, `ARG`, and `COPY` all persist in image layers and + in `docker history` — anyone who can pull the image can read them. Inject secrets at **run** + time, never build time. +- **Never put real secret values in `docker-compose.yml`** (it is committed). Reference `${VAR}` + and keep the values in `.env` or a secrets manager. +- **Rotation is a restart, not a rebuild.** Because secrets are injected at runtime, rotating a + credential means updating the secret/`.env` and restarting the container — the image is + unchanged. + +--- + +## TLS and the forwarded scheme + +TLS is **not** terminated inside the container. Terminate it at your edge (reverse proxy, load +balancer, ingress) and forward the original scheme: + +``` +proxy_set_header X-Forwarded-Proto $scheme; # or the ingress equivalent +``` + +The bundled nginx config maps `X-Forwarded-Proto` to the `HTTPS` / `REQUEST_SCHEME` FastCGI +params, so PHP sees the real client scheme (used for absolute URLs, secure cookies, redirects). +There is deliberately no in-container HTTP→HTTPS redirect. + +--- + +## What the nginx config does + +`docker/nginx/default.conf.template` reproduces the five behaviors the IIS `web.config` provided +(scheme from the edge, trailing-slash strip, static pass-through for `theme/` and `favicon.ico`, +front-controller routing to `index.php` with `REQUEST_URI` preserved, and CORS with an origin +allowlist + preflight). The CORS origins come from `CORS_ORIGIN_APP`, `CORS_ORIGIN_FRONTEND`, and +`CORS_ORIGIN_SWAGGER`; only those exact origins receive `Access-Control-Allow-Origin`. + +--- + +## Production image + +```bash +docker build --target prod -t your-app:latest . +``` + +The `prod` target installs `--no-dev` dependencies and runs PHP-FPM. Serve it behind an nginx +container using `docker/nginx/default.conf.template` (both containers need the application files — +bake them in or share a volume — because nginx serves the static assets and PHP-FPM executes +`index.php`). Commit `composer.lock` for reproducible builds. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8507878 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +# syntax=docker/dockerfile:1 +# +# Multi-stage build for a gcgov/framework API running on PHP-FPM behind Nginx. +# Targets: +# dev — full (incl. dev) dependencies, for local docker-compose development +# prod — minimal runtime image (default), no dev dependencies +# +# NOTE: `composer install` resolves gcgov/framework from Packagist, so the +# framework release that ships the %env() config resolver must be tagged and +# the composer.json constraint set to it (see composer.json). Commit +# composer.lock for reproducible images. + +# ---- base: PHP-FPM + required extensions ---- +FROM php:8.3-fpm AS base +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + git unzip libsodium-dev libzip-dev; \ + docker-php-ext-install -j"$(nproc)" sodium zip; \ + pecl install mongodb; \ + docker-php-ext-enable mongodb; \ + apt-get clean; \ + rm -rf /var/lib/apt/lists/* +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer +WORKDIR /var/www/app + +# ---- vendor: production dependencies only ---- +FROM base AS vendor +# composer.lock is optional here (the `*` glob) but SHOULD be committed. +COPY composer.json composer.lock* ./ +RUN composer install --no-dev --no-scripts --no-interaction --prefer-dist --no-progress + +# ---- dev: full dependencies for local development ---- +FROM base AS dev +COPY composer.json composer.lock* ./ +RUN composer install --no-scripts --no-interaction --prefer-dist --no-progress +COPY . /var/www/app +RUN set -eux; \ + mkdir -p srv/tmp/tmp srv/tmp/sessions srv/tmp/files srv/tmp/opcache srv/tmp/soaptmp srv/profile logs; \ + chown -R www-data:www-data /var/www/app +USER www-data +CMD ["php-fpm"] + +# ---- prod: minimal runtime image (default target) ---- +FROM base AS prod +COPY --from=vendor /var/www/app/vendor ./vendor +COPY . /var/www/app +RUN set -eux; \ + mkdir -p srv/tmp/tmp srv/tmp/sessions srv/tmp/files srv/tmp/opcache srv/tmp/soaptmp srv/profile logs; \ + chown -R www-data:www-data /var/www/app +USER www-data +CMD ["php-fpm"] diff --git a/README.md b/README.md index b581b96..64b79bd 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,48 @@ # Framework App Template App template repository to scaffold a new [gcgov/framework](https://github.com/gcgov/framework) +application. It runs in Docker (Nginx + PHP-FPM) and keeps secrets out of the config files by +resolving them from environment variables / Docker secrets via the framework's `%env(...)%` +syntax. -## Instructions +## Getting started -1. [Use this template](https://github.com/gcgov/framework-app-template/generate) to generate a new repository for your - app -1. Replace app variables across configuration files: - - Variables to replace: - - `{app_guid}` -> unique guid (generate from https://www.guidgenerator.com/) - - `{app_title}` -> human-readable title of app - - `{app_root_url}` -> root url of app (ex: https://signatures.garrettcounty.local) - - `{app_base_path}` -> base url of app (ex: /api/, Or: / if site is at url root) - - `{app_relative_url}` -> if your app will not run at the root of the domain, add the relative url to the app: ie: if your site will serve from http://example.com/api, replace with "/api" - - `{app_redirect_after_login}` -> if appConfig.enableAuthRoutes==true, user will be redirected to this url after successful login - - `{app_redirect_after_logout}` -> if appConfig.enableAuthRoutes==true, user will be redirected to this url after successful login - - `{app_absolute_path}` -> absolute path to app root directory - - `{app_php_path}` -> absolute path to the PHP executable root directory - - `{app_smtp_server}` -> smtp server address - - `{app_smtp_sendmail_from_address}` -> default email address to send emails from - - `{app_smtp_sendmail_from_name}` -> default human-readable name that will appear as the sender of emails - - `{app_ssl_path}` -> absolute path to a current cacert.pem file for CURL and OpenSSL extensions - - Global cacert.pem is available from Mozilla at https://curl.se/docs/caextract.html - - Use behind a firewall with SSL decryption will require appending private - - Microsoft Services - - `{app_microsoft_client_id}` -> Microsoft Azure App client id - - `{app_microsoft_client_secret}` -> Microsoft Azure App client secret - - `{app_microsoft_tenant}` -> Microsoft App tenant - - `{app_microsoft_drive_id}` -> Sharepoint Drive Id (if using files integration) - - `{app_microsoft_default_from_address}` -> Default from email address (if using Graph API Mail.Send) - - Payjunction Services - - `{app_payjunction_username}` -> PayJunction API Username - - `{app_payjunction_password}` -> PayJunction API Password - - `{app_payjunction_api_key}` -> PayJunction API Key - - `{app_payjunction_terminal_id}` -> PayJunction Smart Terminal Id - optional - - `{app_payjunction_merchant_id}` -> PayJunction Smart Terminal Merchant Id - optional but required if using smart terminal - - **Production Variables**: - - `{prod_app_root_url}` - - `{prod_app_base_path}` - - `{prod_app_redirect_after_login}` - - `{prod_app_redirect_after_logout}` - - `{prod_app_absolute_path}` - - `{prod_app_php_path}` - - `{prod_app_ssl_path}` - - Microsoft Services - - `{prod_app_microsoft_client_id}` - - `{prod_app_microsoft_client_secret}` - - `{prod_app_microsoft_tenant}` - - `{prod_app_microsoft_drive_id}` - - `{prod_app_microsoft_default_from_address}` - - Payjunction Services - - `{prod_app_payjunction_username}` - - `{prod_app_payjunction_password}` - - `{prod_app_payjunction_api_key}` - - `{prod_app_payjunction_terminal_id}` - - `{prod_app_payjunction_merchant_id}` - - Files to replace variables in: - - `/srv/app.local/php.ini` - - `/srv/app.local-cli/php.ini` - - `/srv/app.prod/php.ini` - - `/srv/app.prod-cli/php.ini` - - `/app/config/app.json` - - `/app/config/environment.json` - - `/app/cli/local.bat` - - `/app/cli/local-debug.bat` - - `/app/cli/prod.bat` -1. Move `/composer-local.json` to `/composer.json` -1. Move `/app/config/environment-local.json` to `/app/config/environment.json` -1. Move `/www/web-local.config` to `/www/web.config` -1. Configure web server - - Root directory must map to `/www/` - - PHP instance must use `/srv/app.local/php.ini` for configuration -1. Test widget module. -1. Create your models, controllers, and services! \ No newline at end of file +1. [Use this template](https://github.com/gcgov/framework-app-template/generate) to generate a + new repository for your app. +2. Scaffold the identity/URL placeholders. `gf setup` replaces the `{app_*}` and `{prod_app_*}` + tokens across the config, nginx, and compose files: + ```bash + composer install + vendor/bin/gf setup + ``` + Tokens you provide include: `{app_guid}` (generate at https://www.guidgenerator.com/), + `{app_title}`, `{app_root_url}`, `{app_base_path}`, `{app_redirect_after_login}`, + `{app_redirect_after_logout}`, the `{app_microsoft_*}` client id/tenant/drive id, and the + matching `{prod_app_*}` values for production. +3. Provide secrets as **environment variables**, not tokens. The config files reference them with + `%env(...)%` — for example `environment-prod.json` has + `"uri": "%env(MONGO_URI)%"` and `"clientSecret": "%env(MICROSOFT_CLIENT_SECRET)%"`. Copy + `.env.example` to `.env` and fill it in for local development; use Docker/Kubernetes secrets in + production. See **[DOCKER.md](DOCKER.md)** and the framework's + [environment-variables guide](https://github.com/gcgov/framework/blob/main/readme/environment-variables.md). +4. Activate an environment: `vendor/bin/gf env local` (or `gf env prod`) copies the matching + `environment-{name}.json` into place. +5. Run it: + ```bash + cp .env.example .env + docker compose --profile dev up --build + # → http://localhost:8080 + ``` +6. Test the `widget` module, then create your own models, controllers, and services. + +## Documentation + +- **[DOCKER.md](DOCKER.md)** — running in Docker, and how to set environment variables securely + (Docker/Swarm/Kubernetes secrets, TLS at the edge, the CLI). +- The `gf` CLI: `vendor/bin/gf` (`gf setup`, `gf env`, `gf cli`, `gf db:*`, …). + +## Local development without Docker + +You can still run the app under any PHP 8.3+ SAPI with `ext-mongodb`. Point the web root at +`/www/`, resolve config secrets through your shell environment or a `.env` file at the project +root, and use `vendor/bin/gf` for CLI tasks. The Docker stack is the supported, reproducible path. diff --git a/app/cli/local-debug.bat b/app/cli/local-debug.bat deleted file mode 100644 index 4933ddc..0000000 --- a/app/cli/local-debug.bat +++ /dev/null @@ -1 +0,0 @@ -"{app_php_path}\php.exe" -c "{app_absolute_path}\srv\app.local-cli\php.ini" -f "{app_absolute_path}\app\cli\index.php" -dxdebug.mode=debug -dxdebug.client_host=127.0.0.1 -dxdebug.client_port=9003 -dxdebug.start_with_request=yes %1 \ No newline at end of file diff --git a/app/cli/local.bat b/app/cli/local.bat deleted file mode 100644 index 2c9de91..0000000 --- a/app/cli/local.bat +++ /dev/null @@ -1 +0,0 @@ -"{app_php_path}\php.exe" -c "{app_absolute_path}\srv\app.local-cli\php.ini" -f "{app_absolute_path}\app\cli\index.php" %1 \ No newline at end of file diff --git a/app/cli/prod.bat b/app/cli/prod.bat deleted file mode 100644 index 561ffa5..0000000 --- a/app/cli/prod.bat +++ /dev/null @@ -1 +0,0 @@ -"{prod_app_php_path}\php.exe" -c "{prod_app_absolute_path}\srv\app.prod-cli\php.ini" -f "{prod_app_absolute_path}\app\cli\index.php" %1 diff --git a/app/config/app.json b/app/config/app.json index e6e954b..68a6af7 100644 --- a/app/config/app.json +++ b/app/config/app.json @@ -5,7 +5,9 @@ }, "email": { "fromAddress": "{app_smtp_sendmail_from_address}", - "fromName": "{app_smtp_sendmail_from_name}" + "fromName": "{app_smtp_sendmail_from_name}", + "SMTPUsername": "%env(default::SMTP_USERNAME)%", + "SMTPPassword": "%env(default::SMTP_PASSWORD)%" }, "settings": { "useSession": false diff --git a/app/config/environment-local.json b/app/config/environment-local.json index 673f4fa..a5d6a90 100644 --- a/app/config/environment-local.json +++ b/app/config/environment-local.json @@ -3,12 +3,12 @@ "serverName": "app.local", "rootUrl": "{app_root_url}", "basePath": "{app_base_path}", - "phpPath": "{app_php_path}\\php-cgi.exe -c {app_absolute_path}\\srv\\app.local\\php.ini", + "phpPath": "", "mongoDatabases": [ { "default": true, - "database": "", - "uri": "", + "database": "%env(default:app:MONGO_DATABASE)%", + "uri": "%env(default:mongodb://mongodb:27017:MONGO_URI)%", "audit": false, "include_meta": false, "include_metaLabels": false, @@ -29,16 +29,16 @@ }, "microsoft": { "clientId": "{app_microsoft_client_id}", - "clientSecret": "{app_microsoft_client_secret}", + "clientSecret": "%env(default::MICROSOFT_CLIENT_SECRET)%", "tenant": "{app_microsoft_tenant}", "driveId": "{app_microsoft_drive_id}", "fromAddress": "{app_microsoft_default_from_address}" }, "payjunction": { "username": "{app_payjunction_username}", - "password": "{app_payjunction_password}", - "apiKey": "{app_payjunction_api_key}", + "password": "%env(default::PAYJUNCTION_PASSWORD)%", + "apiKey": "%env(default::PAYJUNCTION_API_KEY)%", "terminalId": "{app_payjunction_terminal_id}", "merchantId": "{app_payjunction_merchant_id}" } -} \ No newline at end of file +} diff --git a/app/config/environment-prod.json b/app/config/environment-prod.json index 0c0c5e8..3136418 100644 --- a/app/config/environment-prod.json +++ b/app/config/environment-prod.json @@ -3,12 +3,12 @@ "serverName": "app.prod", "rootUrl": "{prod_app_root_url}", "basePath": "{prod_app_base_path}", - "phpPath": "{prod_app_php_path}\\php-cgi.exe -c {prod_app_absolute_path}\\srv\\app.prod\\php.ini", + "phpPath": "", "mongoDatabases": [ { "default": true, - "database": "", - "uri": "", + "database": "%env(MONGO_DATABASE)%", + "uri": "%env(MONGO_URI)%", "audit": false, "include_meta": false, "include_metaLabels": false, @@ -29,15 +29,15 @@ }, "microsoft": { "clientId": "{prod_app_microsoft_client_id}", - "clientSecret": "{prod_app_microsoft_client_secret}", + "clientSecret": "%env(MICROSOFT_CLIENT_SECRET)%", "tenant": "{prod_app_microsoft_tenant}", "driveId": "{prod_app_microsoft_drive_id}", "fromAddress": "{prod_app_microsoft_default_from_address}" }, "payjunction": { "username": "{prod_app_payjunction_username}", - "password": "{prod_app_payjunction_password}", - "apiKey": "{prod_app_payjunction_api_key}", + "password": "%env(PAYJUNCTION_PASSWORD)%", + "apiKey": "%env(PAYJUNCTION_API_KEY)%", "terminalId": "{prod_app_payjunction_terminal_id}", "merchantId": "{prod_app_payjunction_merchant_id}" } diff --git a/composer-ci.json b/composer-ci.json index 02c6a6f..fd8cceb 100644 --- a/composer-ci.json +++ b/composer-ci.json @@ -7,7 +7,7 @@ "mongodb/mongodb": "^2.1", "phpmailer/phpmailer": "^6.2", "zircote/swagger-php": "^6.1", - "gcgov/framework": "dev-claude/trusting-ptolemy-TFTfV", + "gcgov/framework": "dev-claude/docker-gcgov-framework-tphme2", "gcgov/framework-service-gcgov-cron-monitor": "dev-claude/trusting-ptolemy-TFTfV", "gcgov/framework-service-documentation": "dev-claude/trusting-ptolemy-TFTfV", "gcgov/framework-service-auth-oauth-server": "dev-claude/trusting-ptolemy-TFTfV", diff --git a/composer-local.json b/composer-local.json deleted file mode 100644 index 238e994..0000000 --- a/composer-local.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "gcgov/framework-app-template", - "description": "App template repository to scaffold a new application based on gcgov/framework. CI variant that resolves the framework and sibling services from local paths instead of the production Windows path.", - "require": { - "php": ">=8.2", - "ext-mongodb": "*", - "mongodb/mongodb": "^2.1", - "phpmailer/phpmailer": "^6.2", - "zircote/swagger-php": "^6.1", - "gcgov/framework": "^v6.1", - "gcgov/framework-service-gcgov-cron-monitor": "^v1.1", - "gcgov/framework-service-documentation": "^1.1", - "gcgov/framework-service-auth-oauth-server": "^2.1", - "gcgov/framework-service-user-crud": "^1.1" - }, - "require-dev": { - "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^11.5", - "jetbrains/phpstorm-attributes": "^1.0" - }, - "autoload": { - "psr-4": { - "app\\": "app/" - } - }, - "scripts": { - "phpstan": "phpstan analyse --memory-limit=512M", - "test": "phpunit", - "ci": ["@phpstan", "@test"] - } -} diff --git a/composer-prod.json b/composer-prod.json deleted file mode 100644 index 72080d7..0000000 --- a/composer-prod.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "gcgov/framework-app-template", - "description": "App template repository to scaffold a new application based on gcgov/framework. CI variant that resolves the framework and sibling services from local paths instead of the production Windows path.", - "require": { - "php": ">=8.2", - "ext-mongodb": "*", - "mongodb/mongodb": "^2.1", - "phpmailer/phpmailer": "^6.2", - "zircote/swagger-php": "^6.1", - "gcgov/framework": "^v6.1", - "gcgov/framework-service-gcgov-cron-monitor": "^v1.1", - "gcgov/framework-service-documentation": "^1.1", - "gcgov/framework-service-auth-oauth-server": "^2.1", - "gcgov/framework-service-user-crud": "^1.1" - }, - "autoload": { - "psr-4": { - "app\\": "app/" - } - } -} diff --git a/composer.json b/composer.json index 238e994..835150c 100644 --- a/composer.json +++ b/composer.json @@ -1,13 +1,13 @@ { "name": "gcgov/framework-app-template", - "description": "App template repository to scaffold a new application based on gcgov/framework. CI variant that resolves the framework and sibling services from local paths instead of the production Windows path.", + "description": "App template repository to scaffold a new application based on gcgov/framework.", "require": { - "php": ">=8.2", + "php": ">=8.3", "ext-mongodb": "*", "mongodb/mongodb": "^2.1", "phpmailer/phpmailer": "^6.2", "zircote/swagger-php": "^6.1", - "gcgov/framework": "^v6.1", + "gcgov/framework": "^v6.2", "gcgov/framework-service-gcgov-cron-monitor": "^v1.1", "gcgov/framework-service-documentation": "^1.1", "gcgov/framework-service-auth-oauth-server": "^2.1", diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9ba36c6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,50 @@ +# Local development stack: Nginx + PHP-FPM (+ optional MongoDB). +# +# cp .env.example .env +# docker compose --profile dev up --build # starts php, nginx and mongodb +# open http://localhost:8080 +# +# Without `--profile dev`, mongodb is not started — point MONGO_URI at an +# external database instead. Secrets come from .env / the environment; never +# put real secret values in this file (see DOCKER.md). + +services: + php: + build: + context: . + target: dev + env_file: + - .env + volumes: + - .:/var/www/app + # keep the image-built vendor/ from being shadowed by the bind mount + - /var/www/app/vendor + + nginx: + image: nginx:1.27-alpine + depends_on: + - php + ports: + - "${HTTP_PORT:-8080}:80" + volumes: + - ./docker/nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro + - .:/var/www/app:ro + environment: + PHP_FPM_HOST: "${PHP_FPM_HOST:-php:9000}" + CORS_ORIGIN_APP: "${CORS_ORIGIN_APP:-http://localhost:8080}" + CORS_ORIGIN_FRONTEND: "${CORS_ORIGIN_FRONTEND:-http://localhost:5173}" + CORS_ORIGIN_SWAGGER: "${CORS_ORIGIN_SWAGGER:-http://localhost:8081}" + # Only substitute our own variables; leave nginx's $variables intact. + NGINX_ENVSUBST_FILTER: "^(CORS_ORIGIN_|PHP_FPM_HOST)" + + mongodb: + image: mongo:7 + profiles: + - dev + ports: + - "${MONGO_PORT:-27017}:27017" + volumes: + - mongo-data:/data/db + +volumes: + mongo-data: diff --git a/docker/nginx/default.conf.template b/docker/nginx/default.conf.template new file mode 100644 index 0000000..f02fd91 --- /dev/null +++ b/docker/nginx/default.conf.template @@ -0,0 +1,101 @@ +# Nginx site config for a gcgov/framework API (PHP-FPM upstream). +# +# This file is a template: the stock nginx image runs `envsubst` over it at +# container start, substituting only the ${CORS_ORIGIN_*} and ${PHP_FPM_HOST} +# environment variables (NGINX_ENVSUBST_FILTER). All nginx runtime variables +# ($uri, $http_origin, …) are left untouched. +# +# It replaces the five IIS web.config behaviors the framework relied on: +# 1. HTTPS scheme -> taken from the edge via X-Forwarded-Proto (no in-container redirect) +# 2. trailing slash -> stripped +# 3. static assets -> served directly (theme/, favicon.ico) +# 4. front controller -> everything else routed to index.php (REQUEST_URI preserved) +# 5. CORS -> allowlisted origins, credentials, preflight + +# --- Scheme forwarded by the TLS-terminating edge (reverse proxy / load balancer) --- +map $http_x_forwarded_proto $forwarded_https { + https on; + default ""; +} +map $http_x_forwarded_proto $forwarded_scheme { + default $scheme; + https https; + http http; +} + +# --- CORS allowlist. Only these exact origins get Access-Control-Allow-Origin. --- +# Keep all three distinct and non-empty (set unused ones to a harmless placeholder). +map $http_origin $cors_origin { + default ""; + "${CORS_ORIGIN_APP}" "${CORS_ORIGIN_APP}"; + "${CORS_ORIGIN_FRONTEND}" "${CORS_ORIGIN_FRONTEND}"; + "${CORS_ORIGIN_SWAGGER}" "${CORS_ORIGIN_SWAGGER}"; +} + +server { + listen 80; + server_name _; + + root /var/www/app/www; + index index.php; + + # Large uploads (matches the previous IIS maxAllowedContentLength). + client_max_body_size 1024m; + + # (2) Strip trailing slash. Unconditional approximation of IIS's + # not-a-file / not-a-directory check — safe for an API with no real + # directory URLs. + rewrite ^/(.+)/$ /$1 permanent; + + # CORS response headers on every response (an empty $cors_origin means the + # request origin was not allowlisted). + add_header Access-Control-Allow-Origin $cors_origin always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Expose-Headers "X-Total-Count, X-Page, X-Count, X-Limit, X-Page-Count, Content-Disposition" always; + add_header Vary Origin always; + + # (3) Static pass-through — served straight from disk, never routed to PHP. + location ~ ^/(theme/|favicon\.ico) { + try_files $uri =404; + } + + # (4) Front controller. + location / { + # (5) CORS preflight — answered directly, never routed to PHP. + if ($request_method = OPTIONS) { + add_header Access-Control-Allow-Origin $cors_origin always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-MsAccessToken" always; + add_header Access-Control-Max-Age 120 always; + add_header Content-Type "text/plain; charset=utf-8" always; + add_header Content-Length 0 always; + return 204; + } + # REQUEST_URI is preserved natively — the framework router reads it directly. + try_files $uri /index.php$is_args$args; + } + + # Only the single front controller is executed; all other .php is denied. + location = /index.php { + include fastcgi_params; + fastcgi_pass ${PHP_FPM_HOST}; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root/index.php; + fastcgi_param SCRIPT_NAME /index.php; + # (1) Hand PHP the real client scheme from the edge. + fastcgi_param HTTPS $forwarded_https if_not_empty; + fastcgi_param REQUEST_SCHEME $forwarded_scheme if_not_empty; + fastcgi_read_timeout 300; + + # add_header in a location resets inherited headers, so re-declare CORS here. + add_header Access-Control-Allow-Origin $cors_origin always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Expose-Headers "X-Total-Count, X-Page, X-Count, X-Limit, X-Page-Count, Content-Disposition" always; + add_header Vary Origin always; + } + + location ~ \.php$ { + return 404; + } +} diff --git a/scripts/create-jwt-keys.ps1 b/scripts/create-jwt-keys.ps1 deleted file mode 100644 index 50cc8d6..0000000 --- a/scripts/create-jwt-keys.ps1 +++ /dev/null @@ -1,2 +0,0 @@ -Push-Location $PSScriptRoot -.\..\vendor\gcgov\framework\scripts\create-jwt-keys.ps1 diff --git a/scripts/setup.ps1 b/scripts/setup.ps1 deleted file mode 100644 index 788d390..0000000 --- a/scripts/setup.ps1 +++ /dev/null @@ -1,2 +0,0 @@ -Push-Location $PSScriptRoot -.\..\vendor\gcgov\framework\scripts\setup.ps1 diff --git a/srv/app.local-cli/php.ini b/srv/app.local-cli/php.ini deleted file mode 100644 index 0fb0b68..0000000 --- a/srv/app.local-cli/php.ini +++ /dev/null @@ -1,1968 +0,0 @@ -[PHP] -;8.x.x -;NTS x64 - -;;;;;;;;;;;;;;;;;;;;;;;; -; COMMON APP SETTINGS ; -;;;;;;;;;;;;;;;;;;;;;;;; -; Update the paths to the absolute paths appropriate locations for this server -; Generate a new GUID for each app - use same the guid for each environment -; - GUID generator https://www.guidgenerator.com/ - do not use hyphens -[app] -session.name = {app_guid} - -[app_debug] -;https://xdebug.org/docs/all_settings#mode -xdebug.mode=debug -;https://xdebug.org/docs/all_settings#output_dir -xdebug.output_dir ="{app_absolute_path}\srv\profile" - -[app_logging] -error_log="{app_absolute_path}\logs\error-cli.log" -opcache.error_log="{app_absolute_path}\logs\opcache,v.log" -mail.log = "{app_absolute_path}\logs\mail-cli.log" - -[app_temp] -session.save_path = "{app_absolute_path}\srv\tmp\sessions\" -sys_temp_dir = "{app_absolute_path}\srv\tmp\tmp\" -upload_tmp_dir = "{app_absolute_path}\srv\tmp\files\" -soap.wsdl_cache_dir="{app_absolute_path}\srv\tmp\soaptmp\" -opcache.file_cache="{app_absolute_path}\srv\tmp\opcache\" - -[app_mail] -SMTP = {app_smtp_server} -smtp_port = 25 -sendmail_from = {app_smtp_sendmail_from_address} - -[app_limits] -upload_max_filesize = 1024M -max_execution_time = 0 -memory_limit = 1024M -opcache.enable=Off - -[app_ssl] -curl.cainfo = "{app_ssl_path}\cacert.pem" -openssl.cafile="{app_ssl_path}\cacert.pem" - -[app_extensions] -extension_dir = "{app_php_path}\ext\" -extension=curl -extension=fileinfo -extension=openssl -extension=pdo_sqlsrv -extension=mongodb -zend_extension=xdebug -;extension=mbstring -;extension=soap -;extension=bz2 -;extension=gd2 -;extension=gettext -;extension=gmp -;extension=intl -;extension=imap -;extension=interbase -;extension=ldap -;extension=exif -;extension=mysqli -;extension=oci8_12c -;extension=odbc -;extension=pdo_firebird -;extension=pdo_mysql -;extension=pdo_oci -;extension=pdo_odbc -;extension=pdo_sqlsrv_74_nts_x64 -;extension=pdo_pgsql -;extension=pdo_sqlite -;extension=pgsql -;extension=shmop -;extension=snmp -;extension=sockets -;extension=sodium -;extension=sqlite3 -;extension=tidy -;extension=xmlrpc -;extension=xsl - - - - - -;;;;;;;;;;;;;;;;;;; -; About php.ini ; -;;;;;;;;;;;;;;;;;;; -; PHP's initialization file, generally called php.ini, is responsible for -; configuring many of the aspects of PHP's behavior. - -; PHP attempts to find and load this configuration from a number of locations. -; The following is a summary of its search order: -; 1. SAPI module specific location. -; 2. The PHPRC environment variable. (As of PHP 5.2.0) -; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0) -; 4. Current working directory (except CLI) -; 5. The web server's directory (for SAPI modules), or directory of PHP -; (otherwise in Windows) -; 6. The directory from the --with-config-file-path compile time option, or the -; Windows directory (usually C:\windows) -; See the PHP docs for more specific information. -; http://php.net/configuration.file - -; The syntax of the file is extremely simple. Whitespace and lines -; beginning with a semicolon are silently ignored (as you probably guessed). -; Section headers (e.g. [Foo]) are also silently ignored, even though -; they might mean something in the future. - -; Directives following the section heading [PATH=/www/mysite] only -; apply to PHP files in the /www/mysite directory. Directives -; following the section heading [HOST=www.example.com] only apply to -; PHP files served from www.example.com. Directives set in these -; special sections cannot be overridden by user-defined INI files or -; at runtime. Currently, [PATH=] and [HOST=] sections only work under -; CGI/FastCGI. -; http://php.net/ini.sections - -; Directives are specified using the following syntax: -; directive = value -; Directive names are *case sensitive* - foo=bar is different from FOO=bar. -; Directives are variables used to configure PHP or PHP extensions. -; There is no name validation. If PHP can't find an expected -; directive because it is not set or is mistyped, a default value will be used. - -; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one -; of the INI constants (On, Off, True, False, Yes, No and None) or an expression -; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a -; previously set variable or directive (e.g. ${foo}) - -; Expressions in the INI file are limited to bitwise operators and parentheses: -; | bitwise OR -; ^ bitwise XOR -; & bitwise AND -; ~ bitwise NOT -; ! boolean NOT - -; Boolean flags can be turned on using the values 1, On, True or Yes. -; They can be turned off using the values 0, Off, False or No. - -; An empty string can be denoted by simply not writing anything after the equal -; sign, or by using the None keyword: - -; foo = ; sets foo to an empty string -; foo = None ; sets foo to an empty string -; foo = "None" ; sets foo to the string 'None' - -; If you use constants in your value, and these constants belong to a -; dynamically loaded extension (either a PHP extension or a Zend extension), -; you may only use these constants *after* the line that loads the extension. - -;;;;;;;;;;;;;;;;;;; -; About this file ; -;;;;;;;;;;;;;;;;;;; -; PHP comes packaged with two INI files. One that is recommended to be used -; in production environments and one that is recommended to be used in -; development environments. - -; php.ini-production contains settings which hold security, performance and -; best practices at its core. But please be aware, these settings may break -; compatibility with older or less security conscience applications. We -; recommending using the production ini in production and testing environments. - -; php.ini-development is very similar to its production variant, except it is -; much more verbose when it comes to errors. We recommend using the -; development version only in development environments, as errors shown to -; application users can inadvertently leak otherwise secure information. - -; This is the php.ini-production INI file. - -;;;;;;;;;;;;;;;;;;; -; Quick Reference ; -;;;;;;;;;;;;;;;;;;; -; The following are all the settings which are different in either the production -; or development versions of the INIs with respect to PHP's default behavior. -; Please see the actual settings later in the document for more details as to why -; we recommend these changes in PHP's behavior. - -; display_errors -; Default Value: On -; Development Value: On -; Production Value: Off - -; display_startup_errors -; Default Value: Off -; Development Value: On -; Production Value: Off - -; error_reporting -; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED -; Development Value: E_ALL -; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT - -; html_errors -; Default Value: On -; Development Value: On -; Production value: On - -; log_errors -; Default Value: Off -; Development Value: On -; Production Value: On - -; max_input_time -; Default Value: -1 (Unlimited) -; Development Value: 60 (60 seconds) -; Production Value: 60 (60 seconds) - -; output_buffering -; Default Value: Off -; Development Value: 4096 -; Production Value: 4096 - -; register_argc_argv -; Default Value: On -; Development Value: Off -; Production Value: Off - -; request_order -; Default Value: None -; Development Value: "GP" -; Production Value: "GP" - -; session.gc_divisor -; Default Value: 100 -; Development Value: 1000 -; Production Value: 1000 - -; session.sid_bits_per_character -; Default Value: 4 -; Development Value: 5 -; Production Value: 5 - -; short_open_tag -; Default Value: On -; Development Value: Off -; Production Value: Off - -; track_errors -; Default Value: Off -; Development Value: On -; Production Value: Off - -; variables_order -; Default Value: "EGPCS" -; Development Value: "GPCS" -; Production Value: "GPCS" - -;;;;;;;;;;;;;;;;;;;; -; php.ini Options ; -;;;;;;;;;;;;;;;;;;;; -; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" -;user_ini.filename = ".user.ini" - -; To disable this feature set this option to an empty value -;user_ini.filename = - -; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) -;user_ini.cache_ttl = 300 - -;;;;;;;;;;;;;;;;;;;; -; Language Options ; -;;;;;;;;;;;;;;;;;;;; - -; Enable the PHP scripting language engine under Apache. -; http://php.net/engine -engine = On - -; This directive determines whether or not PHP will recognize code between -; tags as PHP source which should be processed as such. It is -; generally recommended that should be used and that this feature -; should be disabled, as enabling it may result in issues when generating XML -; documents, however this remains supported for backward compatibility reasons. -; Note that this directive does not control the would work. -; http://php.net/syntax-highlighting -;highlight.string = #DD0000 -;highlight.comment = #FF9900 -;highlight.keyword = #007700 -;highlight.default = #0000BB -;highlight.html = #000000 - -; If enabled, the request will be allowed to complete even if the user aborts -; the request. Consider enabling it if executing long requests, which may end up -; being interrupted by the user or a browser timing out. PHP's default behavior -; is to disable this feature. -; http://php.net/ignore-user-abort -;ignore_user_abort = On - -; Determines the size of the realpath cache to be used by PHP. This value should -; be increased on systems where PHP opens many files to reflect the quantity of -; the file operations performed. -; Note: if open_basedir is set, the cache is disabled -; http://php.net/realpath-cache-size -;realpath_cache_size = 4096k - -; Duration of time, in seconds for which to cache realpath information for a given -; file or directory. For systems with rarely changing files, consider increasing this -; value. -; http://php.net/realpath-cache-ttl -;realpath_cache_ttl = 120 - -; Enables or disables the circular reference collector. -; http://php.net/zend.enable-gc -zend.enable_gc = On - -; If enabled, scripts may be written in encodings that are incompatible with -; the scanner. CP936, Big5, CP949 and Shift_JIS are the examples of such -; encodings. To use this feature, mbstring extension must be enabled. -; Default: Off -;zend.multibyte = Off - -; Allows to set the default encoding for the scripts. This value will be used -; unless "declare(encoding=...)" directive appears at the top of the script. -; Only affects if zend.multibyte is set. -; Default: "" -;zend.script_encoding = - -;;;;;;;;;;;;;;;;; -; Miscellaneous ; -;;;;;;;;;;;;;;;;; - -; Decides whether PHP may expose the fact that it is installed on the server -; (e.g. by adding its signature to the Web server header). It is no security -; threat in any way, but it makes it possible to determine whether you use PHP -; on your server or not. -; http://php.net/expose-php -expose_php = Off - -;;;;;;;;;;;;;;;;;;; -; Resource Limits ; -;;;;;;;;;;;;;;;;;;; - -; Maximum execution time of each script, in seconds -; http://php.net/max-execution-time -; Note: This directive is hardcoded to 0 for the CLI SAPI - - -; Maximum amount of time each script may spend parsing request data. It's a good -; idea to limit this time on productions servers in order to eliminate unexpectedly -; long running scripts. -; Note: This directive is hardcoded to -1 for the CLI SAPI -; Default Value: -1 (Unlimited) -; Development Value: 60 (60 seconds) -; Production Value: 60 (60 seconds) -; http://php.net/max-input-time -max_input_time = 60 - -; Maximum input variable nesting level -; http://php.net/max-input-nesting-level -;max_input_nesting_level = 64 - -; How many GET/POST/COOKIE input variables may be accepted -;max_input_vars = 1000 - -; Maximum amount of memory a script may consume (128MB) -; http://php.net/memory-limit -;see [app_limits] - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Error handling and logging ; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -; This directive informs PHP of which errors, warnings and notices you would like -; it to take action for. The recommended way of setting values for this -; directive is through the use of the error level constants and bitwise -; operators. The error level constants are below here for convenience as well as -; some common settings and their meanings. -; By default, PHP is set to take action on all errors, notices and warnings EXCEPT -; those related to E_NOTICE and E_STRICT, which together cover best practices and -; recommended coding standards in PHP. For performance reasons, this is the -; recommend error reporting setting. Your production server shouldn't be wasting -; resources complaining about best practices and coding standards. That's what -; development servers and development settings are for. -; Note: The php.ini-development file has this setting as E_ALL. This -; means it pretty much reports everything which is exactly what you want during -; development and early testing. -; -; Error Level Constants: -; E_ALL - All errors and warnings (includes E_STRICT as of PHP 5.4.0) -; E_ERROR - fatal run-time errors -; E_RECOVERABLE_ERROR - almost fatal run-time errors -; E_WARNING - run-time warnings (non-fatal errors) -; E_PARSE - compile-time parse errors -; E_NOTICE - run-time notices (these are warnings which often result -; from a bug in your code, but it's possible that it was -; intentional (e.g., using an uninitialized variable and -; relying on the fact it is automatically initialized to an -; empty string) -; E_STRICT - run-time notices, enable to have PHP suggest changes -; to your code which will ensure the best interoperability -; and forward compatibility of your code -; E_CORE_ERROR - fatal errors that occur during PHP's initial startup -; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's -; initial startup -; E_COMPILE_ERROR - fatal compile-time errors -; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) -; E_USER_ERROR - user-generated error message -; E_USER_WARNING - user-generated warning message -; E_USER_NOTICE - user-generated notice message -; E_DEPRECATED - warn about code that will not work in future versions -; of PHP -; E_USER_DEPRECATED - user-generated deprecation warnings -; -; Common Values: -; E_ALL (Show all errors, warnings and notices including coding standards.) -; E_ALL & ~E_NOTICE (Show all errors, except for notices) -; E_ALL & ~E_NOTICE & ~E_STRICT (Show all errors, except for notices and coding standards warnings.) -; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors) -; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED -; Development Value: E_ALL -; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT -; http://php.net/error-reporting -error_reporting=E_ALL - -; This directive controls whether or not and where PHP will output errors, -; notices and warnings too. Error output is very useful during development, but -; it could be very dangerous in production environments. Depending on the code -; which is triggering the error, sensitive information could potentially leak -; out of your application such as database usernames and passwords or worse. -; For production environments, we recommend logging errors rather than -; sending them to STDOUT. -; Possible Values: -; Off = Do not display any errors -; stderr = Display errors to STDERR (affects only CGI/CLI binaries!) -; On or stdout = Display errors to STDOUT -; Default Value: On -; Development Value: On -; Production Value: Off -; http://php.net/display-errors -display_errors = Off - -; The display of errors which occur during PHP's startup sequence are handled -; separately from display_errors. PHP's default behavior is to suppress those -; errors from clients. Turning the display of startup errors on can be useful in -; debugging configuration problems. We strongly recommend you -; set this to 'off' for production servers. -; Default Value: Off -; Development Value: On -; Production Value: Off -; http://php.net/display-startup-errors -display_startup_errors = Off - -; Besides displaying errors, PHP can also log errors to locations such as a -; server-specific log, STDERR, or a location specified by the error_log -; directive found below. While errors should not be displayed on productions -; servers they should still be monitored and logging is a great way to do that. -; Default Value: Off -; Development Value: On -; Production Value: On -; http://php.net/log-errors -log_errors = On - -; Set maximum length of log_errors. In error_log information about the source is -; added. The default is 1024 and 0 allows to not apply any maximum length at all. -; http://php.net/log-errors-max-len -log_errors_max_len = 1024 - -; Do not log repeated messages. Repeated errors must occur in same file on same -; line unless ignore_repeated_source is set true. -; http://php.net/ignore-repeated-errors -ignore_repeated_errors = Off - -; Ignore source of message when ignoring repeated messages. When this setting -; is On you will not log errors with repeated messages from different files or -; source lines. -; http://php.net/ignore-repeated-source -ignore_repeated_source = Off - -; If this parameter is set to Off, then memory leaks will not be shown (on -; stdout or in the log). This has only effect in a debug compile, and if -; error reporting includes E_WARNING in the allowed list -; http://php.net/report-memleaks -report_memleaks = On - -; This setting is on by default. -;report_zend_debug = 0 - -; Store the last error/warning message in $php_errormsg (boolean). Setting this value -; to On can assist in debugging and is appropriate for development servers. It should -; however be disabled on production servers. -; This directive is DEPRECATED. -; Default Value: Off -; Development Value: Off -; Production Value: Off -; http://php.net/track-errors -;track_errors = Off - -; Turn off normal error reporting and emit XML-RPC error XML -; http://php.net/xmlrpc-errors -;xmlrpc_errors = 0 - -; An XML-RPC faultCode -;xmlrpc_error_number = 0 - -; When PHP displays or logs an error, it has the capability of formatting the -; error message as HTML for easier reading. This directive controls whether -; the error message is formatted as HTML or not. -; Note: This directive is hardcoded to Off for the CLI SAPI -; Default Value: On -; Development Value: On -; Production value: On -; http://php.net/html-errors -html_errors = On - -; If html_errors is set to On *and* docref_root is not empty, then PHP -; produces clickable error messages that direct to a page describing the error -; or function causing the error in detail. -; You can download a copy of the PHP manual from http://php.net/docs -; and change docref_root to the base URL of your local copy including the -; leading '/'. You must also specify the file extension being used including -; the dot. PHP's default behavior is to leave these settings empty, in which -; case no links to documentation are generated. -; Note: Never use this feature for production boxes. -; http://php.net/docref-root -; Examples -;docref_root = "/phpmanual/" - -; http://php.net/docref-ext -;docref_ext = .html - -; String to output before an error message. PHP's default behavior is to leave -; this setting blank. -; http://php.net/error-prepend-string -; Example: -;error_prepend_string = "" - -; String to output after an error message. PHP's default behavior is to leave -; this setting blank. -; http://php.net/error-append-string -; Example: -;error_append_string = "" - -; Log errors to specified file. PHP's default behavior is to leave this value -; empty. -; http://php.net/error-log -; Example: -; Log errors to syslog (Event Log on Windows). -;error_log = syslog - -; The syslog ident is a string which is prepended to every message logged -; to syslog. Only used when error_log is set to syslog. -;syslog.ident = php - -; The syslog facility is used to specify what type of program is logging -; the message. Only used when error_log is set to syslog. -;syslog.facility = user - -; Set this to disable filtering control characters (the default). -; Some loggers only accept NVT-ASCII, others accept anything that's not -; control characters. If your logger accepts everything, then no filtering -; is needed at all. -; Allowed values are: -; ascii (only base ASCII characters) -; no_ctrl (all characters except control characters) -; all (all characters) -;syslog.filter = ascii - -;windows.show_crt_warning -; Default value: 0 -; Development value: 0 -; Production value: 0 - -;;;;;;;;;;;;;;;;; -; Data Handling ; -;;;;;;;;;;;;;;;;; - -; The separator used in PHP generated URLs to separate arguments. -; PHP's default setting is "&". -; http://php.net/arg-separator.output -; Example: -;arg_separator.output = "&" - -; List of separator(s) used by PHP to parse input URLs into variables. -; PHP's default setting is "&". -; NOTE: Every character in this directive is considered as separator! -; http://php.net/arg-separator.input -; Example: -;arg_separator.input = ";&" - -; This directive determines which super global arrays are registered when PHP -; starts up. G,P,C,E & S are abbreviations for the following respective super -; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty -; paid for the registration of these arrays and because ENV is not as commonly -; used as the others, ENV is not recommended on productions servers. You -; can still get access to the environment variables through getenv() should you -; need to. -; Default Value: "EGPCS" -; Development Value: "GPCS" -; Production Value: "GPCS"; -; http://php.net/variables-order -variables_order = "GPCS" - -; This directive determines which super global data (G,P & C) should be -; registered into the super global array REQUEST. If so, it also determines -; the order in which that data is registered. The values for this directive -; are specified in the same manner as the variables_order directive, -; EXCEPT one. Leaving this value empty will cause PHP to use the value set -; in the variables_order directive. It does not mean it will leave the super -; globals array REQUEST empty. -; Default Value: None -; Development Value: "GP" -; Production Value: "GP" -; http://php.net/request-order -request_order = "GP" - -; This directive determines whether PHP registers $argv & $argc each time it -; runs. $argv contains an array of all the arguments passed to PHP when a script -; is invoked. $argc contains an integer representing the number of arguments -; that were passed when the script was invoked. These arrays are extremely -; useful when running scripts from the command line. When this directive is -; enabled, registering these variables consumes CPU cycles and memory each time -; a script is executed. For performance reasons, this feature should be disabled -; on production servers. -; Note: This directive is hardcoded to On for the CLI SAPI -; Default Value: On -; Development Value: Off -; Production Value: Off -; http://php.net/register-argc-argv -register_argc_argv = Off - -; When enabled, the ENV, REQUEST and SERVER variables are created when they're -; first used (Just In Time) instead of when the script starts. If these -; variables are not used within a script, having this directive on will result -; in a performance gain. The PHP directive register_argc_argv must be disabled -; for this directive to have any affect. -; http://php.net/auto-globals-jit -auto_globals_jit = On - -; Whether PHP will read the POST data. -; This option is enabled by default. -; Most likely, you won't want to disable this option globally. It causes $_POST -; and $_FILES to always be empty; the only way you will be able to read the -; POST data will be through the php://input stream wrapper. This can be useful -; to proxy requests or to process the POST data in a memory efficient fashion. -; http://php.net/enable-post-data-reading -;enable_post_data_reading = Off - -; Maximum size of POST data that PHP will accept. -; Its value may be 0 to disable the limit. It is ignored if POST data reading -; is disabled through enable_post_data_reading. -; http://php.net/post-max-size -post_max_size = 1024M - -; Automatically add files before PHP document. -; http://php.net/auto-prepend-file -auto_prepend_file = - -; Automatically add files after PHP document. -; http://php.net/auto-append-file -auto_append_file = - -; By default, PHP will output a media type using the Content-Type header. To -; disable this, simply set it to be empty. -; -; PHP's built-in default media type is set to text/html. -; http://php.net/default-mimetype -default_mimetype = "text/html" - -; PHP's default character set is set to UTF-8. -; http://php.net/default-charset -default_charset = "UTF-8" - -; PHP internal character encoding is set to empty. -; If empty, default_charset is used. -; http://php.net/internal-encoding -;internal_encoding = - -; PHP input character encoding is set to empty. -; If empty, default_charset is used. -; http://php.net/input-encoding -;input_encoding = - -; PHP output character encoding is set to empty. -; If empty, default_charset is used. -; See also output_buffer. -; http://php.net/output-encoding -;output_encoding = - -;;;;;;;;;;;;;;;;;;;;;;;;; -; Paths and Directories ; -;;;;;;;;;;;;;;;;;;;;;;;;; - -; UNIX: "/path1:/path2" -;include_path = ".:/php/includes" -; -; Windows: "\path1;\path2" -;include_path = ".;c:\php\includes" -; -; PHP's default setting for include_path is ".;/path/to/php/pear" -; http://php.net/include-path - -; The root of the PHP pages, used only if nonempty. -; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root -; if you are running php as a CGI under any web server (other than IIS) -; see documentation for security issues. The alternate is to use the -; cgi.force_redirect configuration below -; http://php.net/doc-root -doc_root = - -; The directory under which PHP opens the script using /~username used only -; if nonempty. -; http://php.net/user-dir -user_dir = - -; Directory in which the loadable extensions (modules) reside. -; http://php.net/extension-dir -;extension_dir = "./" -; On windows: - -; Directory where the temporary files should be placed. -; Defaults to the system default (see sys_get_temp_dir) - -; Whether or not to enable the dl() function. The dl() function does NOT work -; properly in multithreaded servers, such as IIS or Zeus, and is automatically -; disabled on them. -; http://php.net/enable-dl -enable_dl = Off - -; cgi.force_redirect is necessary to provide security running PHP as a CGI under -; most web servers. Left undefined, PHP turns this on by default. You can -; turn it off here AT YOUR OWN RISK -; **You CAN safely turn this off for IIS, in fact, you MUST.** -; http://php.net/cgi.force-redirect -;cgi.force_redirect = 1 - -; if cgi.nph is enabled it will force cgi to always sent Status: 200 with -; every request. PHP's default behavior is to disable this feature. -;cgi.nph = 1 - -; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape -; (iPlanet) web servers, you MAY need to set an environment variable name that PHP -; will look for to know it is OK to continue execution. Setting this variable MAY -; cause security issues, KNOW WHAT YOU ARE DOING FIRST. -; http://php.net/cgi.redirect-status-env -;cgi.redirect_status_env = - -; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's -; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok -; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting -; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting -; of zero causes PHP to behave as before. Default is 1. You should fix your scripts -; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. -; http://php.net/cgi.fix-pathinfo -;cgi.fix_pathinfo=1 - -; if cgi.discard_path is enabled, the PHP CGI binary can safely be placed outside -; of the web tree and people will not be able to circumvent .htaccess security. -;cgi.discard_path=1 - -; FastCGI under IIS supports the ability to impersonate -; security tokens of the calling client. This allows IIS to define the -; security context that the request runs under. mod_fastcgi under Apache -; does not currently support this feature (03/17/2002) -; Set to 1 if running under IIS. Default is zero. -; http://php.net/fastcgi.impersonate -;fastcgi.impersonate = 1 - -; Disable logging through FastCGI connection. PHP's default behavior is to enable -; this feature. -;fastcgi.logging = 0 - -; cgi.rfc2616_headers configuration option tells PHP what type of headers to -; use when sending HTTP response code. If set to 0, PHP sends Status: header that -; is supported by Apache. When this option is set to 1, PHP will send -; RFC2616 compliant header. -; Default is zero. -; http://php.net/cgi.rfc2616-headers -;cgi.rfc2616_headers = 0 - -; cgi.check_shebang_line controls whether CGI PHP checks for line starting with #! -; (shebang) at the top of the running script. This line might be needed if the -; script support running both as stand-alone script and via PHP CGI<. PHP in CGI -; mode skips this line and ignores its content if this directive is turned on. -; http://php.net/cgi.check-shebang-line -;cgi.check_shebang_line=1 - -;;;;;;;;;;;;;;;; -; File Uploads ; -;;;;;;;;;;;;;;;; - -; Whether to allow HTTP file uploads. -; http://php.net/file-uploads -file_uploads = On - -; Temporary directory for HTTP uploaded files (will use system default if not -; specified). -; http://php.net/upload-tmp-dir -;see [app] - -; Maximum allowed size for uploaded files. -; http://php.net/upload-max-filesize - -; Maximum number of files that can be uploaded via a single request -max_file_uploads = 20 - -;;;;;;;;;;;;;;;;;; -; Fopen wrappers ; -;;;;;;;;;;;;;;;;;; - -; Whether to allow the treatment of URLs (like http:// or ftp://) as files. -; http://php.net/allow-url-fopen -allow_url_fopen = On - -; Whether to allow include/require to open URLs (like http:// or ftp://) as files. -; http://php.net/allow-url-include -allow_url_include = Off - -; Define the anonymous ftp password (your email address). PHP's default setting -; for this is empty. -; http://php.net/from -;from="john@doe.com" - -; Define the User-Agent string. PHP's default setting for this is empty. -; http://php.net/user-agent -;user_agent="PHP" - -; Default timeout for socket based streams (seconds) -; http://php.net/default-socket-timeout -default_socket_timeout = 60 - -; If your scripts have to deal with files from Macintosh systems, -; or you are running on a Mac and need to deal with files from -; unix or win32 systems, setting this flag will cause PHP to -; automatically detect the EOL character in those files so that -; fgets() and file() will work regardless of the source of the file. -; http://php.net/auto-detect-line-endings -;auto_detect_line_endings = Off - -;;;;;;;;;;;;;;;;;;;;;; -; Dynamic Extensions ; -;;;;;;;;;;;;;;;;;;;;;; - -; If you wish to have an extension loaded automatically, use the following -; syntax: -; -; extension=modulename -; -; For example: -; -; extension=mysqli -; -; When the extension library to load is not located in the default extension -; directory, You may specify an absolute path to the library file: -; -; extension=/path/to/extension/mysqli.so -; -; Note : The syntax used in previous PHP versions ('extension=.so' and -; 'extension='php_.dll') is supported for legacy reasons and may be -; deprecated in a future PHP major version. So, when it is possible, please -; move to the new ('extension=) syntax. -; -; Notes for Windows environments : -; -; - Many DLL files are located in the extensions/ (PHP 4) or ext/ (PHP 5+) -; extension folders as well as the separate PECL DLL download (PHP 5+). -; Be sure to appropriately set the extension_dir directive. -; - -;SEE [app_extensions] - - -;;;;;;;;;;;;;;;;;;; -; Module Settings ; -;;;;;;;;;;;;;;;;;;; - -[CLI Server] -; Whether the CLI web server uses ANSI color coding in its terminal output. -cli_server.color = On - -[Date] -; Defines the default timezone used by the date functions -; http://php.net/date.timezone -date.timezone = "America/New_York" - -; http://php.net/date.default-latitude -;date.default_latitude = 31.7667 - -; http://php.net/date.default-longitude -;date.default_longitude = 35.2333 - -; http://php.net/date.sunrise-zenith -;date.sunrise_zenith = 90.583333 - -; http://php.net/date.sunset-zenith -;date.sunset_zenith = 90.583333 - -[filter] -; http://php.net/filter.default -;filter.default = unsafe_raw - -; http://php.net/filter.default-flags -;filter.default_flags = - -[iconv] -; Use of this INI entry is deprecated, use global input_encoding instead. -; If empty, default_charset or input_encoding or iconv.input_encoding is used. -; The precedence is: default_charset < input_encoding < iconv.input_encoding -;iconv.input_encoding = - -; Use of this INI entry is deprecated, use global internal_encoding instead. -; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. -; The precedence is: default_charset < internal_encoding < iconv.internal_encoding -;iconv.internal_encoding = - -; Use of this INI entry is deprecated, use global output_encoding instead. -; If empty, default_charset or output_encoding or iconv.output_encoding is used. -; The precedence is: default_charset < output_encoding < iconv.output_encoding -; To use an output encoding conversion, iconv's output handler must be set -; otherwise output encoding conversion cannot be performed. -;iconv.output_encoding = - -[imap] -; rsh/ssh logins are disabled by default. Use this INI entry if you want to -; enable them. Note that the IMAP library does not filter mailbox names before -; passing them to rsh/ssh command, thus passing untrusted data to this function -; with rsh/ssh enabled is insecure. -;imap.enable_insecure_rsh=0 - -[intl] -;intl.default_locale = -; This directive allows you to produce PHP errors when some error -; happens within intl functions. The value is the level of the error produced. -; Default is 0, which does not produce any errors. -;intl.error_level = E_WARNING -;intl.use_exceptions = 0 - -[sqlite3] -;sqlite3.extension_dir = - -[Pcre] -; PCRE library backtracking limit. -; http://php.net/pcre.backtrack-limit -;pcre.backtrack_limit=100000 - -; PCRE library recursion limit. -; Please note that if you set this value to a high number you may consume all -; the available process stack and eventually crash PHP (due to reaching the -; stack size limit imposed by the Operating System). -; http://php.net/pcre.recursion-limit -;pcre.recursion_limit=100000 - -; Enables or disables JIT compilation of patterns. This requires the PCRE -; library to be compiled with JIT support. -;pcre.jit=1 - -[Pdo] -; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" -; http://php.net/pdo-odbc.connection-pooling -;pdo_odbc.connection_pooling=strict - -;pdo_odbc.db2_instance_name - -[Pdo_mysql] -; Default socket name for local MySQL connects. If empty, uses the built-in -; MySQL defaults. -pdo_mysql.default_socket= - -[Phar] -; http://php.net/phar.readonly -;phar.readonly = On - -; http://php.net/phar.require-hash -;phar.require_hash = On - -;phar.cache_list = - -[mail function] -;SEE [app_mail] - -; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). -; http://php.net/sendmail-path -;sendmail_path = - -; Force the addition of the specified parameters to be passed as extra parameters -; to the sendmail binary. These parameters will always replace the value of -; the 5th parameter to mail(). -;mail.force_extra_parameters = - -; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename -mail.add_x_header = Off - -; The path to a log file that will log all mail() calls. Log entries include -; the full path of the script, line number, To address and headers. -; Log mail to syslog (Event Log on Windows). -;mail.log = syslog - -[ODBC] -; http://php.net/odbc.default-db -;odbc.default_db = Not yet implemented - -; http://php.net/odbc.default-user -;odbc.default_user = Not yet implemented - -; http://php.net/odbc.default-pw -;odbc.default_pw = Not yet implemented - -; Controls the ODBC cursor model. -; Default: SQL_CURSOR_STATIC (default). -;odbc.default_cursortype - -; Allow or prevent persistent links. -; http://php.net/odbc.allow-persistent -odbc.allow_persistent = On - -; Check that a connection is still valid before reuse. -; http://php.net/odbc.check-persistent -odbc.check_persistent = On - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/odbc.max-persistent -odbc.max_persistent = -1 - -; Maximum number of links (persistent + non-persistent). -1 means no limit. -; http://php.net/odbc.max-links -odbc.max_links = -1 - -; Handling of LONG fields. Returns number of bytes to variables. 0 means -; passthru. -; http://php.net/odbc.defaultlrl -odbc.defaultlrl = 4096 - -; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. -; See the documentation on odbc_binmode and odbc_longreadlen for an explanation -; of odbc.defaultlrl and odbc.defaultbinmode -; http://php.net/odbc.defaultbinmode -odbc.defaultbinmode = 1 - -[Interbase] -; Allow or prevent persistent links. -ibase.allow_persistent = 1 - -; Maximum number of persistent links. -1 means no limit. -ibase.max_persistent = -1 - -; Maximum number of links (persistent + non-persistent). -1 means no limit. -ibase.max_links = -1 - -; Default database name for ibase_connect(). -;ibase.default_db = - -; Default username for ibase_connect(). -;ibase.default_user = - -; Default password for ibase_connect(). -;ibase.default_password = - -; Default charset for ibase_connect(). -;ibase.default_charset = - -; Default timestamp format. -ibase.timestampformat = "%Y-%m-%d %H:%M:%S" - -; Default date format. -ibase.dateformat = "%Y-%m-%d" - -; Default time format. -ibase.timeformat = "%H:%M:%S" - -[MySQLi] - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/mysqli.max-persistent -mysqli.max_persistent = -1 - -; Allow accessing, from PHP's perspective, local files with LOAD DATA statements -; http://php.net/mysqli.allow_local_infile -;mysqli.allow_local_infile = On - -; Allow or prevent persistent links. -; http://php.net/mysqli.allow-persistent -mysqli.allow_persistent = On - -; Maximum number of links. -1 means no limit. -; http://php.net/mysqli.max-links -mysqli.max_links = -1 - -; Default port number for mysqli_connect(). If unset, mysqli_connect() will use -; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the -; compile-time value defined MYSQL_PORT (in that order). Win32 will only look -; at MYSQL_PORT. -; http://php.net/mysqli.default-port -mysqli.default_port = 3306 - -; Default socket name for local MySQL connects. If empty, uses the built-in -; MySQL defaults. -; http://php.net/mysqli.default-socket -mysqli.default_socket = - -; Default host for mysql_connect() (doesn't apply in safe mode). -; http://php.net/mysqli.default-host -mysqli.default_host = - -; Default user for mysql_connect() (doesn't apply in safe mode). -; http://php.net/mysqli.default-user -mysqli.default_user = - -; Default password for mysqli_connect() (doesn't apply in safe mode). -; Note that this is generally a *bad* idea to store passwords in this file. -; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") -; and reveal this password! And of course, any users with read access to this -; file will be able to reveal the password as well. -; http://php.net/mysqli.default-pw -mysqli.default_pw = - -; Allow or prevent reconnect -mysqli.reconnect = Off - -[mysqlnd] -; Enable / Disable collection of general statistics by mysqlnd which can be -; used to tune and monitor MySQL operations. -mysqlnd.collect_statistics = On - -; Enable / Disable collection of memory usage statistics by mysqlnd which can be -; used to tune and monitor MySQL operations. -mysqlnd.collect_memory_statistics = Off - -; Records communication from all extensions using mysqlnd to the specified log -; file. -; http://php.net/mysqlnd.debug -;mysqlnd.debug = - -; Defines which queries will be logged. -;mysqlnd.log_mask = 0 - -; Default size of the mysqlnd memory pool, which is used by result sets. -;mysqlnd.mempool_default_size = 16000 - -; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. -;mysqlnd.net_cmd_buffer_size = 2048 - -; Size of a pre-allocated buffer used for reading data sent by the server in -; bytes. -;mysqlnd.net_read_buffer_size = 32768 - -; Timeout for network requests in seconds. -;mysqlnd.net_read_timeout = 31536000 - -; SHA-256 Authentication Plugin related. File with the MySQL server public RSA -; key. -;mysqlnd.sha256_server_public_key = - -[OCI8] - -; Connection: Enables privileged connections using external -; credentials (OCI_SYSOPER, OCI_SYSDBA) -; http://php.net/oci8.privileged-connect -;oci8.privileged_connect = Off - -; Connection: The maximum number of persistent OCI8 connections per -; process. Using -1 means no limit. -; http://php.net/oci8.max-persistent -;oci8.max_persistent = -1 - -; Connection: The maximum number of seconds a process is allowed to -; maintain an idle persistent connection. Using -1 means idle -; persistent connections will be maintained forever. -; http://php.net/oci8.persistent-timeout -;oci8.persistent_timeout = -1 - -; Connection: The number of seconds that must pass before issuing a -; ping during oci_pconnect() to check the connection validity. When -; set to 0, each oci_pconnect() will cause a ping. Using -1 disables -; pings completely. -; http://php.net/oci8.ping-interval -;oci8.ping_interval = 60 - -; Connection: Set this to a user chosen connection class to be used -; for all pooled server requests with Oracle 11g Database Resident -; Connection Pooling (DRCP). To use DRCP, this value should be set to -; the same string for all web servers running the same application, -; the database pool must be configured, and the connection string must -; specify to use a pooled server. -;oci8.connection_class = - -; High Availability: Using On lets PHP receive Fast Application -; Notification (FAN) events generated when a database node fails. The -; database must also be configured to post FAN events. -;oci8.events = Off - -; Tuning: This option enables statement caching, and specifies how -; many statements to cache. Using 0 disables statement caching. -; http://php.net/oci8.statement-cache-size -;oci8.statement_cache_size = 20 - -; Tuning: Enables statement prefetching and sets the default number of -; rows that will be fetched automatically after statement execution. -; http://php.net/oci8.default-prefetch -;oci8.default_prefetch = 100 - -; Compatibility. Using On means oci_close() will not close -; oci_connect() and oci_new_connect() connections. -; http://php.net/oci8.old-oci-close-semantics -;oci8.old_oci_close_semantics = Off - -[PostgreSQL] -; Allow or prevent persistent links. -; http://php.net/pgsql.allow-persistent -pgsql.allow_persistent = On - -; Detect broken persistent links always with pg_pconnect(). -; Auto reset feature requires a little overheads. -; http://php.net/pgsql.auto-reset-persistent -pgsql.auto_reset_persistent = Off - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/pgsql.max-persistent -pgsql.max_persistent = -1 - -; Maximum number of links (persistent+non persistent). -1 means no limit. -; http://php.net/pgsql.max-links -pgsql.max_links = -1 - -; Ignore PostgreSQL backends Notice message or not. -; Notice message logging require a little overheads. -; http://php.net/pgsql.ignore-notice -pgsql.ignore_notice = 0 - -; Log PostgreSQL backends Notice message or not. -; Unless pgsql.ignore_notice=0, module cannot log notice message. -; http://php.net/pgsql.log-notice -pgsql.log_notice = 0 - -[bcmath] -; Number of decimal digits for all bcmath functions. -; http://php.net/bcmath.scale -bcmath.scale = 0 - -[browscap] -; http://php.net/browscap -;browscap = extra/browscap.ini - -[Session] -; Handler used to store/retrieve data. -; http://php.net/session.save-handler -session.save_handler = files - -; Argument passed to save_handler. In the case of files, this is the path -; where data files are stored. Note: Windows users have to change this -; variable in order to use PHP's session functions. -; -; The path can be defined as: -; -; session.save_path = "N;/path" -; -; where N is an integer. Instead of storing all the session files in -; /path, what this will do is use subdirectories N-levels deep, and -; store the session data in those directories. This is useful if -; your OS has problems with many files in one directory, and is -; a more efficient layout for servers that handle many sessions. -; -; NOTE 1: PHP will not create this directory structure automatically. -; You can use the script in the ext/session dir for that purpose. -; NOTE 2: See the section on garbage collection below if you choose to -; use subdirectories for session storage -; -; The file storage module creates files using mode 600 by default. -; You can change that by using -; -; session.save_path = "N;MODE;/path" -; -; where MODE is the octal representation of the mode. Note that this -; does not overwrite the process's umask. -; http://php.net/session.save-path - -; Whether to use strict session mode. -; Strict session mode does not accept an uninitialized session ID, and -; regenerates the session ID if the browser sends an uninitialized session ID. -; Strict mode protects applications from session fixation via a session adoption -; vulnerability. It is disabled by default for maximum compatibility, but -; enabling it is encouraged. -; https://wiki.php.net/rfc/strict_sessions -session.use_strict_mode = 0 - -; Whether to use cookies. -; http://php.net/session.use-cookies -session.use_cookies = 1 - -; http://php.net/session.cookie-secure -session.cookie_secure = 1 - -; This option forces PHP to fetch and use a cookie for storing and maintaining -; the session id. We encourage this operation as it's very helpful in combating -; session hijacking when not specifying and managing your own session id. It is -; not the be-all and end-all of session hijacking defense, but it's a good start. -; http://php.net/session.use-only-cookies -session.use_only_cookies = 1 - -; Name of the session (used as cookie name). -; http://php.net/session.name - -; Initialize session on request startup. -; http://php.net/session.auto-start -session.auto_start = 0 - -; Lifetime in seconds of cookie or, if 0, until browser is restarted. -; http://php.net/session.cookie-lifetime -session.cookie_lifetime = 0 - -; The path for which the cookie is valid. -; http://php.net/session.cookie-path -session.cookie_path = / - -; The domain for which the cookie is valid. -; http://php.net/session.cookie-domain -session.cookie_domain = - -; Whether or not to add the httpOnly flag to the cookie, which makes it -; inaccessible to browser scripting languages such as JavaScript. -; http://php.net/session.cookie-httponly -session.cookie_httponly = 1 - -; Add SameSite attribute to cookie to help mitigate Cross-Site Request Forgery (CSRF/XSRF) -; Current valid values are "Lax" or "Strict" -; https://tools.ietf.org/html/draft-west-first-party-cookies-07 -session.cookie_samesite = - -; Handler used to serialize data. php is the standard serializer of PHP. -; http://php.net/session.serialize-handler -session.serialize_handler = php - -; Defines the probability that the 'garbage collection' process is started -; on every session initialization. The probability is calculated by using -; gc_probability/gc_divisor. Where session.gc_probability is the numerator -; and gc_divisor is the denominator in the equation. Setting this value to 1 -; when the session.gc_divisor value is 100 will give you approximately a 1% chance -; the gc will run on any given request. -; Default Value: 1 -; Development Value: 1 -; Production Value: 1 -; http://php.net/session.gc-probability -session.gc_probability = 1 - -; Defines the probability that the 'garbage collection' process is started on every -; session initialization. The probability is calculated by using the following equation: -; gc_probability/gc_divisor. Where session.gc_probability is the numerator and -; session.gc_divisor is the denominator in the equation. Setting this value to 100 -; when the session.gc_probability value is 1 will give you approximately a 1% chance -; the gc will run on any given request. Increasing this value to 1000 will give you -; a 0.1% chance the gc will run on any given request. For high volume production servers, -; this is a more efficient approach. -; Default Value: 100 -; Development Value: 1000 -; Production Value: 1000 -; http://php.net/session.gc-divisor -session.gc_divisor = 1000 - -; After this number of seconds, stored data will be seen as 'garbage' and -; cleaned up by the garbage collection process. -; http://php.net/session.gc-maxlifetime -session.gc_maxlifetime = 1440 - -; NOTE: If you are using the subdirectory option for storing session files -; (see session.save_path above), then garbage collection does *not* -; happen automatically. You will need to do your own garbage -; collection through a shell script, cron entry, or some other method. -; For example, the following script would is the equivalent of -; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): -; find /path/to/sessions -cmin +24 -type f | xargs rm - -; Check HTTP Referer to invalidate externally stored URLs containing ids. -; HTTP_REFERER has to contain this substring for the session to be -; considered as valid. -; http://php.net/session.referer-check -session.referer_check = - -; Set to {nocache,private,public,} to determine HTTP caching aspects -; or leave this empty to avoid sending anti-caching headers. -; http://php.net/session.cache-limiter -session.cache_limiter = nocache - -; Document expires after n minutes. -; http://php.net/session.cache-expire -session.cache_expire = 180 - -; trans sid support is disabled by default. -; Use of trans sid may risk your users' security. -; Use this option with caution. -; - User may send URL contains active session ID -; to other person via. email/irc/etc. -; - URL that contains active session ID may be stored -; in publicly accessible computer. -; - User may access your site with the same session ID -; always using URL stored in browser's history or bookmarks. -; http://php.net/session.use-trans-sid -session.use_trans_sid = 0 - -; Set session ID character length. This value could be between 22 to 256. -; Shorter length than default is supported only for compatibility reason. -; Users should use 32 or more chars. -; http://php.net/session.sid-length -; Default Value: 32 -; Development Value: 26 -; Production Value: 26 -session.sid_length = 26 - -; The URL rewriter will look for URLs in a defined set of HTML tags. -;
is special; if you include them here, the rewriter will -; add a hidden field with the info which is otherwise appended -; to URLs. tag's action attribute URL will not be modified -; unless it is specified. -; Note that all valid entries require a "=", even if no value follows. -; Default Value: "a=href,area=href,frame=src,form=" -; Development Value: "a=href,area=href,frame=src,form=" -; Production Value: "a=href,area=href,frame=src,form=" -; http://php.net/url-rewriter.tags -session.trans_sid_tags = "a=href,area=href,frame=src,form=" - -; URL rewriter does not rewrite absolute URLs by default. -; To enable rewrites for absolute paths, target hosts must be specified -; at RUNTIME. i.e. use ini_set() -; tags is special. PHP will check action attribute's URL regardless -; of session.trans_sid_tags setting. -; If no host is defined, HTTP_HOST will be used for allowed host. -; Example value: php.net,www.php.net,wiki.php.net -; Use "," for multiple hosts. No spaces are allowed. -; Default Value: "" -; Development Value: "" -; Production Value: "" -;session.trans_sid_hosts="" - -; Define how many bits are stored in each character when converting -; the binary hash data to something readable. -; Possible values: -; 4 (4 bits: 0-9, a-f) -; 5 (5 bits: 0-9, a-v) -; 6 (6 bits: 0-9, a-z, A-Z, "-", ",") -; Default Value: 4 -; Development Value: 5 -; Production Value: 5 -; http://php.net/session.hash-bits-per-character -session.sid_bits_per_character = 5 - -; Enable upload progress tracking in $_SESSION -; Default Value: On -; Development Value: On -; Production Value: On -; http://php.net/session.upload-progress.enabled -;session.upload_progress.enabled = On - -; Cleanup the progress information as soon as all POST data has been read -; (i.e. upload completed). -; Default Value: On -; Development Value: On -; Production Value: On -; http://php.net/session.upload-progress.cleanup -;session.upload_progress.cleanup = On - -; A prefix used for the upload progress key in $_SESSION -; Default Value: "upload_progress_" -; Development Value: "upload_progress_" -; Production Value: "upload_progress_" -; http://php.net/session.upload-progress.prefix -;session.upload_progress.prefix = "upload_progress_" - -; The index name (concatenated with the prefix) in $_SESSION -; containing the upload progress information -; Default Value: "PHP_SESSION_UPLOAD_PROGRESS" -; Development Value: "PHP_SESSION_UPLOAD_PROGRESS" -; Production Value: "PHP_SESSION_UPLOAD_PROGRESS" -; http://php.net/session.upload-progress.name -;session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS" - -; How frequently the upload progress should be updated. -; Given either in percentages (per-file), or in bytes -; Default Value: "1%" -; Development Value: "1%" -; Production Value: "1%" -; http://php.net/session.upload-progress.freq -;session.upload_progress.freq = "1%" - -; The minimum delay between updates, in seconds -; Default Value: 1 -; Development Value: 1 -; Production Value: 1 -; http://php.net/session.upload-progress.min-freq -;session.upload_progress.min_freq = "1" - -; Only write session data when session data is changed. Enabled by default. -; http://php.net/session.lazy-write -;session.lazy_write = On - -[Assertion] -; Switch whether to compile assertions at all (to have no overhead at run-time) -; -1: Do not compile at all -; 0: Jump over assertion at run-time -; 1: Execute assertions -; Changing from or to a negative value is only possible in php.ini! (For turning assertions on and off at run-time, see assert.active, when zend.assertions = 1) -; Default Value: 1 -; Development Value: 1 -; Production Value: -1 -; http://php.net/zend.assertions -zend.assertions = -1 - -; Assert(expr); active by default. -; http://php.net/assert.active -;assert.active = On - -; Throw an AssertionError on failed assertions -; http://php.net/assert.exception -;assert.exception = On - -; Issue a PHP warning for each failed assertion. (Overridden by assert.exception if active) -; http://php.net/assert.warning -;assert.warning = On - -; Don't bail out by default. -; http://php.net/assert.bail -;assert.bail = Off - -; User-function to be called if an assertion fails. -; http://php.net/assert.callback -;assert.callback = 0 - -; Eval the expression with current error_reporting(). Set to true if you want -; error_reporting(0) around the eval(). -; http://php.net/assert.quiet-eval -;assert.quiet_eval = 0 - -[COM] -; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs -; http://php.net/com.typelib-file -;com.typelib_file = - -; allow Distributed-COM calls -; http://php.net/com.allow-dcom -;com.allow_dcom = true - -; autoregister constants of a component's typlib on com_load() -; http://php.net/com.autoregister-typelib -;com.autoregister_typelib = true - -; register constants casesensitive -; http://php.net/com.autoregister-casesensitive -;com.autoregister_casesensitive = false - -; show warnings on duplicate constant registrations -; http://php.net/com.autoregister-verbose -;com.autoregister_verbose = true - -; The default character set code-page to use when passing strings to and from COM objects. -; Default: system ANSI code page -;com.code_page= - -[mbstring] -; language for internal character representation. -; This affects mb_send_mail() and mbstring.detect_order. -; http://php.net/mbstring.language -;mbstring.language = Japanese - -; Use of this INI entry is deprecated, use global internal_encoding instead. -; internal/script encoding. -; Some encoding cannot work as internal encoding. (e.g. SJIS, BIG5, ISO-2022-*) -; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. -; The precedence is: default_charset < internal_encoding < iconv.internal_encoding -;mbstring.internal_encoding = - -; Use of this INI entry is deprecated, use global input_encoding instead. -; http input encoding. -; mbstring.encoding_translation = On is needed to use this setting. -; If empty, default_charset or input_encoding or mbstring.input is used. -; The precedence is: default_charset < input_encoding < mbsting.http_input -; http://php.net/mbstring.http-input -;mbstring.http_input = - -; Use of this INI entry is deprecated, use global output_encoding instead. -; http output encoding. -; mb_output_handler must be registered as output buffer to function. -; If empty, default_charset or output_encoding or mbstring.http_output is used. -; The precedence is: default_charset < output_encoding < mbstring.http_output -; To use an output encoding conversion, mbstring's output handler must be set -; otherwise output encoding conversion cannot be performed. -; http://php.net/mbstring.http-output -;mbstring.http_output = - -; enable automatic encoding translation according to -; mbstring.internal_encoding setting. Input chars are -; converted to internal encoding by setting this to On. -; Note: Do _not_ use automatic encoding translation for -; portable libs/applications. -; http://php.net/mbstring.encoding-translation -;mbstring.encoding_translation = Off - -; automatic encoding detection order. -; "auto" detect order is changed according to mbstring.language -; http://php.net/mbstring.detect-order -;mbstring.detect_order = auto - -; substitute_character used when character cannot be converted -; one from another -; http://php.net/mbstring.substitute-character -;mbstring.substitute_character = none - -; overload(replace) single byte functions by mbstring functions. -; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(), -; etc. Possible values are 0,1,2,4 or combination of them. -; For example, 7 for overload everything. -; 0: No overload -; 1: Overload mail() function -; 2: Overload str*() functions -; 4: Overload ereg*() functions -; http://php.net/mbstring.func-overload -;mbstring.func_overload = 0 - -; enable strict encoding detection. -; Default: Off -;mbstring.strict_detection = On - -; This directive specifies the regex pattern of content types for which mb_output_handler() -; is activated. -; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml) -;mbstring.http_output_conv_mimetype= - -[gd] -; Tell the jpeg decode to ignore warnings and try to create -; a gd image. The warning will then be displayed as notices -; disabled by default -; http://php.net/gd.jpeg-ignore-warning -;gd.jpeg_ignore_warning = 1 - -[exif] -; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. -; With mbstring support this will automatically be converted into the encoding -; given by corresponding encode setting. When empty mbstring.internal_encoding -; is used. For the decode settings you can distinguish between motorola and -; intel byte order. A decode setting cannot be empty. -; http://php.net/exif.encode-unicode -;exif.encode_unicode = ISO-8859-15 - -; http://php.net/exif.decode-unicode-motorola -;exif.decode_unicode_motorola = UCS-2BE - -; http://php.net/exif.decode-unicode-intel -;exif.decode_unicode_intel = UCS-2LE - -; http://php.net/exif.encode-jis -;exif.encode_jis = - -; http://php.net/exif.decode-jis-motorola -;exif.decode_jis_motorola = JIS - -; http://php.net/exif.decode-jis-intel -;exif.decode_jis_intel = JIS - -[Tidy] -; The path to a default tidy configuration file to use when using tidy -; http://php.net/tidy.default-config -;tidy.default_config = /usr/local/lib/php/default.tcfg - -; Should tidy clean and repair output automatically? -; WARNING: Do not use this option if you are generating non-html content -; such as dynamic images -; http://php.net/tidy.clean-output -tidy.clean_output = Off - -[soap] -; Enables or disables WSDL caching feature. -; http://php.net/soap.wsdl-cache-enabled -soap.wsdl_cache_enabled=1 - -; Sets the directory name where SOAP extension will put cache files. -; http://php.net/soap.wsdl-cache-dir - -; (time to live) Sets the number of second while cached file will be used -; instead of original one. -; http://php.net/soap.wsdl-cache-ttl -soap.wsdl_cache_ttl=86400 - -; Sets the size of the cache limit. (Max. number of WSDL files to cache) -soap.wsdl_cache_limit = 5 - -[sysvshm] -; A default size of the shared memory segment -;sysvshm.init_mem = 10000 - -[ldap] -; Sets the maximum number of open links or -1 for unlimited. -ldap.max_links = -1 - -[dba] -;dba.default_handler= - -[opcache] -; Determines if Zend OPCache is enabled - -; Determines if Zend OPCache is enabled for the CLI version of PHP -opcache.enable_cli=On - -; The OPcache shared memory storage size. -;opcache.memory_consumption=128 - -; The amount of memory for interned strings in Mbytes. -;opcache.interned_strings_buffer=8 - -; The maximum number of keys (scripts) in the OPcache hash table. -; Only numbers between 200 and 1000000 are allowed. -;opcache.max_accelerated_files=10000 - -; The maximum percentage of "wasted" memory until a restart is scheduled. -;opcache.max_wasted_percentage=5 - -; When this directive is enabled, the OPcache appends the current working -; directory to the script key, thus eliminating possible collisions between -; files with the same name (basename). Disabling the directive improves -; performance, but may break existing applications. -;opcache.use_cwd=1 - -; When disabled, you must reset the OPcache manually or restart the -; webserver for changes to the filesystem to take effect. -;opcache.validate_timestamps=1 - -; How often (in seconds) to check file timestamps for changes to the shared -; memory storage allocation. ("1" means validate once per second, but only -; once per request. "0" means always validate) -;opcache.revalidate_freq=2 - -; Enables or disables file search in include_path optimization -;opcache.revalidate_path=0 - -; If disabled, all PHPDoc comments are dropped from the code to reduce the -; size of the optimized code. -;opcache.save_comments=1 - -; Allow file existence override (file_exists, etc.) performance feature. -;opcache.enable_file_override=0 - -; A bitmask, where each bit enables or disables the appropriate OPcache -; passes -;opcache.optimization_level=0x7FFFBFFF - -;opcache.dups_fix=0 - -; The location of the OPcache blacklist file (wildcards allowed). -; Each OPcache blacklist file is a text file that holds the names of files -; that should not be accelerated. The file format is to add each filename -; to a new line. The filename may be a full path or just a file prefix -; (i.e., /var/www/x blacklists all the files and directories in /var/www -; that start with 'x'). Line starting with a ; are ignored (comments). -;opcache.blacklist_filename= - -; Allows exclusion of large files from being cached. By default all files -; are cached. -;opcache.max_file_size=0 - -; Check the cache checksum each N requests. -; The default value of "0" means that the checks are disabled. -;opcache.consistency_checks=0 - -; How long to wait (in seconds) for a scheduled restart to begin if the cache -; is not being accessed. -;opcache.force_restart_timeout=180 - -; OPcache error_log file name. Empty string assumes "stderr". - -; All OPcache errors go to the Web server log. -; By default, only fatal errors (level 0) or errors (level 1) are logged. -; You can also enable warnings (level 2), info messages (level 3) or -; debug messages (level 4). -;opcache.log_verbosity_level=1 - -; Preferred Shared Memory back-end. Leave empty and let the system decide. -;opcache.preferred_memory_model= - -; Protect the shared memory from unexpected writing during script execution. -; Useful for internal debugging only. -;opcache.protect_memory=0 - -; Allows calling OPcache API functions only from PHP scripts which path is -; started from specified string. The default "" means no restriction -;opcache.restrict_api= - -; Mapping base of shared memory segments (for Windows only). All the PHP -; processes have to map shared memory into the same address space. This -; directive allows to manually fix the "Unable to reattach to base address" -; errors. -;opcache.mmap_base= - -; Enables and sets the second level cache directory. -; It should improve performance when SHM memory is full, at server restart or -; SHM reset. The default "" disables file based caching. -;opcache.file_cache= - -; Enables or disables opcode caching in shared memory. -;opcache.file_cache_only=0 - -; Enables or disables checksum validation when script loaded from file cache. -;opcache.file_cache_consistency_checks=1 - -; Implies opcache.file_cache_only=1 for a certain process that failed to -; reattach to the shared memory (for Windows only). Explicitly enabled file -; cache is required. -;opcache.file_cache_fallback=1 - -; Enables or disables copying of PHP code (text segment) into HUGE PAGES. -; This should improve performance, but requires appropriate OS configuration. -;opcache.huge_code_pages=1 - -; Validate cached file permissions. -;opcache.validate_permission=0 - -; Prevent name collisions in chroot'ed environment. -;opcache.validate_root=0 - -; If specified, it produces opcode dumps for debugging different stages of -; optimizations. -;opcache.opt_debug_level=0 - -[openssl] -; The location of a Certificate Authority (CA) file on the local filesystem -; to use when verifying the identity of SSL/TLS peers. Most users should -; not specify a value for this directive as PHP will attempt to use the -; OS-managed cert stores in its absence. If specified, this value may still -; be overridden on a per-stream basis via the "cafile" SSL stream context -; option. - -; If openssl.cafile is not specified or if the CA file is not found, the -; directory pointed to by openssl.capath is searched for a suitable -; certificate. This value must be a correctly hashed certificate directory. -; Most users should not specify a value for this directive as PHP will -; attempt to use the OS-managed cert stores in its absence. If specified, -; this value may still be overridden on a per-stream basis via the "capath" -; SSL stream context option. -;openssl.capath= - -; Local Variables: -; tab-width: 4 -; End: - -[xdebug] -xdebug.client_port="9000" -xdebug.var_display_max_depth = -1 -xdebug.var_display_max_children = -1 -xdebug.var_display_max_data = -1 diff --git a/srv/app.local/php.ini b/srv/app.local/php.ini deleted file mode 100644 index e01f1d2..0000000 --- a/srv/app.local/php.ini +++ /dev/null @@ -1,1970 +0,0 @@ -[PHP] -;8.x.x -;NTS x64 - -;;;;;;;;;;;;;;;;;;;;;;;; -; COMMON APP SETTINGS ; -;;;;;;;;;;;;;;;;;;;;;;;; -; Update the paths to the absolute paths appropriate locations for this server -; Generate a new GUID for each app - use same the guid for each environment -; - GUID generator https://www.guidgenerator.com/ - do not use hyphens -[app] -session.name = {app_guid} - -[app_debug] -;https://xdebug.org/docs/all_settings#mode -xdebug.mode=debug -;https://xdebug.org/docs/all_settings#output_dir -xdebug.output_dir = "{app_absolute_path}\srv\profile" - -[app_logging] -error_log = "{app_absolute_path}\logs\error.log" -opcache.error_log = "{app_absolute_path}\logs\opcache.log" -mail.log = "{app_absolute_path}\logs\mail.log" - -[app_temp] -session.save_path = "{app_absolute_path}\srv\tmp\sessions\" -sys_temp_dir = "{app_absolute_path}\srv\tmp\tmp\" -upload_tmp_dir = "{app_absolute_path}\srv\tmp\files\" -soap.wsdl_cache_dir = "{app_absolute_path}\srv\tmp\soaptmp\" -opcache.file_cache = "{app_absolute_path}\srv\tmp\opcache\" - -[app_mail] -SMTP = {app_smtp_server} -smtp_port = 25 -sendmail_from = {app_smtp_sendmail_from_address} - -[app_limits] -upload_max_filesize = 1024M -max_execution_time = 30 -memory_limit = 256M -opcache.enable=Off - -[app_ssl] -curl.cainfo = "{app_ssl_path}\cacert.pem" -openssl.cafile="{app_ssl_path}\cacert.pem" - -[app_extensions] -extension_dir = "{app_php_path}\ext\" -extension=curl -extension=fileinfo -extension=openssl -extension=pdo_sqlsrv -extension=mongodb -zend_extension=xdebug -;extension=mbstring -;extension=soap -;extension=bz2 -;extension=gd2 -;extension=gettext -;extension=gmp -;extension=intl -;extension=imap -;extension=interbase -;extension=ldap -;extension=exif -;extension=mysqli -;extension=oci8_12c -;extension=odbc -;extension=pdo_firebird -;extension=pdo_mysql -;extension=pdo_oci -;extension=pdo_odbc -;extension=pdo_sqlsrv_74_nts_x64 -;extension=pdo_pgsql -;extension=pdo_sqlite -;extension=pgsql -;extension=shmop -;extension=snmp -;extension=sockets -;extension=sodium -;extension=sqlite3 -;extension=tidy -;extension=xmlrpc -;extension=xsl - - - - - -;;;;;;;;;;;;;;;;;;; -; About php.ini ; -;;;;;;;;;;;;;;;;;;; -; PHP's initialization file, generally called php.ini, is responsible for -; configuring many of the aspects of PHP's behavior. - -; PHP attempts to find and load this configuration from a number of locations. -; The following is a summary of its search order: -; 1. SAPI module specific location. -; 2. The PHPRC environment variable. (As of PHP 5.2.0) -; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0) -; 4. Current working directory (except CLI) -; 5. The web server's directory (for SAPI modules), or directory of PHP -; (otherwise in Windows) -; 6. The directory from the --with-config-file-path compile time option, or the -; Windows directory (usually C:\windows) -; See the PHP docs for more specific information. -; http://php.net/configuration.file - -; The syntax of the file is extremely simple. Whitespace and lines -; beginning with a semicolon are silently ignored (as you probably guessed). -; Section headers (e.g. [Foo]) are also silently ignored, even though -; they might mean something in the future. - -; Directives following the section heading [PATH=/www/mysite] only -; apply to PHP files in the /www/mysite directory. Directives -; following the section heading [HOST=www.example.com] only apply to -; PHP files served from www.example.com. Directives set in these -; special sections cannot be overridden by user-defined INI files or -; at runtime. Currently, [PATH=] and [HOST=] sections only work under -; CGI/FastCGI. -; http://php.net/ini.sections - -; Directives are specified using the following syntax: -; directive = value -; Directive names are *case sensitive* - foo=bar is different from FOO=bar. -; Directives are variables used to configure PHP or PHP extensions. -; There is no name validation. If PHP can't find an expected -; directive because it is not set or is mistyped, a default value will be used. - -; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one -; of the INI constants (On, Off, True, False, Yes, No and None) or an expression -; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a -; previously set variable or directive (e.g. ${foo}) - -; Expressions in the INI file are limited to bitwise operators and parentheses: -; | bitwise OR -; ^ bitwise XOR -; & bitwise AND -; ~ bitwise NOT -; ! boolean NOT - -; Boolean flags can be turned on using the values 1, On, True or Yes. -; They can be turned off using the values 0, Off, False or No. - -; An empty string can be denoted by simply not writing anything after the equal -; sign, or by using the None keyword: - -; foo = ; sets foo to an empty string -; foo = None ; sets foo to an empty string -; foo = "None" ; sets foo to the string 'None' - -; If you use constants in your value, and these constants belong to a -; dynamically loaded extension (either a PHP extension or a Zend extension), -; you may only use these constants *after* the line that loads the extension. - -;;;;;;;;;;;;;;;;;;; -; About this file ; -;;;;;;;;;;;;;;;;;;; -; PHP comes packaged with two INI files. One that is recommended to be used -; in production environments and one that is recommended to be used in -; development environments. - -; php.ini-production contains settings which hold security, performance and -; best practices at its core. But please be aware, these settings may break -; compatibility with older or less security conscience applications. We -; recommending using the production ini in production and testing environments. - -; php.ini-development is very similar to its production variant, except it is -; much more verbose when it comes to errors. We recommend using the -; development version only in development environments, as errors shown to -; application users can inadvertently leak otherwise secure information. - -; This is the php.ini-production INI file. - -;;;;;;;;;;;;;;;;;;; -; Quick Reference ; -;;;;;;;;;;;;;;;;;;; -; The following are all the settings which are different in either the production -; or development versions of the INIs with respect to PHP's default behavior. -; Please see the actual settings later in the document for more details as to why -; we recommend these changes in PHP's behavior. - -; display_errors -; Default Value: On -; Development Value: On -; Production Value: Off - -; display_startup_errors -; Default Value: Off -; Development Value: On -; Production Value: Off - -; error_reporting -; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED -; Development Value: E_ALL -; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT - -; html_errors -; Default Value: On -; Development Value: On -; Production value: On - -; log_errors -; Default Value: Off -; Development Value: On -; Production Value: On - -; max_input_time -; Default Value: -1 (Unlimited) -; Development Value: 60 (60 seconds) -; Production Value: 60 (60 seconds) - -; output_buffering -; Default Value: Off -; Development Value: 4096 -; Production Value: 4096 - -; register_argc_argv -; Default Value: On -; Development Value: Off -; Production Value: Off - -; request_order -; Default Value: None -; Development Value: "GP" -; Production Value: "GP" - -; session.gc_divisor -; Default Value: 100 -; Development Value: 1000 -; Production Value: 1000 - -; session.sid_bits_per_character -; Default Value: 4 -; Development Value: 5 -; Production Value: 5 - -; short_open_tag -; Default Value: On -; Development Value: Off -; Production Value: Off - -; track_errors -; Default Value: Off -; Development Value: On -; Production Value: Off - -; variables_order -; Default Value: "EGPCS" -; Development Value: "GPCS" -; Production Value: "GPCS" - -;;;;;;;;;;;;;;;;;;;; -; php.ini Options ; -;;;;;;;;;;;;;;;;;;;; -; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" -;user_ini.filename = ".user.ini" - -; To disable this feature set this option to an empty value -;user_ini.filename = - -; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) -;user_ini.cache_ttl = 300 - -;;;;;;;;;;;;;;;;;;;; -; Language Options ; -;;;;;;;;;;;;;;;;;;;; - -; Enable the PHP scripting language engine under Apache. -; http://php.net/engine -engine = On - -; This directive determines whether or not PHP will recognize code between -; tags as PHP source which should be processed as such. It is -; generally recommended that should be used and that this feature -; should be disabled, as enabling it may result in issues when generating XML -; documents, however this remains supported for backward compatibility reasons. -; Note that this directive does not control the would work. -; http://php.net/syntax-highlighting -;highlight.string = #DD0000 -;highlight.comment = #FF9900 -;highlight.keyword = #007700 -;highlight.default = #0000BB -;highlight.html = #000000 - -; If enabled, the request will be allowed to complete even if the user aborts -; the request. Consider enabling it if executing long requests, which may end up -; being interrupted by the user or a browser timing out. PHP's default behavior -; is to disable this feature. -; http://php.net/ignore-user-abort -;ignore_user_abort = On - -; Determines the size of the realpath cache to be used by PHP. This value should -; be increased on systems where PHP opens many files to reflect the quantity of -; the file operations performed. -; Note: if open_basedir is set, the cache is disabled -; http://php.net/realpath-cache-size -;realpath_cache_size = 4096k - -; Duration of time, in seconds for which to cache realpath information for a given -; file or directory. For systems with rarely changing files, consider increasing this -; value. -; http://php.net/realpath-cache-ttl -;realpath_cache_ttl = 120 - -; Enables or disables the circular reference collector. -; http://php.net/zend.enable-gc -zend.enable_gc = On - -; If enabled, scripts may be written in encodings that are incompatible with -; the scanner. CP936, Big5, CP949 and Shift_JIS are the examples of such -; encodings. To use this feature, mbstring extension must be enabled. -; Default: Off -;zend.multibyte = Off - -; Allows to set the default encoding for the scripts. This value will be used -; unless "declare(encoding=...)" directive appears at the top of the script. -; Only affects if zend.multibyte is set. -; Default: "" -;zend.script_encoding = - -;;;;;;;;;;;;;;;;; -; Miscellaneous ; -;;;;;;;;;;;;;;;;; - -; Decides whether PHP may expose the fact that it is installed on the server -; (e.g. by adding its signature to the Web server header). It is no security -; threat in any way, but it makes it possible to determine whether you use PHP -; on your server or not. -; http://php.net/expose-php -expose_php = Off - -;;;;;;;;;;;;;;;;;;; -; Resource Limits ; -;;;;;;;;;;;;;;;;;;; - -; Maximum execution time of each script, in seconds -; http://php.net/max-execution-time -; Note: This directive is hardcoded to 0 for the CLI SAPI - - -; Maximum amount of time each script may spend parsing request data. It's a good -; idea to limit this time on productions servers in order to eliminate unexpectedly -; long running scripts. -; Note: This directive is hardcoded to -1 for the CLI SAPI -; Default Value: -1 (Unlimited) -; Development Value: 60 (60 seconds) -; Production Value: 60 (60 seconds) -; http://php.net/max-input-time -max_input_time = 60 - -; Maximum input variable nesting level -; http://php.net/max-input-nesting-level -;max_input_nesting_level = 64 - -; How many GET/POST/COOKIE input variables may be accepted -;max_input_vars = 1000 - -; Maximum amount of memory a script may consume (128MB) -; http://php.net/memory-limit -;see [app_limits] - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Error handling and logging ; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -; This directive informs PHP of which errors, warnings and notices you would like -; it to take action for. The recommended way of setting values for this -; directive is through the use of the error level constants and bitwise -; operators. The error level constants are below here for convenience as well as -; some common settings and their meanings. -; By default, PHP is set to take action on all errors, notices and warnings EXCEPT -; those related to E_NOTICE and E_STRICT, which together cover best practices and -; recommended coding standards in PHP. For performance reasons, this is the -; recommend error reporting setting. Your production server shouldn't be wasting -; resources complaining about best practices and coding standards. That's what -; development servers and development settings are for. -; Note: The php.ini-development file has this setting as E_ALL. This -; means it pretty much reports everything which is exactly what you want during -; development and early testing. -; -; Error Level Constants: -; E_ALL - All errors and warnings (includes E_STRICT as of PHP 5.4.0) -; E_ERROR - fatal run-time errors -; E_RECOVERABLE_ERROR - almost fatal run-time errors -; E_WARNING - run-time warnings (non-fatal errors) -; E_PARSE - compile-time parse errors -; E_NOTICE - run-time notices (these are warnings which often result -; from a bug in your code, but it's possible that it was -; intentional (e.g., using an uninitialized variable and -; relying on the fact it is automatically initialized to an -; empty string) -; E_STRICT - run-time notices, enable to have PHP suggest changes -; to your code which will ensure the best interoperability -; and forward compatibility of your code -; E_CORE_ERROR - fatal errors that occur during PHP's initial startup -; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's -; initial startup -; E_COMPILE_ERROR - fatal compile-time errors -; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) -; E_USER_ERROR - user-generated error message -; E_USER_WARNING - user-generated warning message -; E_USER_NOTICE - user-generated notice message -; E_DEPRECATED - warn about code that will not work in future versions -; of PHP -; E_USER_DEPRECATED - user-generated deprecation warnings -; -; Common Values: -; E_ALL (Show all errors, warnings and notices including coding standards.) -; E_ALL & ~E_NOTICE (Show all errors, except for notices) -; E_ALL & ~E_NOTICE & ~E_STRICT (Show all errors, except for notices and coding standards warnings.) -; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors) -; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED -; Development Value: E_ALL -; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT -; http://php.net/error-reporting -error_reporting=E_ALL - -; This directive controls whether or not and where PHP will output errors, -; notices and warnings too. Error output is very useful during development, but -; it could be very dangerous in production environments. Depending on the code -; which is triggering the error, sensitive information could potentially leak -; out of your application such as database usernames and passwords or worse. -; For production environments, we recommend logging errors rather than -; sending them to STDOUT. -; Possible Values: -; Off = Do not display any errors -; stderr = Display errors to STDERR (affects only CGI/CLI binaries!) -; On or stdout = Display errors to STDOUT -; Default Value: On -; Development Value: On -; Production Value: Off -; http://php.net/display-errors -display_errors = Off - -; The display of errors which occur during PHP's startup sequence are handled -; separately from display_errors. PHP's default behavior is to suppress those -; errors from clients. Turning the display of startup errors on can be useful in -; debugging configuration problems. We strongly recommend you -; set this to 'off' for production servers. -; Default Value: Off -; Development Value: On -; Production Value: Off -; http://php.net/display-startup-errors -display_startup_errors = Off - -; Besides displaying errors, PHP can also log errors to locations such as a -; server-specific log, STDERR, or a location specified by the error_log -; directive found below. While errors should not be displayed on productions -; servers they should still be monitored and logging is a great way to do that. -; Default Value: Off -; Development Value: On -; Production Value: On -; http://php.net/log-errors -log_errors = On - -; Set maximum length of log_errors. In error_log information about the source is -; added. The default is 1024 and 0 allows to not apply any maximum length at all. -; http://php.net/log-errors-max-len -log_errors_max_len = 1024 - -; Do not log repeated messages. Repeated errors must occur in same file on same -; line unless ignore_repeated_source is set true. -; http://php.net/ignore-repeated-errors -ignore_repeated_errors = Off - -; Ignore source of message when ignoring repeated messages. When this setting -; is On you will not log errors with repeated messages from different files or -; source lines. -; http://php.net/ignore-repeated-source -ignore_repeated_source = Off - -; If this parameter is set to Off, then memory leaks will not be shown (on -; stdout or in the log). This has only effect in a debug compile, and if -; error reporting includes E_WARNING in the allowed list -; http://php.net/report-memleaks -report_memleaks = On - -; This setting is on by default. -;report_zend_debug = 0 - -; Store the last error/warning message in $php_errormsg (boolean). Setting this value -; to On can assist in debugging and is appropriate for development servers. It should -; however be disabled on production servers. -; This directive is DEPRECATED. -; Default Value: Off -; Development Value: Off -; Production Value: Off -; http://php.net/track-errors -;track_errors = Off - -; Turn off normal error reporting and emit XML-RPC error XML -; http://php.net/xmlrpc-errors -;xmlrpc_errors = 0 - -; An XML-RPC faultCode -;xmlrpc_error_number = 0 - -; When PHP displays or logs an error, it has the capability of formatting the -; error message as HTML for easier reading. This directive controls whether -; the error message is formatted as HTML or not. -; Note: This directive is hardcoded to Off for the CLI SAPI -; Default Value: On -; Development Value: On -; Production value: On -; http://php.net/html-errors -html_errors = On - -; If html_errors is set to On *and* docref_root is not empty, then PHP -; produces clickable error messages that direct to a page describing the error -; or function causing the error in detail. -; You can download a copy of the PHP manual from http://php.net/docs -; and change docref_root to the base URL of your local copy including the -; leading '/'. You must also specify the file extension being used including -; the dot. PHP's default behavior is to leave these settings empty, in which -; case no links to documentation are generated. -; Note: Never use this feature for production boxes. -; http://php.net/docref-root -; Examples -;docref_root = "/phpmanual/" - -; http://php.net/docref-ext -;docref_ext = .html - -; String to output before an error message. PHP's default behavior is to leave -; this setting blank. -; http://php.net/error-prepend-string -; Example: -;error_prepend_string = "" - -; String to output after an error message. PHP's default behavior is to leave -; this setting blank. -; http://php.net/error-append-string -; Example: -;error_append_string = "" - -; Log errors to specified file. PHP's default behavior is to leave this value -; empty. -; http://php.net/error-log -; Example: -; Log errors to syslog (Event Log on Windows). -;error_log = syslog - -; The syslog ident is a string which is prepended to every message logged -; to syslog. Only used when error_log is set to syslog. -;syslog.ident = php - -; The syslog facility is used to specify what type of program is logging -; the message. Only used when error_log is set to syslog. -;syslog.facility = user - -; Set this to disable filtering control characters (the default). -; Some loggers only accept NVT-ASCII, others accept anything that's not -; control characters. If your logger accepts everything, then no filtering -; is needed at all. -; Allowed values are: -; ascii (only base ASCII characters) -; no_ctrl (all characters except control characters) -; all (all characters) -;syslog.filter = ascii - -;windows.show_crt_warning -; Default value: 0 -; Development value: 0 -; Production value: 0 - -;;;;;;;;;;;;;;;;; -; Data Handling ; -;;;;;;;;;;;;;;;;; - -; The separator used in PHP generated URLs to separate arguments. -; PHP's default setting is "&". -; http://php.net/arg-separator.output -; Example: -;arg_separator.output = "&" - -; List of separator(s) used by PHP to parse input URLs into variables. -; PHP's default setting is "&". -; NOTE: Every character in this directive is considered as separator! -; http://php.net/arg-separator.input -; Example: -;arg_separator.input = ";&" - -; This directive determines which super global arrays are registered when PHP -; starts up. G,P,C,E & S are abbreviations for the following respective super -; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty -; paid for the registration of these arrays and because ENV is not as commonly -; used as the others, ENV is not recommended on productions servers. You -; can still get access to the environment variables through getenv() should you -; need to. -; Default Value: "EGPCS" -; Development Value: "GPCS" -; Production Value: "GPCS"; -; http://php.net/variables-order -variables_order = "GPCS" - -; This directive determines which super global data (G,P & C) should be -; registered into the super global array REQUEST. If so, it also determines -; the order in which that data is registered. The values for this directive -; are specified in the same manner as the variables_order directive, -; EXCEPT one. Leaving this value empty will cause PHP to use the value set -; in the variables_order directive. It does not mean it will leave the super -; globals array REQUEST empty. -; Default Value: None -; Development Value: "GP" -; Production Value: "GP" -; http://php.net/request-order -request_order = "GP" - -; This directive determines whether PHP registers $argv & $argc each time it -; runs. $argv contains an array of all the arguments passed to PHP when a script -; is invoked. $argc contains an integer representing the number of arguments -; that were passed when the script was invoked. These arrays are extremely -; useful when running scripts from the command line. When this directive is -; enabled, registering these variables consumes CPU cycles and memory each time -; a script is executed. For performance reasons, this feature should be disabled -; on production servers. -; Note: This directive is hardcoded to On for the CLI SAPI -; Default Value: On -; Development Value: Off -; Production Value: Off -; http://php.net/register-argc-argv -register_argc_argv = Off - -; When enabled, the ENV, REQUEST and SERVER variables are created when they're -; first used (Just In Time) instead of when the script starts. If these -; variables are not used within a script, having this directive on will result -; in a performance gain. The PHP directive register_argc_argv must be disabled -; for this directive to have any affect. -; http://php.net/auto-globals-jit -auto_globals_jit = On - -; Whether PHP will read the POST data. -; This option is enabled by default. -; Most likely, you won't want to disable this option globally. It causes $_POST -; and $_FILES to always be empty; the only way you will be able to read the -; POST data will be through the php://input stream wrapper. This can be useful -; to proxy requests or to process the POST data in a memory efficient fashion. -; http://php.net/enable-post-data-reading -;enable_post_data_reading = Off - -; Maximum size of POST data that PHP will accept. -; Its value may be 0 to disable the limit. It is ignored if POST data reading -; is disabled through enable_post_data_reading. -; http://php.net/post-max-size -post_max_size = 1024M - -; Automatically add files before PHP document. -; http://php.net/auto-prepend-file -auto_prepend_file = - -; Automatically add files after PHP document. -; http://php.net/auto-append-file -auto_append_file = - -; By default, PHP will output a media type using the Content-Type header. To -; disable this, simply set it to be empty. -; -; PHP's built-in default media type is set to text/html. -; http://php.net/default-mimetype -default_mimetype = "text/html" - -; PHP's default character set is set to UTF-8. -; http://php.net/default-charset -default_charset = "UTF-8" - -; PHP internal character encoding is set to empty. -; If empty, default_charset is used. -; http://php.net/internal-encoding -;internal_encoding = - -; PHP input character encoding is set to empty. -; If empty, default_charset is used. -; http://php.net/input-encoding -;input_encoding = - -; PHP output character encoding is set to empty. -; If empty, default_charset is used. -; See also output_buffer. -; http://php.net/output-encoding -;output_encoding = - -;;;;;;;;;;;;;;;;;;;;;;;;; -; Paths and Directories ; -;;;;;;;;;;;;;;;;;;;;;;;;; - -; UNIX: "/path1:/path2" -;include_path = ".:/php/includes" -; -; Windows: "\path1;\path2" -;include_path = ".;c:\php\includes" -; -; PHP's default setting for include_path is ".;/path/to/php/pear" -; http://php.net/include-path - -; The root of the PHP pages, used only if nonempty. -; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root -; if you are running php as a CGI under any web server (other than IIS) -; see documentation for security issues. The alternate is to use the -; cgi.force_redirect configuration below -; http://php.net/doc-root -doc_root = - -; The directory under which PHP opens the script using /~username used only -; if nonempty. -; http://php.net/user-dir -user_dir = - -; Directory in which the loadable extensions (modules) reside. -; http://php.net/extension-dir -;extension_dir = "./" -; On windows: - -; Directory where the temporary files should be placed. -; Defaults to the system default (see sys_get_temp_dir) - -; Whether or not to enable the dl() function. The dl() function does NOT work -; properly in multithreaded servers, such as IIS or Zeus, and is automatically -; disabled on them. -; http://php.net/enable-dl -enable_dl = Off - -; cgi.force_redirect is necessary to provide security running PHP as a CGI under -; most web servers. Left undefined, PHP turns this on by default. You can -; turn it off here AT YOUR OWN RISK -; **You CAN safely turn this off for IIS, in fact, you MUST.** -; http://php.net/cgi.force-redirect -;cgi.force_redirect = 1 - -; if cgi.nph is enabled it will force cgi to always sent Status: 200 with -; every request. PHP's default behavior is to disable this feature. -;cgi.nph = 1 - -; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape -; (iPlanet) web servers, you MAY need to set an environment variable name that PHP -; will look for to know it is OK to continue execution. Setting this variable MAY -; cause security issues, KNOW WHAT YOU ARE DOING FIRST. -; http://php.net/cgi.redirect-status-env -;cgi.redirect_status_env = - -; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's -; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok -; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting -; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting -; of zero causes PHP to behave as before. Default is 1. You should fix your scripts -; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. -; http://php.net/cgi.fix-pathinfo -;cgi.fix_pathinfo=1 - -; if cgi.discard_path is enabled, the PHP CGI binary can safely be placed outside -; of the web tree and people will not be able to circumvent .htaccess security. -;cgi.discard_path=1 - -; FastCGI under IIS supports the ability to impersonate -; security tokens of the calling client. This allows IIS to define the -; security context that the request runs under. mod_fastcgi under Apache -; does not currently support this feature (03/17/2002) -; Set to 1 if running under IIS. Default is zero. -; http://php.net/fastcgi.impersonate -;fastcgi.impersonate = 1 - -; Disable logging through FastCGI connection. PHP's default behavior is to enable -; this feature. -;fastcgi.logging = 0 - -; cgi.rfc2616_headers configuration option tells PHP what type of headers to -; use when sending HTTP response code. If set to 0, PHP sends Status: header that -; is supported by Apache. When this option is set to 1, PHP will send -; RFC2616 compliant header. -; Default is zero. -; http://php.net/cgi.rfc2616-headers -;cgi.rfc2616_headers = 0 - -; cgi.check_shebang_line controls whether CGI PHP checks for line starting with #! -; (shebang) at the top of the running script. This line might be needed if the -; script support running both as stand-alone script and via PHP CGI<. PHP in CGI -; mode skips this line and ignores its content if this directive is turned on. -; http://php.net/cgi.check-shebang-line -;cgi.check_shebang_line=1 - -;;;;;;;;;;;;;;;; -; File Uploads ; -;;;;;;;;;;;;;;;; - -; Whether to allow HTTP file uploads. -; http://php.net/file-uploads -file_uploads = On - -; Temporary directory for HTTP uploaded files (will use system default if not -; specified). -; http://php.net/upload-tmp-dir -;see [app] - -; Maximum allowed size for uploaded files. -; http://php.net/upload-max-filesize - -; Maximum number of files that can be uploaded via a single request -max_file_uploads = 20 - -;;;;;;;;;;;;;;;;;; -; Fopen wrappers ; -;;;;;;;;;;;;;;;;;; - -; Whether to allow the treatment of URLs (like http:// or ftp://) as files. -; http://php.net/allow-url-fopen -allow_url_fopen = On - -; Whether to allow include/require to open URLs (like http:// or ftp://) as files. -; http://php.net/allow-url-include -allow_url_include = Off - -; Define the anonymous ftp password (your email address). PHP's default setting -; for this is empty. -; http://php.net/from -;from="john@doe.com" - -; Define the User-Agent string. PHP's default setting for this is empty. -; http://php.net/user-agent -;user_agent="PHP" - -; Default timeout for socket based streams (seconds) -; http://php.net/default-socket-timeout -default_socket_timeout = 60 - -; If your scripts have to deal with files from Macintosh systems, -; or you are running on a Mac and need to deal with files from -; unix or win32 systems, setting this flag will cause PHP to -; automatically detect the EOL character in those files so that -; fgets() and file() will work regardless of the source of the file. -; http://php.net/auto-detect-line-endings -;auto_detect_line_endings = Off - -;;;;;;;;;;;;;;;;;;;;;; -; Dynamic Extensions ; -;;;;;;;;;;;;;;;;;;;;;; - -; If you wish to have an extension loaded automatically, use the following -; syntax: -; -; extension=modulename -; -; For example: -; -; extension=mysqli -; -; When the extension library to load is not located in the default extension -; directory, You may specify an absolute path to the library file: -; -; extension=/path/to/extension/mysqli.so -; -; Note : The syntax used in previous PHP versions ('extension=.so' and -; 'extension='php_.dll') is supported for legacy reasons and may be -; deprecated in a future PHP major version. So, when it is possible, please -; move to the new ('extension=) syntax. -; -; Notes for Windows environments : -; -; - Many DLL files are located in the extensions/ (PHP 4) or ext/ (PHP 5+) -; extension folders as well as the separate PECL DLL download (PHP 5+). -; Be sure to appropriately set the extension_dir directive. -; - -;SEE [app_extensions] - - -;;;;;;;;;;;;;;;;;;; -; Module Settings ; -;;;;;;;;;;;;;;;;;;; - -[CLI Server] -; Whether the CLI web server uses ANSI color coding in its terminal output. -cli_server.color = On - -[Date] -; Defines the default timezone used by the date functions -; http://php.net/date.timezone -date.timezone = "America/New_York" - -; http://php.net/date.default-latitude -;date.default_latitude = 31.7667 - -; http://php.net/date.default-longitude -;date.default_longitude = 35.2333 - -; http://php.net/date.sunrise-zenith -;date.sunrise_zenith = 90.583333 - -; http://php.net/date.sunset-zenith -;date.sunset_zenith = 90.583333 - -[filter] -; http://php.net/filter.default -;filter.default = unsafe_raw - -; http://php.net/filter.default-flags -;filter.default_flags = - -[iconv] -; Use of this INI entry is deprecated, use global input_encoding instead. -; If empty, default_charset or input_encoding or iconv.input_encoding is used. -; The precedence is: default_charset < input_encoding < iconv.input_encoding -;iconv.input_encoding = - -; Use of this INI entry is deprecated, use global internal_encoding instead. -; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. -; The precedence is: default_charset < internal_encoding < iconv.internal_encoding -;iconv.internal_encoding = - -; Use of this INI entry is deprecated, use global output_encoding instead. -; If empty, default_charset or output_encoding or iconv.output_encoding is used. -; The precedence is: default_charset < output_encoding < iconv.output_encoding -; To use an output encoding conversion, iconv's output handler must be set -; otherwise output encoding conversion cannot be performed. -;iconv.output_encoding = - -[imap] -; rsh/ssh logins are disabled by default. Use this INI entry if you want to -; enable them. Note that the IMAP library does not filter mailbox names before -; passing them to rsh/ssh command, thus passing untrusted data to this function -; with rsh/ssh enabled is insecure. -;imap.enable_insecure_rsh=0 - -[intl] -;intl.default_locale = -; This directive allows you to produce PHP errors when some error -; happens within intl functions. The value is the level of the error produced. -; Default is 0, which does not produce any errors. -;intl.error_level = E_WARNING -;intl.use_exceptions = 0 - -[sqlite3] -;sqlite3.extension_dir = - -[Pcre] -; PCRE library backtracking limit. -; http://php.net/pcre.backtrack-limit -;pcre.backtrack_limit=100000 - -; PCRE library recursion limit. -; Please note that if you set this value to a high number you may consume all -; the available process stack and eventually crash PHP (due to reaching the -; stack size limit imposed by the Operating System). -; http://php.net/pcre.recursion-limit -;pcre.recursion_limit=100000 - -; Enables or disables JIT compilation of patterns. This requires the PCRE -; library to be compiled with JIT support. -;pcre.jit=1 - -[Pdo] -; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" -; http://php.net/pdo-odbc.connection-pooling -;pdo_odbc.connection_pooling=strict - -;pdo_odbc.db2_instance_name - -[Pdo_mysql] -; Default socket name for local MySQL connects. If empty, uses the built-in -; MySQL defaults. -pdo_mysql.default_socket= - -[Phar] -; http://php.net/phar.readonly -;phar.readonly = On - -; http://php.net/phar.require-hash -;phar.require_hash = On - -;phar.cache_list = - -[mail function] -;SEE [app_mail] - -; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). -; http://php.net/sendmail-path -;sendmail_path = - -; Force the addition of the specified parameters to be passed as extra parameters -; to the sendmail binary. These parameters will always replace the value of -; the 5th parameter to mail(). -;mail.force_extra_parameters = - -; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename -mail.add_x_header = Off - -; The path to a log file that will log all mail() calls. Log entries include -; the full path of the script, line number, To address and headers. -; Log mail to syslog (Event Log on Windows). -;mail.log = syslog - -[ODBC] -; http://php.net/odbc.default-db -;odbc.default_db = Not yet implemented - -; http://php.net/odbc.default-user -;odbc.default_user = Not yet implemented - -; http://php.net/odbc.default-pw -;odbc.default_pw = Not yet implemented - -; Controls the ODBC cursor model. -; Default: SQL_CURSOR_STATIC (default). -;odbc.default_cursortype - -; Allow or prevent persistent links. -; http://php.net/odbc.allow-persistent -odbc.allow_persistent = On - -; Check that a connection is still valid before reuse. -; http://php.net/odbc.check-persistent -odbc.check_persistent = On - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/odbc.max-persistent -odbc.max_persistent = -1 - -; Maximum number of links (persistent + non-persistent). -1 means no limit. -; http://php.net/odbc.max-links -odbc.max_links = -1 - -; Handling of LONG fields. Returns number of bytes to variables. 0 means -; passthru. -; http://php.net/odbc.defaultlrl -odbc.defaultlrl = 4096 - -; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. -; See the documentation on odbc_binmode and odbc_longreadlen for an explanation -; of odbc.defaultlrl and odbc.defaultbinmode -; http://php.net/odbc.defaultbinmode -odbc.defaultbinmode = 1 - -[Interbase] -; Allow or prevent persistent links. -ibase.allow_persistent = 1 - -; Maximum number of persistent links. -1 means no limit. -ibase.max_persistent = -1 - -; Maximum number of links (persistent + non-persistent). -1 means no limit. -ibase.max_links = -1 - -; Default database name for ibase_connect(). -;ibase.default_db = - -; Default username for ibase_connect(). -;ibase.default_user = - -; Default password for ibase_connect(). -;ibase.default_password = - -; Default charset for ibase_connect(). -;ibase.default_charset = - -; Default timestamp format. -ibase.timestampformat = "%Y-%m-%d %H:%M:%S" - -; Default date format. -ibase.dateformat = "%Y-%m-%d" - -; Default time format. -ibase.timeformat = "%H:%M:%S" - -[MySQLi] - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/mysqli.max-persistent -mysqli.max_persistent = -1 - -; Allow accessing, from PHP's perspective, local files with LOAD DATA statements -; http://php.net/mysqli.allow_local_infile -;mysqli.allow_local_infile = On - -; Allow or prevent persistent links. -; http://php.net/mysqli.allow-persistent -mysqli.allow_persistent = On - -; Maximum number of links. -1 means no limit. -; http://php.net/mysqli.max-links -mysqli.max_links = -1 - -; Default port number for mysqli_connect(). If unset, mysqli_connect() will use -; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the -; compile-time value defined MYSQL_PORT (in that order). Win32 will only look -; at MYSQL_PORT. -; http://php.net/mysqli.default-port -mysqli.default_port = 3306 - -; Default socket name for local MySQL connects. If empty, uses the built-in -; MySQL defaults. -; http://php.net/mysqli.default-socket -mysqli.default_socket = - -; Default host for mysql_connect() (doesn't apply in safe mode). -; http://php.net/mysqli.default-host -mysqli.default_host = - -; Default user for mysql_connect() (doesn't apply in safe mode). -; http://php.net/mysqli.default-user -mysqli.default_user = - -; Default password for mysqli_connect() (doesn't apply in safe mode). -; Note that this is generally a *bad* idea to store passwords in this file. -; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") -; and reveal this password! And of course, any users with read access to this -; file will be able to reveal the password as well. -; http://php.net/mysqli.default-pw -mysqli.default_pw = - -; Allow or prevent reconnect -mysqli.reconnect = Off - -[mysqlnd] -; Enable / Disable collection of general statistics by mysqlnd which can be -; used to tune and monitor MySQL operations. -mysqlnd.collect_statistics = On - -; Enable / Disable collection of memory usage statistics by mysqlnd which can be -; used to tune and monitor MySQL operations. -mysqlnd.collect_memory_statistics = Off - -; Records communication from all extensions using mysqlnd to the specified log -; file. -; http://php.net/mysqlnd.debug -;mysqlnd.debug = - -; Defines which queries will be logged. -;mysqlnd.log_mask = 0 - -; Default size of the mysqlnd memory pool, which is used by result sets. -;mysqlnd.mempool_default_size = 16000 - -; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. -;mysqlnd.net_cmd_buffer_size = 2048 - -; Size of a pre-allocated buffer used for reading data sent by the server in -; bytes. -;mysqlnd.net_read_buffer_size = 32768 - -; Timeout for network requests in seconds. -;mysqlnd.net_read_timeout = 31536000 - -; SHA-256 Authentication Plugin related. File with the MySQL server public RSA -; key. -;mysqlnd.sha256_server_public_key = - -[OCI8] - -; Connection: Enables privileged connections using external -; credentials (OCI_SYSOPER, OCI_SYSDBA) -; http://php.net/oci8.privileged-connect -;oci8.privileged_connect = Off - -; Connection: The maximum number of persistent OCI8 connections per -; process. Using -1 means no limit. -; http://php.net/oci8.max-persistent -;oci8.max_persistent = -1 - -; Connection: The maximum number of seconds a process is allowed to -; maintain an idle persistent connection. Using -1 means idle -; persistent connections will be maintained forever. -; http://php.net/oci8.persistent-timeout -;oci8.persistent_timeout = -1 - -; Connection: The number of seconds that must pass before issuing a -; ping during oci_pconnect() to check the connection validity. When -; set to 0, each oci_pconnect() will cause a ping. Using -1 disables -; pings completely. -; http://php.net/oci8.ping-interval -;oci8.ping_interval = 60 - -; Connection: Set this to a user chosen connection class to be used -; for all pooled server requests with Oracle 11g Database Resident -; Connection Pooling (DRCP). To use DRCP, this value should be set to -; the same string for all web servers running the same application, -; the database pool must be configured, and the connection string must -; specify to use a pooled server. -;oci8.connection_class = - -; High Availability: Using On lets PHP receive Fast Application -; Notification (FAN) events generated when a database node fails. The -; database must also be configured to post FAN events. -;oci8.events = Off - -; Tuning: This option enables statement caching, and specifies how -; many statements to cache. Using 0 disables statement caching. -; http://php.net/oci8.statement-cache-size -;oci8.statement_cache_size = 20 - -; Tuning: Enables statement prefetching and sets the default number of -; rows that will be fetched automatically after statement execution. -; http://php.net/oci8.default-prefetch -;oci8.default_prefetch = 100 - -; Compatibility. Using On means oci_close() will not close -; oci_connect() and oci_new_connect() connections. -; http://php.net/oci8.old-oci-close-semantics -;oci8.old_oci_close_semantics = Off - -[PostgreSQL] -; Allow or prevent persistent links. -; http://php.net/pgsql.allow-persistent -pgsql.allow_persistent = On - -; Detect broken persistent links always with pg_pconnect(). -; Auto reset feature requires a little overheads. -; http://php.net/pgsql.auto-reset-persistent -pgsql.auto_reset_persistent = Off - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/pgsql.max-persistent -pgsql.max_persistent = -1 - -; Maximum number of links (persistent+non persistent). -1 means no limit. -; http://php.net/pgsql.max-links -pgsql.max_links = -1 - -; Ignore PostgreSQL backends Notice message or not. -; Notice message logging require a little overheads. -; http://php.net/pgsql.ignore-notice -pgsql.ignore_notice = 0 - -; Log PostgreSQL backends Notice message or not. -; Unless pgsql.ignore_notice=0, module cannot log notice message. -; http://php.net/pgsql.log-notice -pgsql.log_notice = 0 - -[bcmath] -; Number of decimal digits for all bcmath functions. -; http://php.net/bcmath.scale -bcmath.scale = 0 - -[browscap] -; http://php.net/browscap -;browscap = extra/browscap.ini - -[Session] -; Handler used to store/retrieve data. -; http://php.net/session.save-handler -session.save_handler = files - -; Argument passed to save_handler. In the case of files, this is the path -; where data files are stored. Note: Windows users have to change this -; variable in order to use PHP's session functions. -; -; The path can be defined as: -; -; session.save_path = "N;/path" -; -; where N is an integer. Instead of storing all the session files in -; /path, what this will do is use subdirectories N-levels deep, and -; store the session data in those directories. This is useful if -; your OS has problems with many files in one directory, and is -; a more efficient layout for servers that handle many sessions. -; -; NOTE 1: PHP will not create this directory structure automatically. -; You can use the script in the ext/session dir for that purpose. -; NOTE 2: See the section on garbage collection below if you choose to -; use subdirectories for session storage -; -; The file storage module creates files using mode 600 by default. -; You can change that by using -; -; session.save_path = "N;MODE;/path" -; -; where MODE is the octal representation of the mode. Note that this -; does not overwrite the process's umask. -; http://php.net/session.save-path - -; Whether to use strict session mode. -; Strict session mode does not accept an uninitialized session ID, and -; regenerates the session ID if the browser sends an uninitialized session ID. -; Strict mode protects applications from session fixation via a session adoption -; vulnerability. It is disabled by default for maximum compatibility, but -; enabling it is encouraged. -; https://wiki.php.net/rfc/strict_sessions -session.use_strict_mode = 0 - -; Whether to use cookies. -; http://php.net/session.use-cookies -session.use_cookies = 1 - -; http://php.net/session.cookie-secure -session.cookie_secure = 1 - -; This option forces PHP to fetch and use a cookie for storing and maintaining -; the session id. We encourage this operation as it's very helpful in combating -; session hijacking when not specifying and managing your own session id. It is -; not the be-all and end-all of session hijacking defense, but it's a good start. -; http://php.net/session.use-only-cookies -session.use_only_cookies = 1 - -; Name of the session (used as cookie name). -; http://php.net/session.name - -; Initialize session on request startup. -; http://php.net/session.auto-start -session.auto_start = 0 - -; Lifetime in seconds of cookie or, if 0, until browser is restarted. -; http://php.net/session.cookie-lifetime -session.cookie_lifetime = 0 - -; The path for which the cookie is valid. -; http://php.net/session.cookie-path -session.cookie_path = / - -; The domain for which the cookie is valid. -; http://php.net/session.cookie-domain -session.cookie_domain = - -; Whether or not to add the httpOnly flag to the cookie, which makes it -; inaccessible to browser scripting languages such as JavaScript. -; http://php.net/session.cookie-httponly -session.cookie_httponly = 1 - -; Add SameSite attribute to cookie to help mitigate Cross-Site Request Forgery (CSRF/XSRF) -; Current valid values are "Lax" or "Strict" -; https://tools.ietf.org/html/draft-west-first-party-cookies-07 -session.cookie_samesite = - -; Handler used to serialize data. php is the standard serializer of PHP. -; http://php.net/session.serialize-handler -session.serialize_handler = php - -; Defines the probability that the 'garbage collection' process is started -; on every session initialization. The probability is calculated by using -; gc_probability/gc_divisor. Where session.gc_probability is the numerator -; and gc_divisor is the denominator in the equation. Setting this value to 1 -; when the session.gc_divisor value is 100 will give you approximately a 1% chance -; the gc will run on any given request. -; Default Value: 1 -; Development Value: 1 -; Production Value: 1 -; http://php.net/session.gc-probability -session.gc_probability = 1 - -; Defines the probability that the 'garbage collection' process is started on every -; session initialization. The probability is calculated by using the following equation: -; gc_probability/gc_divisor. Where session.gc_probability is the numerator and -; session.gc_divisor is the denominator in the equation. Setting this value to 100 -; when the session.gc_probability value is 1 will give you approximately a 1% chance -; the gc will run on any given request. Increasing this value to 1000 will give you -; a 0.1% chance the gc will run on any given request. For high volume production servers, -; this is a more efficient approach. -; Default Value: 100 -; Development Value: 1000 -; Production Value: 1000 -; http://php.net/session.gc-divisor -session.gc_divisor = 1000 - -; After this number of seconds, stored data will be seen as 'garbage' and -; cleaned up by the garbage collection process. -; http://php.net/session.gc-maxlifetime -session.gc_maxlifetime = 1440 - -; NOTE: If you are using the subdirectory option for storing session files -; (see session.save_path above), then garbage collection does *not* -; happen automatically. You will need to do your own garbage -; collection through a shell script, cron entry, or some other method. -; For example, the following script would is the equivalent of -; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): -; find /path/to/sessions -cmin +24 -type f | xargs rm - -; Check HTTP Referer to invalidate externally stored URLs containing ids. -; HTTP_REFERER has to contain this substring for the session to be -; considered as valid. -; http://php.net/session.referer-check -session.referer_check = - -; Set to {nocache,private,public,} to determine HTTP caching aspects -; or leave this empty to avoid sending anti-caching headers. -; http://php.net/session.cache-limiter -session.cache_limiter = nocache - -; Document expires after n minutes. -; http://php.net/session.cache-expire -session.cache_expire = 180 - -; trans sid support is disabled by default. -; Use of trans sid may risk your users' security. -; Use this option with caution. -; - User may send URL contains active session ID -; to other person via. email/irc/etc. -; - URL that contains active session ID may be stored -; in publicly accessible computer. -; - User may access your site with the same session ID -; always using URL stored in browser's history or bookmarks. -; http://php.net/session.use-trans-sid -session.use_trans_sid = 0 - -; Set session ID character length. This value could be between 22 to 256. -; Shorter length than default is supported only for compatibility reason. -; Users should use 32 or more chars. -; http://php.net/session.sid-length -; Default Value: 32 -; Development Value: 26 -; Production Value: 26 -session.sid_length = 26 - -; The URL rewriter will look for URLs in a defined set of HTML tags. -; is special; if you include them here, the rewriter will -; add a hidden field with the info which is otherwise appended -; to URLs. tag's action attribute URL will not be modified -; unless it is specified. -; Note that all valid entries require a "=", even if no value follows. -; Default Value: "a=href,area=href,frame=src,form=" -; Development Value: "a=href,area=href,frame=src,form=" -; Production Value: "a=href,area=href,frame=src,form=" -; http://php.net/url-rewriter.tags -session.trans_sid_tags = "a=href,area=href,frame=src,form=" - -; URL rewriter does not rewrite absolute URLs by default. -; To enable rewrites for absolute paths, target hosts must be specified -; at RUNTIME. i.e. use ini_set() -; tags is special. PHP will check action attribute's URL regardless -; of session.trans_sid_tags setting. -; If no host is defined, HTTP_HOST will be used for allowed host. -; Example value: php.net,www.php.net,wiki.php.net -; Use "," for multiple hosts. No spaces are allowed. -; Default Value: "" -; Development Value: "" -; Production Value: "" -;session.trans_sid_hosts="" - -; Define how many bits are stored in each character when converting -; the binary hash data to something readable. -; Possible values: -; 4 (4 bits: 0-9, a-f) -; 5 (5 bits: 0-9, a-v) -; 6 (6 bits: 0-9, a-z, A-Z, "-", ",") -; Default Value: 4 -; Development Value: 5 -; Production Value: 5 -; http://php.net/session.hash-bits-per-character -session.sid_bits_per_character = 5 - -; Enable upload progress tracking in $_SESSION -; Default Value: On -; Development Value: On -; Production Value: On -; http://php.net/session.upload-progress.enabled -;session.upload_progress.enabled = On - -; Cleanup the progress information as soon as all POST data has been read -; (i.e. upload completed). -; Default Value: On -; Development Value: On -; Production Value: On -; http://php.net/session.upload-progress.cleanup -;session.upload_progress.cleanup = On - -; A prefix used for the upload progress key in $_SESSION -; Default Value: "upload_progress_" -; Development Value: "upload_progress_" -; Production Value: "upload_progress_" -; http://php.net/session.upload-progress.prefix -;session.upload_progress.prefix = "upload_progress_" - -; The index name (concatenated with the prefix) in $_SESSION -; containing the upload progress information -; Default Value: "PHP_SESSION_UPLOAD_PROGRESS" -; Development Value: "PHP_SESSION_UPLOAD_PROGRESS" -; Production Value: "PHP_SESSION_UPLOAD_PROGRESS" -; http://php.net/session.upload-progress.name -;session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS" - -; How frequently the upload progress should be updated. -; Given either in percentages (per-file), or in bytes -; Default Value: "1%" -; Development Value: "1%" -; Production Value: "1%" -; http://php.net/session.upload-progress.freq -;session.upload_progress.freq = "1%" - -; The minimum delay between updates, in seconds -; Default Value: 1 -; Development Value: 1 -; Production Value: 1 -; http://php.net/session.upload-progress.min-freq -;session.upload_progress.min_freq = "1" - -; Only write session data when session data is changed. Enabled by default. -; http://php.net/session.lazy-write -;session.lazy_write = On - -[Assertion] -; Switch whether to compile assertions at all (to have no overhead at run-time) -; -1: Do not compile at all -; 0: Jump over assertion at run-time -; 1: Execute assertions -; Changing from or to a negative value is only possible in php.ini! (For turning assertions on and off at run-time, see assert.active, when zend.assertions = 1) -; Default Value: 1 -; Development Value: 1 -; Production Value: -1 -; http://php.net/zend.assertions -zend.assertions = -1 - -; Assert(expr); active by default. -; http://php.net/assert.active -;assert.active = On - -; Throw an AssertionError on failed assertions -; http://php.net/assert.exception -;assert.exception = On - -; Issue a PHP warning for each failed assertion. (Overridden by assert.exception if active) -; http://php.net/assert.warning -;assert.warning = On - -; Don't bail out by default. -; http://php.net/assert.bail -;assert.bail = Off - -; User-function to be called if an assertion fails. -; http://php.net/assert.callback -;assert.callback = 0 - -; Eval the expression with current error_reporting(). Set to true if you want -; error_reporting(0) around the eval(). -; http://php.net/assert.quiet-eval -;assert.quiet_eval = 0 - -[COM] -; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs -; http://php.net/com.typelib-file -;com.typelib_file = - -; allow Distributed-COM calls -; http://php.net/com.allow-dcom -;com.allow_dcom = true - -; autoregister constants of a component's typlib on com_load() -; http://php.net/com.autoregister-typelib -;com.autoregister_typelib = true - -; register constants casesensitive -; http://php.net/com.autoregister-casesensitive -;com.autoregister_casesensitive = false - -; show warnings on duplicate constant registrations -; http://php.net/com.autoregister-verbose -;com.autoregister_verbose = true - -; The default character set code-page to use when passing strings to and from COM objects. -; Default: system ANSI code page -;com.code_page= - -[mbstring] -; language for internal character representation. -; This affects mb_send_mail() and mbstring.detect_order. -; http://php.net/mbstring.language -;mbstring.language = Japanese - -; Use of this INI entry is deprecated, use global internal_encoding instead. -; internal/script encoding. -; Some encoding cannot work as internal encoding. (e.g. SJIS, BIG5, ISO-2022-*) -; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. -; The precedence is: default_charset < internal_encoding < iconv.internal_encoding -;mbstring.internal_encoding = - -; Use of this INI entry is deprecated, use global input_encoding instead. -; http input encoding. -; mbstring.encoding_translation = On is needed to use this setting. -; If empty, default_charset or input_encoding or mbstring.input is used. -; The precedence is: default_charset < input_encoding < mbsting.http_input -; http://php.net/mbstring.http-input -;mbstring.http_input = - -; Use of this INI entry is deprecated, use global output_encoding instead. -; http output encoding. -; mb_output_handler must be registered as output buffer to function. -; If empty, default_charset or output_encoding or mbstring.http_output is used. -; The precedence is: default_charset < output_encoding < mbstring.http_output -; To use an output encoding conversion, mbstring's output handler must be set -; otherwise output encoding conversion cannot be performed. -; http://php.net/mbstring.http-output -;mbstring.http_output = - -; enable automatic encoding translation according to -; mbstring.internal_encoding setting. Input chars are -; converted to internal encoding by setting this to On. -; Note: Do _not_ use automatic encoding translation for -; portable libs/applications. -; http://php.net/mbstring.encoding-translation -;mbstring.encoding_translation = Off - -; automatic encoding detection order. -; "auto" detect order is changed according to mbstring.language -; http://php.net/mbstring.detect-order -;mbstring.detect_order = auto - -; substitute_character used when character cannot be converted -; one from another -; http://php.net/mbstring.substitute-character -;mbstring.substitute_character = none - -; overload(replace) single byte functions by mbstring functions. -; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(), -; etc. Possible values are 0,1,2,4 or combination of them. -; For example, 7 for overload everything. -; 0: No overload -; 1: Overload mail() function -; 2: Overload str*() functions -; 4: Overload ereg*() functions -; http://php.net/mbstring.func-overload -;mbstring.func_overload = 0 - -; enable strict encoding detection. -; Default: Off -;mbstring.strict_detection = On - -; This directive specifies the regex pattern of content types for which mb_output_handler() -; is activated. -; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml) -;mbstring.http_output_conv_mimetype= - -[gd] -; Tell the jpeg decode to ignore warnings and try to create -; a gd image. The warning will then be displayed as notices -; disabled by default -; http://php.net/gd.jpeg-ignore-warning -;gd.jpeg_ignore_warning = 1 - -[exif] -; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. -; With mbstring support this will automatically be converted into the encoding -; given by corresponding encode setting. When empty mbstring.internal_encoding -; is used. For the decode settings you can distinguish between motorola and -; intel byte order. A decode setting cannot be empty. -; http://php.net/exif.encode-unicode -;exif.encode_unicode = ISO-8859-15 - -; http://php.net/exif.decode-unicode-motorola -;exif.decode_unicode_motorola = UCS-2BE - -; http://php.net/exif.decode-unicode-intel -;exif.decode_unicode_intel = UCS-2LE - -; http://php.net/exif.encode-jis -;exif.encode_jis = - -; http://php.net/exif.decode-jis-motorola -;exif.decode_jis_motorola = JIS - -; http://php.net/exif.decode-jis-intel -;exif.decode_jis_intel = JIS - -[Tidy] -; The path to a default tidy configuration file to use when using tidy -; http://php.net/tidy.default-config -;tidy.default_config = /usr/local/lib/php/default.tcfg - -; Should tidy clean and repair output automatically? -; WARNING: Do not use this option if you are generating non-html content -; such as dynamic images -; http://php.net/tidy.clean-output -tidy.clean_output = Off - -[soap] -; Enables or disables WSDL caching feature. -; http://php.net/soap.wsdl-cache-enabled -soap.wsdl_cache_enabled=1 - -; Sets the directory name where SOAP extension will put cache files. -; http://php.net/soap.wsdl-cache-dir - -; (time to live) Sets the number of second while cached file will be used -; instead of original one. -; http://php.net/soap.wsdl-cache-ttl -soap.wsdl_cache_ttl=86400 - -; Sets the size of the cache limit. (Max. number of WSDL files to cache) -soap.wsdl_cache_limit = 5 - -[sysvshm] -; A default size of the shared memory segment -;sysvshm.init_mem = 10000 - -[ldap] -; Sets the maximum number of open links or -1 for unlimited. -ldap.max_links = -1 - -[dba] -;dba.default_handler= - -[opcache] -; Determines if Zend OPCache is enabled - -; Determines if Zend OPCache is enabled for the CLI version of PHP -opcache.enable_cli=On - -; The OPcache shared memory storage size. -;opcache.memory_consumption=128 - -; The amount of memory for interned strings in Mbytes. -;opcache.interned_strings_buffer=8 - -; The maximum number of keys (scripts) in the OPcache hash table. -; Only numbers between 200 and 1000000 are allowed. -;opcache.max_accelerated_files=10000 - -; The maximum percentage of "wasted" memory until a restart is scheduled. -;opcache.max_wasted_percentage=5 - -; When this directive is enabled, the OPcache appends the current working -; directory to the script key, thus eliminating possible collisions between -; files with the same name (basename). Disabling the directive improves -; performance, but may break existing applications. -;opcache.use_cwd=1 - -; When disabled, you must reset the OPcache manually or restart the -; webserver for changes to the filesystem to take effect. -;opcache.validate_timestamps=1 - -; How often (in seconds) to check file timestamps for changes to the shared -; memory storage allocation. ("1" means validate once per second, but only -; once per request. "0" means always validate) -;opcache.revalidate_freq=2 - -; Enables or disables file search in include_path optimization -;opcache.revalidate_path=0 - -; If disabled, all PHPDoc comments are dropped from the code to reduce the -; size of the optimized code. -;opcache.save_comments=1 - -; Allow file existence override (file_exists, etc.) performance feature. -;opcache.enable_file_override=0 - -; A bitmask, where each bit enables or disables the appropriate OPcache -; passes -;opcache.optimization_level=0x7FFFBFFF - -;opcache.dups_fix=0 - -; The location of the OPcache blacklist file (wildcards allowed). -; Each OPcache blacklist file is a text file that holds the names of files -; that should not be accelerated. The file format is to add each filename -; to a new line. The filename may be a full path or just a file prefix -; (i.e., /var/www/x blacklists all the files and directories in /var/www -; that start with 'x'). Line starting with a ; are ignored (comments). -;opcache.blacklist_filename= - -; Allows exclusion of large files from being cached. By default all files -; are cached. -;opcache.max_file_size=0 - -; Check the cache checksum each N requests. -; The default value of "0" means that the checks are disabled. -;opcache.consistency_checks=0 - -; How long to wait (in seconds) for a scheduled restart to begin if the cache -; is not being accessed. -;opcache.force_restart_timeout=180 - -; OPcache error_log file name. Empty string assumes "stderr". - -; All OPcache errors go to the Web server log. -; By default, only fatal errors (level 0) or errors (level 1) are logged. -; You can also enable warnings (level 2), info messages (level 3) or -; debug messages (level 4). -;opcache.log_verbosity_level=1 - -; Preferred Shared Memory back-end. Leave empty and let the system decide. -;opcache.preferred_memory_model= - -; Protect the shared memory from unexpected writing during script execution. -; Useful for internal debugging only. -;opcache.protect_memory=0 - -; Allows calling OPcache API functions only from PHP scripts which path is -; started from specified string. The default "" means no restriction -;opcache.restrict_api= - -; Mapping base of shared memory segments (for Windows only). All the PHP -; processes have to map shared memory into the same address space. This -; directive allows to manually fix the "Unable to reattach to base address" -; errors. -;opcache.mmap_base= - -; Enables and sets the second level cache directory. -; It should improve performance when SHM memory is full, at server restart or -; SHM reset. The default "" disables file based caching. -;opcache.file_cache= - -; Enables or disables opcode caching in shared memory. -;opcache.file_cache_only=0 - -; Enables or disables checksum validation when script loaded from file cache. -;opcache.file_cache_consistency_checks=1 - -; Implies opcache.file_cache_only=1 for a certain process that failed to -; reattach to the shared memory (for Windows only). Explicitly enabled file -; cache is required. -;opcache.file_cache_fallback=1 - -; Enables or disables copying of PHP code (text segment) into HUGE PAGES. -; This should improve performance, but requires appropriate OS configuration. -;opcache.huge_code_pages=1 - -; Validate cached file permissions. -;opcache.validate_permission=0 - -; Prevent name collisions in chroot'ed environment. -;opcache.validate_root=0 - -; If specified, it produces opcode dumps for debugging different stages of -; optimizations. -;opcache.opt_debug_level=0 - -[openssl] -; The location of a Certificate Authority (CA) file on the local filesystem -; to use when verifying the identity of SSL/TLS peers. Most users should -; not specify a value for this directive as PHP will attempt to use the -; OS-managed cert stores in its absence. If specified, this value may still -; be overridden on a per-stream basis via the "cafile" SSL stream context -; option. - -; If openssl.cafile is not specified or if the CA file is not found, the -; directory pointed to by openssl.capath is searched for a suitable -; certificate. This value must be a correctly hashed certificate directory. -; Most users should not specify a value for this directive as PHP will -; attempt to use the OS-managed cert stores in its absence. If specified, -; this value may still be overridden on a per-stream basis via the "capath" -; SSL stream context option. -;openssl.capath= - -; Local Variables: -; tab-width: 4 -; End: - -[xdebug] -xdebug.client_port="9000" -xdebug.var_display_max_depth = -1 -xdebug.var_display_max_children = -1 -xdebug.var_display_max_data = -1 - - diff --git a/srv/app.prod-cli/php.ini b/srv/app.prod-cli/php.ini deleted file mode 100644 index f9974b0..0000000 --- a/srv/app.prod-cli/php.ini +++ /dev/null @@ -1,1923 +0,0 @@ -[PHP] -;;;;;;;;;;;;;;;;;;;;;;;; -; COMMON APP SETTINGS ; -;;;;;;;;;;;;;;;;;;;;;;;; -; Update the paths to the absolute paths appropriate locations for this server -; Generate a new GUID for each app - use same the guid for each environment -; - GUID generator https://www.guidgenerator.com/ - do not use hyphens -[app] -session.name = {app_guid} - -[app_logging] -error_log = "{prod_app_absolute_path}\logs\error-cli.log" -opcache.error_log = "{prod_app_absolute_path}\logs\opcache-cli.log" -mail.log = "{prod_app_absolute_path}\logs\mail-cli.log" - -[app_temp] -session.save_path = "{prod_app_absolute_path}\srv\tmp\sessions\" -sys_temp_dir = "{prod_app_absolute_path}\srv\tmp\tmp\" -upload_tmp_dir = "{prod_app_absolute_path}\srv\tmp\files\" -soap.wsdl_cache_dir = "{prod_app_absolute_path}\srv\tmp\soaptmp\" -opcache.file_cache = "{prod_app_absolute_path}\srv\tmp\opcache\" - -[app_mail] -SMTP = {app_smtp_server} -smtp_port = 25 -sendmail_from = {app_smtp_sendmail_from_address} - -[app_limits] -upload_max_filesize = 1024M -max_execution_time = 0 -memory_limit = 1024M -opcache.enable = Off - -[app_ssl] -curl.cainfo = "{prod_app_ssl_path}\cacert.pem" -openssl.cafile = "{prod_app_ssl_path}\cacert.pem" - -[app_extensions] -extension_dir = "{prod_app_php_path}\ext\" -extension = curl -extension = fileinfo -extension = openssl -;extension=pdo_sqlsrv -extension = mongodb - -;;;;;;;;;;;;;;;;;;; -; About php.ini ; -;;;;;;;;;;;;;;;;;;; -; PHP's initialization file, generally called php.ini, is responsible for -; configuring many of the aspects of PHP's behavior. - -; PHP attempts to find and load this configuration from a number of locations. -; The following is a summary of its search order: -; 1. SAPI module specific location. -; 2. The PHPRC environment variable. (As of PHP 5.2.0) -; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0) -; 4. Current working directory (except CLI) -; 5. The web server's directory (for SAPI modules), or directory of PHP -; (otherwise in Windows) -; 6. The directory from the --with-config-file-path compile time option, or the -; Windows directory (usually C:\windows) -; See the PHP docs for more specific information. -; http://php.net/configuration.file - -; The syntax of the file is extremely simple. Whitespace and lines -; beginning with a semicolon are silently ignored (as you probably guessed). -; Section headers (e.g. [Foo]) are also silently ignored, even though -; they might mean something in the future. - -; Directives following the section heading [PATH=/www/mysite] only -; apply to PHP files in the /www/mysite directory. Directives -; following the section heading [HOST=www.example.com] only apply to -; PHP files served from www.example.com. Directives set in these -; special sections cannot be overridden by user-defined INI files or -; at runtime. Currently, [PATH=] and [HOST=] sections only work under -; CGI/FastCGI. -; http://php.net/ini.sections - -; Directives are specified using the following syntax: -; directive = value -; Directive names are *case sensitive* - foo=bar is different from FOO=bar. -; Directives are variables used to configure PHP or PHP extensions. -; There is no name validation. If PHP can't find an expected -; directive because it is not set or is mistyped, a default value will be used. - -; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one -; of the INI constants (On, Off, True, False, Yes, No and None) or an expression -; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a -; previously set variable or directive (e.g. ${foo}) - -; Expressions in the INI file are limited to bitwise operators and parentheses: -; | bitwise OR -; ^ bitwise XOR -; & bitwise AND -; ~ bitwise NOT -; ! boolean NOT - -; Boolean flags can be turned on using the values 1, On, True or Yes. -; They can be turned off using the values 0, Off, False or No. - -; An empty string can be denoted by simply not writing anything after the equal -; sign, or by using the None keyword: - -; foo = ; sets foo to an empty string -; foo = None ; sets foo to an empty string -; foo = "None" ; sets foo to the string 'None' - -; If you use constants in your value, and these constants belong to a -; dynamically loaded extension (either a PHP extension or a Zend extension), -; you may only use these constants *after* the line that loads the extension. - -;;;;;;;;;;;;;;;;;;; -; About this file ; -;;;;;;;;;;;;;;;;;;; -; PHP comes packaged with two INI files. One that is recommended to be used -; in production environments and one that is recommended to be used in -; development environments. - -; php.ini-production contains settings which hold security, performance and -; best practices at its core. But please be aware, these settings may break -; compatibility with older or less security conscience applications. We -; recommending using the production ini in production and testing environments. - -; php.ini-development is very similar to its production variant, except it is -; much more verbose when it comes to errors. We recommend using the -; development version only in development environments, as errors shown to -; application users can inadvertently leak otherwise secure information. - -; This is the php.ini-production INI file. - -;;;;;;;;;;;;;;;;;;; -; Quick Reference ; -;;;;;;;;;;;;;;;;;;; - -; The following are all the settings which are different in either the production -; or development versions of the INIs with respect to PHP's default behavior. -; Please see the actual settings later in the document for more details as to why -; we recommend these changes in PHP's behavior. - -; display_errors -; Default Value: On -; Development Value: On -; Production Value: Off - -; display_startup_errors -; Default Value: On -; Development Value: On -; Production Value: Off - -; error_reporting -; Default Value: E_ALL -; Development Value: E_ALL -; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT - -; log_errors -; Default Value: Off -; Development Value: On -; Production Value: On - -; max_input_time -; Default Value: -1 (Unlimited) -; Development Value: 60 (60 seconds) -; Production Value: 60 (60 seconds) - -; output_buffering -; Default Value: Off -; Development Value: 4096 -; Production Value: 4096 - -; register_argc_argv -; Default Value: On -; Development Value: Off -; Production Value: Off - -; request_order -; Default Value: None -; Development Value: "GP" -; Production Value: "GP" - -; session.gc_divisor -; Default Value: 100 -; Development Value: 1000 -; Production Value: 1000 - -; session.sid_bits_per_character -; Default Value: 4 -; Development Value: 5 -; Production Value: 5 - -; short_open_tag -; Default Value: On -; Development Value: Off -; Production Value: Off - -; variables_order -; Default Value: "EGPCS" -; Development Value: "GPCS" -; Production Value: "GPCS" - -; zend.exception_ignore_args -; Default Value: Off -; Development Value: Off -; Production Value: On - -; zend.exception_string_param_max_len -; Default Value: 15 -; Development Value: 15 -; Production Value: 0 - -;;;;;;;;;;;;;;;;;;;; -; php.ini Options ; -;;;;;;;;;;;;;;;;;;;; -; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" -;user_ini.filename = ".user.ini" - -; To disable this feature set this option to an empty value -;user_ini.filename = - -; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) -;user_ini.cache_ttl = 300 - -;;;;;;;;;;;;;;;;;;;; -; Language Options ; -;;;;;;;;;;;;;;;;;;;; - -; Enable the PHP scripting language engine under Apache. -; http://php.net/engine -engine = On - -; This directive determines whether or not PHP will recognize code between -; tags as PHP source which should be processed as such. It is -; generally recommended that should be used and that this feature -; should be disabled, as enabling it may result in issues when generating XML -; documents, however this remains supported for backward compatibility reasons. -; Note that this directive does not control the would work. -; http://php.net/syntax-highlighting -;highlight.string = #DD0000 -;highlight.comment = #FF9900 -;highlight.keyword = #007700 -;highlight.default = #0000BB -;highlight.html = #000000 - -; If enabled, the request will be allowed to complete even if the user aborts -; the request. Consider enabling it if executing long requests, which may end up -; being interrupted by the user or a browser timing out. PHP's default behavior -; is to disable this feature. -; http://php.net/ignore-user-abort -;ignore_user_abort = On - -; Determines the size of the realpath cache to be used by PHP. This value should -; be increased on systems where PHP opens many files to reflect the quantity of -; the file operations performed. -; Note: if open_basedir is set, the cache is disabled -; http://php.net/realpath-cache-size -;realpath_cache_size = 4096k - -; Duration of time, in seconds for which to cache realpath information for a given -; file or directory. For systems with rarely changing files, consider increasing this -; value. -; http://php.net/realpath-cache-ttl -;realpath_cache_ttl = 120 - -; Enables or disables the circular reference collector. -; http://php.net/zend.enable-gc -zend.enable_gc = On - -; If enabled, scripts may be written in encodings that are incompatible with -; the scanner. CP936, Big5, CP949 and Shift_JIS are the examples of such -; encodings. To use this feature, mbstring extension must be enabled. -;zend.multibyte = Off - -; Allows to set the default encoding for the scripts. This value will be used -; unless "declare(encoding=...)" directive appears at the top of the script. -; Only affects if zend.multibyte is set. -;zend.script_encoding = - -; Allows to include or exclude arguments from stack traces generated for exceptions. -; In production, it is recommended to turn this setting on to prohibit the output -; of sensitive information in stack traces -; Default Value: Off -; Development Value: Off -; Production Value: On -zend.exception_ignore_args = On - -; Allows setting the maximum string length in an argument of a stringified stack trace -; to a value between 0 and 1000000. -; This has no effect when zend.exception_ignore_args is enabled. -; Default Value: 15 -; Development Value: 15 -; Production Value: 0 -; In production, it is recommended to set this to 0 to reduce the output -; of sensitive information in stack traces. -zend.exception_string_param_max_len = 0 - -;;;;;;;;;;;;;;;;; -; Miscellaneous ; -;;;;;;;;;;;;;;;;; - -; Decides whether PHP may expose the fact that it is installed on the server -; (e.g. by adding its signature to the Web server header). It is no security -; threat in any way, but it makes it possible to determine whether you use PHP -; on your server or not. -; http://php.net/expose-php -expose_php = Off - -;;;;;;;;;;;;;;;;;;; -; Resource Limits ; -;;;;;;;;;;;;;;;;;;; - -; Maximum execution time of each script, in seconds -; http://php.net/max-execution-time -; Note: This directive is hardcoded to 0 for the CLI SAPI -; SEE [app_limits] - -; Maximum amount of time each script may spend parsing request data. It's a good -; idea to limit this time on productions servers in order to eliminate unexpectedly -; long running scripts. -; Note: This directive is hardcoded to -1 for the CLI SAPI -; Default Value: -1 (Unlimited) -; Development Value: 60 (60 seconds) -; Production Value: 60 (60 seconds) -; http://php.net/max-input-time -max_input_time = 60 - -; Maximum input variable nesting level -; http://php.net/max-input-nesting-level -;max_input_nesting_level = 64 - -; How many GET/POST/COOKIE input variables may be accepted -;max_input_vars = 1000 - -; Maximum amount of memory a script may consume -; http://php.net/memory-limit -; SEE [app_limits] - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Error handling and logging ; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -; This directive informs PHP of which errors, warnings and notices you would like -; it to take action for. The recommended way of setting values for this -; directive is through the use of the error level constants and bitwise -; operators. The error level constants are below here for convenience as well as -; some common settings and their meanings. -; By default, PHP is set to take action on all errors, notices and warnings EXCEPT -; those related to E_NOTICE and E_STRICT, which together cover best practices and -; recommended coding standards in PHP. For performance reasons, this is the -; recommend error reporting setting. Your production server shouldn't be wasting -; resources complaining about best practices and coding standards. That's what -; development servers and development settings are for. -; Note: The php.ini-development file has this setting as E_ALL. This -; means it pretty much reports everything which is exactly what you want during -; development and early testing. -; -; Error Level Constants: -; E_ALL - All errors and warnings (includes E_STRICT as of PHP 5.4.0) -; E_ERROR - fatal run-time errors -; E_RECOVERABLE_ERROR - almost fatal run-time errors -; E_WARNING - run-time warnings (non-fatal errors) -; E_PARSE - compile-time parse errors -; E_NOTICE - run-time notices (these are warnings which often result -; from a bug in your code, but it's possible that it was -; intentional (e.g., using an uninitialized variable and -; relying on the fact it is automatically initialized to an -; empty string) -; E_STRICT - run-time notices, enable to have PHP suggest changes -; to your code which will ensure the best interoperability -; and forward compatibility of your code -; E_CORE_ERROR - fatal errors that occur during PHP's initial startup -; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's -; initial startup -; E_COMPILE_ERROR - fatal compile-time errors -; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) -; E_USER_ERROR - user-generated error message -; E_USER_WARNING - user-generated warning message -; E_USER_NOTICE - user-generated notice message -; E_DEPRECATED - warn about code that will not work in future versions -; of PHP -; E_USER_DEPRECATED - user-generated deprecation warnings -; -; Common Values: -; E_ALL (Show all errors, warnings and notices including coding standards.) -; E_ALL & ~E_NOTICE (Show all errors, except for notices) -; E_ALL & ~E_NOTICE & ~E_STRICT (Show all errors, except for notices and coding standards warnings.) -; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors) -; Default Value: E_ALL -; Development Value: E_ALL -; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT -; http://php.net/error-reporting -error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT - -; This directive controls whether or not and where PHP will output errors, -; notices and warnings too. Error output is very useful during development, but -; it could be very dangerous in production environments. Depending on the code -; which is triggering the error, sensitive information could potentially leak -; out of your application such as database usernames and passwords or worse. -; For production environments, we recommend logging errors rather than -; sending them to STDOUT. -; Possible Values: -; Off = Do not display any errors -; stderr = Display errors to STDERR (affects only CGI/CLI binaries!) -; On or stdout = Display errors to STDOUT -; Default Value: On -; Development Value: On -; Production Value: Off -; http://php.net/display-errors -display_errors = Off - -; The display of errors which occur during PHP's startup sequence are handled -; separately from display_errors. We strongly recommend you set this to 'off' -; for production servers to avoid leaking configuration details. -; Default Value: On -; Development Value: On -; Production Value: Off -; http://php.net/display-startup-errors -display_startup_errors = Off - -; Besides displaying errors, PHP can also log errors to locations such as a -; server-specific log, STDERR, or a location specified by the error_log -; directive found below. While errors should not be displayed on productions -; servers they should still be monitored and logging is a great way to do that. -; Default Value: Off -; Development Value: On -; Production Value: On -; http://php.net/log-errors -log_errors = On - -; Set maximum length of log_errors. In error_log information about the source is -; added. The default is 1024 and 0 allows to not apply any maximum length at all. -; http://php.net/log-errors-max-len -log_errors_max_len = 1024 - -; Do not log repeated messages. Repeated errors must occur in same file on same -; line unless ignore_repeated_source is set true. -; http://php.net/ignore-repeated-errors -ignore_repeated_errors = Off - -; Ignore source of message when ignoring repeated messages. When this setting -; is On you will not log errors with repeated messages from different files or -; source lines. -; http://php.net/ignore-repeated-source -ignore_repeated_source = Off - -; If this parameter is set to Off, then memory leaks will not be shown (on -; stdout or in the log). This is only effective in a debug compile, and if -; error reporting includes E_WARNING in the allowed list -; http://php.net/report-memleaks -report_memleaks = On - -; This setting is off by default. -;report_zend_debug = 0 - -; Turn off normal error reporting and emit XML-RPC error XML -; http://php.net/xmlrpc-errors -;xmlrpc_errors = 0 - -; An XML-RPC faultCode -;xmlrpc_error_number = 0 - -; When PHP displays or logs an error, it has the capability of formatting the -; error message as HTML for easier reading. This directive controls whether -; the error message is formatted as HTML or not. -; Note: This directive is hardcoded to Off for the CLI SAPI -; http://php.net/html-errors -;html_errors = On - -; If html_errors is set to On *and* docref_root is not empty, then PHP -; produces clickable error messages that direct to a page describing the error -; or function causing the error in detail. -; You can download a copy of the PHP manual from http://php.net/docs -; and change docref_root to the base URL of your local copy including the -; leading '/'. You must also specify the file extension being used including -; the dot. PHP's default behavior is to leave these settings empty, in which -; case no links to documentation are generated. -; Note: Never use this feature for production boxes. -; http://php.net/docref-root -; Examples -;docref_root = "/phpmanual/" - -; http://php.net/docref-ext -;docref_ext = .html - -; String to output before an error message. PHP's default behavior is to leave -; this setting blank. -; http://php.net/error-prepend-string -; Example: -;error_prepend_string = "" - -; String to output after an error message. PHP's default behavior is to leave -; this setting blank. -; http://php.net/error-append-string -; Example: -;error_append_string = "" - -; Log errors to specified file. PHP's default behavior is to leave this value -; empty. -; http://php.net/error-log -; Example: -;error_log = php_errors.log -; Log errors to syslog (Event Log on Windows). -;error_log = syslog - -; The syslog ident is a string which is prepended to every message logged -; to syslog. Only used when error_log is set to syslog. -;syslog.ident = php - -; The syslog facility is used to specify what type of program is logging -; the message. Only used when error_log is set to syslog. -;syslog.facility = user - -; Set this to disable filtering control characters (the default). -; Some loggers only accept NVT-ASCII, others accept anything that's not -; control characters. If your logger accepts everything, then no filtering -; is needed at all. -; Allowed values are: -; ascii (all printable ASCII characters and NL) -; no-ctrl (all characters except control characters) -; all (all characters) -; raw (like "all", but messages are not split at newlines) -; http://php.net/syslog.filter -;syslog.filter = ascii - -;windows.show_crt_warning -; Default value: 0 -; Development value: 0 -; Production value: 0 - -;;;;;;;;;;;;;;;;; -; Data Handling ; -;;;;;;;;;;;;;;;;; - -; The separator used in PHP generated URLs to separate arguments. -; PHP's default setting is "&". -; http://php.net/arg-separator.output -; Example: -;arg_separator.output = "&" - -; List of separator(s) used by PHP to parse input URLs into variables. -; PHP's default setting is "&". -; NOTE: Every character in this directive is considered as separator! -; http://php.net/arg-separator.input -; Example: -;arg_separator.input = ";&" - -; This directive determines which super global arrays are registered when PHP -; starts up. G,P,C,E & S are abbreviations for the following respective super -; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty -; paid for the registration of these arrays and because ENV is not as commonly -; used as the others, ENV is not recommended on productions servers. You -; can still get access to the environment variables through getenv() should you -; need to. -; Default Value: "EGPCS" -; Development Value: "GPCS" -; Production Value: "GPCS"; -; http://php.net/variables-order -variables_order = "GPCS" - -; This directive determines which super global data (G,P & C) should be -; registered into the super global array REQUEST. If so, it also determines -; the order in which that data is registered. The values for this directive -; are specified in the same manner as the variables_order directive, -; EXCEPT one. Leaving this value empty will cause PHP to use the value set -; in the variables_order directive. It does not mean it will leave the super -; globals array REQUEST empty. -; Default Value: None -; Development Value: "GP" -; Production Value: "GP" -; http://php.net/request-order -request_order = "GP" - -; This directive determines whether PHP registers $argv & $argc each time it -; runs. $argv contains an array of all the arguments passed to PHP when a script -; is invoked. $argc contains an integer representing the number of arguments -; that were passed when the script was invoked. These arrays are extremely -; useful when running scripts from the command line. When this directive is -; enabled, registering these variables consumes CPU cycles and memory each time -; a script is executed. For performance reasons, this feature should be disabled -; on production servers. -; Note: This directive is hardcoded to On for the CLI SAPI -; Default Value: On -; Development Value: Off -; Production Value: Off -; http://php.net/register-argc-argv -register_argc_argv = Off - -; When enabled, the ENV, REQUEST and SERVER variables are created when they're -; first used (Just In Time) instead of when the script starts. If these -; variables are not used within a script, having this directive on will result -; in a performance gain. The PHP directive register_argc_argv must be disabled -; for this directive to have any effect. -; http://php.net/auto-globals-jit -auto_globals_jit = On - -; Whether PHP will read the POST data. -; This option is enabled by default. -; Most likely, you won't want to disable this option globally. It causes $_POST -; and $_FILES to always be empty; the only way you will be able to read the -; POST data will be through the php://input stream wrapper. This can be useful -; to proxy requests or to process the POST data in a memory efficient fashion. -; http://php.net/enable-post-data-reading -;enable_post_data_reading = Off - -; Maximum size of POST data that PHP will accept. -; Its value may be 0 to disable the limit. It is ignored if POST data reading -; is disabled through enable_post_data_reading. -; http://php.net/post-max-size -post_max_size = 8M - -; Automatically add files before PHP document. -; http://php.net/auto-prepend-file -auto_prepend_file = - -; Automatically add files after PHP document. -; http://php.net/auto-append-file -auto_append_file = - -; By default, PHP will output a media type using the Content-Type header. To -; disable this, simply set it to be empty. -; -; PHP's built-in default media type is set to text/html. -; http://php.net/default-mimetype -default_mimetype = "text/html" - -; PHP's default character set is set to UTF-8. -; http://php.net/default-charset -default_charset = "UTF-8" - -; PHP internal character encoding is set to empty. -; If empty, default_charset is used. -; http://php.net/internal-encoding -;internal_encoding = - -; PHP input character encoding is set to empty. -; If empty, default_charset is used. -; http://php.net/input-encoding -;input_encoding = - -; PHP output character encoding is set to empty. -; If empty, default_charset is used. -; See also output_buffer. -; http://php.net/output-encoding -;output_encoding = - -;;;;;;;;;;;;;;;;;;;;;;;;; -; Paths and Directories ; -;;;;;;;;;;;;;;;;;;;;;;;;; - -; UNIX: "/path1:/path2" -;include_path = ".:/php/includes" -; -; Windows: "\path1;\path2" -;include_path = ".;c:\php\includes" -; -; PHP's default setting for include_path is ".;/path/to/php/pear" -; http://php.net/include-path - -; The root of the PHP pages, used only if nonempty. -; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root -; if you are running php as a CGI under any web server (other than IIS) -; see documentation for security issues. The alternate is to use the -; cgi.force_redirect configuration below -; http://php.net/doc-root -doc_root = - -; The directory under which PHP opens the script using /~username used only -; if nonempty. -; http://php.net/user-dir -user_dir = - -; Directory in which the loadable extensions (modules) reside. -; http://php.net/extension-dir -;extension_dir = "./" -; On windows: -; see [app_extensions] - -; Directory where the temporary files should be placed. -; Defaults to the system default (see sys_get_temp_dir) -; SEE [app_temp] - -; Whether or not to enable the dl() function. The dl() function does NOT work -; properly in multithreaded servers, such as IIS or Zeus, and is automatically -; disabled on them. -; http://php.net/enable-dl -enable_dl = Off - -; cgi.force_redirect is necessary to provide security running PHP as a CGI under -; most web servers. Left undefined, PHP turns this on by default. You can -; turn it off here AT YOUR OWN RISK -; **You CAN safely turn this off for IIS, in fact, you MUST.** -; http://php.net/cgi.force-redirect -;cgi.force_redirect = 1 - -; if cgi.nph is enabled it will force cgi to always sent Status: 200 with -; every request. PHP's default behavior is to disable this feature. -;cgi.nph = 1 - -; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape -; (iPlanet) web servers, you MAY need to set an environment variable name that PHP -; will look for to know it is OK to continue execution. Setting this variable MAY -; cause security issues, KNOW WHAT YOU ARE DOING FIRST. -; http://php.net/cgi.redirect-status-env -;cgi.redirect_status_env = - -; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's -; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok -; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting -; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting -; of zero causes PHP to behave as before. Default is 1. You should fix your scripts -; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. -; http://php.net/cgi.fix-pathinfo -;cgi.fix_pathinfo=1 - -; if cgi.discard_path is enabled, the PHP CGI binary can safely be placed outside -; of the web tree and people will not be able to circumvent .htaccess security. -;cgi.discard_path=1 - -; FastCGI under IIS supports the ability to impersonate -; security tokens of the calling client. This allows IIS to define the -; security context that the request runs under. mod_fastcgi under Apache -; does not currently support this feature (03/17/2002) -; Set to 1 if running under IIS. Default is zero. -; http://php.net/fastcgi.impersonate -;fastcgi.impersonate = 1 - -; Disable logging through FastCGI connection. PHP's default behavior is to enable -; this feature. -;fastcgi.logging = 0 - -; cgi.rfc2616_headers configuration option tells PHP what type of headers to -; use when sending HTTP response code. If set to 0, PHP sends Status: header that -; is supported by Apache. When this option is set to 1, PHP will send -; RFC2616 compliant header. -; Default is zero. -; http://php.net/cgi.rfc2616-headers -;cgi.rfc2616_headers = 0 - -; cgi.check_shebang_line controls whether CGI PHP checks for line starting with #! -; (shebang) at the top of the running script. This line might be needed if the -; script support running both as stand-alone script and via PHP CGI<. PHP in CGI -; mode skips this line and ignores its content if this directive is turned on. -; http://php.net/cgi.check-shebang-line -;cgi.check_shebang_line=1 - -;;;;;;;;;;;;;;;; -; File Uploads ; -;;;;;;;;;;;;;;;; - -; Whether to allow HTTP file uploads. -; http://php.net/file-uploads -file_uploads = On - -; Temporary directory for HTTP uploaded files (will use system default if not -; specified). -; http://php.net/upload-tmp-dir -; SEE [app_temp] - -; Maximum allowed size for uploaded files. -; http://php.net/upload-max-filesize -; SEE [app_limits] - -; Maximum number of files that can be uploaded via a single request -max_file_uploads = 20 - -;;;;;;;;;;;;;;;;;; -; Fopen wrappers ; -;;;;;;;;;;;;;;;;;; - -; Whether to allow the treatment of URLs (like http:// or ftp://) as files. -; http://php.net/allow-url-fopen -allow_url_fopen = On - -; Whether to allow include/require to open URLs (like http:// or ftp://) as files. -; http://php.net/allow-url-include -allow_url_include = Off - -; Define the anonymous ftp password (your email address). PHP's default setting -; for this is empty. -; http://php.net/from -;from="john@doe.com" - -; Define the User-Agent string. PHP's default setting for this is empty. -; http://php.net/user-agent -;user_agent="PHP" - -; Default timeout for socket based streams (seconds) -; http://php.net/default-socket-timeout -default_socket_timeout = 60 - -; If your scripts have to deal with files from Macintosh systems, -; or you are running on a Mac and need to deal with files from -; unix or win32 systems, setting this flag will cause PHP to -; automatically detect the EOL character in those files so that -; fgets() and file() will work regardless of the source of the file. -; http://php.net/auto-detect-line-endings -;auto_detect_line_endings = Off - -;;;;;;;;;;;;;;;;;;;;;; -; Dynamic Extensions ; -;;;;;;;;;;;;;;;;;;;;;; -; see [app_extensions] - -;;;;;;;;;;;;;;;;;;; -; Module Settings ; -;;;;;;;;;;;;;;;;;;; - -[CLI Server] -; Whether the CLI web server uses ANSI color coding in its terminal output. -cli_server.color = On - -[Date] -; Defines the default timezone used by the date functions -; http://php.net/date.timezone -date.timezone = "America/New_York" - -; http://php.net/date.default-latitude -;date.default_latitude = 31.7667 - -; http://php.net/date.default-longitude -;date.default_longitude = 35.2333 - -; http://php.net/date.sunrise-zenith -;date.sunrise_zenith = 90.833333 - -; http://php.net/date.sunset-zenith -;date.sunset_zenith = 90.833333 - -[filter] -; http://php.net/filter.default -;filter.default = unsafe_raw - -; http://php.net/filter.default-flags -;filter.default_flags = - -[iconv] -; Use of this INI entry is deprecated, use global input_encoding instead. -; If empty, default_charset or input_encoding or iconv.input_encoding is used. -; The precedence is: default_charset < input_encoding < iconv.input_encoding -;iconv.input_encoding = - -; Use of this INI entry is deprecated, use global internal_encoding instead. -; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. -; The precedence is: default_charset < internal_encoding < iconv.internal_encoding -;iconv.internal_encoding = - -; Use of this INI entry is deprecated, use global output_encoding instead. -; If empty, default_charset or output_encoding or iconv.output_encoding is used. -; The precedence is: default_charset < output_encoding < iconv.output_encoding -; To use an output encoding conversion, iconv's output handler must be set -; otherwise output encoding conversion cannot be performed. -;iconv.output_encoding = - -[imap] -; rsh/ssh logins are disabled by default. Use this INI entry if you want to -; enable them. Note that the IMAP library does not filter mailbox names before -; passing them to rsh/ssh command, thus passing untrusted data to this function -; with rsh/ssh enabled is insecure. -;imap.enable_insecure_rsh=0 - -[intl] -;intl.default_locale = -; This directive allows you to produce PHP errors when some error -; happens within intl functions. The value is the level of the error produced. -; Default is 0, which does not produce any errors. -;intl.error_level = E_WARNING -;intl.use_exceptions = 0 - -[sqlite3] -; Directory pointing to SQLite3 extensions -; http://php.net/sqlite3.extension-dir -;sqlite3.extension_dir = - -; SQLite defensive mode flag (only available from SQLite 3.26+) -; When the defensive flag is enabled, language features that allow ordinary -; SQL to deliberately corrupt the database file are disabled. This forbids -; writing directly to the schema, shadow tables (eg. FTS data tables), or -; the sqlite_dbpage virtual table. -; https://www.sqlite.org/c3ref/c_dbconfig_defensive.html -; (for older SQLite versions, this flag has no use) -;sqlite3.defensive = 1 - -[Pcre] -; PCRE library backtracking limit. -; http://php.net/pcre.backtrack-limit -;pcre.backtrack_limit=100000 - -; PCRE library recursion limit. -; Please note that if you set this value to a high number you may consume all -; the available process stack and eventually crash PHP (due to reaching the -; stack size limit imposed by the Operating System). -; http://php.net/pcre.recursion-limit -;pcre.recursion_limit=100000 - -; Enables or disables JIT compilation of patterns. This requires the PCRE -; library to be compiled with JIT support. -;pcre.jit=1 - -[Pdo] -; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" -; http://php.net/pdo-odbc.connection-pooling -;pdo_odbc.connection_pooling=strict - -[Pdo_mysql] -; Default socket name for local MySQL connects. If empty, uses the built-in -; MySQL defaults. -pdo_mysql.default_socket = - -[Phar] -; http://php.net/phar.readonly -;phar.readonly = On - -; http://php.net/phar.require-hash -;phar.require_hash = On - -;phar.cache_list = - -[mail function] -; For Win32 only. -; http://php.net/smtp -; SEE [app_mail] - -; For Win32 only. -; http://php.net/sendmail-from -; SEE [app_mail] - - -; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). -; http://php.net/sendmail-path -;sendmail_path = - -; Force the addition of the specified parameters to be passed as extra parameters -; to the sendmail binary. These parameters will always replace the value of -; the 5th parameter to mail(). -;mail.force_extra_parameters = - -; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename -mail.add_x_header = Off - -; The path to a log file that will log all mail() calls. Log entries include -; the full path of the script, line number, To address and headers. -;mail.log = -; Log mail to syslog (Event Log on Windows). -; SEE [app_logging] - - -[ODBC] -; http://php.net/odbc.default-db -;odbc.default_db = Not yet implemented - -; http://php.net/odbc.default-user -;odbc.default_user = Not yet implemented - -; http://php.net/odbc.default-pw -;odbc.default_pw = Not yet implemented - -; Controls the ODBC cursor model. -; Default: SQL_CURSOR_STATIC (default). -;odbc.default_cursortype - -; Allow or prevent persistent links. -; http://php.net/odbc.allow-persistent -odbc.allow_persistent = On - -; Check that a connection is still valid before reuse. -; http://php.net/odbc.check-persistent -odbc.check_persistent = On - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/odbc.max-persistent -odbc.max_persistent = -1 - -; Maximum number of links (persistent + non-persistent). -1 means no limit. -; http://php.net/odbc.max-links -odbc.max_links = -1 - -; Handling of LONG fields. Returns number of bytes to variables. 0 means -; passthru. -; http://php.net/odbc.defaultlrl -odbc.defaultlrl = 4096 - -; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. -; See the documentation on odbc_binmode and odbc_longreadlen for an explanation -; of odbc.defaultlrl and odbc.defaultbinmode -; http://php.net/odbc.defaultbinmode -odbc.defaultbinmode = 1 - -[MySQLi] - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/mysqli.max-persistent -mysqli.max_persistent = -1 - -; Allow accessing, from PHP's perspective, local files with LOAD DATA statements -; http://php.net/mysqli.allow_local_infile -;mysqli.allow_local_infile = On - -; Allow or prevent persistent links. -; http://php.net/mysqli.allow-persistent -mysqli.allow_persistent = On - -; Maximum number of links. -1 means no limit. -; http://php.net/mysqli.max-links -mysqli.max_links = -1 - -; Default port number for mysqli_connect(). If unset, mysqli_connect() will use -; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the -; compile-time value defined MYSQL_PORT (in that order). Win32 will only look -; at MYSQL_PORT. -; http://php.net/mysqli.default-port -mysqli.default_port = 3306 - -; Default socket name for local MySQL connects. If empty, uses the built-in -; MySQL defaults. -; http://php.net/mysqli.default-socket -mysqli.default_socket = - -; Default host for mysqli_connect() (doesn't apply in safe mode). -; http://php.net/mysqli.default-host -mysqli.default_host = - -; Default user for mysqli_connect() (doesn't apply in safe mode). -; http://php.net/mysqli.default-user -mysqli.default_user = - -; Default password for mysqli_connect() (doesn't apply in safe mode). -; Note that this is generally a *bad* idea to store passwords in this file. -; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") -; and reveal this password! And of course, any users with read access to this -; file will be able to reveal the password as well. -; http://php.net/mysqli.default-pw -mysqli.default_pw = - -; Allow or prevent reconnect -mysqli.reconnect = Off - -[mysqlnd] -; Enable / Disable collection of general statistics by mysqlnd which can be -; used to tune and monitor MySQL operations. -mysqlnd.collect_statistics = On - -; Enable / Disable collection of memory usage statistics by mysqlnd which can be -; used to tune and monitor MySQL operations. -mysqlnd.collect_memory_statistics = Off - -; Records communication from all extensions using mysqlnd to the specified log -; file. -; http://php.net/mysqlnd.debug -;mysqlnd.debug = - -; Defines which queries will be logged. -;mysqlnd.log_mask = 0 - -; Default size of the mysqlnd memory pool, which is used by result sets. -;mysqlnd.mempool_default_size = 16000 - -; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. -;mysqlnd.net_cmd_buffer_size = 2048 - -; Size of a pre-allocated buffer used for reading data sent by the server in -; bytes. -;mysqlnd.net_read_buffer_size = 32768 - -; Timeout for network requests in seconds. -;mysqlnd.net_read_timeout = 31536000 - -; SHA-256 Authentication Plugin related. File with the MySQL server public RSA -; key. -;mysqlnd.sha256_server_public_key = - -[OCI8] - -; Connection: Enables privileged connections using external -; credentials (OCI_SYSOPER, OCI_SYSDBA) -; http://php.net/oci8.privileged-connect -;oci8.privileged_connect = Off - -; Connection: The maximum number of persistent OCI8 connections per -; process. Using -1 means no limit. -; http://php.net/oci8.max-persistent -;oci8.max_persistent = -1 - -; Connection: The maximum number of seconds a process is allowed to -; maintain an idle persistent connection. Using -1 means idle -; persistent connections will be maintained forever. -; http://php.net/oci8.persistent-timeout -;oci8.persistent_timeout = -1 - -; Connection: The number of seconds that must pass before issuing a -; ping during oci_pconnect() to check the connection validity. When -; set to 0, each oci_pconnect() will cause a ping. Using -1 disables -; pings completely. -; http://php.net/oci8.ping-interval -;oci8.ping_interval = 60 - -; Connection: Set this to a user chosen connection class to be used -; for all pooled server requests with Oracle 11g Database Resident -; Connection Pooling (DRCP). To use DRCP, this value should be set to -; the same string for all web servers running the same application, -; the database pool must be configured, and the connection string must -; specify to use a pooled server. -;oci8.connection_class = - -; High Availability: Using On lets PHP receive Fast Application -; Notification (FAN) events generated when a database node fails. The -; database must also be configured to post FAN events. -;oci8.events = Off - -; Tuning: This option enables statement caching, and specifies how -; many statements to cache. Using 0 disables statement caching. -; http://php.net/oci8.statement-cache-size -;oci8.statement_cache_size = 20 - -; Tuning: Enables statement prefetching and sets the default number of -; rows that will be fetched automatically after statement execution. -; http://php.net/oci8.default-prefetch -;oci8.default_prefetch = 100 - -; Compatibility. Using On means oci_close() will not close -; oci_connect() and oci_new_connect() connections. -; http://php.net/oci8.old-oci-close-semantics -;oci8.old_oci_close_semantics = Off - -[PostgreSQL] -; Allow or prevent persistent links. -; http://php.net/pgsql.allow-persistent -pgsql.allow_persistent = On - -; Detect broken persistent links always with pg_pconnect(). -; Auto reset feature requires a little overheads. -; http://php.net/pgsql.auto-reset-persistent -pgsql.auto_reset_persistent = Off - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/pgsql.max-persistent -pgsql.max_persistent = -1 - -; Maximum number of links (persistent+non persistent). -1 means no limit. -; http://php.net/pgsql.max-links -pgsql.max_links = -1 - -; Ignore PostgreSQL backends Notice message or not. -; Notice message logging require a little overheads. -; http://php.net/pgsql.ignore-notice -pgsql.ignore_notice = 0 - -; Log PostgreSQL backends Notice message or not. -; Unless pgsql.ignore_notice=0, module cannot log notice message. -; http://php.net/pgsql.log-notice -pgsql.log_notice = 0 - -[bcmath] -; Number of decimal digits for all bcmath functions. -; http://php.net/bcmath.scale -bcmath.scale = 0 - -[browscap] -; http://php.net/browscap -;browscap = extra/browscap.ini - -[Session] -; Handler used to store/retrieve data. -; http://php.net/session.save-handler -session.save_handler = files - -; Argument passed to save_handler. In the case of files, this is the path -; where data files are stored. Note: Windows users have to change this -; variable in order to use PHP's session functions. -; -; The path can be defined as: -; -; session.save_path = "N;/path" -; -; where N is an integer. Instead of storing all the session files in -; /path, what this will do is use subdirectories N-levels deep, and -; store the session data in those directories. This is useful if -; your OS has problems with many files in one directory, and is -; a more efficient layout for servers that handle many sessions. -; -; NOTE 1: PHP will not create this directory structure automatically. -; You can use the script in the ext/session dir for that purpose. -; NOTE 2: See the section on garbage collection below if you choose to -; use subdirectories for session storage -; -; The file storage module creates files using mode 600 by default. -; You can change that by using -; -; session.save_path = "N;MODE;/path" -; -; where MODE is the octal representation of the mode. Note that this -; does not overwrite the process's umask. -; http://php.net/session.save-path -; SEE [app_temp] - -; Whether to use strict session mode. -; Strict session mode does not accept an uninitialized session ID, and -; regenerates the session ID if the browser sends an uninitialized session ID. -; Strict mode protects applications from session fixation via a session adoption -; vulnerability. It is disabled by default for maximum compatibility, but -; enabling it is encouraged. -; https://wiki.php.net/rfc/strict_sessions -session.use_strict_mode = 0 - -; Whether to use cookies. -; http://php.net/session.use-cookies -session.use_cookies = 1 - -; http://php.net/session.cookie-secure -session.cookie_secure = 1 - -; This option forces PHP to fetch and use a cookie for storing and maintaining -; the session id. We encourage this operation as it's very helpful in combating -; session hijacking when not specifying and managing your own session id. It is -; not the be-all and end-all of session hijacking defense, but it's a good start. -; http://php.net/session.use-only-cookies -session.use_only_cookies = 1 - -; Name of the session (used as cookie name). - -; Initialize session on request startup. -; http://php.net/session.auto-start -session.auto_start = 0 - -; Lifetime in seconds of cookie or, if 0, until browser is restarted. -; http://php.net/session.cookie-lifetime -session.cookie_lifetime = 0 - -; The path for which the cookie is valid. -; http://php.net/session.cookie-path -session.cookie_path = / - -; The domain for which the cookie is valid. -; http://php.net/session.cookie-domain -session.cookie_domain = - -; Whether or not to add the httpOnly flag to the cookie, which makes it -; inaccessible to browser scripting languages such as JavaScript. -; http://php.net/session.cookie-httponly -session.cookie_httponly = 1 - -; Add SameSite attribute to cookie to help mitigate Cross-Site Request Forgery (CSRF/XSRF) -; Current valid values are "Strict", "Lax" or "None". When using "None", -; make sure to include the quotes, as `none` is interpreted like `false` in ini files. -; https://tools.ietf.org/html/draft-west-first-party-cookies-07 -session.cookie_samesite = - -; Handler used to serialize data. php is the standard serializer of PHP. -; http://php.net/session.serialize-handler -session.serialize_handler = php - -; Defines the probability that the 'garbage collection' process is started on every -; session initialization. The probability is calculated by using gc_probability/gc_divisor, -; e.g. 1/100 means there is a 1% chance that the GC process starts on each request. -; Default Value: 1 -; Development Value: 1 -; Production Value: 1 -; http://php.net/session.gc-probability -session.gc_probability = 1 - -; Defines the probability that the 'garbage collection' process is started on every -; session initialization. The probability is calculated by using gc_probability/gc_divisor, -; e.g. 1/100 means there is a 1% chance that the GC process starts on each request. -; For high volume production servers, using a value of 1000 is a more efficient approach. -; Default Value: 100 -; Development Value: 1000 -; Production Value: 1000 -; http://php.net/session.gc-divisor -session.gc_divisor = 1000 - -; After this number of seconds, stored data will be seen as 'garbage' and -; cleaned up by the garbage collection process. -; http://php.net/session.gc-maxlifetime -session.gc_maxlifetime = 1440 - -; NOTE: If you are using the subdirectory option for storing session files -; (see session.save_path above), then garbage collection does *not* -; happen automatically. You will need to do your own garbage -; collection through a shell script, cron entry, or some other method. -; For example, the following script is the equivalent of setting -; session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): -; find /path/to/sessions -cmin +24 -type f | xargs rm - -; Check HTTP Referer to invalidate externally stored URLs containing ids. -; HTTP_REFERER has to contain this substring for the session to be -; considered as valid. -; http://php.net/session.referer-check -session.referer_check = - -; Set to {nocache,private,public,} to determine HTTP caching aspects -; or leave this empty to avoid sending anti-caching headers. -; http://php.net/session.cache-limiter -session.cache_limiter = nocache - -; Document expires after n minutes. -; http://php.net/session.cache-expire -session.cache_expire = 180 - -; trans sid support is disabled by default. -; Use of trans sid may risk your users' security. -; Use this option with caution. -; - User may send URL contains active session ID -; to other person via. email/irc/etc. -; - URL that contains active session ID may be stored -; in publicly accessible computer. -; - User may access your site with the same session ID -; always using URL stored in browser's history or bookmarks. -; http://php.net/session.use-trans-sid -session.use_trans_sid = 0 - -; Set session ID character length. This value could be between 22 to 256. -; Shorter length than default is supported only for compatibility reason. -; Users should use 32 or more chars. -; http://php.net/session.sid-length -; Default Value: 32 -; Development Value: 26 -; Production Value: 26 -session.sid_length = 26 - -; The URL rewriter will look for URLs in a defined set of HTML tags. -; is special; if you include them here, the rewriter will -; add a hidden field with the info which is otherwise appended -; to URLs. tag's action attribute URL will not be modified -; unless it is specified. -; Note that all valid entries require a "=", even if no value follows. -; Default Value: "a=href,area=href,frame=src,form=" -; Development Value: "a=href,area=href,frame=src,form=" -; Production Value: "a=href,area=href,frame=src,form=" -; http://php.net/url-rewriter.tags -session.trans_sid_tags = "a=href,area=href,frame=src,form=" - -; URL rewriter does not rewrite absolute URLs by default. -; To enable rewrites for absolute paths, target hosts must be specified -; at RUNTIME. i.e. use ini_set() -; tags is special. PHP will check action attribute's URL regardless -; of session.trans_sid_tags setting. -; If no host is defined, HTTP_HOST will be used for allowed host. -; Example value: php.net,www.php.net,wiki.php.net -; Use "," for multiple hosts. No spaces are allowed. -; Default Value: "" -; Development Value: "" -; Production Value: "" -;session.trans_sid_hosts="" - -; Define how many bits are stored in each character when converting -; the binary hash data to something readable. -; Possible values: -; 4 (4 bits: 0-9, a-f) -; 5 (5 bits: 0-9, a-v) -; 6 (6 bits: 0-9, a-z, A-Z, "-", ",") -; Default Value: 4 -; Development Value: 5 -; Production Value: 5 -; http://php.net/session.hash-bits-per-character -session.sid_bits_per_character = 5 - -; Enable upload progress tracking in $_SESSION -; Default Value: On -; Development Value: On -; Production Value: On -; http://php.net/session.upload-progress.enabled -;session.upload_progress.enabled = On - -; Cleanup the progress information as soon as all POST data has been read -; (i.e. upload completed). -; Default Value: On -; Development Value: On -; Production Value: On -; http://php.net/session.upload-progress.cleanup -;session.upload_progress.cleanup = On - -; A prefix used for the upload progress key in $_SESSION -; Default Value: "upload_progress_" -; Development Value: "upload_progress_" -; Production Value: "upload_progress_" -; http://php.net/session.upload-progress.prefix -;session.upload_progress.prefix = "upload_progress_" - -; The index name (concatenated with the prefix) in $_SESSION -; containing the upload progress information -; Default Value: "PHP_SESSION_UPLOAD_PROGRESS" -; Development Value: "PHP_SESSION_UPLOAD_PROGRESS" -; Production Value: "PHP_SESSION_UPLOAD_PROGRESS" -; http://php.net/session.upload-progress.name -;session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS" - -; How frequently the upload progress should be updated. -; Given either in percentages (per-file), or in bytes -; Default Value: "1%" -; Development Value: "1%" -; Production Value: "1%" -; http://php.net/session.upload-progress.freq -;session.upload_progress.freq = "1%" - -; The minimum delay between updates, in seconds -; Default Value: 1 -; Development Value: 1 -; Production Value: 1 -; http://php.net/session.upload-progress.min-freq -;session.upload_progress.min_freq = "1" - -; Only write session data when session data is changed. Enabled by default. -; http://php.net/session.lazy-write -;session.lazy_write = On - -[Assertion] -; Switch whether to compile assertions at all (to have no overhead at run-time) -; -1: Do not compile at all -; 0: Jump over assertion at run-time -; 1: Execute assertions -; Changing from or to a negative value is only possible in php.ini! (For turning assertions on and off at run-time, see assert.active, when zend.assertions = 1) -; Default Value: 1 -; Development Value: 1 -; Production Value: -1 -; http://php.net/zend.assertions -zend.assertions = -1 - -; Assert(expr); active by default. -; http://php.net/assert.active -;assert.active = On - -; Throw an AssertionError on failed assertions -; http://php.net/assert.exception -;assert.exception = On - -; Issue a PHP warning for each failed assertion. (Overridden by assert.exception if active) -; http://php.net/assert.warning -;assert.warning = On - -; Don't bail out by default. -; http://php.net/assert.bail -;assert.bail = Off - -; User-function to be called if an assertion fails. -; http://php.net/assert.callback -;assert.callback = 0 - -[COM] -; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs -; http://php.net/com.typelib-file -;com.typelib_file = - -; allow Distributed-COM calls -; http://php.net/com.allow-dcom -;com.allow_dcom = true - -; autoregister constants of a component's typlib on com_load() -; http://php.net/com.autoregister-typelib -;com.autoregister_typelib = true - -; register constants casesensitive -; http://php.net/com.autoregister-casesensitive -;com.autoregister_casesensitive = false - -; show warnings on duplicate constant registrations -; http://php.net/com.autoregister-verbose -;com.autoregister_verbose = true - -; The default character set code-page to use when passing strings to and from COM objects. -; Default: system ANSI code page -;com.code_page= - -; The version of the .NET framework to use. The value of the setting are the first three parts -; of the framework's version number, separated by dots, and prefixed with "v", e.g. "v4.0.30319". -;com.dotnet_version= - -[mbstring] -; language for internal character representation. -; This affects mb_send_mail() and mbstring.detect_order. -; http://php.net/mbstring.language -;mbstring.language = Japanese - -; Use of this INI entry is deprecated, use global internal_encoding instead. -; internal/script encoding. -; Some encoding cannot work as internal encoding. (e.g. SJIS, BIG5, ISO-2022-*) -; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. -; The precedence is: default_charset < internal_encoding < iconv.internal_encoding -;mbstring.internal_encoding = - -; Use of this INI entry is deprecated, use global input_encoding instead. -; http input encoding. -; mbstring.encoding_translation = On is needed to use this setting. -; If empty, default_charset or input_encoding or mbstring.input is used. -; The precedence is: default_charset < input_encoding < mbstring.http_input -; http://php.net/mbstring.http-input -;mbstring.http_input = - -; Use of this INI entry is deprecated, use global output_encoding instead. -; http output encoding. -; mb_output_handler must be registered as output buffer to function. -; If empty, default_charset or output_encoding or mbstring.http_output is used. -; The precedence is: default_charset < output_encoding < mbstring.http_output -; To use an output encoding conversion, mbstring's output handler must be set -; otherwise output encoding conversion cannot be performed. -; http://php.net/mbstring.http-output -;mbstring.http_output = - -; enable automatic encoding translation according to -; mbstring.internal_encoding setting. Input chars are -; converted to internal encoding by setting this to On. -; Note: Do _not_ use automatic encoding translation for -; portable libs/applications. -; http://php.net/mbstring.encoding-translation -;mbstring.encoding_translation = Off - -; automatic encoding detection order. -; "auto" detect order is changed according to mbstring.language -; http://php.net/mbstring.detect-order -;mbstring.detect_order = auto - -; substitute_character used when character cannot be converted -; one from another -; http://php.net/mbstring.substitute-character -;mbstring.substitute_character = none - -; Enable strict encoding detection. -;mbstring.strict_detection = Off - -; This directive specifies the regex pattern of content types for which mb_output_handler() -; is activated. -; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml) -;mbstring.http_output_conv_mimetype= - -; This directive specifies maximum stack depth for mbstring regular expressions. It is similar -; to the pcre.recursion_limit for PCRE. -;mbstring.regex_stack_limit=100000 - -; This directive specifies maximum retry count for mbstring regular expressions. It is similar -; to the pcre.backtrack_limit for PCRE. -;mbstring.regex_retry_limit=1000000 - -[gd] -; Tell the jpeg decode to ignore warnings and try to create -; a gd image. The warning will then be displayed as notices -; disabled by default -; http://php.net/gd.jpeg-ignore-warning -;gd.jpeg_ignore_warning = 1 - -[exif] -; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. -; With mbstring support this will automatically be converted into the encoding -; given by corresponding encode setting. When empty mbstring.internal_encoding -; is used. For the decode settings you can distinguish between motorola and -; intel byte order. A decode setting cannot be empty. -; http://php.net/exif.encode-unicode -;exif.encode_unicode = ISO-8859-15 - -; http://php.net/exif.decode-unicode-motorola -;exif.decode_unicode_motorola = UCS-2BE - -; http://php.net/exif.decode-unicode-intel -;exif.decode_unicode_intel = UCS-2LE - -; http://php.net/exif.encode-jis -;exif.encode_jis = - -; http://php.net/exif.decode-jis-motorola -;exif.decode_jis_motorola = JIS - -; http://php.net/exif.decode-jis-intel -;exif.decode_jis_intel = JIS - -[Tidy] -; The path to a default tidy configuration file to use when using tidy -; http://php.net/tidy.default-config -;tidy.default_config = /usr/local/lib/php/default.tcfg - -; Should tidy clean and repair output automatically? -; WARNING: Do not use this option if you are generating non-html content -; such as dynamic images -; http://php.net/tidy.clean-output -tidy.clean_output = Off - -[soap] -; Enables or disables WSDL caching feature. -; http://php.net/soap.wsdl-cache-enabled -soap.wsdl_cache_enabled = 1 - -; Sets the directory name where SOAP extension will put cache files. -; http://php.net/soap.wsdl-cache-dir -; SEE [app_limits] - -; (time to live) Sets the number of second while cached file will be used -; instead of original one. -; http://php.net/soap.wsdl-cache-ttl -soap.wsdl_cache_ttl = 86400 - -; Sets the size of the cache limit. (Max. number of WSDL files to cache) -soap.wsdl_cache_limit = 5 - -[sysvshm] -; A default size of the shared memory segment -;sysvshm.init_mem = 10000 - -[ldap] -; Sets the maximum number of open links or -1 for unlimited. -ldap.max_links = -1 - -[dba] -;dba.default_handler= - -[opcache] -; Determines if Zend OPCache is enabled -; SEE [app_limits] - -; Determines if Zend OPCache is enabled for the CLI version of PHP -;opcache.enable_cli=0 - -; The OPcache shared memory storage size. -;opcache.memory_consumption=128 - -; The amount of memory for interned strings in Mbytes. -;opcache.interned_strings_buffer=8 - -; The maximum number of keys (scripts) in the OPcache hash table. -; Only numbers between 200 and 1000000 are allowed. -;opcache.max_accelerated_files=10000 - -; The maximum percentage of "wasted" memory until a restart is scheduled. -;opcache.max_wasted_percentage=5 - -; When this directive is enabled, the OPcache appends the current working -; directory to the script key, thus eliminating possible collisions between -; files with the same name (basename). Disabling the directive improves -; performance, but may break existing applications. -;opcache.use_cwd=1 - -; When disabled, you must reset the OPcache manually or restart the -; webserver for changes to the filesystem to take effect. -;opcache.validate_timestamps=1 - -; How often (in seconds) to check file timestamps for changes to the shared -; memory storage allocation. ("1" means validate once per second, but only -; once per request. "0" means always validate) -;opcache.revalidate_freq=2 - -; Enables or disables file search in include_path optimization -;opcache.revalidate_path=0 - -; If disabled, all PHPDoc comments are dropped from the code to reduce the -; size of the optimized code. -;opcache.save_comments=1 - -; If enabled, compilation warnings (including notices and deprecations) will -; be recorded and replayed each time a file is included. Otherwise, compilation -; warnings will only be emitted when the file is first cached. -;opcache.record_warnings=0 - -; Allow file existence override (file_exists, etc.) performance feature. -;opcache.enable_file_override=0 - -; A bitmask, where each bit enables or disables the appropriate OPcache -; passes -;opcache.optimization_level=0x7FFFBFFF - -;opcache.dups_fix=0 - -; The location of the OPcache blacklist file (wildcards allowed). -; Each OPcache blacklist file is a text file that holds the names of files -; that should not be accelerated. The file format is to add each filename -; to a new line. The filename may be a full path or just a file prefix -; (i.e., /var/www/x blacklists all the files and directories in /var/www -; that start with 'x'). Line starting with a ; are ignored (comments). -;opcache.blacklist_filename= - -; Allows exclusion of large files from being cached. By default all files -; are cached. -;opcache.max_file_size=0 - -; Check the cache checksum each N requests. -; The default value of "0" means that the checks are disabled. -;opcache.consistency_checks=0 - -; How long to wait (in seconds) for a scheduled restart to begin if the cache -; is not being accessed. -;opcache.force_restart_timeout=180 - -; OPcache error_log file name. Empty string assumes "stderr". -;opcache.error_log= - -; All OPcache errors go to the Web server log. -; By default, only fatal errors (level 0) or errors (level 1) are logged. -; You can also enable warnings (level 2), info messages (level 3) or -; debug messages (level 4). -;opcache.log_verbosity_level=1 - -; Preferred Shared Memory back-end. Leave empty and let the system decide. -;opcache.preferred_memory_model= - -; Protect the shared memory from unexpected writing during script execution. -; Useful for internal debugging only. -;opcache.protect_memory=0 - -; Allows calling OPcache API functions only from PHP scripts which path is -; started from specified string. The default "" means no restriction -;opcache.restrict_api= - -; Mapping base of shared memory segments (for Windows only). All the PHP -; processes have to map shared memory into the same address space. This -; directive allows to manually fix the "Unable to reattach to base address" -; errors. -;opcache.mmap_base= - -; Facilitates multiple OPcache instances per user (for Windows only). All PHP -; processes with the same cache ID and user share an OPcache instance. -;opcache.cache_id= - -; Enables and sets the second level cache directory. -; It should improve performance when SHM memory is full, at server restart or -; SHM reset. The default "" disables file based caching. -; SEE [app_temp] - -; Enables or disables opcode caching in shared memory. -;opcache.file_cache_only=0 - -; Enables or disables checksum validation when script loaded from file cache. -;opcache.file_cache_consistency_checks=1 - -; Implies opcache.file_cache_only=1 for a certain process that failed to -; reattach to the shared memory (for Windows only). Explicitly enabled file -; cache is required. -;opcache.file_cache_fallback=1 - -; Enables or disables copying of PHP code (text segment) into HUGE PAGES. -; This should improve performance, but requires appropriate OS configuration. -;opcache.huge_code_pages=1 - -; Validate cached file permissions. -;opcache.validate_permission=0 - -; Prevent name collisions in chroot'ed environment. -;opcache.validate_root=0 - -; If specified, it produces opcode dumps for debugging different stages of -; optimizations. -;opcache.opt_debug_level=0 - -; Specifies a PHP script that is going to be compiled and executed at server -; start-up. -; http://php.net/opcache.preload -;opcache.preload= - -; Preloading code as root is not allowed for security reasons. This directive -; facilitates to let the preloading to be run as another user. -; http://php.net/opcache.preload_user -;opcache.preload_user= - -; Prevents caching files that are less than this number of seconds old. It -; protects from caching of incompletely updated files. In case all file updates -; on your site are atomic, you may increase performance by setting it to "0". -;opcache.file_update_protection=2 - -; Absolute path used to store shared lockfiles (for *nix only). -;opcache.lockfile_path=/tmp - -[curl] -; A default value for the CURLOPT_CAINFO option. This is required to be an -; absolute path. -; SEE [app_limits] - -[openssl] -; The location of a Certificate Authority (CA) file on the local filesystem -; to use when verifying the identity of SSL/TLS peers. Most users should -; not specify a value for this directive as PHP will attempt to use the -; OS-managed cert stores in its absence. If specified, this value may still -; be overridden on a per-stream basis via the "cafile" SSL stream context -; option. -; SEE [app_limits] - -; If openssl.cafile is not specified or if the CA file is not found, the -; directory pointed to by openssl.capath is searched for a suitable -; certificate. This value must be a correctly hashed certificate directory. -; Most users should not specify a value for this directive as PHP will -; attempt to use the OS-managed cert stores in its absence. If specified, -; this value may still be overridden on a per-stream basis via the "capath" -; SSL stream context option. -;openssl.capath= - -[ffi] -; FFI API restriction. Possible values: -; "preload" - enabled in CLI scripts and preloaded files (default) -; "false" - always disabled -; "true" - always enabled -;ffi.enable=preload - -; List of headers files to preload, wildcard patterns allowed. -;ffi.preload= diff --git a/srv/app.prod/php.ini b/srv/app.prod/php.ini deleted file mode 100644 index 24b6f3b..0000000 --- a/srv/app.prod/php.ini +++ /dev/null @@ -1,1923 +0,0 @@ -[PHP] -;;;;;;;;;;;;;;;;;;;;;;;; -; COMMON APP SETTINGS ; -;;;;;;;;;;;;;;;;;;;;;;;; -; Update the paths to the absolute paths appropriate locations for this server -; Generate a new GUID for each app - use same the guid for each environment -; - GUID generator https://www.guidgenerator.com/ - do not use hyphens -[app] -session.name = {app_guid} - -[app_logging] -error_log = "{prod_app_absolute_path}\logs\error.log" -opcache.error_log = "{prod_app_absolute_path}\logs\opcache.log" -mail.log = "{prod_app_absolute_path}\logs\mail.log" - -[app_temp] -session.save_path = "{prod_app_absolute_path}\srv\tmp\sessions\" -sys_temp_dir = "{prod_app_absolute_path}\srv\tmp\tmp\" -upload_tmp_dir = "{prod_app_absolute_path}\srv\tmp\files\" -soap.wsdl_cache_dir = "{prod_app_absolute_path}\srv\tmp\soaptmp\" -opcache.file_cache = "{prod_app_absolute_path}\srv\tmp\opcache\" - -[app_mail] -SMTP = {app_smtp_server} -smtp_port = 25 -sendmail_from = {app_smtp_sendmail_from_address} - -[app_limits] -upload_max_filesize = 1024M -max_execution_time = 30 -memory_limit = 256M -opcache.enable=On - -[app_ssl] -curl.cainfo = "{prod_app_ssl_path}\cacert.pem" -openssl.cafile="{prod_app_ssl_path}\cacert.pem" - -[app_extensions] -extension_dir = "{prod_app_php_path}\ext\" -extension=curl -extension=fileinfo -extension=openssl -;extension=pdo_sqlsrv -extension=mongodb - -;;;;;;;;;;;;;;;;;;; -; About php.ini ; -;;;;;;;;;;;;;;;;;;; -; PHP's initialization file, generally called php.ini, is responsible for -; configuring many of the aspects of PHP's behavior. - -; PHP attempts to find and load this configuration from a number of locations. -; The following is a summary of its search order: -; 1. SAPI module specific location. -; 2. The PHPRC environment variable. (As of PHP 5.2.0) -; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0) -; 4. Current working directory (except CLI) -; 5. The web server's directory (for SAPI modules), or directory of PHP -; (otherwise in Windows) -; 6. The directory from the --with-config-file-path compile time option, or the -; Windows directory (usually C:\windows) -; See the PHP docs for more specific information. -; http://php.net/configuration.file - -; The syntax of the file is extremely simple. Whitespace and lines -; beginning with a semicolon are silently ignored (as you probably guessed). -; Section headers (e.g. [Foo]) are also silently ignored, even though -; they might mean something in the future. - -; Directives following the section heading [PATH=/www/mysite] only -; apply to PHP files in the /www/mysite directory. Directives -; following the section heading [HOST=www.example.com] only apply to -; PHP files served from www.example.com. Directives set in these -; special sections cannot be overridden by user-defined INI files or -; at runtime. Currently, [PATH=] and [HOST=] sections only work under -; CGI/FastCGI. -; http://php.net/ini.sections - -; Directives are specified using the following syntax: -; directive = value -; Directive names are *case sensitive* - foo=bar is different from FOO=bar. -; Directives are variables used to configure PHP or PHP extensions. -; There is no name validation. If PHP can't find an expected -; directive because it is not set or is mistyped, a default value will be used. - -; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one -; of the INI constants (On, Off, True, False, Yes, No and None) or an expression -; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a -; previously set variable or directive (e.g. ${foo}) - -; Expressions in the INI file are limited to bitwise operators and parentheses: -; | bitwise OR -; ^ bitwise XOR -; & bitwise AND -; ~ bitwise NOT -; ! boolean NOT - -; Boolean flags can be turned on using the values 1, On, True or Yes. -; They can be turned off using the values 0, Off, False or No. - -; An empty string can be denoted by simply not writing anything after the equal -; sign, or by using the None keyword: - -; foo = ; sets foo to an empty string -; foo = None ; sets foo to an empty string -; foo = "None" ; sets foo to the string 'None' - -; If you use constants in your value, and these constants belong to a -; dynamically loaded extension (either a PHP extension or a Zend extension), -; you may only use these constants *after* the line that loads the extension. - -;;;;;;;;;;;;;;;;;;; -; About this file ; -;;;;;;;;;;;;;;;;;;; -; PHP comes packaged with two INI files. One that is recommended to be used -; in production environments and one that is recommended to be used in -; development environments. - -; php.ini-production contains settings which hold security, performance and -; best practices at its core. But please be aware, these settings may break -; compatibility with older or less security conscience applications. We -; recommending using the production ini in production and testing environments. - -; php.ini-development is very similar to its production variant, except it is -; much more verbose when it comes to errors. We recommend using the -; development version only in development environments, as errors shown to -; application users can inadvertently leak otherwise secure information. - -; This is the php.ini-production INI file. - -;;;;;;;;;;;;;;;;;;; -; Quick Reference ; -;;;;;;;;;;;;;;;;;;; - -; The following are all the settings which are different in either the production -; or development versions of the INIs with respect to PHP's default behavior. -; Please see the actual settings later in the document for more details as to why -; we recommend these changes in PHP's behavior. - -; display_errors -; Default Value: On -; Development Value: On -; Production Value: Off - -; display_startup_errors -; Default Value: On -; Development Value: On -; Production Value: Off - -; error_reporting -; Default Value: E_ALL -; Development Value: E_ALL -; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT - -; log_errors -; Default Value: Off -; Development Value: On -; Production Value: On - -; max_input_time -; Default Value: -1 (Unlimited) -; Development Value: 60 (60 seconds) -; Production Value: 60 (60 seconds) - -; output_buffering -; Default Value: Off -; Development Value: 4096 -; Production Value: 4096 - -; register_argc_argv -; Default Value: On -; Development Value: Off -; Production Value: Off - -; request_order -; Default Value: None -; Development Value: "GP" -; Production Value: "GP" - -; session.gc_divisor -; Default Value: 100 -; Development Value: 1000 -; Production Value: 1000 - -; session.sid_bits_per_character -; Default Value: 4 -; Development Value: 5 -; Production Value: 5 - -; short_open_tag -; Default Value: On -; Development Value: Off -; Production Value: Off - -; variables_order -; Default Value: "EGPCS" -; Development Value: "GPCS" -; Production Value: "GPCS" - -; zend.exception_ignore_args -; Default Value: Off -; Development Value: Off -; Production Value: On - -; zend.exception_string_param_max_len -; Default Value: 15 -; Development Value: 15 -; Production Value: 0 - -;;;;;;;;;;;;;;;;;;;; -; php.ini Options ; -;;;;;;;;;;;;;;;;;;;; -; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" -;user_ini.filename = ".user.ini" - -; To disable this feature set this option to an empty value -;user_ini.filename = - -; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) -;user_ini.cache_ttl = 300 - -;;;;;;;;;;;;;;;;;;;; -; Language Options ; -;;;;;;;;;;;;;;;;;;;; - -; Enable the PHP scripting language engine under Apache. -; http://php.net/engine -engine = On - -; This directive determines whether or not PHP will recognize code between -; tags as PHP source which should be processed as such. It is -; generally recommended that should be used and that this feature -; should be disabled, as enabling it may result in issues when generating XML -; documents, however this remains supported for backward compatibility reasons. -; Note that this directive does not control the would work. -; http://php.net/syntax-highlighting -;highlight.string = #DD0000 -;highlight.comment = #FF9900 -;highlight.keyword = #007700 -;highlight.default = #0000BB -;highlight.html = #000000 - -; If enabled, the request will be allowed to complete even if the user aborts -; the request. Consider enabling it if executing long requests, which may end up -; being interrupted by the user or a browser timing out. PHP's default behavior -; is to disable this feature. -; http://php.net/ignore-user-abort -;ignore_user_abort = On - -; Determines the size of the realpath cache to be used by PHP. This value should -; be increased on systems where PHP opens many files to reflect the quantity of -; the file operations performed. -; Note: if open_basedir is set, the cache is disabled -; http://php.net/realpath-cache-size -;realpath_cache_size = 4096k - -; Duration of time, in seconds for which to cache realpath information for a given -; file or directory. For systems with rarely changing files, consider increasing this -; value. -; http://php.net/realpath-cache-ttl -;realpath_cache_ttl = 120 - -; Enables or disables the circular reference collector. -; http://php.net/zend.enable-gc -zend.enable_gc = On - -; If enabled, scripts may be written in encodings that are incompatible with -; the scanner. CP936, Big5, CP949 and Shift_JIS are the examples of such -; encodings. To use this feature, mbstring extension must be enabled. -;zend.multibyte = Off - -; Allows to set the default encoding for the scripts. This value will be used -; unless "declare(encoding=...)" directive appears at the top of the script. -; Only affects if zend.multibyte is set. -;zend.script_encoding = - -; Allows to include or exclude arguments from stack traces generated for exceptions. -; In production, it is recommended to turn this setting on to prohibit the output -; of sensitive information in stack traces -; Default Value: Off -; Development Value: Off -; Production Value: On -zend.exception_ignore_args = On - -; Allows setting the maximum string length in an argument of a stringified stack trace -; to a value between 0 and 1000000. -; This has no effect when zend.exception_ignore_args is enabled. -; Default Value: 15 -; Development Value: 15 -; Production Value: 0 -; In production, it is recommended to set this to 0 to reduce the output -; of sensitive information in stack traces. -zend.exception_string_param_max_len = 0 - -;;;;;;;;;;;;;;;;; -; Miscellaneous ; -;;;;;;;;;;;;;;;;; - -; Decides whether PHP may expose the fact that it is installed on the server -; (e.g. by adding its signature to the Web server header). It is no security -; threat in any way, but it makes it possible to determine whether you use PHP -; on your server or not. -; http://php.net/expose-php -expose_php = Off - -;;;;;;;;;;;;;;;;;;; -; Resource Limits ; -;;;;;;;;;;;;;;;;;;; - -; Maximum execution time of each script, in seconds -; http://php.net/max-execution-time -; Note: This directive is hardcoded to 0 for the CLI SAPI -; SEE [app_limits] - -; Maximum amount of time each script may spend parsing request data. It's a good -; idea to limit this time on productions servers in order to eliminate unexpectedly -; long running scripts. -; Note: This directive is hardcoded to -1 for the CLI SAPI -; Default Value: -1 (Unlimited) -; Development Value: 60 (60 seconds) -; Production Value: 60 (60 seconds) -; http://php.net/max-input-time -max_input_time = 60 - -; Maximum input variable nesting level -; http://php.net/max-input-nesting-level -;max_input_nesting_level = 64 - -; How many GET/POST/COOKIE input variables may be accepted -;max_input_vars = 1000 - -; Maximum amount of memory a script may consume -; http://php.net/memory-limit -; SEE [app_limits] - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Error handling and logging ; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -; This directive informs PHP of which errors, warnings and notices you would like -; it to take action for. The recommended way of setting values for this -; directive is through the use of the error level constants and bitwise -; operators. The error level constants are below here for convenience as well as -; some common settings and their meanings. -; By default, PHP is set to take action on all errors, notices and warnings EXCEPT -; those related to E_NOTICE and E_STRICT, which together cover best practices and -; recommended coding standards in PHP. For performance reasons, this is the -; recommend error reporting setting. Your production server shouldn't be wasting -; resources complaining about best practices and coding standards. That's what -; development servers and development settings are for. -; Note: The php.ini-development file has this setting as E_ALL. This -; means it pretty much reports everything which is exactly what you want during -; development and early testing. -; -; Error Level Constants: -; E_ALL - All errors and warnings (includes E_STRICT as of PHP 5.4.0) -; E_ERROR - fatal run-time errors -; E_RECOVERABLE_ERROR - almost fatal run-time errors -; E_WARNING - run-time warnings (non-fatal errors) -; E_PARSE - compile-time parse errors -; E_NOTICE - run-time notices (these are warnings which often result -; from a bug in your code, but it's possible that it was -; intentional (e.g., using an uninitialized variable and -; relying on the fact it is automatically initialized to an -; empty string) -; E_STRICT - run-time notices, enable to have PHP suggest changes -; to your code which will ensure the best interoperability -; and forward compatibility of your code -; E_CORE_ERROR - fatal errors that occur during PHP's initial startup -; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's -; initial startup -; E_COMPILE_ERROR - fatal compile-time errors -; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) -; E_USER_ERROR - user-generated error message -; E_USER_WARNING - user-generated warning message -; E_USER_NOTICE - user-generated notice message -; E_DEPRECATED - warn about code that will not work in future versions -; of PHP -; E_USER_DEPRECATED - user-generated deprecation warnings -; -; Common Values: -; E_ALL (Show all errors, warnings and notices including coding standards.) -; E_ALL & ~E_NOTICE (Show all errors, except for notices) -; E_ALL & ~E_NOTICE & ~E_STRICT (Show all errors, except for notices and coding standards warnings.) -; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors) -; Default Value: E_ALL -; Development Value: E_ALL -; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT -; http://php.net/error-reporting -error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT - -; This directive controls whether or not and where PHP will output errors, -; notices and warnings too. Error output is very useful during development, but -; it could be very dangerous in production environments. Depending on the code -; which is triggering the error, sensitive information could potentially leak -; out of your application such as database usernames and passwords or worse. -; For production environments, we recommend logging errors rather than -; sending them to STDOUT. -; Possible Values: -; Off = Do not display any errors -; stderr = Display errors to STDERR (affects only CGI/CLI binaries!) -; On or stdout = Display errors to STDOUT -; Default Value: On -; Development Value: On -; Production Value: Off -; http://php.net/display-errors -display_errors = Off - -; The display of errors which occur during PHP's startup sequence are handled -; separately from display_errors. We strongly recommend you set this to 'off' -; for production servers to avoid leaking configuration details. -; Default Value: On -; Development Value: On -; Production Value: Off -; http://php.net/display-startup-errors -display_startup_errors = Off - -; Besides displaying errors, PHP can also log errors to locations such as a -; server-specific log, STDERR, or a location specified by the error_log -; directive found below. While errors should not be displayed on productions -; servers they should still be monitored and logging is a great way to do that. -; Default Value: Off -; Development Value: On -; Production Value: On -; http://php.net/log-errors -log_errors = On - -; Set maximum length of log_errors. In error_log information about the source is -; added. The default is 1024 and 0 allows to not apply any maximum length at all. -; http://php.net/log-errors-max-len -log_errors_max_len = 1024 - -; Do not log repeated messages. Repeated errors must occur in same file on same -; line unless ignore_repeated_source is set true. -; http://php.net/ignore-repeated-errors -ignore_repeated_errors = Off - -; Ignore source of message when ignoring repeated messages. When this setting -; is On you will not log errors with repeated messages from different files or -; source lines. -; http://php.net/ignore-repeated-source -ignore_repeated_source = Off - -; If this parameter is set to Off, then memory leaks will not be shown (on -; stdout or in the log). This is only effective in a debug compile, and if -; error reporting includes E_WARNING in the allowed list -; http://php.net/report-memleaks -report_memleaks = On - -; This setting is off by default. -;report_zend_debug = 0 - -; Turn off normal error reporting and emit XML-RPC error XML -; http://php.net/xmlrpc-errors -;xmlrpc_errors = 0 - -; An XML-RPC faultCode -;xmlrpc_error_number = 0 - -; When PHP displays or logs an error, it has the capability of formatting the -; error message as HTML for easier reading. This directive controls whether -; the error message is formatted as HTML or not. -; Note: This directive is hardcoded to Off for the CLI SAPI -; http://php.net/html-errors -;html_errors = On - -; If html_errors is set to On *and* docref_root is not empty, then PHP -; produces clickable error messages that direct to a page describing the error -; or function causing the error in detail. -; You can download a copy of the PHP manual from http://php.net/docs -; and change docref_root to the base URL of your local copy including the -; leading '/'. You must also specify the file extension being used including -; the dot. PHP's default behavior is to leave these settings empty, in which -; case no links to documentation are generated. -; Note: Never use this feature for production boxes. -; http://php.net/docref-root -; Examples -;docref_root = "/phpmanual/" - -; http://php.net/docref-ext -;docref_ext = .html - -; String to output before an error message. PHP's default behavior is to leave -; this setting blank. -; http://php.net/error-prepend-string -; Example: -;error_prepend_string = "" - -; String to output after an error message. PHP's default behavior is to leave -; this setting blank. -; http://php.net/error-append-string -; Example: -;error_append_string = "" - -; Log errors to specified file. PHP's default behavior is to leave this value -; empty. -; http://php.net/error-log -; Example: -;error_log = php_errors.log -; Log errors to syslog (Event Log on Windows). -;error_log = syslog - -; The syslog ident is a string which is prepended to every message logged -; to syslog. Only used when error_log is set to syslog. -;syslog.ident = php - -; The syslog facility is used to specify what type of program is logging -; the message. Only used when error_log is set to syslog. -;syslog.facility = user - -; Set this to disable filtering control characters (the default). -; Some loggers only accept NVT-ASCII, others accept anything that's not -; control characters. If your logger accepts everything, then no filtering -; is needed at all. -; Allowed values are: -; ascii (all printable ASCII characters and NL) -; no-ctrl (all characters except control characters) -; all (all characters) -; raw (like "all", but messages are not split at newlines) -; http://php.net/syslog.filter -;syslog.filter = ascii - -;windows.show_crt_warning -; Default value: 0 -; Development value: 0 -; Production value: 0 - -;;;;;;;;;;;;;;;;; -; Data Handling ; -;;;;;;;;;;;;;;;;; - -; The separator used in PHP generated URLs to separate arguments. -; PHP's default setting is "&". -; http://php.net/arg-separator.output -; Example: -;arg_separator.output = "&" - -; List of separator(s) used by PHP to parse input URLs into variables. -; PHP's default setting is "&". -; NOTE: Every character in this directive is considered as separator! -; http://php.net/arg-separator.input -; Example: -;arg_separator.input = ";&" - -; This directive determines which super global arrays are registered when PHP -; starts up. G,P,C,E & S are abbreviations for the following respective super -; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty -; paid for the registration of these arrays and because ENV is not as commonly -; used as the others, ENV is not recommended on productions servers. You -; can still get access to the environment variables through getenv() should you -; need to. -; Default Value: "EGPCS" -; Development Value: "GPCS" -; Production Value: "GPCS"; -; http://php.net/variables-order -variables_order = "GPCS" - -; This directive determines which super global data (G,P & C) should be -; registered into the super global array REQUEST. If so, it also determines -; the order in which that data is registered. The values for this directive -; are specified in the same manner as the variables_order directive, -; EXCEPT one. Leaving this value empty will cause PHP to use the value set -; in the variables_order directive. It does not mean it will leave the super -; globals array REQUEST empty. -; Default Value: None -; Development Value: "GP" -; Production Value: "GP" -; http://php.net/request-order -request_order = "GP" - -; This directive determines whether PHP registers $argv & $argc each time it -; runs. $argv contains an array of all the arguments passed to PHP when a script -; is invoked. $argc contains an integer representing the number of arguments -; that were passed when the script was invoked. These arrays are extremely -; useful when running scripts from the command line. When this directive is -; enabled, registering these variables consumes CPU cycles and memory each time -; a script is executed. For performance reasons, this feature should be disabled -; on production servers. -; Note: This directive is hardcoded to On for the CLI SAPI -; Default Value: On -; Development Value: Off -; Production Value: Off -; http://php.net/register-argc-argv -register_argc_argv = Off - -; When enabled, the ENV, REQUEST and SERVER variables are created when they're -; first used (Just In Time) instead of when the script starts. If these -; variables are not used within a script, having this directive on will result -; in a performance gain. The PHP directive register_argc_argv must be disabled -; for this directive to have any effect. -; http://php.net/auto-globals-jit -auto_globals_jit = On - -; Whether PHP will read the POST data. -; This option is enabled by default. -; Most likely, you won't want to disable this option globally. It causes $_POST -; and $_FILES to always be empty; the only way you will be able to read the -; POST data will be through the php://input stream wrapper. This can be useful -; to proxy requests or to process the POST data in a memory efficient fashion. -; http://php.net/enable-post-data-reading -;enable_post_data_reading = Off - -; Maximum size of POST data that PHP will accept. -; Its value may be 0 to disable the limit. It is ignored if POST data reading -; is disabled through enable_post_data_reading. -; http://php.net/post-max-size -post_max_size = 8M - -; Automatically add files before PHP document. -; http://php.net/auto-prepend-file -auto_prepend_file = - -; Automatically add files after PHP document. -; http://php.net/auto-append-file -auto_append_file = - -; By default, PHP will output a media type using the Content-Type header. To -; disable this, simply set it to be empty. -; -; PHP's built-in default media type is set to text/html. -; http://php.net/default-mimetype -default_mimetype = "text/html" - -; PHP's default character set is set to UTF-8. -; http://php.net/default-charset -default_charset = "UTF-8" - -; PHP internal character encoding is set to empty. -; If empty, default_charset is used. -; http://php.net/internal-encoding -;internal_encoding = - -; PHP input character encoding is set to empty. -; If empty, default_charset is used. -; http://php.net/input-encoding -;input_encoding = - -; PHP output character encoding is set to empty. -; If empty, default_charset is used. -; See also output_buffer. -; http://php.net/output-encoding -;output_encoding = - -;;;;;;;;;;;;;;;;;;;;;;;;; -; Paths and Directories ; -;;;;;;;;;;;;;;;;;;;;;;;;; - -; UNIX: "/path1:/path2" -;include_path = ".:/php/includes" -; -; Windows: "\path1;\path2" -;include_path = ".;c:\php\includes" -; -; PHP's default setting for include_path is ".;/path/to/php/pear" -; http://php.net/include-path - -; The root of the PHP pages, used only if nonempty. -; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root -; if you are running php as a CGI under any web server (other than IIS) -; see documentation for security issues. The alternate is to use the -; cgi.force_redirect configuration below -; http://php.net/doc-root -doc_root = - -; The directory under which PHP opens the script using /~username used only -; if nonempty. -; http://php.net/user-dir -user_dir = - -; Directory in which the loadable extensions (modules) reside. -; http://php.net/extension-dir -;extension_dir = "./" -; On windows: -; see [app_extensions] - -; Directory where the temporary files should be placed. -; Defaults to the system default (see sys_get_temp_dir) -; SEE [app_temp] - -; Whether or not to enable the dl() function. The dl() function does NOT work -; properly in multithreaded servers, such as IIS or Zeus, and is automatically -; disabled on them. -; http://php.net/enable-dl -enable_dl = Off - -; cgi.force_redirect is necessary to provide security running PHP as a CGI under -; most web servers. Left undefined, PHP turns this on by default. You can -; turn it off here AT YOUR OWN RISK -; **You CAN safely turn this off for IIS, in fact, you MUST.** -; http://php.net/cgi.force-redirect -;cgi.force_redirect = 1 - -; if cgi.nph is enabled it will force cgi to always sent Status: 200 with -; every request. PHP's default behavior is to disable this feature. -;cgi.nph = 1 - -; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape -; (iPlanet) web servers, you MAY need to set an environment variable name that PHP -; will look for to know it is OK to continue execution. Setting this variable MAY -; cause security issues, KNOW WHAT YOU ARE DOING FIRST. -; http://php.net/cgi.redirect-status-env -;cgi.redirect_status_env = - -; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's -; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok -; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting -; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting -; of zero causes PHP to behave as before. Default is 1. You should fix your scripts -; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. -; http://php.net/cgi.fix-pathinfo -;cgi.fix_pathinfo=1 - -; if cgi.discard_path is enabled, the PHP CGI binary can safely be placed outside -; of the web tree and people will not be able to circumvent .htaccess security. -;cgi.discard_path=1 - -; FastCGI under IIS supports the ability to impersonate -; security tokens of the calling client. This allows IIS to define the -; security context that the request runs under. mod_fastcgi under Apache -; does not currently support this feature (03/17/2002) -; Set to 1 if running under IIS. Default is zero. -; http://php.net/fastcgi.impersonate -;fastcgi.impersonate = 1 - -; Disable logging through FastCGI connection. PHP's default behavior is to enable -; this feature. -;fastcgi.logging = 0 - -; cgi.rfc2616_headers configuration option tells PHP what type of headers to -; use when sending HTTP response code. If set to 0, PHP sends Status: header that -; is supported by Apache. When this option is set to 1, PHP will send -; RFC2616 compliant header. -; Default is zero. -; http://php.net/cgi.rfc2616-headers -;cgi.rfc2616_headers = 0 - -; cgi.check_shebang_line controls whether CGI PHP checks for line starting with #! -; (shebang) at the top of the running script. This line might be needed if the -; script support running both as stand-alone script and via PHP CGI<. PHP in CGI -; mode skips this line and ignores its content if this directive is turned on. -; http://php.net/cgi.check-shebang-line -;cgi.check_shebang_line=1 - -;;;;;;;;;;;;;;;; -; File Uploads ; -;;;;;;;;;;;;;;;; - -; Whether to allow HTTP file uploads. -; http://php.net/file-uploads -file_uploads = On - -; Temporary directory for HTTP uploaded files (will use system default if not -; specified). -; http://php.net/upload-tmp-dir -; SEE [app_temp] - -; Maximum allowed size for uploaded files. -; http://php.net/upload-max-filesize -; SEE [app_limits] - -; Maximum number of files that can be uploaded via a single request -max_file_uploads = 20 - -;;;;;;;;;;;;;;;;;; -; Fopen wrappers ; -;;;;;;;;;;;;;;;;;; - -; Whether to allow the treatment of URLs (like http:// or ftp://) as files. -; http://php.net/allow-url-fopen -allow_url_fopen = On - -; Whether to allow include/require to open URLs (like http:// or ftp://) as files. -; http://php.net/allow-url-include -allow_url_include = Off - -; Define the anonymous ftp password (your email address). PHP's default setting -; for this is empty. -; http://php.net/from -;from="john@doe.com" - -; Define the User-Agent string. PHP's default setting for this is empty. -; http://php.net/user-agent -;user_agent="PHP" - -; Default timeout for socket based streams (seconds) -; http://php.net/default-socket-timeout -default_socket_timeout = 60 - -; If your scripts have to deal with files from Macintosh systems, -; or you are running on a Mac and need to deal with files from -; unix or win32 systems, setting this flag will cause PHP to -; automatically detect the EOL character in those files so that -; fgets() and file() will work regardless of the source of the file. -; http://php.net/auto-detect-line-endings -;auto_detect_line_endings = Off - -;;;;;;;;;;;;;;;;;;;;;; -; Dynamic Extensions ; -;;;;;;;;;;;;;;;;;;;;;; -; see [app_extensions] - -;;;;;;;;;;;;;;;;;;; -; Module Settings ; -;;;;;;;;;;;;;;;;;;; - -[CLI Server] -; Whether the CLI web server uses ANSI color coding in its terminal output. -cli_server.color = On - -[Date] -; Defines the default timezone used by the date functions -; http://php.net/date.timezone -date.timezone = "America/New_York" - -; http://php.net/date.default-latitude -;date.default_latitude = 31.7667 - -; http://php.net/date.default-longitude -;date.default_longitude = 35.2333 - -; http://php.net/date.sunrise-zenith -;date.sunrise_zenith = 90.833333 - -; http://php.net/date.sunset-zenith -;date.sunset_zenith = 90.833333 - -[filter] -; http://php.net/filter.default -;filter.default = unsafe_raw - -; http://php.net/filter.default-flags -;filter.default_flags = - -[iconv] -; Use of this INI entry is deprecated, use global input_encoding instead. -; If empty, default_charset or input_encoding or iconv.input_encoding is used. -; The precedence is: default_charset < input_encoding < iconv.input_encoding -;iconv.input_encoding = - -; Use of this INI entry is deprecated, use global internal_encoding instead. -; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. -; The precedence is: default_charset < internal_encoding < iconv.internal_encoding -;iconv.internal_encoding = - -; Use of this INI entry is deprecated, use global output_encoding instead. -; If empty, default_charset or output_encoding or iconv.output_encoding is used. -; The precedence is: default_charset < output_encoding < iconv.output_encoding -; To use an output encoding conversion, iconv's output handler must be set -; otherwise output encoding conversion cannot be performed. -;iconv.output_encoding = - -[imap] -; rsh/ssh logins are disabled by default. Use this INI entry if you want to -; enable them. Note that the IMAP library does not filter mailbox names before -; passing them to rsh/ssh command, thus passing untrusted data to this function -; with rsh/ssh enabled is insecure. -;imap.enable_insecure_rsh=0 - -[intl] -;intl.default_locale = -; This directive allows you to produce PHP errors when some error -; happens within intl functions. The value is the level of the error produced. -; Default is 0, which does not produce any errors. -;intl.error_level = E_WARNING -;intl.use_exceptions = 0 - -[sqlite3] -; Directory pointing to SQLite3 extensions -; http://php.net/sqlite3.extension-dir -;sqlite3.extension_dir = - -; SQLite defensive mode flag (only available from SQLite 3.26+) -; When the defensive flag is enabled, language features that allow ordinary -; SQL to deliberately corrupt the database file are disabled. This forbids -; writing directly to the schema, shadow tables (eg. FTS data tables), or -; the sqlite_dbpage virtual table. -; https://www.sqlite.org/c3ref/c_dbconfig_defensive.html -; (for older SQLite versions, this flag has no use) -;sqlite3.defensive = 1 - -[Pcre] -; PCRE library backtracking limit. -; http://php.net/pcre.backtrack-limit -;pcre.backtrack_limit=100000 - -; PCRE library recursion limit. -; Please note that if you set this value to a high number you may consume all -; the available process stack and eventually crash PHP (due to reaching the -; stack size limit imposed by the Operating System). -; http://php.net/pcre.recursion-limit -;pcre.recursion_limit=100000 - -; Enables or disables JIT compilation of patterns. This requires the PCRE -; library to be compiled with JIT support. -;pcre.jit=1 - -[Pdo] -; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" -; http://php.net/pdo-odbc.connection-pooling -;pdo_odbc.connection_pooling=strict - -[Pdo_mysql] -; Default socket name for local MySQL connects. If empty, uses the built-in -; MySQL defaults. -pdo_mysql.default_socket= - -[Phar] -; http://php.net/phar.readonly -;phar.readonly = On - -; http://php.net/phar.require-hash -;phar.require_hash = On - -;phar.cache_list = - -[mail function] -; For Win32 only. -; http://php.net/smtp -; SEE [app_mail] - -; For Win32 only. -; http://php.net/sendmail-from -; SEE [app_mail] - - -; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). -; http://php.net/sendmail-path -;sendmail_path = - -; Force the addition of the specified parameters to be passed as extra parameters -; to the sendmail binary. These parameters will always replace the value of -; the 5th parameter to mail(). -;mail.force_extra_parameters = - -; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename -mail.add_x_header = Off - -; The path to a log file that will log all mail() calls. Log entries include -; the full path of the script, line number, To address and headers. -;mail.log = -; Log mail to syslog (Event Log on Windows). -; SEE [app_logging] - - -[ODBC] -; http://php.net/odbc.default-db -;odbc.default_db = Not yet implemented - -; http://php.net/odbc.default-user -;odbc.default_user = Not yet implemented - -; http://php.net/odbc.default-pw -;odbc.default_pw = Not yet implemented - -; Controls the ODBC cursor model. -; Default: SQL_CURSOR_STATIC (default). -;odbc.default_cursortype - -; Allow or prevent persistent links. -; http://php.net/odbc.allow-persistent -odbc.allow_persistent = On - -; Check that a connection is still valid before reuse. -; http://php.net/odbc.check-persistent -odbc.check_persistent = On - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/odbc.max-persistent -odbc.max_persistent = -1 - -; Maximum number of links (persistent + non-persistent). -1 means no limit. -; http://php.net/odbc.max-links -odbc.max_links = -1 - -; Handling of LONG fields. Returns number of bytes to variables. 0 means -; passthru. -; http://php.net/odbc.defaultlrl -odbc.defaultlrl = 4096 - -; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. -; See the documentation on odbc_binmode and odbc_longreadlen for an explanation -; of odbc.defaultlrl and odbc.defaultbinmode -; http://php.net/odbc.defaultbinmode -odbc.defaultbinmode = 1 - -[MySQLi] - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/mysqli.max-persistent -mysqli.max_persistent = -1 - -; Allow accessing, from PHP's perspective, local files with LOAD DATA statements -; http://php.net/mysqli.allow_local_infile -;mysqli.allow_local_infile = On - -; Allow or prevent persistent links. -; http://php.net/mysqli.allow-persistent -mysqli.allow_persistent = On - -; Maximum number of links. -1 means no limit. -; http://php.net/mysqli.max-links -mysqli.max_links = -1 - -; Default port number for mysqli_connect(). If unset, mysqli_connect() will use -; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the -; compile-time value defined MYSQL_PORT (in that order). Win32 will only look -; at MYSQL_PORT. -; http://php.net/mysqli.default-port -mysqli.default_port = 3306 - -; Default socket name for local MySQL connects. If empty, uses the built-in -; MySQL defaults. -; http://php.net/mysqli.default-socket -mysqli.default_socket = - -; Default host for mysqli_connect() (doesn't apply in safe mode). -; http://php.net/mysqli.default-host -mysqli.default_host = - -; Default user for mysqli_connect() (doesn't apply in safe mode). -; http://php.net/mysqli.default-user -mysqli.default_user = - -; Default password for mysqli_connect() (doesn't apply in safe mode). -; Note that this is generally a *bad* idea to store passwords in this file. -; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") -; and reveal this password! And of course, any users with read access to this -; file will be able to reveal the password as well. -; http://php.net/mysqli.default-pw -mysqli.default_pw = - -; Allow or prevent reconnect -mysqli.reconnect = Off - -[mysqlnd] -; Enable / Disable collection of general statistics by mysqlnd which can be -; used to tune and monitor MySQL operations. -mysqlnd.collect_statistics = On - -; Enable / Disable collection of memory usage statistics by mysqlnd which can be -; used to tune and monitor MySQL operations. -mysqlnd.collect_memory_statistics = Off - -; Records communication from all extensions using mysqlnd to the specified log -; file. -; http://php.net/mysqlnd.debug -;mysqlnd.debug = - -; Defines which queries will be logged. -;mysqlnd.log_mask = 0 - -; Default size of the mysqlnd memory pool, which is used by result sets. -;mysqlnd.mempool_default_size = 16000 - -; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. -;mysqlnd.net_cmd_buffer_size = 2048 - -; Size of a pre-allocated buffer used for reading data sent by the server in -; bytes. -;mysqlnd.net_read_buffer_size = 32768 - -; Timeout for network requests in seconds. -;mysqlnd.net_read_timeout = 31536000 - -; SHA-256 Authentication Plugin related. File with the MySQL server public RSA -; key. -;mysqlnd.sha256_server_public_key = - -[OCI8] - -; Connection: Enables privileged connections using external -; credentials (OCI_SYSOPER, OCI_SYSDBA) -; http://php.net/oci8.privileged-connect -;oci8.privileged_connect = Off - -; Connection: The maximum number of persistent OCI8 connections per -; process. Using -1 means no limit. -; http://php.net/oci8.max-persistent -;oci8.max_persistent = -1 - -; Connection: The maximum number of seconds a process is allowed to -; maintain an idle persistent connection. Using -1 means idle -; persistent connections will be maintained forever. -; http://php.net/oci8.persistent-timeout -;oci8.persistent_timeout = -1 - -; Connection: The number of seconds that must pass before issuing a -; ping during oci_pconnect() to check the connection validity. When -; set to 0, each oci_pconnect() will cause a ping. Using -1 disables -; pings completely. -; http://php.net/oci8.ping-interval -;oci8.ping_interval = 60 - -; Connection: Set this to a user chosen connection class to be used -; for all pooled server requests with Oracle 11g Database Resident -; Connection Pooling (DRCP). To use DRCP, this value should be set to -; the same string for all web servers running the same application, -; the database pool must be configured, and the connection string must -; specify to use a pooled server. -;oci8.connection_class = - -; High Availability: Using On lets PHP receive Fast Application -; Notification (FAN) events generated when a database node fails. The -; database must also be configured to post FAN events. -;oci8.events = Off - -; Tuning: This option enables statement caching, and specifies how -; many statements to cache. Using 0 disables statement caching. -; http://php.net/oci8.statement-cache-size -;oci8.statement_cache_size = 20 - -; Tuning: Enables statement prefetching and sets the default number of -; rows that will be fetched automatically after statement execution. -; http://php.net/oci8.default-prefetch -;oci8.default_prefetch = 100 - -; Compatibility. Using On means oci_close() will not close -; oci_connect() and oci_new_connect() connections. -; http://php.net/oci8.old-oci-close-semantics -;oci8.old_oci_close_semantics = Off - -[PostgreSQL] -; Allow or prevent persistent links. -; http://php.net/pgsql.allow-persistent -pgsql.allow_persistent = On - -; Detect broken persistent links always with pg_pconnect(). -; Auto reset feature requires a little overheads. -; http://php.net/pgsql.auto-reset-persistent -pgsql.auto_reset_persistent = Off - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/pgsql.max-persistent -pgsql.max_persistent = -1 - -; Maximum number of links (persistent+non persistent). -1 means no limit. -; http://php.net/pgsql.max-links -pgsql.max_links = -1 - -; Ignore PostgreSQL backends Notice message or not. -; Notice message logging require a little overheads. -; http://php.net/pgsql.ignore-notice -pgsql.ignore_notice = 0 - -; Log PostgreSQL backends Notice message or not. -; Unless pgsql.ignore_notice=0, module cannot log notice message. -; http://php.net/pgsql.log-notice -pgsql.log_notice = 0 - -[bcmath] -; Number of decimal digits for all bcmath functions. -; http://php.net/bcmath.scale -bcmath.scale = 0 - -[browscap] -; http://php.net/browscap -;browscap = extra/browscap.ini - -[Session] -; Handler used to store/retrieve data. -; http://php.net/session.save-handler -session.save_handler = files - -; Argument passed to save_handler. In the case of files, this is the path -; where data files are stored. Note: Windows users have to change this -; variable in order to use PHP's session functions. -; -; The path can be defined as: -; -; session.save_path = "N;/path" -; -; where N is an integer. Instead of storing all the session files in -; /path, what this will do is use subdirectories N-levels deep, and -; store the session data in those directories. This is useful if -; your OS has problems with many files in one directory, and is -; a more efficient layout for servers that handle many sessions. -; -; NOTE 1: PHP will not create this directory structure automatically. -; You can use the script in the ext/session dir for that purpose. -; NOTE 2: See the section on garbage collection below if you choose to -; use subdirectories for session storage -; -; The file storage module creates files using mode 600 by default. -; You can change that by using -; -; session.save_path = "N;MODE;/path" -; -; where MODE is the octal representation of the mode. Note that this -; does not overwrite the process's umask. -; http://php.net/session.save-path -; SEE [app_temp] - -; Whether to use strict session mode. -; Strict session mode does not accept an uninitialized session ID, and -; regenerates the session ID if the browser sends an uninitialized session ID. -; Strict mode protects applications from session fixation via a session adoption -; vulnerability. It is disabled by default for maximum compatibility, but -; enabling it is encouraged. -; https://wiki.php.net/rfc/strict_sessions -session.use_strict_mode = 0 - -; Whether to use cookies. -; http://php.net/session.use-cookies -session.use_cookies = 1 - -; http://php.net/session.cookie-secure -session.cookie_secure = 1 - -; This option forces PHP to fetch and use a cookie for storing and maintaining -; the session id. We encourage this operation as it's very helpful in combating -; session hijacking when not specifying and managing your own session id. It is -; not the be-all and end-all of session hijacking defense, but it's a good start. -; http://php.net/session.use-only-cookies -session.use_only_cookies = 1 - -; Name of the session (used as cookie name). - -; Initialize session on request startup. -; http://php.net/session.auto-start -session.auto_start = 0 - -; Lifetime in seconds of cookie or, if 0, until browser is restarted. -; http://php.net/session.cookie-lifetime -session.cookie_lifetime = 0 - -; The path for which the cookie is valid. -; http://php.net/session.cookie-path -session.cookie_path = / - -; The domain for which the cookie is valid. -; http://php.net/session.cookie-domain -session.cookie_domain = - -; Whether or not to add the httpOnly flag to the cookie, which makes it -; inaccessible to browser scripting languages such as JavaScript. -; http://php.net/session.cookie-httponly -session.cookie_httponly = 1 - -; Add SameSite attribute to cookie to help mitigate Cross-Site Request Forgery (CSRF/XSRF) -; Current valid values are "Strict", "Lax" or "None". When using "None", -; make sure to include the quotes, as `none` is interpreted like `false` in ini files. -; https://tools.ietf.org/html/draft-west-first-party-cookies-07 -session.cookie_samesite = - -; Handler used to serialize data. php is the standard serializer of PHP. -; http://php.net/session.serialize-handler -session.serialize_handler = php - -; Defines the probability that the 'garbage collection' process is started on every -; session initialization. The probability is calculated by using gc_probability/gc_divisor, -; e.g. 1/100 means there is a 1% chance that the GC process starts on each request. -; Default Value: 1 -; Development Value: 1 -; Production Value: 1 -; http://php.net/session.gc-probability -session.gc_probability = 1 - -; Defines the probability that the 'garbage collection' process is started on every -; session initialization. The probability is calculated by using gc_probability/gc_divisor, -; e.g. 1/100 means there is a 1% chance that the GC process starts on each request. -; For high volume production servers, using a value of 1000 is a more efficient approach. -; Default Value: 100 -; Development Value: 1000 -; Production Value: 1000 -; http://php.net/session.gc-divisor -session.gc_divisor = 1000 - -; After this number of seconds, stored data will be seen as 'garbage' and -; cleaned up by the garbage collection process. -; http://php.net/session.gc-maxlifetime -session.gc_maxlifetime = 1440 - -; NOTE: If you are using the subdirectory option for storing session files -; (see session.save_path above), then garbage collection does *not* -; happen automatically. You will need to do your own garbage -; collection through a shell script, cron entry, or some other method. -; For example, the following script is the equivalent of setting -; session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): -; find /path/to/sessions -cmin +24 -type f | xargs rm - -; Check HTTP Referer to invalidate externally stored URLs containing ids. -; HTTP_REFERER has to contain this substring for the session to be -; considered as valid. -; http://php.net/session.referer-check -session.referer_check = - -; Set to {nocache,private,public,} to determine HTTP caching aspects -; or leave this empty to avoid sending anti-caching headers. -; http://php.net/session.cache-limiter -session.cache_limiter = nocache - -; Document expires after n minutes. -; http://php.net/session.cache-expire -session.cache_expire = 180 - -; trans sid support is disabled by default. -; Use of trans sid may risk your users' security. -; Use this option with caution. -; - User may send URL contains active session ID -; to other person via. email/irc/etc. -; - URL that contains active session ID may be stored -; in publicly accessible computer. -; - User may access your site with the same session ID -; always using URL stored in browser's history or bookmarks. -; http://php.net/session.use-trans-sid -session.use_trans_sid = 0 - -; Set session ID character length. This value could be between 22 to 256. -; Shorter length than default is supported only for compatibility reason. -; Users should use 32 or more chars. -; http://php.net/session.sid-length -; Default Value: 32 -; Development Value: 26 -; Production Value: 26 -session.sid_length = 26 - -; The URL rewriter will look for URLs in a defined set of HTML tags. -; is special; if you include them here, the rewriter will -; add a hidden field with the info which is otherwise appended -; to URLs. tag's action attribute URL will not be modified -; unless it is specified. -; Note that all valid entries require a "=", even if no value follows. -; Default Value: "a=href,area=href,frame=src,form=" -; Development Value: "a=href,area=href,frame=src,form=" -; Production Value: "a=href,area=href,frame=src,form=" -; http://php.net/url-rewriter.tags -session.trans_sid_tags = "a=href,area=href,frame=src,form=" - -; URL rewriter does not rewrite absolute URLs by default. -; To enable rewrites for absolute paths, target hosts must be specified -; at RUNTIME. i.e. use ini_set() -; tags is special. PHP will check action attribute's URL regardless -; of session.trans_sid_tags setting. -; If no host is defined, HTTP_HOST will be used for allowed host. -; Example value: php.net,www.php.net,wiki.php.net -; Use "," for multiple hosts. No spaces are allowed. -; Default Value: "" -; Development Value: "" -; Production Value: "" -;session.trans_sid_hosts="" - -; Define how many bits are stored in each character when converting -; the binary hash data to something readable. -; Possible values: -; 4 (4 bits: 0-9, a-f) -; 5 (5 bits: 0-9, a-v) -; 6 (6 bits: 0-9, a-z, A-Z, "-", ",") -; Default Value: 4 -; Development Value: 5 -; Production Value: 5 -; http://php.net/session.hash-bits-per-character -session.sid_bits_per_character = 5 - -; Enable upload progress tracking in $_SESSION -; Default Value: On -; Development Value: On -; Production Value: On -; http://php.net/session.upload-progress.enabled -;session.upload_progress.enabled = On - -; Cleanup the progress information as soon as all POST data has been read -; (i.e. upload completed). -; Default Value: On -; Development Value: On -; Production Value: On -; http://php.net/session.upload-progress.cleanup -;session.upload_progress.cleanup = On - -; A prefix used for the upload progress key in $_SESSION -; Default Value: "upload_progress_" -; Development Value: "upload_progress_" -; Production Value: "upload_progress_" -; http://php.net/session.upload-progress.prefix -;session.upload_progress.prefix = "upload_progress_" - -; The index name (concatenated with the prefix) in $_SESSION -; containing the upload progress information -; Default Value: "PHP_SESSION_UPLOAD_PROGRESS" -; Development Value: "PHP_SESSION_UPLOAD_PROGRESS" -; Production Value: "PHP_SESSION_UPLOAD_PROGRESS" -; http://php.net/session.upload-progress.name -;session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS" - -; How frequently the upload progress should be updated. -; Given either in percentages (per-file), or in bytes -; Default Value: "1%" -; Development Value: "1%" -; Production Value: "1%" -; http://php.net/session.upload-progress.freq -;session.upload_progress.freq = "1%" - -; The minimum delay between updates, in seconds -; Default Value: 1 -; Development Value: 1 -; Production Value: 1 -; http://php.net/session.upload-progress.min-freq -;session.upload_progress.min_freq = "1" - -; Only write session data when session data is changed. Enabled by default. -; http://php.net/session.lazy-write -;session.lazy_write = On - -[Assertion] -; Switch whether to compile assertions at all (to have no overhead at run-time) -; -1: Do not compile at all -; 0: Jump over assertion at run-time -; 1: Execute assertions -; Changing from or to a negative value is only possible in php.ini! (For turning assertions on and off at run-time, see assert.active, when zend.assertions = 1) -; Default Value: 1 -; Development Value: 1 -; Production Value: -1 -; http://php.net/zend.assertions -zend.assertions = -1 - -; Assert(expr); active by default. -; http://php.net/assert.active -;assert.active = On - -; Throw an AssertionError on failed assertions -; http://php.net/assert.exception -;assert.exception = On - -; Issue a PHP warning for each failed assertion. (Overridden by assert.exception if active) -; http://php.net/assert.warning -;assert.warning = On - -; Don't bail out by default. -; http://php.net/assert.bail -;assert.bail = Off - -; User-function to be called if an assertion fails. -; http://php.net/assert.callback -;assert.callback = 0 - -[COM] -; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs -; http://php.net/com.typelib-file -;com.typelib_file = - -; allow Distributed-COM calls -; http://php.net/com.allow-dcom -;com.allow_dcom = true - -; autoregister constants of a component's typlib on com_load() -; http://php.net/com.autoregister-typelib -;com.autoregister_typelib = true - -; register constants casesensitive -; http://php.net/com.autoregister-casesensitive -;com.autoregister_casesensitive = false - -; show warnings on duplicate constant registrations -; http://php.net/com.autoregister-verbose -;com.autoregister_verbose = true - -; The default character set code-page to use when passing strings to and from COM objects. -; Default: system ANSI code page -;com.code_page= - -; The version of the .NET framework to use. The value of the setting are the first three parts -; of the framework's version number, separated by dots, and prefixed with "v", e.g. "v4.0.30319". -;com.dotnet_version= - -[mbstring] -; language for internal character representation. -; This affects mb_send_mail() and mbstring.detect_order. -; http://php.net/mbstring.language -;mbstring.language = Japanese - -; Use of this INI entry is deprecated, use global internal_encoding instead. -; internal/script encoding. -; Some encoding cannot work as internal encoding. (e.g. SJIS, BIG5, ISO-2022-*) -; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. -; The precedence is: default_charset < internal_encoding < iconv.internal_encoding -;mbstring.internal_encoding = - -; Use of this INI entry is deprecated, use global input_encoding instead. -; http input encoding. -; mbstring.encoding_translation = On is needed to use this setting. -; If empty, default_charset or input_encoding or mbstring.input is used. -; The precedence is: default_charset < input_encoding < mbstring.http_input -; http://php.net/mbstring.http-input -;mbstring.http_input = - -; Use of this INI entry is deprecated, use global output_encoding instead. -; http output encoding. -; mb_output_handler must be registered as output buffer to function. -; If empty, default_charset or output_encoding or mbstring.http_output is used. -; The precedence is: default_charset < output_encoding < mbstring.http_output -; To use an output encoding conversion, mbstring's output handler must be set -; otherwise output encoding conversion cannot be performed. -; http://php.net/mbstring.http-output -;mbstring.http_output = - -; enable automatic encoding translation according to -; mbstring.internal_encoding setting. Input chars are -; converted to internal encoding by setting this to On. -; Note: Do _not_ use automatic encoding translation for -; portable libs/applications. -; http://php.net/mbstring.encoding-translation -;mbstring.encoding_translation = Off - -; automatic encoding detection order. -; "auto" detect order is changed according to mbstring.language -; http://php.net/mbstring.detect-order -;mbstring.detect_order = auto - -; substitute_character used when character cannot be converted -; one from another -; http://php.net/mbstring.substitute-character -;mbstring.substitute_character = none - -; Enable strict encoding detection. -;mbstring.strict_detection = Off - -; This directive specifies the regex pattern of content types for which mb_output_handler() -; is activated. -; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml) -;mbstring.http_output_conv_mimetype= - -; This directive specifies maximum stack depth for mbstring regular expressions. It is similar -; to the pcre.recursion_limit for PCRE. -;mbstring.regex_stack_limit=100000 - -; This directive specifies maximum retry count for mbstring regular expressions. It is similar -; to the pcre.backtrack_limit for PCRE. -;mbstring.regex_retry_limit=1000000 - -[gd] -; Tell the jpeg decode to ignore warnings and try to create -; a gd image. The warning will then be displayed as notices -; disabled by default -; http://php.net/gd.jpeg-ignore-warning -;gd.jpeg_ignore_warning = 1 - -[exif] -; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. -; With mbstring support this will automatically be converted into the encoding -; given by corresponding encode setting. When empty mbstring.internal_encoding -; is used. For the decode settings you can distinguish between motorola and -; intel byte order. A decode setting cannot be empty. -; http://php.net/exif.encode-unicode -;exif.encode_unicode = ISO-8859-15 - -; http://php.net/exif.decode-unicode-motorola -;exif.decode_unicode_motorola = UCS-2BE - -; http://php.net/exif.decode-unicode-intel -;exif.decode_unicode_intel = UCS-2LE - -; http://php.net/exif.encode-jis -;exif.encode_jis = - -; http://php.net/exif.decode-jis-motorola -;exif.decode_jis_motorola = JIS - -; http://php.net/exif.decode-jis-intel -;exif.decode_jis_intel = JIS - -[Tidy] -; The path to a default tidy configuration file to use when using tidy -; http://php.net/tidy.default-config -;tidy.default_config = /usr/local/lib/php/default.tcfg - -; Should tidy clean and repair output automatically? -; WARNING: Do not use this option if you are generating non-html content -; such as dynamic images -; http://php.net/tidy.clean-output -tidy.clean_output = Off - -[soap] -; Enables or disables WSDL caching feature. -; http://php.net/soap.wsdl-cache-enabled -soap.wsdl_cache_enabled=1 - -; Sets the directory name where SOAP extension will put cache files. -; http://php.net/soap.wsdl-cache-dir -; SEE [app_limits] - -; (time to live) Sets the number of second while cached file will be used -; instead of original one. -; http://php.net/soap.wsdl-cache-ttl -soap.wsdl_cache_ttl=86400 - -; Sets the size of the cache limit. (Max. number of WSDL files to cache) -soap.wsdl_cache_limit = 5 - -[sysvshm] -; A default size of the shared memory segment -;sysvshm.init_mem = 10000 - -[ldap] -; Sets the maximum number of open links or -1 for unlimited. -ldap.max_links = -1 - -[dba] -;dba.default_handler= - -[opcache] -; Determines if Zend OPCache is enabled -; SEE [app_limits] - -; Determines if Zend OPCache is enabled for the CLI version of PHP -;opcache.enable_cli=0 - -; The OPcache shared memory storage size. -;opcache.memory_consumption=128 - -; The amount of memory for interned strings in Mbytes. -;opcache.interned_strings_buffer=8 - -; The maximum number of keys (scripts) in the OPcache hash table. -; Only numbers between 200 and 1000000 are allowed. -;opcache.max_accelerated_files=10000 - -; The maximum percentage of "wasted" memory until a restart is scheduled. -;opcache.max_wasted_percentage=5 - -; When this directive is enabled, the OPcache appends the current working -; directory to the script key, thus eliminating possible collisions between -; files with the same name (basename). Disabling the directive improves -; performance, but may break existing applications. -;opcache.use_cwd=1 - -; When disabled, you must reset the OPcache manually or restart the -; webserver for changes to the filesystem to take effect. -;opcache.validate_timestamps=1 - -; How often (in seconds) to check file timestamps for changes to the shared -; memory storage allocation. ("1" means validate once per second, but only -; once per request. "0" means always validate) -;opcache.revalidate_freq=2 - -; Enables or disables file search in include_path optimization -;opcache.revalidate_path=0 - -; If disabled, all PHPDoc comments are dropped from the code to reduce the -; size of the optimized code. -;opcache.save_comments=1 - -; If enabled, compilation warnings (including notices and deprecations) will -; be recorded and replayed each time a file is included. Otherwise, compilation -; warnings will only be emitted when the file is first cached. -;opcache.record_warnings=0 - -; Allow file existence override (file_exists, etc.) performance feature. -;opcache.enable_file_override=0 - -; A bitmask, where each bit enables or disables the appropriate OPcache -; passes -;opcache.optimization_level=0x7FFFBFFF - -;opcache.dups_fix=0 - -; The location of the OPcache blacklist file (wildcards allowed). -; Each OPcache blacklist file is a text file that holds the names of files -; that should not be accelerated. The file format is to add each filename -; to a new line. The filename may be a full path or just a file prefix -; (i.e., /var/www/x blacklists all the files and directories in /var/www -; that start with 'x'). Line starting with a ; are ignored (comments). -;opcache.blacklist_filename= - -; Allows exclusion of large files from being cached. By default all files -; are cached. -;opcache.max_file_size=0 - -; Check the cache checksum each N requests. -; The default value of "0" means that the checks are disabled. -;opcache.consistency_checks=0 - -; How long to wait (in seconds) for a scheduled restart to begin if the cache -; is not being accessed. -;opcache.force_restart_timeout=180 - -; OPcache error_log file name. Empty string assumes "stderr". -;opcache.error_log= - -; All OPcache errors go to the Web server log. -; By default, only fatal errors (level 0) or errors (level 1) are logged. -; You can also enable warnings (level 2), info messages (level 3) or -; debug messages (level 4). -;opcache.log_verbosity_level=1 - -; Preferred Shared Memory back-end. Leave empty and let the system decide. -;opcache.preferred_memory_model= - -; Protect the shared memory from unexpected writing during script execution. -; Useful for internal debugging only. -;opcache.protect_memory=0 - -; Allows calling OPcache API functions only from PHP scripts which path is -; started from specified string. The default "" means no restriction -;opcache.restrict_api= - -; Mapping base of shared memory segments (for Windows only). All the PHP -; processes have to map shared memory into the same address space. This -; directive allows to manually fix the "Unable to reattach to base address" -; errors. -;opcache.mmap_base= - -; Facilitates multiple OPcache instances per user (for Windows only). All PHP -; processes with the same cache ID and user share an OPcache instance. -;opcache.cache_id= - -; Enables and sets the second level cache directory. -; It should improve performance when SHM memory is full, at server restart or -; SHM reset. The default "" disables file based caching. -; SEE [app_temp] - -; Enables or disables opcode caching in shared memory. -;opcache.file_cache_only=0 - -; Enables or disables checksum validation when script loaded from file cache. -;opcache.file_cache_consistency_checks=1 - -; Implies opcache.file_cache_only=1 for a certain process that failed to -; reattach to the shared memory (for Windows only). Explicitly enabled file -; cache is required. -;opcache.file_cache_fallback=1 - -; Enables or disables copying of PHP code (text segment) into HUGE PAGES. -; This should improve performance, but requires appropriate OS configuration. -;opcache.huge_code_pages=1 - -; Validate cached file permissions. -;opcache.validate_permission=0 - -; Prevent name collisions in chroot'ed environment. -;opcache.validate_root=0 - -; If specified, it produces opcode dumps for debugging different stages of -; optimizations. -;opcache.opt_debug_level=0 - -; Specifies a PHP script that is going to be compiled and executed at server -; start-up. -; http://php.net/opcache.preload -;opcache.preload= - -; Preloading code as root is not allowed for security reasons. This directive -; facilitates to let the preloading to be run as another user. -; http://php.net/opcache.preload_user -;opcache.preload_user= - -; Prevents caching files that are less than this number of seconds old. It -; protects from caching of incompletely updated files. In case all file updates -; on your site are atomic, you may increase performance by setting it to "0". -;opcache.file_update_protection=2 - -; Absolute path used to store shared lockfiles (for *nix only). -;opcache.lockfile_path=/tmp - -[curl] -; A default value for the CURLOPT_CAINFO option. This is required to be an -; absolute path. -; SEE [app_limits] - -[openssl] -; The location of a Certificate Authority (CA) file on the local filesystem -; to use when verifying the identity of SSL/TLS peers. Most users should -; not specify a value for this directive as PHP will attempt to use the -; OS-managed cert stores in its absence. If specified, this value may still -; be overridden on a per-stream basis via the "cafile" SSL stream context -; option. -; SEE [app_limits] - -; If openssl.cafile is not specified or if the CA file is not found, the -; directory pointed to by openssl.capath is searched for a suitable -; certificate. This value must be a correctly hashed certificate directory. -; Most users should not specify a value for this directive as PHP will -; attempt to use the OS-managed cert stores in its absence. If specified, -; this value may still be overridden on a per-stream basis via the "capath" -; SSL stream context option. -;openssl.capath= - -[ffi] -; FFI API restriction. Possible values: -; "preload" - enabled in CLI scripts and preloaded files (default) -; "false" - always disabled -; "true" - always enabled -;ffi.enable=preload - -; List of headers files to preload, wildcard patterns allowed. -;ffi.preload= diff --git a/update-production.ps1 b/update-production.ps1 deleted file mode 100644 index cbbf95e..0000000 --- a/update-production.ps1 +++ /dev/null @@ -1,66 +0,0 @@ -Param( - [Parameter()] - [string] - $env='prod' -) -if ($env -eq $null) { - $env = read-host -Prompt "Please enter an environment (local, dev, or prod)" -} - -function Menu ($object, $prompt) { - if (!$object) { Throw 'Must provide an object.' } - $ok = $false - Write-Host '' - do { - if ($prompt) { Write-Host $prompt } - for ($i = 0; $i -lt $object.count; $i++) { - Write-Host $i`. $object[$i] - } - Write-Host '' - $answer = Read-Host - if ($answer -in 0..($object.count-1)) { - $object[$answer] - $ok = $true - } else { - Write-Host 'Not an option!' -ForegroundColor Red - Write-Host '' - } - } while (!$ok) -} - -Write-Host "git pull" -git pull - -Write-Host "`nMost Recent Tags:" -$recentTags = git tag --sort=version:refname | select -Last 5 - -$tag = menu -object $recentTags -prompt 'Which tag do you want to check out?' - -Write-Host "git checkout tags/$tag" -git checkout tags/$tag - -Write-Host "git submodule sync" -git submodule sync - -Write-Host "git submodule update" -git submodule update - -Write-Host "update environment files" -Copy-Item -Path app/config/environment-$env.json -Destination app/config/environment.json -Copy-Item -Path composer-$env.json -Destination composer.json -Copy-Item -Path www/web-$env.config -Destination www/web.config - -#UPDATE VERSION -Write-Host "Set versions" -$status = git status | Select -first 1 -$statusWords = -split $status -$apiVersion = $statusWords[-1] -Write-Host "Version: $apiVersion" - -$jsonBase = @{} -$jsonBase.Add("version",$apiVersion) -$jsonBase.Add("inherit",$true) -$jsonBase | ConvertTo-Json | Out-File "version.json" - -Write-Host "composer update" -composer update \ No newline at end of file diff --git a/www/index.php b/www/index.php index 2bdfbf2..10498f7 100644 --- a/www/index.php +++ b/www/index.php @@ -1,7 +1,6 @@ runApp(); \ No newline at end of file +$framework = new \gcgov\framework\framework(); +echo $framework->runApp(); diff --git a/www/web-local.config b/www/web-local.config deleted file mode 100644 index a062fba..0000000 --- a/www/web-local.config +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/www/web-prod.config b/www/web-prod.config deleted file mode 100644 index ade16b0..0000000 --- a/www/web-prod.config +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 38cff36a973cea07f73538f2c3445ed185653b09 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 13:01:10 +0000 Subject: [PATCH 02/17] v7: single committed environment.json; env-var-driven environments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to gcgov/framework v7: the gf CLI no longer reads committed environment-{env}.json variants, so the template ships ONE committed app/config/environment.json and the environment is decided entirely by which variable values the process is given. - environment-local.json / environment-prod.json deleted; environment.json committed (removed from .gitignore). Identity fields use the token-as-default pattern (%env(default:{app_*}:VAR)% — `gf setup` bakes the dev value, env vars override per environment); secrets and Mongo coordinates are hard %env(VAR)% references that fail loudly when unset. This also fixes the clean-checkout Docker image shipping no environment.json at all (it was gitignored and only created by the old `gf env` copy step). - rootUrl AND basePath are now per-environment (APP_ROOT_URL, APP_BASE_PATH): app/router.php derives its route prefix at runtime from config::getEnvironmentConfig()->getBasePath() instead of a baked {app_base_path} token, and jwtAuth tokenIssuedBy/tokenPermittedFor reference the same variables so they can never desync from routing. RouterTest updated to the runtime-derived /api prefix. - New app/config/prod.env.example: copy to gitignored app/config/prod.env to enable `gf db:restore --from=prod` / `gf db:run --env=prod` / `gf env prod` (variant overlay reads). APP_TYPE=prod feeds the db:restore prod guard. .gitignore/.dockerignore exclude app/config/*.env so a real prod.env never reaches git or image layers. - .env.example: commented identity overrides; new tests/Unit/ConfigFilesTest.php pins that every hard %env() reference in environment.json/app.json is covered by .env.example and prod.env.example (the completeness contract). - composer.json: gcgov/framework ^v7.0. CI docker-build job asserts the built image contains app/config/environment.json. - README.md / DOCKER.md rewritten for the no-activation model, including a production configuration checklist. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JWacdhEDN9upUvYeagNKru --- .dockerignore | 1 + .env.example | 10 ++++- .github/workflows/ci.yml | 2 + .gitignore | 3 +- DOCKER.md | 25 ++++++++--- README.md | 25 +++++++---- app/config/environment-local.json | 44 -------------------- app/config/environment-prod.json | 44 -------------------- app/config/environment.json | 44 ++++++++++++++++++++ app/config/prod.env.example | 35 ++++++++++++++++ app/router.php | 15 +++---- composer.json | 2 +- tests/Unit/ConfigFilesTest.php | 69 +++++++++++++++++++++++++++++++ tests/Unit/RouterTest.php | 6 ++- 14 files changed, 210 insertions(+), 115 deletions(-) delete mode 100644 app/config/environment-local.json delete mode 100644 app/config/environment-prod.json create mode 100644 app/config/environment.json create mode 100644 app/config/prod.env.example create mode 100644 tests/Unit/ConfigFilesTest.php diff --git a/.dockerignore b/.dockerignore index 44aa3c6..a76b7a4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,6 +5,7 @@ node_modules .env .env.local secrets +app/config/*.env *.md .idea .phpunit.cache diff --git a/.env.example b/.env.example index c8c4432..9dc0618 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,14 @@ CORS_ORIGIN_SWAGGER=http://localhost:8081 MONGO_URI=mongodb://mongodb:27017 MONGO_DATABASE=app +# ---- Identity overrides (optional locally) ---- +# environment.json bakes the dev values for these at `gf setup` time via +# %env(default:...)% — uncomment to override without editing config. +# APP_TYPE=local +# APP_SERVER_NAME=app.local +# APP_ROOT_URL=https://app.local +# APP_BASE_PATH=/api/ + # ---- Microsoft OAuth (leave blank if unused) ---- MICROSOFT_CLIENT_SECRET= @@ -31,6 +39,6 @@ PAYJUNCTION_PASSWORD= PAYJUNCTION_API_KEY= # ---- Production: file-based secrets (Docker/Swarm/Kubernetes) ---- -# Prefer mounting secrets as files and referencing them in environment-prod.json +# Prefer mounting secrets as files and referencing them in environment.json # with %env(trim:file:MONGO_URI_FILE)%. Example: # MONGO_URI_FILE=/run/secrets/mongo_uri diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a3459f..0c111c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,3 +143,5 @@ jobs: - uses: actions/checkout@v4 - name: Build the production image run: docker build --target prod -t framework-app-template:ci . + - name: Image contains the committed environment.json + run: docker run --rm --entrypoint test framework-app-template:ci -f app/config/environment.json diff --git a/.gitignore b/.gitignore index 8771399..b9cbaad 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,13 @@ composer.phar /vendor/ /.idea version.json -app/config/environment.json # Local secrets — never commit real values (see DOCKER.md) .env .env.local /secrets/ +# per-environment variable overlays for gf db:*/env (keep prod.env.example committed) +app/config/*.env .phpunit.cache/ .phpunit.result.cache diff --git a/DOCKER.md b/DOCKER.md index 716a4e1..ded2f49 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -19,8 +19,8 @@ docker compose --profile dev up --build # → API on http://localhost:8080 ``` -The `dev` profile also starts a throwaway MongoDB at `mongodb:27017`, which the default -`environment-local.json` points at (`%env(default:mongodb://mongodb:27017:MONGO_URI)%`). +The `dev` profile also starts a throwaway MongoDB at `mongodb:27017`, which `.env.example`'s +`MONGO_URI` points at. Run framework CLI routes and tooling inside the PHP container: @@ -31,9 +31,22 @@ docker compose exec php composer ci ``` Before first run you still scaffold the identity/URL placeholders with `gf setup` (it replaces -the `{app_*}` / `{prod_app_*}` tokens in the config, nginx, and compose files). Environment -selection is unchanged: `gf env local` / `gf env prod` copy the matching -`environment-{name}.json` into place. +the `{app_*}` tokens in the config, nginx, and compose files — they become the baked +`default:` fallbacks inside `environment.json`'s `%env(...)%` references). + +**Environment selection is the environment itself.** The committed +`app/config/environment.json` is the only config file; the variable values the container is +given decide whether it behaves as local, prod, or anything else. `gf env` validates that +resolution (`gf env` for the active environment, `gf env prod` for the +`app/config/prod.env` overlay used by `gf db:*` commands). + +### Production configuration checklist + +A prod container must supply (hard `%env()` references — missing ones fail loudly naming the +variable): `MONGO_URI`, `MONGO_DATABASE`, `MICROSOFT_CLIENT_SECRET`, `PAYJUNCTION_PASSWORD`, +`PAYJUNCTION_API_KEY` — plus the identity overrides `APP_TYPE=prod`, `APP_SERVER_NAME`, +`APP_ROOT_URL`, `APP_BASE_PATH`, and the `APP_REDIRECT_AFTER_*` urls. Prefer the `*_FILE` +secret pattern below for the secrets. --- @@ -49,7 +62,7 @@ A secret mounted as a file never appears in the process environment, so it is ** processor (the leading `trim:` strips the trailing newline): ```jsonc -// environment-prod.json +// app/config/environment.json "uri": "%env(trim:file:MONGO_URI_FILE)%" ``` diff --git a/README.md b/README.md index 64b79bd..f4c0a2e 100644 --- a/README.md +++ b/README.md @@ -19,21 +19,28 @@ syntax. `{app_title}`, `{app_root_url}`, `{app_base_path}`, `{app_redirect_after_login}`, `{app_redirect_after_logout}`, the `{app_microsoft_*}` client id/tenant/drive id, and the matching `{prod_app_*}` values for production. -3. Provide secrets as **environment variables**, not tokens. The config files reference them with - `%env(...)%` — for example `environment-prod.json` has - `"uri": "%env(MONGO_URI)%"` and `"clientSecret": "%env(MICROSOFT_CLIENT_SECRET)%"`. Copy - `.env.example` to `.env` and fill it in for local development; use Docker/Kubernetes secrets in - production. See **[DOCKER.md](DOCKER.md)** and the framework's +3. Provide secrets and per-environment values as **environment variables**, not tokens. The + committed `app/config/environment.json` references them with `%env(...)%` — e.g. + `"uri": "%env(MONGO_URI)%"`, `"clientSecret": "%env(MICROSOFT_CLIENT_SECRET)%"`, + `"basePath": "%env(default:...:APP_BASE_PATH)%"`. Whichever values the process environment + supplies *are* the environment — there is nothing to activate. Copy `.env.example` to `.env` + for local development; use container env / Docker/Kubernetes secrets in production. See + **[DOCKER.md](DOCKER.md)** and the framework's [environment-variables guide](https://github.com/gcgov/framework/blob/main/readme/environment-variables.md). -4. Activate an environment: `vendor/bin/gf env local` (or `gf env prod`) copies the matching - `environment-{name}.json` into place. -5. Run it: +4. Run it: ```bash cp .env.example .env docker compose --profile dev up --build # → http://localhost:8080 ``` -6. Test the `widget` module, then create your own models, controllers, and services. +5. Test the `widget` module, then create your own models, controllers, and services. + +### Working with production data + +Copy `app/config/prod.env.example` to `app/config/prod.env` (gitignored) and fill in the prod +values; validate it with `vendor/bin/gf env prod`. Then `gf db:restore --from=prod` and +`gf db:run --env=prod` resolve `environment.json` with that overlay — no prod config file ever +lives in the repo. ## Documentation diff --git a/app/config/environment-local.json b/app/config/environment-local.json deleted file mode 100644 index a5d6a90..0000000 --- a/app/config/environment-local.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "type": "local", - "serverName": "app.local", - "rootUrl": "{app_root_url}", - "basePath": "{app_base_path}", - "phpPath": "", - "mongoDatabases": [ - { - "default": true, - "database": "%env(default:app:MONGO_DATABASE)%", - "uri": "%env(default:mongodb://mongodb:27017:MONGO_URI)%", - "audit": false, - "include_meta": false, - "include_metaLabels": false, - "include_metaFields": false, - "logging": true, - "auditDatabaseName": "", - "auditDatabaseUri": "" - } - ], - "jwtAuth": { - "tokenIssuedBy": "{app_root_url}", - "tokenPermittedFor": "{app_base_path}", - "redirectAfterLoginUrl": "{app_redirect_after_login}", - "redirectAfterLogoutUrl": "{app_redirect_after_logout}" - }, - "appDictionary": { - "key": "value" - }, - "microsoft": { - "clientId": "{app_microsoft_client_id}", - "clientSecret": "%env(default::MICROSOFT_CLIENT_SECRET)%", - "tenant": "{app_microsoft_tenant}", - "driveId": "{app_microsoft_drive_id}", - "fromAddress": "{app_microsoft_default_from_address}" - }, - "payjunction": { - "username": "{app_payjunction_username}", - "password": "%env(default::PAYJUNCTION_PASSWORD)%", - "apiKey": "%env(default::PAYJUNCTION_API_KEY)%", - "terminalId": "{app_payjunction_terminal_id}", - "merchantId": "{app_payjunction_merchant_id}" - } -} diff --git a/app/config/environment-prod.json b/app/config/environment-prod.json deleted file mode 100644 index 3136418..0000000 --- a/app/config/environment-prod.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "type": "prod", - "serverName": "app.prod", - "rootUrl": "{prod_app_root_url}", - "basePath": "{prod_app_base_path}", - "phpPath": "", - "mongoDatabases": [ - { - "default": true, - "database": "%env(MONGO_DATABASE)%", - "uri": "%env(MONGO_URI)%", - "audit": false, - "include_meta": false, - "include_metaLabels": false, - "include_metaFields": false, - "logging": true, - "auditDatabaseName": "", - "auditDatabaseUri": "" - } - ], - "jwtAuth": { - "tokenIssuedBy": "{prod_app_root_url}", - "tokenPermittedFor": "{prod_app_base_path}", - "redirectAfterLoginUrl": "{prod_app_redirect_after_login}", - "redirectAfterLogoutUrl": "{prod_app_redirect_after_logout}" - }, - "appDictionary": { - "key": "value" - }, - "microsoft": { - "clientId": "{prod_app_microsoft_client_id}", - "clientSecret": "%env(MICROSOFT_CLIENT_SECRET)%", - "tenant": "{prod_app_microsoft_tenant}", - "driveId": "{prod_app_microsoft_drive_id}", - "fromAddress": "{prod_app_microsoft_default_from_address}" - }, - "payjunction": { - "username": "{prod_app_payjunction_username}", - "password": "%env(PAYJUNCTION_PASSWORD)%", - "apiKey": "%env(PAYJUNCTION_API_KEY)%", - "terminalId": "{prod_app_payjunction_terminal_id}", - "merchantId": "{prod_app_payjunction_merchant_id}" - } -} diff --git a/app/config/environment.json b/app/config/environment.json new file mode 100644 index 0000000..dda4904 --- /dev/null +++ b/app/config/environment.json @@ -0,0 +1,44 @@ +{ + "type": "%env(default:local:APP_TYPE)%", + "serverName": "%env(default:app.local:APP_SERVER_NAME)%", + "rootUrl": "%env(default:{app_root_url}:APP_ROOT_URL)%", + "basePath": "%env(default:{app_base_path}:APP_BASE_PATH)%", + "phpPath": "", + "mongoDatabases": [ + { + "default": true, + "database": "%env(MONGO_DATABASE)%", + "uri": "%env(MONGO_URI)%", + "audit": false, + "include_meta": false, + "include_metaLabels": false, + "include_metaFields": false, + "logging": true, + "auditDatabaseName": "", + "auditDatabaseUri": "" + } + ], + "jwtAuth": { + "tokenIssuedBy": "%env(default:{app_root_url}:APP_ROOT_URL)%", + "tokenPermittedFor": "%env(default:{app_base_path}:APP_BASE_PATH)%", + "redirectAfterLoginUrl": "%env(default:{app_redirect_after_login}:APP_REDIRECT_AFTER_LOGIN)%", + "redirectAfterLogoutUrl": "%env(default:{app_redirect_after_logout}:APP_REDIRECT_AFTER_LOGOUT)%" + }, + "appDictionary": { + "key": "value" + }, + "microsoft": { + "clientId": "%env(default:{app_microsoft_client_id}:MICROSOFT_CLIENT_ID)%", + "clientSecret": "%env(MICROSOFT_CLIENT_SECRET)%", + "tenant": "%env(default:{app_microsoft_tenant}:MICROSOFT_TENANT)%", + "driveId": "%env(default:{app_microsoft_drive_id}:MICROSOFT_DRIVE_ID)%", + "fromAddress": "%env(default:{app_microsoft_default_from_address}:MICROSOFT_FROM_ADDRESS)%" + }, + "payjunction": { + "username": "%env(default:{app_payjunction_username}:PAYJUNCTION_USERNAME)%", + "password": "%env(PAYJUNCTION_PASSWORD)%", + "apiKey": "%env(PAYJUNCTION_API_KEY)%", + "terminalId": "%env(default:{app_payjunction_terminal_id}:PAYJUNCTION_TERMINAL_ID)%", + "merchantId": "%env(default:{app_payjunction_merchant_id}:PAYJUNCTION_MERCHANT_ID)%" + } +} diff --git a/app/config/prod.env.example b/app/config/prod.env.example new file mode 100644 index 0000000..d90154b --- /dev/null +++ b/app/config/prod.env.example @@ -0,0 +1,35 @@ +# Copy this file to app/config/prod.env (gitignored) to let the gf CLI read the PROD +# environment's configuration without activating anything: +# +# gf env prod # validate that this overlay fully resolves environment.json +# gf db:restore --from=prod # dump prod -> restore into your local databases +# gf db:run db/fix.js --env=prod +# +# IMPORTANT: this file must define EVERY environment-specific variable. A variable +# missing here silently falls back to your local value during a variant read (gf +# detects the same-database case, but not everything). Validate with `gf env prod` +# before trusting it. NEVER commit the real prod.env. + +# Drives the db:restore prod guard (restoring INTO type=prod is refused without --allow-prod) +APP_TYPE=prod + +APP_SERVER_NAME=app.prod +APP_ROOT_URL=https://app.example.gov +APP_BASE_PATH=/api/ +APP_REDIRECT_AFTER_LOGIN= +APP_REDIRECT_AFTER_LOGOUT= + +MONGO_URI= +MONGO_DATABASE= + +MICROSOFT_CLIENT_ID= +MICROSOFT_CLIENT_SECRET= +MICROSOFT_TENANT= +MICROSOFT_DRIVE_ID= +MICROSOFT_FROM_ADDRESS= + +PAYJUNCTION_USERNAME= +PAYJUNCTION_PASSWORD= +PAYJUNCTION_API_KEY= +PAYJUNCTION_TERMINAL_ID= +PAYJUNCTION_MERCHANT_ID= diff --git a/app/router.php b/app/router.php index a7c646b..2d82b02 100644 --- a/app/router.php +++ b/app/router.php @@ -35,17 +35,18 @@ public function getRoutes() : array { /** @var \gcgov\framework\models\route[] $routes */ $routes = []; - //if your app will not run at the root of the domain, add the relative url to the app: ie: if your site will serve from http://example.com/api, $routePrepend="/api"; - $routePrepend = '/{app_base_path}'; + //the base path comes from environment.json (env var APP_BASE_PATH), so it can differ per + //environment with no code change; getBasePath() returns '/api' style ('/' at domain root) + $routePrepend = rtrim( \gcgov\framework\config::getEnvironmentConfig()->getBasePath(), '/' ); //WIDGETS - $routes[] = new route( 'GET', $routePrepend.'widgets', '\app\controllers\widget', 'getAll', true, [ 'Widget.Read' ] ); - $routes[] = new route( 'GET', $routePrepend.'widgets/{_id}', '\app\controllers\widget', 'getOne', true, [ 'Widget.Read' ] ); - $routes[] = new route( 'POST', $routePrepend.'widgets/{_id}', '\app\controllers\widget', 'save', true, [ 'Widget.Read', 'Widget.Write' ] ); - $routes[] = new route( 'DELETE', $routePrepend.'widgets/{_id}', '\app\controllers\widget', 'delete', true, [ 'Widget.Read', 'Widget.Write' ] ); + $routes[] = new route( 'GET', $routePrepend.'/widgets', '\app\controllers\widget', 'getAll', true, [ 'Widget.Read' ] ); + $routes[] = new route( 'GET', $routePrepend.'/widgets/{_id}', '\app\controllers\widget', 'getOne', true, [ 'Widget.Read' ] ); + $routes[] = new route( 'POST', $routePrepend.'/widgets/{_id}', '\app\controllers\widget', 'save', true, [ 'Widget.Read', 'Widget.Write' ] ); + $routes[] = new route( 'DELETE', $routePrepend.'/widgets/{_id}', '\app\controllers\widget', 'delete', true, [ 'Widget.Read', 'Widget.Write' ] ); //CLI example - //to run in command line: `/app/cli/local.bat /cli/widgets` + //to run in command line: `vendor/bin/gf cli /cli/widgets` $routes[] = new route( 'CLI', '/cli/widgets', '\app\controllers\widget', 'getAll', false ); return $routes; diff --git a/composer.json b/composer.json index 835150c..7b64f96 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "mongodb/mongodb": "^2.1", "phpmailer/phpmailer": "^6.2", "zircote/swagger-php": "^6.1", - "gcgov/framework": "^v6.2", + "gcgov/framework": "^v7.0", "gcgov/framework-service-gcgov-cron-monitor": "^v1.1", "gcgov/framework-service-documentation": "^1.1", "gcgov/framework-service-auth-oauth-server": "^2.1", diff --git a/tests/Unit/ConfigFilesTest.php b/tests/Unit/ConfigFilesTest.php new file mode 100644 index 0000000..ecc7076 --- /dev/null +++ b/tests/Unit/ConfigFilesTest.php @@ -0,0 +1,69 @@ + */ + private function exampleOverlay( string $relativePath ): array { + return dotEnvLoader::parseFile( self::ROOT . '/' . $relativePath ); + } + + + public function testEnvironmentJsonResolvesWithDevExampleEnv(): void { + $resolved = envVarResolver::resolveJson( + (string)file_get_contents( self::ROOT . '/app/config/environment.json' ), + 'app/config/environment.json', + $this->exampleOverlay( '.env.example' ) + ); + + $environmentConfig = environmentConfig::jsonDeserialize( $resolved ); + $this->assertSame( 'local', $environmentConfig->type ); + $this->assertSame( 'mongodb://mongodb:27017', $environmentConfig->mongoDatabases[ 0 ]->uri ); + $this->assertSame( 'app', $environmentConfig->mongoDatabases[ 0 ]->database ); + } + + + public function testEnvironmentJsonResolvesWithProdExampleOverlay(): void { + $resolved = envVarResolver::resolveJson( + (string)file_get_contents( self::ROOT . '/app/config/environment.json' ), + 'app/config/environment.json', + $this->exampleOverlay( 'app/config/prod.env.example' ) + ); + + $environmentConfig = environmentConfig::jsonDeserialize( $resolved ); + $this->assertSame( 'prod', $environmentConfig->type, 'prod.env.example must set APP_TYPE=prod — the db:restore guard depends on it' ); + $this->assertSame( '/api', $environmentConfig->getBasePath() ); + } + + + public function testAppJsonResolvesWithDevExampleEnv(): void { + $resolved = envVarResolver::resolveJson( + (string)file_get_contents( self::ROOT . '/app/config/app.json' ), + 'app/config/app.json', + $this->exampleOverlay( '.env.example' ) + ); + + $appConfig = appConfig::jsonDeserialize( $resolved ); + $this->assertNotNull( $appConfig->email ); + } + +} diff --git a/tests/Unit/RouterTest.php b/tests/Unit/RouterTest.php index cef0842..5d29571 100644 --- a/tests/Unit/RouterTest.php +++ b/tests/Unit/RouterTest.php @@ -29,9 +29,11 @@ public function testGetRoutesReturnsFiveRoutes(): void { } public function testWidgetGetAllRoute(): void { + // tests/bootstrap.php seeds environmentConfig with basePath 'api', so the + // runtime-derived route prefix is '/api' $routes = ( new router() )->getRoutes(); $this->assertSame( 'GET', $routes[0]->httpMethod ); - $this->assertSame( '/{app_base_path}widgets', $routes[0]->route ); + $this->assertSame( '/api/widgets', $routes[0]->route ); $this->assertSame( 'getAll', $routes[0]->method ); $this->assertTrue( $routes[0]->authentication ); $this->assertSame( [ 'Widget.Read' ], $routes[0]->requiredRoles ); @@ -40,7 +42,7 @@ public function testWidgetGetAllRoute(): void { public function testWidgetGetOneRoute(): void { $routes = ( new router() )->getRoutes(); $this->assertSame( 'GET', $routes[1]->httpMethod ); - $this->assertSame( '/{app_base_path}widgets/{_id}', $routes[1]->route ); + $this->assertSame( '/api/widgets/{_id}', $routes[1]->route ); $this->assertSame( 'getOne', $routes[1]->method ); } From 9ff261867706cf8522f5c2c4b86d45caa264c61d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 13:20:27 +0000 Subject: [PATCH 03/17] v7: merge app.json + environment.json into root config.json Companion to gcgov/framework v7's unified-config change: the app ships ONE committed config file at the application root, and the per-variant overlay files live at the root too. - New /config.json merges the former app/config/app.json (app, email, settings sections) and app/config/environment.json (everything else); app/config/ is deleted. Secrets and Mongo coordinates stay hard %env() references; identity values keep the token-as-default pattern. - prod.env.example moves to the application root (overlay files are now {root}/{name}.env), and gains SMTP_USERNAME/SMTP_PASSWORD entries so the example stays a complete variable inventory. - Root-level placement verified collision-free before choosing it over a /config directory: docker compose reads only .env, glob('*.env') excludes dotfiles and *.env.example, and the gitignore (/*.env) and dockerignore (*.env) patterns keep real overlays out of git and image layers while prod.env.example stays committed. - app/router.php uses the flattened config::getBasePath() accessor (config::getEnvironmentConfig() no longer exists in v7). - tests/bootstrap.php seeds the new unifiedConfig; ConfigFilesTest pins the unified file against both example env files (including the merged app/email/settings sections). CI docker smoke asserts /config.json in the built image. README/DOCKER.md/.env.example updated. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JWacdhEDN9upUvYeagNKru --- .dockerignore | 2 +- .env.example | 4 +- .github/workflows/ci.yml | 4 +- .gitignore | 4 +- DOCKER.md | 8 +-- README.md | 6 +-- app/config/app.json | 15 ------ app/router.php | 4 +- app/config/environment.json => config.json | 13 +++++ .../prod.env.example => prod.env.example | 9 ++-- tests/Unit/ConfigFilesTest.php | 53 ++++++++----------- tests/bootstrap.php | 8 +-- 12 files changed, 61 insertions(+), 69 deletions(-) delete mode 100644 app/config/app.json rename app/config/environment.json => config.json (84%) rename app/config/prod.env.example => prod.env.example (82%) diff --git a/.dockerignore b/.dockerignore index a76b7a4..ab41934 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,7 +5,7 @@ node_modules .env .env.local secrets -app/config/*.env +*.env *.md .idea .phpunit.cache diff --git a/.env.example b/.env.example index 9dc0618..a044c7c 100644 --- a/.env.example +++ b/.env.example @@ -20,7 +20,7 @@ MONGO_URI=mongodb://mongodb:27017 MONGO_DATABASE=app # ---- Identity overrides (optional locally) ---- -# environment.json bakes the dev values for these at `gf setup` time via +# config.json bakes the dev values for these at `gf setup` time via # %env(default:...)% — uncomment to override without editing config. # APP_TYPE=local # APP_SERVER_NAME=app.local @@ -39,6 +39,6 @@ PAYJUNCTION_PASSWORD= PAYJUNCTION_API_KEY= # ---- Production: file-based secrets (Docker/Swarm/Kubernetes) ---- -# Prefer mounting secrets as files and referencing them in environment.json +# Prefer mounting secrets as files and referencing them in config.json # with %env(trim:file:MONGO_URI_FILE)%. Example: # MONGO_URI_FILE=/run/secrets/mongo_uri diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c111c3..d15d25d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,5 +143,5 @@ jobs: - uses: actions/checkout@v4 - name: Build the production image run: docker build --target prod -t framework-app-template:ci . - - name: Image contains the committed environment.json - run: docker run --rm --entrypoint test framework-app-template:ci -f app/config/environment.json + - name: Image contains the committed config.json + run: docker run --rm --entrypoint test framework-app-template:ci -f config.json diff --git a/.gitignore b/.gitignore index b9cbaad..3a652b1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ version.json .env .env.local /secrets/ -# per-environment variable overlays for gf db:*/env (keep prod.env.example committed) -app/config/*.env +# per-environment variable overlays for gf db:*/env at the app root (prod.env.example stays committed) +/*.env .phpunit.cache/ .phpunit.result.cache diff --git a/DOCKER.md b/DOCKER.md index ded2f49..dfbde5c 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -32,13 +32,13 @@ docker compose exec php composer ci Before first run you still scaffold the identity/URL placeholders with `gf setup` (it replaces the `{app_*}` tokens in the config, nginx, and compose files — they become the baked -`default:` fallbacks inside `environment.json`'s `%env(...)%` references). +`default:` fallbacks inside `config.json`'s `%env(...)%` references). **Environment selection is the environment itself.** The committed -`app/config/environment.json` is the only config file; the variable values the container is +root-level `config.json` is the only config file (app + environment sections merged); the variable values the container is given decide whether it behaves as local, prod, or anything else. `gf env` validates that resolution (`gf env` for the active environment, `gf env prod` for the -`app/config/prod.env` overlay used by `gf db:*` commands). +`prod.env` overlay used by `gf db:*` commands). ### Production configuration checklist @@ -62,7 +62,7 @@ A secret mounted as a file never appears in the process environment, so it is ** processor (the leading `trim:` strips the trailing newline): ```jsonc -// app/config/environment.json +// config.json "uri": "%env(trim:file:MONGO_URI_FILE)%" ``` diff --git a/README.md b/README.md index f4c0a2e..c0a2ade 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ syntax. `{app_redirect_after_logout}`, the `{app_microsoft_*}` client id/tenant/drive id, and the matching `{prod_app_*}` values for production. 3. Provide secrets and per-environment values as **environment variables**, not tokens. The - committed `app/config/environment.json` references them with `%env(...)%` — e.g. + committed root `config.json` references them with `%env(...)%` — e.g. `"uri": "%env(MONGO_URI)%"`, `"clientSecret": "%env(MICROSOFT_CLIENT_SECRET)%"`, `"basePath": "%env(default:...:APP_BASE_PATH)%"`. Whichever values the process environment supplies *are* the environment — there is nothing to activate. Copy `.env.example` to `.env` @@ -37,9 +37,9 @@ syntax. ### Working with production data -Copy `app/config/prod.env.example` to `app/config/prod.env` (gitignored) and fill in the prod +Copy `prod.env.example` to `prod.env` at the application root (gitignored) and fill in the prod values; validate it with `vendor/bin/gf env prod`. Then `gf db:restore --from=prod` and -`gf db:run --env=prod` resolve `environment.json` with that overlay — no prod config file ever +`gf db:run --env=prod` resolve `config.json` with that overlay — no prod config file ever lives in the repo. ## Documentation diff --git a/app/config/app.json b/app/config/app.json deleted file mode 100644 index 68a6af7..0000000 --- a/app/config/app.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "app": { - "title": "{app_title}", - "guid": "{app_guid}" - }, - "email": { - "fromAddress": "{app_smtp_sendmail_from_address}", - "fromName": "{app_smtp_sendmail_from_name}", - "SMTPUsername": "%env(default::SMTP_USERNAME)%", - "SMTPPassword": "%env(default::SMTP_PASSWORD)%" - }, - "settings": { - "useSession": false - } -} diff --git a/app/router.php b/app/router.php index 2d82b02..1219dda 100644 --- a/app/router.php +++ b/app/router.php @@ -35,9 +35,9 @@ public function getRoutes() : array { /** @var \gcgov\framework\models\route[] $routes */ $routes = []; - //the base path comes from environment.json (env var APP_BASE_PATH), so it can differ per + //the base path comes from config.json (env var APP_BASE_PATH), so it can differ per //environment with no code change; getBasePath() returns '/api' style ('/' at domain root) - $routePrepend = rtrim( \gcgov\framework\config::getEnvironmentConfig()->getBasePath(), '/' ); + $routePrepend = rtrim( \gcgov\framework\config::getBasePath(), '/' ); //WIDGETS $routes[] = new route( 'GET', $routePrepend.'/widgets', '\app\controllers\widget', 'getAll', true, [ 'Widget.Read' ] ); diff --git a/app/config/environment.json b/config.json similarity index 84% rename from app/config/environment.json rename to config.json index dda4904..e68fd30 100644 --- a/app/config/environment.json +++ b/config.json @@ -1,4 +1,17 @@ { + "app": { + "title": "{app_title}", + "guid": "{app_guid}" + }, + "email": { + "fromAddress": "{app_smtp_sendmail_from_address}", + "fromName": "{app_smtp_sendmail_from_name}", + "SMTPUsername": "%env(default::SMTP_USERNAME)%", + "SMTPPassword": "%env(default::SMTP_PASSWORD)%" + }, + "settings": { + "useSession": false + }, "type": "%env(default:local:APP_TYPE)%", "serverName": "%env(default:app.local:APP_SERVER_NAME)%", "rootUrl": "%env(default:{app_root_url}:APP_ROOT_URL)%", diff --git a/app/config/prod.env.example b/prod.env.example similarity index 82% rename from app/config/prod.env.example rename to prod.env.example index d90154b..1d06478 100644 --- a/app/config/prod.env.example +++ b/prod.env.example @@ -1,7 +1,7 @@ -# Copy this file to app/config/prod.env (gitignored) to let the gf CLI read the PROD -# environment's configuration without activating anything: +# Copy this file to prod.env at the application root (gitignored) to let the gf CLI read +# the PROD environment's configuration without activating anything: # -# gf env prod # validate that this overlay fully resolves environment.json +# gf env prod # validate that this overlay fully resolves config.json # gf db:restore --from=prod # dump prod -> restore into your local databases # gf db:run db/fix.js --env=prod # @@ -22,6 +22,9 @@ APP_REDIRECT_AFTER_LOGOUT= MONGO_URI= MONGO_DATABASE= +SMTP_USERNAME= +SMTP_PASSWORD= + MICROSOFT_CLIENT_ID= MICROSOFT_CLIENT_SECRET= MICROSOFT_TENANT= diff --git a/tests/Unit/ConfigFilesTest.php b/tests/Unit/ConfigFilesTest.php index ecc7076..11b7280 100644 --- a/tests/Unit/ConfigFilesTest.php +++ b/tests/Unit/ConfigFilesTest.php @@ -4,17 +4,15 @@ namespace app\tests\Unit; -use gcgov\framework\models\appConfig; -use gcgov\framework\models\environmentConfig; +use gcgov\framework\models\unifiedConfig; use gcgov\framework\services\environment\dotEnvLoader; use gcgov\framework\services\environment\envVarResolver; use PHPUnit\Framework\TestCase; /** - * Pins the completeness contract between the committed config files and the example - * env files: every hard %env(VAR)% reference in app/config/environment.json and - * app/config/app.json must have a key in .env.example (dev) and in - * app/config/prod.env.example (variant overlay reads). A failure here means a clean + * Pins the completeness contract between the committed root config.json and the example + * env files: every hard %env(VAR)% reference must have a key in .env.example (dev) and + * in prod.env.example (variant overlay reads). A failure here means a clean * `cp .env.example .env` checkout — or the Docker image built from it — would 500. */ final class ConfigFilesTest extends TestCase { @@ -28,42 +26,35 @@ private function exampleOverlay( string $relativePath ): array { } - public function testEnvironmentJsonResolvesWithDevExampleEnv(): void { + private function resolveConfig( array $overlay ): unifiedConfig { $resolved = envVarResolver::resolveJson( - (string)file_get_contents( self::ROOT . '/app/config/environment.json' ), - 'app/config/environment.json', - $this->exampleOverlay( '.env.example' ) + (string)file_get_contents( self::ROOT . '/config.json' ), + 'config.json', + $overlay ); - $environmentConfig = environmentConfig::jsonDeserialize( $resolved ); - $this->assertSame( 'local', $environmentConfig->type ); - $this->assertSame( 'mongodb://mongodb:27017', $environmentConfig->mongoDatabases[ 0 ]->uri ); - $this->assertSame( 'app', $environmentConfig->mongoDatabases[ 0 ]->database ); + return unifiedConfig::jsonDeserialize( $resolved ); } - public function testEnvironmentJsonResolvesWithProdExampleOverlay(): void { - $resolved = envVarResolver::resolveJson( - (string)file_get_contents( self::ROOT . '/app/config/environment.json' ), - 'app/config/environment.json', - $this->exampleOverlay( 'app/config/prod.env.example' ) - ); + public function testConfigJsonResolvesWithDevExampleEnv(): void { + $config = $this->resolveConfig( $this->exampleOverlay( '.env.example' ) ); - $environmentConfig = environmentConfig::jsonDeserialize( $resolved ); - $this->assertSame( 'prod', $environmentConfig->type, 'prod.env.example must set APP_TYPE=prod — the db:restore guard depends on it' ); - $this->assertSame( '/api', $environmentConfig->getBasePath() ); + $this->assertSame( 'local', $config->type ); + $this->assertSame( 'mongodb://mongodb:27017', $config->mongoDatabases[ 0 ]->uri ); + $this->assertSame( 'app', $config->mongoDatabases[ 0 ]->database ); + // merged app.json sections hydrate from the same file + $this->assertSame( '{app_title}', $config->app->title ); + $this->assertSame( '', $config->email->SMTPUsername ); + $this->assertFalse( $config->settings->useSession ); } - public function testAppJsonResolvesWithDevExampleEnv(): void { - $resolved = envVarResolver::resolveJson( - (string)file_get_contents( self::ROOT . '/app/config/app.json' ), - 'app/config/app.json', - $this->exampleOverlay( '.env.example' ) - ); + public function testConfigJsonResolvesWithProdExampleOverlay(): void { + $config = $this->resolveConfig( $this->exampleOverlay( 'prod.env.example' ) ); - $appConfig = appConfig::jsonDeserialize( $resolved ); - $this->assertNotNull( $appConfig->email ); + $this->assertSame( 'prod', $config->type, 'prod.env.example must set APP_TYPE=prod — the db:restore guard depends on it' ); + $this->assertSame( '/api', $config->getBasePath() ); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 52e6438..509f0f7 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -11,9 +11,9 @@ require __DIR__ . '/Shims/MongoDBShims.php'; } -// Seed a minimal environmentConfig so framework code that accesses it via -// config::getEnvironmentConfig() doesn't try to read a JSON file from disk. -$envConfig = new \gcgov\framework\models\environmentConfig(); +// Seed a minimal unifiedConfig so framework code that reads config via the +// static accessors (config::getBasePath() etc.) doesn't try to read config.json from disk. +$envConfig = new \gcgov\framework\models\unifiedConfig(); $envConfig->basePath = 'api'; -$prop = new \ReflectionProperty( \gcgov\framework\config::class, 'environmentConfig' ); +$prop = new \ReflectionProperty( \gcgov\framework\config::class, 'unifiedConfig' ); $prop->setValue( null, $envConfig ); From 177dd0b362f2c052bfd8139864a689afa795d08c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 12:10:00 +0000 Subject: [PATCH 04/17] v7: foreign-env config moves to config.json environments section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to the framework redesign: the gitignored prod.env overlay file is replaced by a committed, CLI-only `environments.prod` entry inside config.json. - config.json gains `environments.prod` (type literal "prod" + mongoDatabases referencing PROD_MONGO_URI / PROD_MONGO_DATABASE); the runtime strips this section, so those variables need not be set to run. - prod.env.example deleted; .env.example documents the commented PROD_* variables (used by gf db:restore --from=prod / db:run --env=prod) — the prefix means a missing value fails loudly instead of resolving to the local MONGO_URI. - .gitignore drops the obsolete /*.env overlay rule (only .env/.env.local remain). - ConfigFilesTest seeds the environment from .env.example (mirroring cp .env.example .env) and pins: the active config resolves without any PROD_* set, and environments.prod resolves once PROD_* are supplied. - README/DOCKER updated to the environments-section workflow. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JWacdhEDN9upUvYeagNKru --- .env.example | 7 ++++ .gitignore | 2 - DOCKER.md | 4 +- README.md | 10 +++-- config.json | 12 ++++++ prod.env.example | 38 ----------------- tests/Unit/ConfigFilesTest.php | 74 +++++++++++++++++++++++----------- 7 files changed, 78 insertions(+), 69 deletions(-) delete mode 100644 prod.env.example diff --git a/.env.example b/.env.example index a044c7c..65ab66e 100644 --- a/.env.example +++ b/.env.example @@ -38,6 +38,13 @@ SMTP_PASSWORD= PAYJUNCTION_PASSWORD= PAYJUNCTION_API_KEY= +# ---- Foreign-environment reads for gf db:restore --from=prod / db:run --env=prod ---- +# These feed config.json's `environments.prod` entry (CLI-only; stripped at runtime). +# They are PREFIXED (PROD_*) so a missing value fails loudly instead of resolving to +# your local MONGO_URI. Fill them in only when you need to pull prod data locally. +# PROD_MONGO_URI=mongodb+srv://user:pass@prod-cluster/ +# PROD_MONGO_DATABASE=app + # ---- Production: file-based secrets (Docker/Swarm/Kubernetes) ---- # Prefer mounting secrets as files and referencing them in config.json # with %env(trim:file:MONGO_URI_FILE)%. Example: diff --git a/.gitignore b/.gitignore index 3a652b1..0b6afd1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,6 @@ version.json .env .env.local /secrets/ -# per-environment variable overlays for gf db:*/env at the app root (prod.env.example stays committed) -/*.env .phpunit.cache/ .phpunit.result.cache diff --git a/DOCKER.md b/DOCKER.md index dfbde5c..6da002f 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -37,8 +37,8 @@ the `{app_*}` tokens in the config, nginx, and compose files — they become the **Environment selection is the environment itself.** The committed root-level `config.json` is the only config file (app + environment sections merged); the variable values the container is given decide whether it behaves as local, prod, or anything else. `gf env` validates that -resolution (`gf env` for the active environment, `gf env prod` for the -`prod.env` overlay used by `gf db:*` commands). +resolution (`gf env` for the active configuration, `gf env prod` for the CLI-only +`environments.prod` entry used by `gf db:*` commands). ### Production configuration checklist diff --git a/README.md b/README.md index c0a2ade..6e5096b 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,12 @@ syntax. ### Working with production data -Copy `prod.env.example` to `prod.env` at the application root (gitignored) and fill in the prod -values; validate it with `vendor/bin/gf env prod`. Then `gf db:restore --from=prod` and -`gf db:run --env=prod` resolve `config.json` with that overlay — no prod config file ever -lives in the repo. +`config.json` has a CLI-only `environments.prod` entry (stripped at runtime) whose Mongo +connection references `PROD_MONGO_URI` / `PROD_MONGO_DATABASE`. To pull prod data locally, +set those two variables in your gitignored `.env` (they are listed, commented, in +`.env.example`), validate with `vendor/bin/gf env prod`, then run `gf db:restore --from=prod` +or `gf db:run --env=prod`. No prod config file ever lives in the repo, and the prefixed names +mean a missing value fails loudly instead of quietly using your local database. ## Documentation diff --git a/config.json b/config.json index e68fd30..76f691f 100644 --- a/config.json +++ b/config.json @@ -53,5 +53,17 @@ "apiKey": "%env(PAYJUNCTION_API_KEY)%", "terminalId": "%env(default:{app_payjunction_terminal_id}:PAYJUNCTION_TERMINAL_ID)%", "merchantId": "%env(default:{app_payjunction_merchant_id}:PAYJUNCTION_MERCHANT_ID)%" + }, + "environments": { + "prod": { + "type": "prod", + "mongoDatabases": [ + { + "default": true, + "database": "%env(PROD_MONGO_DATABASE)%", + "uri": "%env(PROD_MONGO_URI)%" + } + ] + } } } diff --git a/prod.env.example b/prod.env.example deleted file mode 100644 index 1d06478..0000000 --- a/prod.env.example +++ /dev/null @@ -1,38 +0,0 @@ -# Copy this file to prod.env at the application root (gitignored) to let the gf CLI read -# the PROD environment's configuration without activating anything: -# -# gf env prod # validate that this overlay fully resolves config.json -# gf db:restore --from=prod # dump prod -> restore into your local databases -# gf db:run db/fix.js --env=prod -# -# IMPORTANT: this file must define EVERY environment-specific variable. A variable -# missing here silently falls back to your local value during a variant read (gf -# detects the same-database case, but not everything). Validate with `gf env prod` -# before trusting it. NEVER commit the real prod.env. - -# Drives the db:restore prod guard (restoring INTO type=prod is refused without --allow-prod) -APP_TYPE=prod - -APP_SERVER_NAME=app.prod -APP_ROOT_URL=https://app.example.gov -APP_BASE_PATH=/api/ -APP_REDIRECT_AFTER_LOGIN= -APP_REDIRECT_AFTER_LOGOUT= - -MONGO_URI= -MONGO_DATABASE= - -SMTP_USERNAME= -SMTP_PASSWORD= - -MICROSOFT_CLIENT_ID= -MICROSOFT_CLIENT_SECRET= -MICROSOFT_TENANT= -MICROSOFT_DRIVE_ID= -MICROSOFT_FROM_ADDRESS= - -PAYJUNCTION_USERNAME= -PAYJUNCTION_PASSWORD= -PAYJUNCTION_API_KEY= -PAYJUNCTION_TERMINAL_ID= -PAYJUNCTION_MERCHANT_ID= diff --git a/tests/Unit/ConfigFilesTest.php b/tests/Unit/ConfigFilesTest.php index 11b7280..477dacf 100644 --- a/tests/Unit/ConfigFilesTest.php +++ b/tests/Unit/ConfigFilesTest.php @@ -4,57 +4,85 @@ namespace app\tests\Unit; +use gcgov\framework\models\config\variantEnvironment; use gcgov\framework\models\unifiedConfig; -use gcgov\framework\services\environment\dotEnvLoader; -use gcgov\framework\services\environment\envVarResolver; +use gcgov\framework\services\environment\configLoader; use PHPUnit\Framework\TestCase; +use Symfony\Component\Dotenv\Dotenv; /** - * Pins the completeness contract between the committed root config.json and the example - * env files: every hard %env(VAR)% reference must have a key in .env.example (dev) and - * in prod.env.example (variant overlay reads). A failure here means a clean - * `cp .env.example .env` checkout — or the Docker image built from it — would 500. + * Pins the completeness contract of the committed root config.json against the committed + * .env.example: after `cp .env.example .env` the active config must fully resolve (every hard + * %env(VAR)% is covered), and the CLI-only `environments.prod` entry must resolve once the + * (commented) PROD_* variables are supplied. A failure here means a clean checkout — or the + * Docker image built from it — would 500. */ final class ConfigFilesTest extends TestCase { private const string ROOT = __DIR__ . '/../..'; + /** @var array */ + private array $envSnapshot = []; - /** @return array */ - private function exampleOverlay( string $relativePath ): array { - return dotEnvLoader::parseFile( self::ROOT . '/' . $relativePath ); + + protected function setUp(): void { + $this->envSnapshot = $_ENV; + // Mirror `cp .env.example .env`: seed the environment from the committed example + // (uncommented entries only — commented PROD_* lines are intentionally absent). + foreach( ( new Dotenv() )->parse( (string)file_get_contents( self::ROOT . '/.env.example' ) ) as $name => $value ) { + $this->setEnv( $name, $value ); + } } - private function resolveConfig( array $overlay ): unifiedConfig { - $resolved = envVarResolver::resolveJson( - (string)file_get_contents( self::ROOT . '/config.json' ), - 'config.json', - $overlay - ); + protected function tearDown(): void { + foreach( array_keys( $_ENV ) as $key ) { + if( !array_key_exists( $key, $this->envSnapshot ) ) { + putenv( $key ); + } + } + $_ENV = $this->envSnapshot; + } + - return unifiedConfig::jsonDeserialize( $resolved ); + private function setEnv( string $name, string $value ): void { + $_ENV[ $name ] = $value; + putenv( $name . '=' . $value ); } - public function testConfigJsonResolvesWithDevExampleEnv(): void { - $config = $this->resolveConfig( $this->exampleOverlay( '.env.example' ) ); + public function testActiveConfigResolvesWithDotEnvExample(): void { + $config = configLoader::load( self::ROOT ); + $this->assertInstanceOf( unifiedConfig::class, $config ); $this->assertSame( 'local', $config->type ); $this->assertSame( 'mongodb://mongodb:27017', $config->mongoDatabases[ 0 ]->uri ); $this->assertSame( 'app', $config->mongoDatabases[ 0 ]->database ); - // merged app.json sections hydrate from the same file + // merged app-side sections hydrate from the same file $this->assertSame( '{app_title}', $config->app->title ); $this->assertSame( '', $config->email->SMTPUsername ); $this->assertFalse( $config->settings->useSession ); } - public function testConfigJsonResolvesWithProdExampleOverlay(): void { - $config = $this->resolveConfig( $this->exampleOverlay( 'prod.env.example' ) ); + public function testActiveConfigResolvesWithoutProdVariables(): void { + // The environments section is CLI-only; the active config resolves even though + // PROD_MONGO_URI / PROD_MONGO_DATABASE are absent from .env.example (commented out). + $this->assertArrayNotHasKey( 'PROD_MONGO_URI', $_ENV ); + $config = configLoader::load( self::ROOT ); + $this->assertSame( 'local', $config->type ); + } + + + public function testProdEnvironmentEntryResolvesWithProdVariables(): void { + $this->setEnv( 'PROD_MONGO_URI', 'mongodb+srv://user:pass@prod-cluster/' ); + $this->setEnv( 'PROD_MONGO_DATABASE', 'app' ); + + $prod = configLoader::loadVariantEnvironment( self::ROOT, 'prod' ); - $this->assertSame( 'prod', $config->type, 'prod.env.example must set APP_TYPE=prod — the db:restore guard depends on it' ); - $this->assertSame( '/api', $config->getBasePath() ); + $this->assertInstanceOf( variantEnvironment::class, $prod ); + $this->assertSame( 'prod', $prod->type, 'the prod entry type must be the literal "prod" — the db:restore guard depends on it' ); + $this->assertSame( 'mongodb+srv://user:pass@prod-cluster/', $prod->mongoDatabases[ 0 ]->uri ); } } From b4095061f4d8d22db41d74e16add2988c98eda04 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 13:31:03 +0000 Subject: [PATCH 05/17] ci: make composer ci self-contained (framework path repo only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The template's CI could not run without the four framework-service plugin repos being checked out as siblings, and their composer-ci.json refs (dev-claude/trusting-ptolemy-TFTfV) no longer matched the ci.yml `ref: main` checkouts — so the path-repo resolution was already broken. But nothing under app/ or tests/ references a plugin class: phpstan analyzes app/ only, and the unit tests never boot the framework router or instantiate a plugin, so the plugins are dead weight for this repo's CI. - composer-ci.json: drop the four gcgov/framework-service-* requires and their path repositories; keep only the gcgov/framework path repo. Now `cp composer-ci.json composer.json && composer install && composer ci` resolves against just the sibling framework checkout — runnable in the Claude Code sandbox and any environment with only the framework present. The committed composer.json still lists the plugins for real scaffolds. - ci.yml: remove the four plugin sibling checkouts from both jobs. - README: document running the CI checks locally. Verified in-sandbox: phpstan clean, 42 tests pass, using only ../framework. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JWacdhEDN9upUvYeagNKru --- .github/workflows/ci.yml | 49 ++++------------------------------------ README.md | 20 ++++++++++++++++ composer-ci.json | 16 ++++--------- 3 files changed, 29 insertions(+), 56 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d15d25d..841bce1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: php: ['8.3'] steps: - uses: actions/checkout@v4 - - name: Check out sibling repositories required by composer-ci.json + - name: Check out gcgov/framework (composer-ci.json resolves it from a sibling path repo) uses: actions/checkout@v4 with: # Framework points at the Docker-support branch until it is tagged/merged @@ -26,26 +26,6 @@ jobs: repository: gcgov/framework ref: claude/docker-gcgov-framework-tphme2 path: ../framework - - uses: actions/checkout@v4 - with: - repository: gcgov/framework-service-gcgov-cron-monitor - ref: main - path: ../framework-service-gcgov-cron-monitor - - uses: actions/checkout@v4 - with: - repository: gcgov/framework-service-documentation - ref: main - path: ../framework-service-documentation - - uses: actions/checkout@v4 - with: - repository: gcgov/framework-service-auth-oauth-server - ref: main - path: ../framework-service-auth-oauth-server - - uses: actions/checkout@v4 - with: - repository: gcgov/framework-service-user-crud - ref: main - path: ../framework-service-user-crud - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -56,7 +36,7 @@ jobs: run: | cp composer-ci.json composer.json # The committed composer.lock is for Packagist/Docker builds; path-repo - # resolution against the sibling checkouts must not reuse it. + # resolution against the framework checkout must not reuse it. rm -f composer.lock - name: Install dependencies run: composer install --no-interaction --no-progress --prefer-dist @@ -72,33 +52,14 @@ jobs: php: ['8.3'] steps: - uses: actions/checkout@v4 - - uses: actions/checkout@v4 + - name: Check out gcgov/framework (composer-ci.json resolves it from a sibling path repo) + uses: actions/checkout@v4 with: # Framework points at the Docker-support branch until it is tagged/merged # to main. Revert this ref to `main` (or the release tag) afterwards. repository: gcgov/framework ref: claude/docker-gcgov-framework-tphme2 path: ../framework - - uses: actions/checkout@v4 - with: - repository: gcgov/framework-service-gcgov-cron-monitor - ref: main - path: ../framework-service-gcgov-cron-monitor - - uses: actions/checkout@v4 - with: - repository: gcgov/framework-service-documentation - ref: main - path: ../framework-service-documentation - - uses: actions/checkout@v4 - with: - repository: gcgov/framework-service-auth-oauth-server - ref: main - path: ../framework-service-auth-oauth-server - - uses: actions/checkout@v4 - with: - repository: gcgov/framework-service-user-crud - ref: main - path: ../framework-service-user-crud - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -109,7 +70,7 @@ jobs: run: | cp composer-ci.json composer.json # The committed composer.lock is for Packagist/Docker builds; path-repo - # resolution against the sibling checkouts must not reuse it. + # resolution against the framework checkout must not reuse it. rm -f composer.lock - name: Install dependencies run: composer install --no-interaction --no-progress --prefer-dist diff --git a/README.md b/README.md index 6e5096b..d52fb49 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,26 @@ mean a missing value fails loudly instead of quietly using your local database. (Docker/Swarm/Kubernetes secrets, TLS at the edge, the CLI). - The `gf` CLI: `vendor/bin/gf` (`gf setup`, `gf env`, `gf cli`, `gf db:*`, …). +## Running the CI checks (phpstan + phpunit) + +CI runs against `composer-ci.json`, which resolves `gcgov/framework` from a sibling checkout +(a `../framework` path repository) instead of Packagist — so the checks run against the exact +framework revision you have locally, before any release is tagged. It intentionally does **not** +require the framework-service plugins the app registers: nothing under `app/` or `tests/` +references a plugin class, so they are not needed to analyze or test this repo (the committed +`composer.json` keeps them for real scaffolded apps). + +```bash +git clone https://github.com/gcgov/framework ../framework # sibling checkout the path repo points at +cp composer-ci.json composer.json +rm -f composer.lock +composer install --prefer-dist # add --ignore-platform-req=ext-mongodb if the extension isn't loaded +composer ci # = composer phpstan && composer test +``` + +The tests shim `ext-mongodb` when it is absent (`tests/bootstrap.php`), so the suite runs without +a live MongoDB. Restore the app manifest afterwards with `git checkout composer.json`. + ## Local development without Docker You can still run the app under any PHP 8.3+ SAPI with `ext-mongodb`. Point the web root at diff --git a/composer-ci.json b/composer-ci.json index fd8cceb..13741ff 100644 --- a/composer-ci.json +++ b/composer-ci.json @@ -1,17 +1,13 @@ { "name": "gcgov/framework-app-template", - "description": "App template repository to scaffold a new application based on gcgov/framework. CI variant that resolves the framework and sibling services from local paths instead of the production Windows path.", + "description": "CI variant of composer.json: resolves gcgov/framework from a local path repo (sibling checkout) instead of Packagist. The framework-service plugins the app registers are NOT needed to phpstan-analyze app/ or run the unit tests (nothing under app/ or tests/ references a plugin class), so they are omitted here to keep CI self-contained and runnable without the sibling plugin repos. The committed composer.json keeps them for real scaffolded apps.", "require": { - "php": ">=8.2", + "php": ">=8.3", "ext-mongodb": "*", "mongodb/mongodb": "^2.1", "phpmailer/phpmailer": "^6.2", "zircote/swagger-php": "^6.1", - "gcgov/framework": "dev-claude/docker-gcgov-framework-tphme2", - "gcgov/framework-service-gcgov-cron-monitor": "dev-claude/trusting-ptolemy-TFTfV", - "gcgov/framework-service-documentation": "dev-claude/trusting-ptolemy-TFTfV", - "gcgov/framework-service-auth-oauth-server": "dev-claude/trusting-ptolemy-TFTfV", - "gcgov/framework-service-user-crud": "dev-claude/trusting-ptolemy-TFTfV" + "gcgov/framework": "dev-claude/docker-gcgov-framework-tphme2" }, "require-dev": { "phpstan/phpstan": "^2.1", @@ -19,11 +15,7 @@ "jetbrains/phpstorm-attributes": "^1.0" }, "repositories": [ - { "type": "path", "url": "../framework", "options": { "symlink": false } }, - { "type": "path", "url": "../framework-service-gcgov-cron-monitor", "options": { "symlink": false } }, - { "type": "path", "url": "../framework-service-documentation", "options": { "symlink": false } }, - { "type": "path", "url": "../framework-service-auth-oauth-server", "options": { "symlink": false } }, - { "type": "path", "url": "../framework-service-user-crud", "options": { "symlink": false } } + { "type": "path", "url": "../framework", "options": { "symlink": false } } ], "autoload": { "psr-4": { From 145ebff73f18febc4a0d68a13c04616d75ac4746 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 21:32:06 +0000 Subject: [PATCH 06/17] Bridge to the v7 framework branch, commit composer.lock, require PHP 8.4 Resolves gcgov/framework through a vcs repository against its v7 branch so the Docker and CI work can proceed before v7.0.0-rc.1 is tagged. This is temporary: at RC the constraint becomes ^7.0, the repositories entry goes, and the lock is regenerated. - composer.lock committed, pinned via config.platform.php to 8.4.0 so the lock always resolves for the runtime the prod image runs. Without the pin, resolving on a newer PHP locks eight Symfony packages requiring >=8.4.1 that would not install in the image. - php >= 8.4; Dockerfile base moves to php:8.4-fpm. - CI installs from the committed lock. Removes the sibling-checkout step (actions/checkout rejects a path outside the workspace, so this job had never run green), the stale framework branch ref, and composer-ci.json along with the swap-and-delete-the-lock dance it required. Verified: lock resolves 110 packages with nothing requiring >8.4.0, and `composer install --no-dev` installs cleanly with every framework and app class autoloading. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012kqJazGHDMq19pQ64732i6 --- .github/workflows/ci.yml | 66 +- Dockerfile | 2 +- README.md | 21 +- composer-ci.json | 30 - composer.json | 20 +- composer.lock | 7387 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 7430 insertions(+), 96 deletions(-) delete mode 100644 composer-ci.json create mode 100644 composer.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 841bce1..53bbae6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,74 +8,41 @@ on: permissions: contents: read +env: + # The prod image is php:8.4-fpm; composer.lock is resolved for that platform + # (see config.platform in composer.json). Keep these in step. + PHP_VERSION: '8.4' + jobs: phpstan: - name: PHPStan (PHP ${{ matrix.php }}) + name: PHPStan runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - php: ['8.3'] steps: - uses: actions/checkout@v4 - - name: Check out gcgov/framework (composer-ci.json resolves it from a sibling path repo) - uses: actions/checkout@v4 - with: - # Framework points at the Docker-support branch until it is tagged/merged - # to main. Revert this ref to `main` (or the release tag) afterwards. - repository: gcgov/framework - ref: claude/docker-gcgov-framework-tphme2 - path: ../framework - uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php }} + php-version: ${{ env.PHP_VERSION }} extensions: mongodb, sodium, fileinfo, pdo, imagick tools: composer:v2 coverage: none - - name: Swap composer.json for CI variant - run: | - cp composer-ci.json composer.json - # The committed composer.lock is for Packagist/Docker builds; path-repo - # resolution against the framework checkout must not reuse it. - rm -f composer.lock - - name: Install dependencies - run: composer install --no-interaction --no-progress --prefer-dist - - name: Run PHPStan - run: composer phpstan + # Installs from the committed composer.lock. gcgov/framework resolves through + # the vcs repository in composer.json until v7.0.0-rc.1 is tagged. + - run: composer install --no-interaction --no-progress --prefer-dist + - run: composer phpstan phpunit: - name: PHPUnit (PHP ${{ matrix.php }}) + name: PHPUnit runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - php: ['8.3'] steps: - uses: actions/checkout@v4 - - name: Check out gcgov/framework (composer-ci.json resolves it from a sibling path repo) - uses: actions/checkout@v4 - with: - # Framework points at the Docker-support branch until it is tagged/merged - # to main. Revert this ref to `main` (or the release tag) afterwards. - repository: gcgov/framework - ref: claude/docker-gcgov-framework-tphme2 - path: ../framework - uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php }} + php-version: ${{ env.PHP_VERSION }} extensions: mongodb, sodium, fileinfo, pdo, imagick tools: composer:v2 coverage: none - - name: Swap composer.json for CI variant - run: | - cp composer-ci.json composer.json - # The committed composer.lock is for Packagist/Docker builds; path-repo - # resolution against the framework checkout must not reuse it. - rm -f composer.lock - - name: Install dependencies - run: composer install --no-interaction --no-progress --prefer-dist - - name: Run PHPUnit - run: composer test + - run: composer install --no-interaction --no-progress --prefer-dist + - run: composer test nginx: name: Nginx config lint @@ -97,9 +64,6 @@ jobs: docker-build: name: Docker build (prod target) runs-on: ubuntu-latest - # Best effort: requires the framework release carrying the %env() resolver to - # be published to Packagist (see composer.json). Allowed to fail until then. - continue-on-error: true steps: - uses: actions/checkout@v4 - name: Build the production image diff --git a/Dockerfile b/Dockerfile index 8507878..69cd614 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ # composer.lock for reproducible images. # ---- base: PHP-FPM + required extensions ---- -FROM php:8.3-fpm AS base +FROM php:8.4-fpm AS base RUN set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends \ diff --git a/README.md b/README.md index d52fb49..7dff10f 100644 --- a/README.md +++ b/README.md @@ -52,26 +52,25 @@ mean a missing value fails loudly instead of quietly using your local database. ## Running the CI checks (phpstan + phpunit) -CI runs against `composer-ci.json`, which resolves `gcgov/framework` from a sibling checkout -(a `../framework` path repository) instead of Packagist — so the checks run against the exact -framework revision you have locally, before any release is tagged. It intentionally does **not** -require the framework-service plugins the app registers: nothing under `app/` or `tests/` -references a plugin class, so they are not needed to analyze or test this repo (the committed -`composer.json` keeps them for real scaffolded apps). +`composer.json` resolves `gcgov/framework` from its v7 development branch through a `vcs` +repository, and `composer.lock` pins the exact revision. This is a temporary bridge: when +`v7.0.0-rc.1` is tagged, the constraint becomes `^7.0`, the `repositories` entry is deleted, and +the lock is regenerated. ```bash -git clone https://github.com/gcgov/framework ../framework # sibling checkout the path repo points at -cp composer-ci.json composer.json -rm -f composer.lock composer install --prefer-dist # add --ignore-platform-req=ext-mongodb if the extension isn't loaded composer ci # = composer phpstan && composer test ``` The tests shim `ext-mongodb` when it is absent (`tests/bootstrap.php`), so the suite runs without -a live MongoDB. Restore the app manifest afterwards with `git checkout composer.json`. +a live MongoDB. + +`composer.lock` is resolved for PHP 8.4.0 (`config.platform.php`), which is what the production +image runs — without that pin, resolving on a newer PHP locks packages that will not install in +the image. Keep the pin, the `php` constraint, and the Dockerfile's base image in step. ## Local development without Docker -You can still run the app under any PHP 8.3+ SAPI with `ext-mongodb`. Point the web root at +You can still run the app under any PHP 8.4+ SAPI with `ext-mongodb`. Point the web root at `/www/`, resolve config secrets through your shell environment or a `.env` file at the project root, and use `vendor/bin/gf` for CLI tasks. The Docker stack is the supported, reproducible path. diff --git a/composer-ci.json b/composer-ci.json deleted file mode 100644 index 13741ff..0000000 --- a/composer-ci.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "gcgov/framework-app-template", - "description": "CI variant of composer.json: resolves gcgov/framework from a local path repo (sibling checkout) instead of Packagist. The framework-service plugins the app registers are NOT needed to phpstan-analyze app/ or run the unit tests (nothing under app/ or tests/ references a plugin class), so they are omitted here to keep CI self-contained and runnable without the sibling plugin repos. The committed composer.json keeps them for real scaffolded apps.", - "require": { - "php": ">=8.3", - "ext-mongodb": "*", - "mongodb/mongodb": "^2.1", - "phpmailer/phpmailer": "^6.2", - "zircote/swagger-php": "^6.1", - "gcgov/framework": "dev-claude/docker-gcgov-framework-tphme2" - }, - "require-dev": { - "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^11.5", - "jetbrains/phpstorm-attributes": "^1.0" - }, - "repositories": [ - { "type": "path", "url": "../framework", "options": { "symlink": false } } - ], - "autoload": { - "psr-4": { - "app\\": "app/" - } - }, - "scripts": { - "phpstan": "phpstan analyse --memory-limit=512M", - "test": "phpunit", - "ci": ["@phpstan", "@test"] - } -} diff --git a/composer.json b/composer.json index 7b64f96..8a71414 100644 --- a/composer.json +++ b/composer.json @@ -2,12 +2,12 @@ "name": "gcgov/framework-app-template", "description": "App template repository to scaffold a new application based on gcgov/framework.", "require": { - "php": ">=8.3", + "php": ">=8.4", "ext-mongodb": "*", "mongodb/mongodb": "^2.1", "phpmailer/phpmailer": "^6.2", "zircote/swagger-php": "^6.1", - "gcgov/framework": "^v7.0", + "gcgov/framework": "dev-claude/v7-config-deployment-review-4tgoxl", "gcgov/framework-service-gcgov-cron-monitor": "^v1.1", "gcgov/framework-service-documentation": "^1.1", "gcgov/framework-service-auth-oauth-server": "^2.1", @@ -18,6 +18,17 @@ "phpunit/phpunit": "^11.5", "jetbrains/phpstorm-attributes": "^1.0" }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/gcgov/framework" + } + ], + "config": { + "platform": { + "php": "8.4.0" + } + }, "autoload": { "psr-4": { "app\\": "app/" @@ -26,6 +37,9 @@ "scripts": { "phpstan": "phpstan analyse --memory-limit=512M", "test": "phpunit", - "ci": ["@phpstan", "@test"] + "ci": [ + "@phpstan", + "@test" + ] } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..41d2dc1 --- /dev/null +++ b/composer.lock @@ -0,0 +1,7387 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "507acfa5108428fd982ac28bdab21449", + "packages": [ + { + "name": "andrewsauder/json-deserialize", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/andrewsauder/jsonDeserialize.git", + "reference": "645aa1d30874bc245b05ff272b0033a44cba884b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/andrewsauder/jsonDeserialize/zipball/645aa1d30874bc245b05ff272b0033a44cba884b", + "reference": "645aa1d30874bc245b05ff272b0033a44cba884b", + "shasum": "" + }, + "require": { + "monolog/monolog": "^3.4.0", + "php": ">=8.1" + }, + "require-dev": { + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "andrewsauder\\jsonDeserialize\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrew Sauder", + "email": "andrew@sauder.email" + } + ], + "description": "PHP Utility to add automated json deserialize into typed objects", + "support": { + "issues": "https://github.com/andrewsauder/jsonDeserialize/issues", + "source": "https://github.com/andrewsauder/jsonDeserialize/tree/v3.1.1" + }, + "time": "2026-07-20T16:04:42+00:00" + }, + { + "name": "bacon/bacon-qr-code", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2", + "reference": "4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^8.1" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.12", + "phpunit/phpunit": "^10.5.11 || ^11.0.4", + "spatie/phpunit-snapshot-assertions": "^5.1.5", + "spatie/pixelmatch-php": "^1.2.0", + "squizlabs/php_codesniffer": "^3.9" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/v3.1.1" + }, + "time": "2026-04-05T21:06:35+00:00" + }, + { + "name": "brick/math", + "version": "0.18.0", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/82944324d1c1bdb2c2618e89978d4e2ad78d69ad", + "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "require-dev": { + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.18.0" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2026-06-14T18:21:03+00:00" + }, + { + "name": "chrome-php/chrome", + "version": "v1.16.1", + "source": { + "type": "git", + "url": "https://github.com/chrome-php/chrome.git", + "reference": "2618fbcd0530e917433675b507eca25ad4c52376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chrome-php/chrome/zipball/2618fbcd0530e917433675b507eca25ad4c52376", + "reference": "2618fbcd0530e917433675b507eca25ad4c52376", + "shasum": "" + }, + "require": { + "chrome-php/wrench": "^1.9", + "evenement/evenement": "^3.0.1", + "monolog/monolog": "^1.27.1 || ^2.8 || ^3.2", + "php": "^7.4.15 || ^8.0.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.26", + "symfony/process": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^9.6.3 || ^10.0.12", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "HeadlessChromium\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Enrico Dias", + "email": "enrico@enricodias.com", + "homepage": "https://github.com/enricodias" + } + ], + "description": "Instrument headless chrome/chromium instances from PHP", + "keywords": [ + "browser", + "chrome", + "chromium", + "crawl", + "headless", + "pdf", + "puppeteer", + "screenshot" + ], + "support": { + "issues": "https://github.com/chrome-php/chrome/issues", + "source": "https://github.com/chrome-php/chrome/tree/v1.16.1" + }, + "time": "2026-07-06T19:06:52+00:00" + }, + { + "name": "chrome-php/wrench", + "version": "v1.9.2", + "source": { + "type": "git", + "url": "https://github.com/chrome-php/wrench.git", + "reference": "c314dcfaca020e836d05e0913a63db97d7d3da05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chrome-php/wrench/zipball/c314dcfaca020e836d05e0913a63db97d7d3da05", + "reference": "c314dcfaca020e836d05e0913a63db97d7d3da05", + "shasum": "" + }, + "require": { + "ext-sockets": "*", + "php": "^7.4.15 || ^8.0.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/polyfill-php80": "^1.26" + }, + "conflict": { + "wrench/wrench": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^9.6.3 || ^10.0.12" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "Wrench\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "A simple PHP WebSocket implementation", + "keywords": [ + "WebSockets", + "hybi", + "websocket" + ], + "support": { + "issues": "https://github.com/chrome-php/wrench/issues", + "source": "https://github.com/chrome-php/wrench/tree/v1.9.2" + }, + "time": "2026-07-06T19:07:11+00:00" + }, + { + "name": "dasprid/enum", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "shasum": "" + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.7" + }, + "time": "2025-09-16T12:23:56+00:00" + }, + { + "name": "doctrine/annotations", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7", + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2 || ^3", + "ext-tokenizer": "*", + "php": "^7.2 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.10.28", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^5.4 || ^6.4 || ^7", + "vimeo/psalm": "^4.30 || ^5.14" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/2.0.2" + }, + "abandoned": true, + "time": "2024-09-05T10:17:24+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "firebase/php-jwt", + "version": "v7.1.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/php-jwt.git", + "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/b374a5d1a4f1f67fadc2165cdb284645945e2fc0", + "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpfastcache/phpfastcache": "^9.2", + "phpseclib/phpseclib": "~3.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", + "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/googleapis/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/googleapis/php-jwt/issues", + "source": "https://github.com/googleapis/php-jwt/tree/v7.1.0" + }, + "time": "2026-06-11T17:54:14+00:00" + }, + { + "name": "gcgov/framework", + "version": "dev-claude/v7-config-deployment-review-4tgoxl", + "source": { + "type": "git", + "url": "git@github.com:gcgov/framework.git", + "reference": "c6a1adb7fb8c233d8e7ba2c2c846b37c84692a43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gcgov/framework/zipball/c6a1adb7fb8c233d8e7ba2c2c846b37c84692a43", + "reference": "c6a1adb7fb8c233d8e7ba2c2c846b37c84692a43", + "shasum": "" + }, + "require": { + "andrewsauder/json-deserialize": "^3.0", + "chrome-php/chrome": "^1.16", + "ext-fileinfo": "*", + "ext-mongodb": "*", + "ext-openssl": "*", + "ext-pdo": "*", + "ext-sodium": "*", + "google/cloud-kms": "^2.1", + "guzzlehttp/guzzle": "^7.0", + "hybridauth/hybridauth": "^3.13", + "lcobucci/clock": "^2.0", + "lcobucci/jwt": "^5.6", + "microsoft/microsoft-graph": "^1.25", + "mongodb/mongodb": "^2.1", + "monolog/monolog": "^3.4", + "nikic/fast-route": "^1.3", + "php": ">=8.4", + "phpmailer/phpmailer": "^6.2", + "spatie/typescript-transformer": "^2.4", + "swaggest/json-diff": "^3.11", + "symfony/console": "^7.1", + "symfony/dotenv": "^7.1", + "symfony/expression-language": "^7.1", + "symfony/process": "^7.1", + "symfony/property-access": "^7.1", + "symfony/validator": "^7.1", + "thenetworg/oauth2-azure": "^2.1", + "zircote/swagger-php": "^6.1" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.5" + }, + "suggest": { + "ext-zip": "Required by `gf chrome:install` / `gf chrome:update` to extract the chrome-headless-shell download" + }, + "bin": [ + "bin/gf" + ], + "type": "library", + "autoload": { + "psr-4": { + "gcgov\\framework\\": "src/" + } + }, + "scripts": { + "phpstan": [ + "phpstan analyse --memory-limit=512M" + ], + "test": [ + "phpunit" + ], + "ci": [ + "@phpstan", + "@test" + ] + }, + "license": [ + "MIT" + ], + "description": "Open source framework for PHP applications. Includes MongoDB modelling system.", + "time": "2026-08-26T21:25:35+00:00" + }, + { + "name": "gcgov/framework-service-auth-oauth-server", + "version": "v2.2.1", + "source": { + "type": "git", + "url": "https://github.com/gcgov/framework-service-auth-oauth-server.git", + "reference": "684372a4b24588699ebfb79c2392ccb87859dac5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gcgov/framework-service-auth-oauth-server/zipball/684372a4b24588699ebfb79c2392ccb87859dac5", + "reference": "684372a4b24588699ebfb79c2392ccb87859dac5", + "shasum": "" + }, + "require": { + "andrewsauder/json-deserialize": "^3.0", + "bacon/bacon-qr-code": "^3.0", + "ext-imagick": "*", + "ext-mongodb": "*", + "ext-sodium": "*", + "hybridauth/hybridauth": "^3.13", + "lcobucci/clock": "^2.0", + "lcobucci/jwt": "^5.6", + "php": ">=8.2", + "robthree/twofactorauth": "^3.0" + }, + "require-dev": { + "gcgov/framework": "dev-main", + "jetbrains/phpstorm-attributes": "^1.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.5" + }, + "type": "framework-service", + "autoload": { + "psr-4": { + "gcgov\\framework\\services\\authoauth\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Plugin enables a full fledged oauth server generating access and refresh tokens. Authentication can be provided as username/password or via third party Oauth providers.", + "support": { + "issues": "https://github.com/gcgov/framework-service-auth-oauth-server/issues", + "source": "https://github.com/gcgov/framework-service-auth-oauth-server/tree/v2.2.1" + }, + "time": "2026-07-14T18:25:55+00:00" + }, + { + "name": "gcgov/framework-service-documentation", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/gcgov/framework-service-documentation.git", + "reference": "03a02585285e19f102d7f818bb095e1d9999f8e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gcgov/framework-service-documentation/zipball/03a02585285e19f102d7f818bb095e1d9999f8e2", + "reference": "03a02585285e19f102d7f818bb095e1d9999f8e2", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^2.0", + "php": ">=8.1", + "zircote/swagger-php": "^6.1" + }, + "require-dev": { + "gcgov/framework": "dev-main", + "jetbrains/phpstorm-attributes": "^1.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^10.5" + }, + "type": "framework-service", + "autoload": { + "psr-4": { + "gcgov\\framework\\services\\documentation\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Plugin that enables a route to generate OpenAPI yaml documentation at the /documentation.yaml route", + "support": { + "issues": "https://github.com/gcgov/framework-service-documentation/issues", + "source": "https://github.com/gcgov/framework-service-documentation/tree/v1.1.2" + }, + "time": "2026-07-14T18:45:33+00:00" + }, + { + "name": "gcgov/framework-service-gcgov-cron-monitor", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/gcgov/framework-service-gcgov-cron-monitor.git", + "reference": "d7cf4c279677c539a878d7fd5b6a8832314b2b98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gcgov/framework-service-gcgov-cron-monitor/zipball/d7cf4c279677c539a878d7fd5b6a8832314b2b98", + "reference": "d7cf4c279677c539a878d7fd5b6a8832314b2b98", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.0", + "php": ">=8.1" + }, + "require-dev": { + "gcgov/framework": "dev-main", + "jetbrains/phpstorm-attributes": "^1.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^10.5" + }, + "type": "framework-service", + "autoload": { + "psr-4": { + "gcgov\\framework\\services\\cronMonitor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "An internal to Garrett County Government plugin that provides access to \\gcgov\\framework\\services\\cronMonitor to record cron task runs", + "support": { + "issues": "https://github.com/gcgov/framework-service-gcgov-cron-monitor/issues", + "source": "https://github.com/gcgov/framework-service-gcgov-cron-monitor/tree/v1.1.1" + }, + "time": "2026-07-14T18:44:16+00:00" + }, + { + "name": "gcgov/framework-service-user-crud", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/gcgov/framework-service-user-crud.git", + "reference": "f04773d50347f769bbf595e04fe12ec3c231c9d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gcgov/framework-service-user-crud/zipball/f04773d50347f769bbf595e04fe12ec3c231c9d7", + "reference": "f04773d50347f769bbf595e04fe12ec3c231c9d7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "gcgov/framework": "dev-main", + "jetbrains/phpstorm-attributes": "^1.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^10.5" + }, + "type": "framework-service", + "autoload": { + "psr-4": { + "gcgov\\framework\\services\\usercrud\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Implement standard CRUD routes to manage the user collection. Even if your app doesn't provide a custom user model (`\\app\\models\\user`), your auth or database service may provide you with a standard user model that implements (`\\gcgov\\framework\\interfaces\\auth\\user`).", + "support": { + "issues": "https://github.com/gcgov/framework-service-user-crud/issues", + "source": "https://github.com/gcgov/framework-service-user-crud/tree/v1.1.1" + }, + "time": "2026-07-14T18:41:10+00:00" + }, + { + "name": "google/auth", + "version": "v1.53.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-auth-library-php.git", + "reference": "d677d0b0c4bd52ab222a85df8e74e0d491c0cc5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/d677d0b0c4bd52ab222a85df8e74e0d491c0cc5a", + "reference": "d677d0b0c4bd52ab222a85df8e74e0d491c0cc5a", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^6.0||^7.0", + "guzzlehttp/guzzle": "^7.8.2||^8.0", + "guzzlehttp/psr7": "^2.6.3||^3.0", + "php": "^8.1", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-message": "^1.1||^2.0", + "psr/log": "^2.0||^3.0" + }, + "require-dev": { + "guzzlehttp/promises": "^2.0.3||^3.0", + "kelvinmo/simplejwt": "^1.1.0", + "phpseclib/phpseclib": "^3.0.35", + "phpspec/prophecy-phpunit": "^2.1", + "phpunit/phpunit": "^9.6", + "sebastian/comparator": ">=1.2.3", + "squizlabs/php_codesniffer": "^4.0", + "symfony/filesystem": "^6.3||^7.3", + "symfony/process": "^6.0||^7.0", + "webmozart/assert": "^1.11||^2.0" + }, + "suggest": { + "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings or for token management. Please require version ^2." + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Auth\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google Auth Library for PHP", + "homepage": "https://github.com/google/google-auth-library-php", + "keywords": [ + "Authentication", + "google", + "oauth2" + ], + "support": { + "docs": "https://cloud.google.com/php/docs/reference/auth/latest", + "issues": "https://github.com/googleapis/google-auth-library-php/issues", + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.53.0" + }, + "time": "2026-07-22T22:36:10+00:00" + }, + { + "name": "google/cloud-kms", + "version": "v2.12.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-cloud-php-kms.git", + "reference": "5d28a9be4a9233091b801a703e20ebab2b2354e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-cloud-php-kms/zipball/5d28a9be4a9233091b801a703e20ebab2b2354e8", + "reference": "5d28a9be4a9233091b801a703e20ebab2b2354e8", + "shasum": "" + }, + "require": { + "google/gax": "^1.38.0", + "php": "^8.1" + }, + "require-dev": { + "google/cloud-core": "^1.52.7", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-grpc": "Enables use of gRPC, a universal high-performance RPC framework created by Google.", + "ext-protobuf": "Provides a significant increase in throughput over the pure PHP protobuf implementation. See https://cloud.google.com/php/grpc for installation instructions." + }, + "type": "library", + "extra": { + "component": { + "id": "cloud-kms", + "path": "Kms", + "entry": null, + "target": "googleapis/google-cloud-php-kms.git" + } + }, + "autoload": { + "psr-4": { + "Google\\Cloud\\Kms\\": "src", + "GPBMetadata\\Google\\Cloud\\Kms\\": "metadata" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Cloud KMS Client for PHP", + "support": { + "source": "https://github.com/googleapis/google-cloud-php-kms/tree/v2.12.0" + }, + "time": "2026-08-10T16:39:50+00:00" + }, + { + "name": "google/common-protos", + "version": "4.14.1", + "source": { + "type": "git", + "url": "https://github.com/googleapis/common-protos-php.git", + "reference": "4eb6813b8068653e055fc8a63dbda3446f3e8869" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/common-protos-php/zipball/4eb6813b8068653e055fc8a63dbda3446f3e8869", + "reference": "4eb6813b8068653e055fc8a63dbda3446f3e8869", + "shasum": "" + }, + "require": { + "google/protobuf": "^4.31||^5.0", + "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.14.1" + }, + "time": "2026-06-17T23:07:32+00:00" + }, + { + "name": "google/gax", + "version": "v1.49.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/gax-php.git", + "reference": "6c46a601c493ad7fe057b457662d79cf7b01d8e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/gax-php/zipball/6c46a601c493ad7fe057b457662d79cf7b01d8e2", + "reference": "6c46a601c493ad7fe057b457662d79cf7b01d8e2", + "shasum": "" + }, + "require": { + "google/auth": "^1.53", + "google/common-protos": "^4.9", + "google/grpc-gcp": "^0.4", + "google/longrunning": "~0.4", + "google/protobuf": "^4.31||^5.34", + "grpc/grpc": "^1.13", + "guzzlehttp/promises": "^2.0.3||^3.0", + "guzzlehttp/psr7": "^2.6.3||^3.0", + "php": "^8.1", + "ramsey/uuid": "^4.0" + }, + "conflict": { + "ext-protobuf": "<4.31.0" + }, + "require-dev": { + "google/cloud-tools": "^0.16.1", + "phpspec/prophecy-phpunit": "^2.1", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "library", + "extra": { + "component": { + "id": "gax", + "path": "Gax", + "entry": "README.md", + "target": "googleapis/gax-php.git" + } + }, + "autoload": { + "psr-4": { + "Google\\ApiCore\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Google API Core for PHP", + "homepage": "https://github.com/googleapis/gax-php", + "keywords": [ + "google" + ], + "support": { + "issues": "https://github.com/googleapis/gax-php/issues", + "source": "https://github.com/googleapis/gax-php/tree/v1.49.0" + }, + "time": "2026-08-24T22:04:34+00:00" + }, + { + "name": "google/grpc-gcp", + "version": "0.4.2", + "source": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/grpc-gcp-php.git", + "reference": "1049c0c15b6a1789fdeb52af688a94d540932469" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GoogleCloudPlatform/grpc-gcp-php/zipball/1049c0c15b6a1789fdeb52af688a94d540932469", + "reference": "1049c0c15b6a1789fdeb52af688a94d540932469", + "shasum": "" + }, + "require": { + "google/auth": "^1.3", + "google/protobuf": "^v3.25.3||^4.26.1||^5.0", + "grpc/grpc": "^v1.13.0", + "php": "^8.0", + "psr/cache": "^1.0.1||^2.0.0||^3.0.0" + }, + "require-dev": { + "google/cloud-spanner": "^1.7", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Grpc\\Gcp\\": "src/" + }, + "classmap": [ + "src/generated/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "gRPC GCP library for channel management", + "support": { + "issues": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/issues", + "source": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/tree/v0.4.2" + }, + "time": "2026-03-12T22:56:09+00:00" + }, + { + "name": "google/longrunning", + "version": "0.8.2", + "source": { + "type": "git", + "url": "https://github.com/googleapis/php-longrunning.git", + "reference": "88aaae8e30a03a3d06efb1a88eeb1dad719d1bc9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/php-longrunning/zipball/88aaae8e30a03a3d06efb1a88eeb1dad719d1bc9", + "reference": "88aaae8e30a03a3d06efb1a88eeb1dad719d1bc9", + "shasum": "" + }, + "require-dev": { + "google/gax": "^1.38.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "component": { + "id": "longrunning", + "path": "LongRunning", + "entry": null, + "target": "googleapis/php-longrunning" + } + }, + "autoload": { + "psr-4": { + "Google\\LongRunning\\": "src/LongRunning", + "Google\\ApiCore\\LongRunning\\": "src/ApiCore/LongRunning", + "GPBMetadata\\Google\\Longrunning\\": "metadata/Longrunning" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google LongRunning Client for PHP", + "support": { + "source": "https://github.com/googleapis/php-longrunning/tree/v0.8.2" + }, + "time": "2026-08-24T22:04:34+00:00" + }, + { + "name": "google/protobuf", + "version": "v5.36.0", + "source": { + "type": "git", + "url": "https://github.com/protocolbuffers/protobuf-php.git", + "reference": "9c105104b54709ecd902494ab340ed2122789b2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/9c105104b54709ecd902494ab340ed2122789b2d", + "reference": "9c105104b54709ecd902494ab340ed2122789b2d", + "shasum": "" + }, + "require": { + "php": ">=8.2.0" + }, + "require-dev": { + "phpunit/phpunit": ">=11.5.50 <12.0.0" + }, + "suggest": { + "ext-bcmath": "Need to support JSON deserialization" + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Protobuf\\": "src/Google/Protobuf", + "GPBMetadata\\Google\\Protobuf\\": "src/GPBMetadata/Google/Protobuf" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "proto library for PHP", + "homepage": "https://developers.google.com/protocol-buffers/", + "keywords": [ + "proto" + ], + "support": { + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v5.36.0" + }, + "time": "2026-08-20T13:06:50+00:00" + }, + { + "name": "grpc/grpc", + "version": "1.82.0", + "source": { + "type": "git", + "url": "https://github.com/grpc/grpc-php.git", + "reference": "be984cb608f21e96453b3cfe54c748cc7b192250" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grpc/grpc-php/zipball/be984cb608f21e96453b3cfe54c748cc7b192250", + "reference": "be984cb608f21e96453b3cfe54c748cc7b192250", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "google/auth": "^v1.3.0" + }, + "suggest": { + "ext-protobuf": "For better performance, install the protobuf C extension.", + "google/protobuf": "To get started using grpc quickly, install the native protobuf library." + }, + "type": "library", + "autoload": { + "psr-4": { + "Grpc\\": "src/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "gRPC library for PHP", + "homepage": "https://grpc.io", + "keywords": [ + "rpc" + ], + "support": { + "source": "https://github.com/grpc/grpc-php/tree/v1.82.0" + }, + "time": "2026-07-03T09:39:53+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.15.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee80339fd9177ba44c49cdb653ff02a4d1106b9a", + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.5.3", + "guzzlehttp/psr7": "^2.13.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.3", + "guzzlehttp/test-server": "^0.7", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.15.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2026-08-24T09:21:06+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.5.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/cde49999552d185d64715fe9c1f77a2aadd2f9f1", + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.5.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2026-08-24T09:11:28+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.13.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "95e7828100de18b4e269fb1703be530082d5166d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/95e7828100de18b4e269fb1703be530082d5166d", + "reference": "95e7828100de18b4e269fb1703be530082d5166d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.13.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2026-08-24T09:13:11+00:00" + }, + { + "name": "hybridauth/hybridauth", + "version": "v3.13.0", + "source": { + "type": "git", + "url": "https://github.com/hybridauth/hybridauth.git", + "reference": "5f799ed5fd35e21f06cf42310f9f6ed3f7bbe7df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hybridauth/hybridauth/zipball/5f799ed5fd35e21f06cf42310f9f6ed3f7bbe7df", + "reference": "5f799ed5fd35e21f06cf42310f9f6ed3f7bbe7df", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^6.5 || ^8.0 || ^12.0" + }, + "suggest": { + "firebase/php-jwt": "Needed to support Apple provider", + "phpseclib/phpseclib": "Needed to support Apple provider" + }, + "type": "library", + "autoload": { + "psr-4": { + "Hybridauth\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Miled", + "email": "hybridauth@gmail.com" + } + ], + "description": "PHP Social Authentication Library", + "homepage": "https://hybridauth.github.io", + "keywords": [ + "Authentication", + "OpenId", + "api", + "apple", + "authorization", + "facebook", + "google", + "oauth", + "social", + "twitter" + ], + "support": { + "gitter": "https://gitter.im/hybridauth/hybridauth", + "issues": "https://github.com/hybridauth/hybridauth/issues", + "source": "https://github.com/hybridauth/hybridauth/tree/v3.13.0" + }, + "time": "2026-04-02T19:11:26+00:00" + }, + { + "name": "lcobucci/clock", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/clock.git", + "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/fb533e093fd61321bfcbac08b131ce805fe183d3", + "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3", + "shasum": "" + }, + "require": { + "php": "^8.0", + "stella-maris/clock": "^0.1.4" + }, + "require-dev": { + "infection/infection": "^0.26", + "lcobucci/coding-standard": "^8.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" + } + ], + "description": "Yet another clock abstraction", + "support": { + "issues": "https://github.com/lcobucci/clock/issues", + "source": "https://github.com/lcobucci/clock/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2022-04-19T19:34:17+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "5.6.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-sodium": "*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/clock": "^1.0" + }, + "require-dev": { + "infection/infection": "^0.29", + "lcobucci/clock": "^3.2", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^11.1" + }, + "suggest": { + "lcobucci/clock": ">= 3.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "support": { + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/5.6.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2025-10-17T11:30:53+00:00" + }, + { + "name": "league/oauth2-client", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "26e8c5da4f3d78cede7021e09b1330a0fc093d5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/26e8c5da4f3d78cede7021e09b1330a0fc093d5e", + "reference": "26e8c5da4f3d78cede7021e09b1330a0fc093d5e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "php": "^7.1 || >=8.0.0 <8.6.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.9.0" + }, + "time": "2025-11-25T22:17:17+00:00" + }, + { + "name": "microsoft/microsoft-graph", + "version": "1.110.0", + "source": { + "type": "git", + "url": "https://github.com/microsoftgraph/msgraph-sdk-php.git", + "reference": "da45ea4a5d5dda97549313129748bd10fdb2930c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/da45ea4a5d5dda97549313129748bd10fdb2930c", + "reference": "da45ea4a5d5dda97549313129748bd10fdb2930c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "php": "^8.0 || ^7.3", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "guzzlehttp/promises": "^1.0 || ^2.0", + "mikey179/vfsstream": "^1.2", + "phpstan/phpstan": "^0.12.90 || ^1.0.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Microsoft\\Graph\\": "src/", + "Beta\\Microsoft\\Graph\\": "src/Beta/Microsoft/Graph/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Microsoft Graph Client Tooling", + "email": "graphtooling@service.microsoft.com", + "role": "Developer" + } + ], + "description": "The Microsoft Graph SDK for PHP", + "homepage": "https://developer.microsoft.com/en-us/graph", + "support": { + "issues": "https://github.com/microsoftgraph/msgraph-sdk-php/issues", + "source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.110.0" + }, + "time": "2024-01-15T18:49:30+00:00" + }, + { + "name": "mongodb/mongodb", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/mongodb/mongo-php-library", + "reference": "066ee1fd2ed0418798d4e024299b33f75f915bb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/066ee1fd2ed0418798d4e024299b33f75f915bb4", + "reference": "066ee1fd2ed0418798d4e024299b33f75f915bb4", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "ext-mongodb": "^2.4", + "php": "^8.1", + "psr/log": "^1.1.4|^2|^3", + "symfony/polyfill-php85": "^1.33" + }, + "replace": { + "mongodb/builder": "*" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0", + "phpunit/phpunit": "^10.5.35", + "rector/rector": "^2.3.4", + "squizlabs/php_codesniffer": "^3.7", + "vimeo/psalm": "~6.14.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "MongoDB\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + }, + { + "name": "Jérôme Tamarelle", + "email": "jerome.tamarelle@mongodb.com" + } + ], + "description": "MongoDB driver library", + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "keywords": [ + "database", + "driver", + "mongodb", + "persistence" + ], + "time": "2026-08-18T14:20:54+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2026-01-02T08:56:05+00:00" + }, + { + "name": "nikic/fast-route", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "7476684f39bb9124b3be69ed3d84b66723920298" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/7476684f39bb9124b3be69ed3d84b66723920298", + "reference": "7476684f39bb9124b3be69ed3d84b66723920298", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/1.3.1" + }, + "time": "2026-07-09T19:38:47+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.8.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" + }, + "time": "2026-07-04T14:30:18+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" + }, + "time": "2025-11-21T15:09:14+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.12.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/d1ac35d784bf9f5e61b424901d5a014967f15b12", + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.12.0" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2025-10-15T16:49:08+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.3" + }, + "time": "2026-07-08T07:01:06+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "radebatz/type-info-extras", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/DerManoMann/type-info-extras.git", + "reference": "95a524a74a61648b44e355cb33d38db4b17ef5ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DerManoMann/type-info-extras/zipball/95a524a74a61648b44e355cb33d38db4b17ef5ce", + "reference": "95a524a74a61648b44e355cb33d38db4b17ef5ce", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "phpstan/phpdoc-parser": "^2.0", + "symfony/type-info": "^7.3.8 || ^7.4.1 || ^8.0 || ^8.1-@dev" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.70", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Radebatz\\TypeInfoExtras\\": "src" + }, + "exclude-from-classmap": [ + "/tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Martin Rademacher", + "email": "mano@radebatz.org" + } + ], + "description": "Extras for symfony/type-info", + "homepage": "http://radebatz.net/mano/", + "keywords": [ + "component", + "symfony", + "type-info", + "types" + ], + "support": { + "issues": "https://github.com/DerManoMann/type-info-extras/issues", + "source": "https://github.com/DerManoMann/type-info-extras/tree/1.0.7" + }, + "time": "2026-03-06T22:40:29+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8", + "shasum": "" + }, + "require": { + "brick/math": ">=0.8.16 <=0.18", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.3" + }, + "time": "2026-06-18T03:57:49+00:00" + }, + { + "name": "robthree/twofactorauth", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/RobThree/TwoFactorAuth.git", + "reference": "85408c4e775dba7c0802f2d928efd921d530bc5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/85408c4e775dba7c0802f2d928efd921d530bc5b", + "reference": "85408c4e775dba7c0802f2d928efd921d530bc5b", + "shasum": "" + }, + "require": { + "php": ">=8.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.13", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^9" + }, + "suggest": { + "bacon/bacon-qr-code": "Needed for BaconQrCodeProvider provider", + "endroid/qr-code": "Needed for EndroidQrCodeProvider" + }, + "type": "library", + "autoload": { + "psr-4": { + "RobThree\\Auth\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Janssen", + "homepage": "http://robiii.me", + "role": "Developer" + }, + { + "name": "Nicolas CARPi", + "homepage": "https://github.com/NicolasCARPi", + "role": "Developer" + }, + { + "name": "Will Power", + "homepage": "https://github.com/willpower232", + "role": "Developer" + } + ], + "description": "Two Factor Authentication", + "homepage": "https://github.com/RobThree/TwoFactorAuth", + "keywords": [ + "Authentication", + "MFA", + "Multi Factor Authentication", + "Two Factor Authentication", + "authenticator", + "authy", + "php", + "tfa" + ], + "support": { + "issues": "https://github.com/RobThree/TwoFactorAuth/issues", + "source": "https://github.com/RobThree/TwoFactorAuth" + }, + "funding": [ + { + "url": "https://paypal.me/robiii", + "type": "custom" + }, + { + "url": "https://github.com/RobThree", + "type": "github" + } + ], + "time": "2026-01-05T13:17:41+00:00" + }, + { + "name": "spatie/typescript-transformer", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/typescript-transformer.git", + "reference": "dd7cbb90b6b8c34f2aee68701cf39c5432400c0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/typescript-transformer/zipball/dd7cbb90b6b8c34f2aee68701cf39c5432400c0d", + "reference": "dd7cbb90b6b8c34f2aee68701cf39c5432400c0d", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18|^5.0", + "php": "^8.1", + "phpdocumentor/type-resolver": "^1.6.2", + "symfony/process": "^5.2|^6.0|^7.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.40", + "larapack/dd": "^1.1", + "myclabs/php-enum": "^1.7", + "pestphp/pest": "^1.22", + "phpstan/extension-installer": "^1.1", + "phpunit/phpunit": "^9.0", + "spatie/data-transfer-object": "^2.0", + "spatie/enum": "^3.0", + "spatie/pest-plugin-snapshots": "^1.1", + "spatie/temporary-directory": "^1.2|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\TypeScriptTransformer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ruben Van Assche", + "email": "ruben@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Transform your PHP structures to TypeScript types", + "homepage": "https://github.com/spatie/typescript-transformer", + "keywords": [ + "spatie", + "typescript-transformer" + ], + "support": { + "issues": "https://github.com/spatie/typescript-transformer/issues", + "source": "https://github.com/spatie/typescript-transformer/tree/2.5.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-04-25T13:53:57+00:00" + }, + { + "name": "stella-maris/clock", + "version": "0.1.7", + "source": { + "type": "git", + "url": "https://github.com/stella-maris-solutions/clock.git", + "reference": "fa23ce16019289a18bb3446fdecd45befcdd94f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stella-maris-solutions/clock/zipball/fa23ce16019289a18bb3446fdecd45befcdd94f8", + "reference": "fa23ce16019289a18bb3446fdecd45befcdd94f8", + "shasum": "" + }, + "require": { + "php": "^7.0|^8.0", + "psr/clock": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "StellaMaris\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Heigl", + "role": "Maintainer" + } + ], + "description": "A pre-release of the proposed PSR-20 Clock-Interface", + "homepage": "https://gitlab.com/stella-maris/clock", + "keywords": [ + "clock", + "datetime", + "point in time", + "psr20" + ], + "support": { + "source": "https://github.com/stella-maris-solutions/clock/tree/0.1.7" + }, + "time": "2022-11-25T16:15:06+00:00" + }, + { + "name": "swaggest/json-diff", + "version": "v3.12.1", + "source": { + "type": "git", + "url": "https://github.com/swaggest/json-diff.git", + "reference": "7ebc4eab95bcc73916433964c266588d09b35052" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swaggest/json-diff/zipball/7ebc4eab95bcc73916433964c266588d09b35052", + "reference": "7ebc4eab95bcc73916433964c266588d09b35052", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.1" + }, + "require-dev": { + "phperf/phpunit": "4.8.37" + }, + "type": "library", + "autoload": { + "psr-4": { + "Swaggest\\JsonDiff\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Viacheslav Poturaev", + "email": "vearutop@gmail.com" + } + ], + "description": "JSON diff/rearrange/patch/pointer library for PHP", + "support": { + "issues": "https://github.com/swaggest/json-diff/issues", + "source": "https://github.com/swaggest/json-diff/tree/v3.12.1" + }, + "time": "2025-03-10T08:22:10+00:00" + }, + { + "name": "symfony/cache", + "version": "v8.0.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "3e9c898a1c2d78661676befcc2f9dd610c56c9dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/3e9c898a1c2d78661676befcc2f9dd610c56c9dd", + "reference": "3e9c898a1c2d78661676befcc2f9dd610c56c9dd", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^3.6", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^7.4|^8.0" + }, + "conflict": { + "ext-redis": "<6.1", + "ext-relay": "<0.12.1" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^4.3", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v8.0.15" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-29T04:09:38+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/9789738bc19af1106dc54d6afba9a0b467516cf2", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "962e18f09ebe68a49039b4c82fc0ea4871824fca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/962e18f09ebe68a49039b4c82fc0ea4871824fca", + "reference": "962e18f09ebe68a49039b4c82fc0ea4871824fca", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.17" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T12:09:28+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v7.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "99b5b14953237c89ed24f1af5ba34225caf598a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/99b5b14953237c89ed24f1af5ba34225caf598a4", + "reference": "99b5b14953237c89ed24f1af5ba34225caf598a4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/process": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v7.4.15" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-26T10:22:28+00:00" + }, + { + "name": "symfony/expression-language", + "version": "v7.4.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "bd5763f92959201816ecc31defdf352d2ea473be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/bd5763f92959201816ecc31defdf352d2ea473be", + "reference": "bd5763f92959201816ecc31defdf352d2ea473be", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an engine that can compile and evaluate expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/expression-language/tree/v7.4.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-08T20:24:16+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v8.0.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "3d190c51f717870eed9aa5e9c7cb927ffc911d05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3d190c51f717870eed9aa5e9c7cb927ffc911d05", + "reference": "3d190c51f717870eed9aa5e9c7cb927ffc911d05", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v8.0.15" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-22T15:20:43+00:00" + }, + { + "name": "symfony/finder", + "version": "v8.0.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "4a93f8c95fabc35fcfd2f5dbb29d138d3fe98f43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/4a93f8c95fabc35fcfd2f5dbb29d138d3fe98f43", + "reference": "4a93f8c95fabc35fcfd2f5dbb29d138d3fe98f43", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/filesystem": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v8.0.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-27T08:56:37+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-28T08:25:59+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-07T06:33:24+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-27T06:59:30+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-01T12:47:55+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-01T12:47:55+00:00" + }, + { + "name": "symfony/process", + "version": "v7.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "058d17fc284cce14efb2385783b55014a461b176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/058d17fc284cce14efb2385783b55014a461b176", + "reference": "058d17fc284cce14efb2385783b55014a461b176", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.4.17" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:40:08+00:00" + }, + { + "name": "symfony/property-access", + "version": "v7.4.16", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "c3dce76a6697c6428f7d80d5cd1cf0aa5d9679f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/c3dce76a6697c6428f7d80d5cd1cf0aa5d9679f3", + "reference": "c3dce76a6697c6428f7d80d5cd1cf0aa5d9679f3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/property-info": "^6.4.32|~7.3.10|^7.4.4|^8.0.4" + }, + "require-dev": { + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4.1|^7.0.1|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v7.4.16" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-30T12:37:26+00:00" + }, + { + "name": "symfony/property-info", + "version": "v8.0.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "8899ae3a3dd858ac58d66dc168fb953ca3548c74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/8899ae3a3dd858ac58d66dc168fb953ca3548c74", + "reference": "8899ae3a3dd858ac58d66dc168fb953ca3548c74", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/string": "^7.4|^8.0", + "symfony/type-info": "^7.4.7|^8.0.7" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v8.0.15" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-22T15:20:43+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-16T09:55:08+00:00" + }, + { + "name": "symfony/string", + "version": "v8.0.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "1a6a4245943af4dabe57d269bd0903f9d140a15e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/1a6a4245943af4dabe57d269bd0903f9d140a15e", + "reference": "1a6a4245943af4dabe57d269bd0903f9d140a15e", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v8.0.15" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-28T07:34:23+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/type-info", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "08723aceb8c3271e8cb3db8b2565728b0c88e866" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/08723aceb8c3271e8cb3db8b2565728b0c88e866", + "reference": "08723aceb8c3271e8cb3db8b2565728b0c88e866", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.30|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-29T15:02:55+00:00" + }, + { + "name": "symfony/validator", + "version": "v7.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "b1cbb758c005fbe0d7b2b8d1561869d318628a10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/b1cbb758c005fbe0d7b2b8d1561869d318628a10", + "reference": "b1cbb758c005fbe0d7b2b8d1561869d318628a10", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php83": "^1.27", + "symfony/translation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/lexer": "<1.1", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<7.0", + "symfony/expression-language": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/intl": "<6.4", + "symfony/property-info": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/var-exporter": "<6.4.25|>=7.0,<7.3.3", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4.3|^7.0.3|^8.0", + "symfony/type-info": "^7.1.8", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Validator\\": "" + }, + "exclude-from-classmap": [ + "/Tests/", + "/Resources/bin/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to validate values", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/validator/tree/v7.4.17" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-22T09:04:42+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v8.0.16", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "ea291a085c804869b333804706b0a58231014671" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/ea291a085c804869b333804706b0a58231014671", + "reference": "ea291a085c804869b333804706b0a58231014671", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v8.0.16" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-29T16:27:17+00:00" + }, + { + "name": "symfony/yaml", + "version": "v8.0.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "38e4b36d74a20dd9124a5b34c3e83e270b9649b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/38e4b36d74a20dd9124a5b34c3e83e270b9649b8", + "reference": "38e4b36d74a20dd9124a5b34c3e83e270b9649b8", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<7.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v8.0.15" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-22T15:20:43+00:00" + }, + { + "name": "thenetworg/oauth2-azure", + "version": "v2.2.6", + "source": { + "type": "git", + "url": "https://github.com/TheNetworg/oauth2-azure.git", + "reference": "8a65bb5f72bb772676e2758261b3c088dd422f72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/8a65bb5f72bb772676e2758261b3c088dd422f72", + "reference": "8a65bb5f72bb772676e2758261b3c088dd422f72", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "firebase/php-jwt": "~3.0||~4.0||~5.0||~6.0||~7.0", + "league/oauth2-client": "~2.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "TheNetworg\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Hajek", + "email": "jan.hajek@thenetw.org", + "homepage": "https://thenetw.org" + } + ], + "description": "Azure Active Directory OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "SSO", + "aad", + "authorization", + "azure", + "azure active directory", + "client", + "microsoft", + "oauth", + "oauth2", + "windows azure" + ], + "support": { + "issues": "https://github.com/TheNetworg/oauth2-azure/issues", + "source": "https://github.com/TheNetworg/oauth2-azure/tree/v2.2.6" + }, + "time": "2026-06-22T14:24:01+00:00" + }, + { + "name": "zircote/swagger-php", + "version": "6.7.0", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "af0a8de6c4b6780e8e07d06ee0c7da538b5f1e46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/af0a8de6c4b6780e8e07d06ee0c7da538b5f1e46", + "reference": "af0a8de6c4b6780e8e07d06ee0c7da538b5f1e46", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.19 || ^5.0", + "php": ">=8.2", + "phpstan/phpdoc-parser": "^2.0", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "radebatz/type-info-extras": "^1.0.2", + "symfony/console": "^7.4 || ^8.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": "^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "conflict": { + "symfony/process": ">=6, <6.4.14" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "friendsofphp/php-cs-fixer": "^3.62", + "phpstan/phpstan": "^2.2", + "phpunit/phpunit": "^11.5 || >=12.5.22", + "rector/rector": "^2.3" + }, + "bin": [ + "bin/openapi" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "description": "Generate interactive documentation for your RESTful API using PHP attributes (preferred) or PHPDoc annotations", + "homepage": "https://github.com/zircote/swagger-php", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/6.7.0" + }, + "funding": [ + { + "url": "https://github.com/zircote", + "type": "github" + } + ], + "time": "2026-08-24T21:55:54+00:00" + } + ], + "packages-dev": [ + { + "name": "jetbrains/phpstorm-attributes", + "version": "1.3", + "source": { + "type": "git", + "url": "https://github.com/JetBrains/phpstorm-attributes", + "reference": "c9afb897cad47b087457f667ec2156dde8aed926" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JetBrains/phpstorm-attributes/zipball/c9afb897cad47b087457f667ec2156dde8aed926", + "reference": "c9afb897cad47b087457f667ec2156dde8aed926", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "JetBrains\\PhpStorm\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "JetBrains", + "homepage": "https://www.jetbrains.com" + } + ], + "description": "PhpStorm specific attributes", + "keywords": [ + "attributes", + "jetbrains", + "phpstorm" + ], + "support": { + "issues": "https://youtrack.jetbrains.com/newIssue?project=WI" + }, + "time": "2026-07-22T11:27:58+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2026-08-11T10:17:44+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.2.9", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/13d6b4f347bad222da436580c8304fa6f83e6bd0", + "reference": "13d6b4f347bad222da436580c8304fa6f83e6bd0", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-08-22T07:38:16+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.1", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.3.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.46" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-12-24T07:01:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T13:52:54+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.56", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f83edffa6967c3db468d48a695ec7bcb02e9256", + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-filter": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.1", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.3", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-07-06T14:52:39+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:26:40+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-05-21T11:55:47+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:12:51+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:42:22+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:55:48+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "gcgov/framework": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.4", + "ext-mongodb": "*" + }, + "platform-dev": {}, + "platform-overrides": { + "php": "8.4.0" + }, + "plugin-api-version": "2.6.0" +} From c0a7ae3b677763df670e6d5b88c1fe4623087123 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 12:39:36 +0000 Subject: [PATCH 07/17] v7 phase 03: five-variable config, PHP runtime config, two images, release caller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config.json - Down to five required references: APP_TYPE, APP_ROOT_URL, APP_BASE_PATH, the two redirect urls, MONGO_DATABASE and %env(secret:MONGO_URI)%. The microsoft, payjunction and SMTP blocks are gone — pre-wiring payment processing into every county application meant every one had to be handed credentials it does not use in order to boot. An absent section hydrates to its defaults. - No {token} syntax anywhere: tokenReplacer no longer exists, so the tokens left in app.php would never have been replaced. OpenAPI metadata is now literals with a relative server url, which is correct in every Environment. - .env.example keeps only the compose variables; the application's come from `gf env --init`, so the two files cannot drift into each other. PHP runtime - docker/php/conf.d/app.ini is what the four deleted php.ini files were never replaced with: the base image ships none, so opcache was off and every limit was a compile default. Fixes the live mismatch where nginx accepted a 1024m body that PHP then rejected. JIT stays off — an API bound on Mongo I/O gains nothing from it. - The pool config replaces www.conf rather than layering on it: www.conf's user/group need a root master, and running one on a public-facing host to gain privilege separation inside an already single-purpose container is a bad trade. Images - Separate php and nginx targets from one context, so the pair cannot serve mismatched releases. nginx gets only www/, never the application's PHP. - APP_VERSION build arg surfaces through /health, so a deploy can be verified. - HEALTHCHECK exercises nginx → FPM → PHP → the route table rather than asking whether a process is running. Liveness only, via a configurable HEALTH_URL; readiness is the deploy gate's business. CI additionally asserts what the image must never contain (.env, JWT keys), that it runs as www-data without the FPM root warning, and that post_max_size still matches nginx's limit. 47 tests pass; PHPStan clean. The image build itself is unverified — no Docker daemon in this environment — so CI is its first real exercise. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012kqJazGHDMq19pQ64732i6 --- .env.example | 56 ++------ .github/workflows/ci.yml | 46 ++++++- .github/workflows/release.yml | 34 +++++ DOCKER.md | 230 +++++++++++++++---------------- Dockerfile | 73 +++++++--- README.md | 69 +++++----- app/app.php | 8 +- composer.lock | 10 +- config.json | 72 +++------- docker-compose.yml | 35 +++-- docker/php/conf.d/app.ini | 57 ++++++++ docker/php/conf.d/dev.ini | 14 ++ docker/php/php-fpm.d/zz-app.conf | 37 +++++ tests/Unit/ConfigFilesTest.php | 147 ++++++++++++++++---- 14 files changed, 577 insertions(+), 311 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 docker/php/conf.d/app.ini create mode 100644 docker/php/conf.d/dev.ini create mode 100644 docker/php/php-fpm.d/zz-app.conf diff --git a/.env.example b/.env.example index 65ab66e..2f8390e 100644 --- a/.env.example +++ b/.env.example @@ -1,51 +1,23 @@ -# Copy this file to .env and fill in real values. NEVER commit .env. -# These variables are read by the framework's %env(...)% config resolver -# (see the framework's readme/environment-variables.md) and by docker-compose. +# Variables that docker compose needs, which config.json knows nothing about. +# Copy to .env alongside the application variables. # -# Precedence, highest wins: real process environment > .env.local > .env +# The APPLICATION's variables are not listed here — `gf env --init` generates those +# from config.json itself, so the two cannot drift. The split is deliberate: +# +# gf env --init writes what config.json references (APP_TYPE, MONGO_URI, …) +# this file everything else the local stack needs +# +# Never commit .env. -# ---- HTTP / container ports (docker-compose) ---- +# ---- Ports published on the host by the dev stack ---- HTTP_PORT=8080 MONGO_PORT=27017 + +# ---- PHP-FPM upstream, as nginx reaches it ---- PHP_FPM_HOST=php:9000 -# ---- CORS allowlist (nginx). Keep all three distinct and non-empty. ---- +# ---- CORS allowlist. Keep all three distinct — nginx will not start with a +# duplicate map key, and only these exact origins are allowed. ---- CORS_ORIGIN_APP=http://localhost:8080 CORS_ORIGIN_FRONTEND=http://localhost:5173 CORS_ORIGIN_SWAGGER=http://localhost:8081 - -# ---- MongoDB ---- -# In the dev profile the compose service is reachable at mongodb:27017. -MONGO_URI=mongodb://mongodb:27017 -MONGO_DATABASE=app - -# ---- Identity overrides (optional locally) ---- -# config.json bakes the dev values for these at `gf setup` time via -# %env(default:...)% — uncomment to override without editing config. -# APP_TYPE=local -# APP_SERVER_NAME=app.local -# APP_ROOT_URL=https://app.local -# APP_BASE_PATH=/api/ - -# ---- Microsoft OAuth (leave blank if unused) ---- -MICROSOFT_CLIENT_SECRET= - -# ---- SMTP (leave blank if unused) ---- -SMTP_USERNAME= -SMTP_PASSWORD= - -# ---- PayJunction (leave blank if unused) ---- -PAYJUNCTION_PASSWORD= -PAYJUNCTION_API_KEY= - -# ---- Foreign-environment reads for gf db:restore --from=prod / db:run --env=prod ---- -# These feed config.json's `environments.prod` entry (CLI-only; stripped at runtime). -# They are PREFIXED (PROD_*) so a missing value fails loudly instead of resolving to -# your local MONGO_URI. Fill them in only when you need to pull prod data locally. -# PROD_MONGO_URI=mongodb+srv://user:pass@prod-cluster/ -# PROD_MONGO_DATABASE=app - -# ---- Production: file-based secrets (Docker/Swarm/Kubernetes) ---- -# Prefer mounting secrets as files and referencing them in config.json -# with %env(trim:file:MONGO_URI_FILE)%. Example: -# MONGO_URI_FILE=/run/secrets/mongo_uri diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53bbae6..214dba5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,11 +62,47 @@ jobs: sh -c 'set -e; /docker-entrypoint.sh nginx -t' docker-build: - name: Docker build (prod target) + name: Docker build (${{ matrix.target }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: [php, nginx] steps: - uses: actions/checkout@v4 - - name: Build the production image - run: docker build --target prod -t framework-app-template:ci . - - name: Image contains the committed config.json - run: docker run --rm --entrypoint test framework-app-template:ci -f config.json + - name: Build + run: docker build --target ${{ matrix.target }} --build-arg APP_VERSION=ci -t app:${{ matrix.target }} . + + - name: php — the committed config.json is in the image, secrets are not + if: matrix.target == 'php' + run: | + set -eux + docker run --rm --entrypoint test app:php -f config.json + # JWT keys and .env are gitignored, so they must not have reached the build + # context. If either appears, authentication material is baked into a layer. + ! docker run --rm --entrypoint test app:php -e .env + ! docker run --rm --entrypoint test app:php -d srv/jwtCertificates + + - name: php — runs as a non-root user with a valid FPM config + if: matrix.target == 'php' + run: | + set -eux + test "$(docker run --rm --entrypoint id app:php -un)" = www-data + # -t exits non-zero on a bad pool config, and prints the warning we removed + # www.conf to avoid; fail if it comes back. + docker run --rm --entrypoint php-fpm app:php -t 2>&1 | tee /tmp/fpm.log + ! grep -qi "when FPM is not running as root" /tmp/fpm.log + + - name: php — opcache and the upload limits the nginx config assumes + if: matrix.target == 'php' + run: | + set -eux + docker run --rm --entrypoint php app:php -r 'exit(ini_get("opcache.enable") ? 0 : 1);' + docker run --rm --entrypoint php app:php -r 'exit(ini_get("display_errors") ? 1 : 0);' + # Must be >= nginx client_max_body_size (1024m), or a large upload is accepted + # by nginx and then rejected by PHP after crossing the wire. + docker run --rm --entrypoint php app:php -r 'exit(ini_get("post_max_size") === "1024M" ? 0 : 1);' + + - name: nginx — serves the application's static assets + if: matrix.target == 'nginx' + run: docker run --rm --entrypoint test app:nginx -f /var/www/app/www/index.php diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b5eb6b3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +# Build a Release and hand it to the ops repository to deploy. +# +# This file is deliberately thin. The real logic lives in a reusable workflow in +# gcgov/deploy so that changing how deployment works is one pull request, not thirty. +# +# push to main build and publish only +# push of v* build, publish, and deploy +# +# The deploy step runs on gcgov/deploy's self-hosted runner for the target Zone, which +# is why nothing here needs SSH access or any secret beyond the registry token: this +# repository's contributors never get code execution inside a Zone. + +name: Release + +on: + push: + branches: [main] + tags: ['v*'] + +permissions: + contents: read + packages: write + +jobs: + release: + uses: gcgov/deploy/.github/workflows/build-and-deploy.yml@main + with: + # Everything else derives from this: image names, ops repo paths, database users. + app: framework-app-template + # Which Zone this application runs in: internal | bridge | isolated. + zone: bridge + # Only a tag deploys. A push to main publishes an image and stops there. + deploy: ${{ startsWith(github.ref, 'refs/tags/v') }} + secrets: inherit diff --git a/DOCKER.md b/DOCKER.md index 6da002f..fd50d51 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -1,171 +1,167 @@ -# Running this app in Docker +# Running and deploying this application -This template ships a Linux container stack — **Nginx + PHP-FPM** (plus an optional MongoDB -for development) — that replaces the old Windows/IIS hosting. Secrets are injected from the -environment or Docker secrets via the framework's `%env(...)%` config resolver, so nothing -sensitive lives in the config files. +This template builds two container images from one context — **php** (PHP-FPM running the +application) and **nginx** (static assets, proxying everything else to php). They are tagged +together, so the pair can never disagree about which release it is serving. -> **Prerequisite:** the images install `gcgov/framework` from Packagist. The release that ships -> the `%env()` config resolver must be tagged and referenced by `composer.json` before -> `composer install` (and therefore `docker build`) can succeed. See `composer.json`. +A **Release** is one immutable image identified by content digest. Deploying and rolling back are +the same operation: point a host at a different digest and restart. Nothing is built, resolved, or +updated on a production host. + +> Production topology — compose files, the Traefik stack, and secrets — lives in the **ops +> repository**, `gcgov/deploy`, not here. This repository builds Releases; the ops repository +> decides what runs. --- -## Quick start (local development) +## Local development ```bash -cp .env.example .env # fill in any real values you have; blanks are fine for dev -docker compose --profile dev up --build -# → API on http://localhost:8080 +vendor/bin/gf env --init # write .env from what config.json references +cp .env.example .env.local # and the variables docker compose needs +# fill in the blanks in .env, then: +docker compose up --build +open http://localhost:8080 ``` -The `dev` profile also starts a throwaway MongoDB at `mongodb:27017`, which `.env.example`'s -`MONGO_URI` points at. - -Run framework CLI routes and tooling inside the PHP container: +The working tree is bind-mounted into the php container and opcache revalidates on every request +(`docker/php/conf.d/dev.ini`), so edits are live. ```bash +docker compose exec php vendor/bin/gf env # does the configuration resolve? docker compose exec php vendor/bin/gf cli:list -docker compose exec php vendor/bin/gf cli /your/cli/route docker compose exec php composer ci ``` -Before first run you still scaffold the identity/URL placeholders with `gf setup` (it replaces -the `{app_*}` tokens in the config, nginx, and compose files — they become the baked -`default:` fallbacks inside `config.json`'s `%env(...)%` references). - -**Environment selection is the environment itself.** The committed -root-level `config.json` is the only config file (app + environment sections merged); the variable values the container is -given decide whether it behaves as local, prod, or anything else. `gf env` validates that -resolution (`gf env` for the active configuration, `gf env prod` for the CLI-only -`environments.prod` entry used by `gf db:*` commands). +--- -### Production configuration checklist +## Configuration -A prod container must supply (hard `%env()` references — missing ones fail loudly naming the -variable): `MONGO_URI`, `MONGO_DATABASE`, `MICROSOFT_CLIENT_SECRET`, `PAYJUNCTION_PASSWORD`, -`PAYJUNCTION_API_KEY` — plus the identity overrides `APP_TYPE=prod`, `APP_SERVER_NAME`, -`APP_ROOT_URL`, `APP_BASE_PATH`, and the `APP_REDIRECT_AFTER_*` urls. Prefer the `*_FILE` -secret pattern below for the secrets. +One committed `config.json`. Every `%env(...)%` reference in it is **required** — there are no +defaults, and a variable set to the empty string counts as unset. A production container that +forgets a variable fails to start and names it, rather than booting in some half-configured state. ---- +```bash +vendor/bin/gf env --list # what this application needs, and which are secrets +vendor/bin/gf env # resolve it against the current environment +``` -## Securely setting environment variables in Docker +The full reference is `readme/environment-variables.md` in the framework. -The framework reads secrets through `%env(...)%`, so **how** you supply those variables is what -keeps them safe. In order of preference: +--- -### 1. Prefer file-based secrets (Docker / Swarm / Kubernetes secrets) +## Secrets -A secret mounted as a file never appears in the process environment, so it is **not** exposed by -`docker inspect` and does not leak into child processes. Mount it and read it with the `file` -processor (the leading `trim:` strips the trailing newline): +Secrets reach a container as **files**, never as environment variables: ```jsonc -// config.json -"uri": "%env(trim:file:MONGO_URI_FILE)%" +// config.json — the same file in every environment +"uri": "%env(secret:MONGO_URI)%" ``` -```yaml -# compose / swarm -services: - php: - environment: - MONGO_URI_FILE: /run/secrets/mongo_uri - secrets: - - mongo_uri -secrets: - mongo_uri: - external: true # `docker secret create mongo_uri ./mongo_uri` -``` +`%env(secret:MONGO_URI)%` reads the file named by `MONGO_URI_FILE` when that variable is set, and +falls back to `MONGO_URI` otherwise. A developer sets `MONGO_URI` in `.env`; production sets +`MONGO_URI_FILE=/run/secrets//mongo_uri` and mounts the file. One config file, both worlds. -Kubernetes — mount the secret as a file and point the `*_FILE` variable at it: - -```yaml -env: - - name: MONGO_URI_FILE - value: /run/secrets/mongo_uri -volumeMounts: - - name: mongo-uri - mountPath: /run/secrets - readOnly: true -volumes: - - name: mongo-uri - secret: - secretName: mongo-uri -``` +A `_FILE` variable naming a missing file is a **hard error**. It does not fall back to the plain +variable — that would silently substitute a stale environment value for a secret that failed to +mount, which is the failure you would least want to happen quietly. -### 2. Process environment variables (acceptable, less private) +Why files rather than environment variables: a value in the environment is visible in +`docker inspect`, readable from `/proc//environ`, and inherited by every child process. A +mounted file is none of those things. -Fine for non-secret config and local development; readable via `docker inspect` and the -container's `/proc`, so avoid for high-value secrets. +**Where the values come from.** Encrypted with SOPS in `gcgov/deploy`, against a GCP KMS key per +Zone plus an offline break-glass key. An operator decrypts on their own workstation and provisions +the files to the host — a step deliberately separate from deploying, so no host holds a decryption +key and CI never sees a secret. See `docs/adr/0003-secrets-never-decrypt-in-ci-or-on-hosts.md`. -```bash -docker run --env-file .env … # a gitignored env file -``` +### JWT signing keys -```yaml -services: - php: - env_file: [.env] # what this template's compose uses -``` +These are secrets too, and they are gitignored — so they are **not in the build context and not in +the image**. A container must point `jwtAuth.keyPath` at a provisioned directory +(`/run/secrets//jwt`) or authentication cannot work at all. Every replica must have the same +keys; regenerating them signs every user out. + +### Rules + +- **Never bake a secret into an image.** `ENV`, `ARG` and `COPY` all persist in layers and in + `docker history`. Anyone who can pull the image can read them. +- **Never put a real value in a compose file.** They are committed. +- **Rotation is a provision plus a restart, not a rebuild.** The image does not change. -Kubernetes can inject individual values from a Secret without a file: +--- + +## Health + +| Route | Checks | Used by | +|---|---|---| +| `GET {basePath}/health` | Configuration resolved. No I/O. | Container `HEALTHCHECK`, Traefik | +| `GET {basePath}/health/ready` | Pings every configured database. 503 when one is down. | The deploy gate | + +They are separate on purpose. If the container's healthcheck also pinged the database, a brief +database outage would fail the probe, restart every replica, and turn a dependency blip into a +crash loop. + +Both come from the framework, so every application has them — a deploy pipeline cannot gate on an +endpoint an application might have skipped. `/health` reports the deployed version from +`APP_VERSION`, baked in at build time, which is how you confirm a deploy actually landed. + +The nginx image's healthcheck URL is `HEALTH_URL`, defaulting to `http://localhost/health`. Set it +to match when the application is not served at the domain root. + +--- -```yaml -env: - - name: MICROSOFT_CLIENT_SECRET - valueFrom: - secretKeyRef: { name: app-secrets, key: microsoft-client-secret } +## Building + +```bash +docker build --target php -t ghcr.io/gcgov//php --build-arg APP_VERSION=$(git rev-parse --short HEAD) . +docker build --target nginx -t ghcr.io/gcgov//nginx . ``` -### Rules — do not break these +In practice `.github/workflows/release.yml` does this: a push to `main` builds and publishes, a +`v*` tag also deploys. Both call a reusable workflow in `gcgov/deploy`, so changing how deployment +works is one pull request rather than one per application. -- **Keep `.env` out of git.** It is gitignored here; commit only `.env.example` with blank/dummy - values. -- **Never bake secrets into an image.** `ENV`, `ARG`, and `COPY` all persist in image layers and - in `docker history` — anyone who can pull the image can read them. Inject secrets at **run** - time, never build time. -- **Never put real secret values in `docker-compose.yml`** (it is committed). Reference `${VAR}` - and keep the values in `.env` or a secrets manager. -- **Rotation is a restart, not a rebuild.** Because secrets are injected at runtime, rotating a - credential means updating the secret/`.env` and restarting the container — the image is - unchanged. +`composer.lock` is committed and `config.platform.php` pins resolution to the runtime the image +runs. Without that pin, resolving on a newer PHP locks packages that will not install in the image. --- ## TLS and the forwarded scheme -TLS is **not** terminated inside the container. Terminate it at your edge (reverse proxy, load -balancer, ingress) and forward the original scheme: +TLS terminates at Traefik, one instance per Zone, with certificates from Let's Encrypt over DNS-01 +— the only challenge type that works for a Zone with no inbound path from the internet, which is why +all three Zones use it. + +The container never terminates TLS and never redirects to HTTPS. It needs the original scheme +forwarded: ``` -proxy_set_header X-Forwarded-Proto $scheme; # or the ingress equivalent +proxy_set_header X-Forwarded-Proto $scheme; ``` -The bundled nginx config maps `X-Forwarded-Proto` to the `HTTPS` / `REQUEST_SCHEME` FastCGI -params, so PHP sees the real client scheme (used for absolute URLs, secure cookies, redirects). -There is deliberately no in-container HTTP→HTTPS redirect. +The bundled nginx config maps that to the `HTTPS` and `REQUEST_SCHEME` FastCGI params, so PHP sees +the real client scheme for absolute URLs, secure cookies, and redirects. --- ## What the nginx config does -`docker/nginx/default.conf.template` reproduces the five behaviors the IIS `web.config` provided -(scheme from the edge, trailing-slash strip, static pass-through for `theme/` and `favicon.ico`, -front-controller routing to `index.php` with `REQUEST_URI` preserved, and CORS with an origin -allowlist + preflight). The CORS origins come from `CORS_ORIGIN_APP`, `CORS_ORIGIN_FRONTEND`, and -`CORS_ORIGIN_SWAGGER`; only those exact origins receive `Access-Control-Allow-Origin`. +`docker/nginx/default.conf.template` reproduces the five behaviours the IIS `web.config` provided: +the scheme from the edge, trailing-slash stripping, static pass-through for `theme/` and +`favicon.ico`, front-controller routing to `index.php` with `REQUEST_URI` preserved, and CORS with +an origin allowlist and preflight handling. ---- +The CORS origins come from `CORS_ORIGIN_APP`, `CORS_ORIGIN_FRONTEND` and `CORS_ORIGIN_SWAGGER`, +substituted at container start. **Keep all three distinct** — they become keys in an nginx `map`, +and a duplicate key stops nginx from starting. -## Production image +--- -```bash -docker build --target prod -t your-app:latest . -``` +## What is deliberately not here -The `prod` target installs `--no-dev` dependencies and runs PHP-FPM. Serve it behind an nginx -container using `docker/nginx/default.conf.template` (both containers need the application files — -bake them in or share a volume — because nginx serves the static assets and PHP-FPM executes -`index.php`). Commit `composer.lock` for reproducible builds. +- **A production compose file.** It is in `gcgov/deploy`, under the Zone that runs this application. +- **Swarm or Kubernetes manifests.** The deployment target is Docker on three Ubuntu hosts. +- **Anything that writes to the container filesystem and expects it to survive.** Logs go to stderr; + uploads and sessions under `srv/tmp` are scratch space that a deploy discards. diff --git a/Dockerfile b/Dockerfile index 69cd614..c5b878e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,38 +1,49 @@ # syntax=docker/dockerfile:1 # -# Multi-stage build for a gcgov/framework API running on PHP-FPM behind Nginx. -# Targets: -# dev — full (incl. dev) dependencies, for local docker-compose development -# prod — minimal runtime image (default), no dev dependencies +# Two images, one build context. # -# NOTE: `composer install` resolves gcgov/framework from Packagist, so the -# framework release that ships the %env() config resolver must be tagged and -# the composer.json constraint set to it (see composer.json). Commit -# composer.lock for reproducible images. +# php — PHP-FPM running the application (default target) +# nginx — nginx serving the application's static assets and proxying to php +# dev — php plus dev dependencies, for the local compose stack +# +# nginx needs the application's www/ directory to serve theme assets, so it is built +# from the same context and tagged in lockstep with php. Two images that can never +# disagree about which release they are serving. +# +# Build both: +# docker build --target php -t ghcr.io/gcgov//php --build-arg APP_VERSION=$(git rev-parse HEAD) . +# docker build --target nginx -t ghcr.io/gcgov//nginx . -# ---- base: PHP-FPM + required extensions ---- +# ---- base: PHP-FPM + the extensions the framework requires ---- FROM php:8.4-fpm AS base RUN set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends \ git unzip libsodium-dev libzip-dev; \ - docker-php-ext-install -j"$(nproc)" sodium zip; \ + docker-php-ext-install -j"$(nproc)" sodium zip opcache; \ pecl install mongodb; \ docker-php-ext-enable mongodb; \ apt-get clean; \ rm -rf /var/lib/apt/lists/* + +# The base image ships no php.ini and a pool config that assumes a root master. +# Replace both — see docker/php/php-fpm.d/zz-app.conf for why www.conf goes. +COPY docker/php/conf.d/app.ini /usr/local/etc/php/conf.d/zz-app.ini +COPY docker/php/php-fpm.d/zz-app.conf /usr/local/etc/php-fpm.d/zz-app.conf +RUN rm -f /usr/local/etc/php-fpm.d/www.conf /usr/local/etc/php-fpm.d/www.conf.default + COPY --from=composer:2 /usr/bin/composer /usr/bin/composer WORKDIR /var/www/app -# ---- vendor: production dependencies only ---- +# ---- vendor: production dependencies, resolved from the committed lock ---- FROM base AS vendor -# composer.lock is optional here (the `*` glob) but SHOULD be committed. -COPY composer.json composer.lock* ./ +COPY composer.json composer.lock ./ RUN composer install --no-dev --no-scripts --no-interaction --prefer-dist --no-progress -# ---- dev: full dependencies for local development ---- +# ---- dev: full dependencies for the local compose stack ---- FROM base AS dev -COPY composer.json composer.lock* ./ +COPY docker/php/conf.d/dev.ini /usr/local/etc/php/conf.d/zzz-dev.ini +COPY composer.json composer.lock ./ RUN composer install --no-scripts --no-interaction --prefer-dist --no-progress COPY . /var/www/app RUN set -eux; \ @@ -41,12 +52,40 @@ RUN set -eux; \ USER www-data CMD ["php-fpm"] -# ---- prod: minimal runtime image (default target) ---- -FROM base AS prod +# ---- php: the production application image ---- +FROM base AS php + +# The deployed release, surfaced by GET {basePath}/health so a deploy can be verified +# rather than assumed. Passed by the release workflow; "unknown" locally. +ARG APP_VERSION=unknown +ENV APP_VERSION=${APP_VERSION} + COPY --from=vendor /var/www/app/vendor ./vendor COPY . /var/www/app RUN set -eux; \ mkdir -p srv/tmp/tmp srv/tmp/sessions srv/tmp/files srv/tmp/opcache srv/tmp/soaptmp srv/profile logs; \ chown -R www-data:www-data /var/www/app + +# Nothing in the image needs to be writable by the application except srv/tmp. +# The JWT signing keys and every other secret are provisioned at runtime, never baked +# in — they are gitignored, so they are not in this build context at all. USER www-data +EXPOSE 9000 CMD ["php-fpm"] + +# ---- nginx: serves static assets, proxies everything else to php ---- +FROM nginx:1.27-alpine AS nginx +COPY docker/nginx/default.conf.template /etc/nginx/templates/default.conf.template +# Only the web root: nginx has no business being able to read the application's PHP. +COPY www /var/www/app/www + +# The framework serves health at {basePath}/health, so the URL depends on the +# application's base path — set HEALTH_URL to match when it is not the domain root. +ENV HEALTH_URL="http://localhost/health" + +# Exercises the whole path — nginx, FPM, PHP, and the framework's route table — rather +# than asking whether a process is running. Liveness only: /health does no I/O, so a +# database blip cannot put the container into a restart loop. Readiness +# ({basePath}/health/ready, which pings Mongo) is what the deploy gate gets to decide on. +HEALTHCHECK --interval=15s --timeout=3s --start-period=20s --retries=3 \ + CMD wget --quiet --tries=1 --spider "$HEALTH_URL" || exit 1 diff --git a/README.md b/README.md index 7dff10f..834bc00 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,55 @@ # Framework App Template -App template repository to scaffold a new [gcgov/framework](https://github.com/gcgov/framework) -application. It runs in Docker (Nginx + PHP-FPM) and keeps secrets out of the config files by -resolving them from environment variables / Docker secrets via the framework's `%env(...)%` -syntax. +Template for a new [gcgov/framework](https://github.com/gcgov/framework) application. It builds two +container images — nginx and PHP-FPM — and keeps every secret out of the committed files: the one +`config.json` references them with `%env(...)%`, and production supplies them as provisioned files. ## Getting started -1. [Use this template](https://github.com/gcgov/framework-app-template/generate) to generate a - new repository for your app. -2. Scaffold the identity/URL placeholders. `gf setup` replaces the `{app_*}` and `{prod_app_*}` - tokens across the config, nginx, and compose files: +1. [Use this template](https://github.com/gcgov/framework-app-template/generate) to generate a new + repository for your application. + +2. Bootstrap it: ```bash composer install - vendor/bin/gf setup + vendor/bin/gf init --title="Permits API" + ``` + `gf init` writes the title and a freshly minted guid into `config.json`, writes a `.env` + skeleton from the variables `config.json` references, generates JWT signing keypairs, and + installs chrome-headless-shell. It is non-interactive, so it also works from a scaffolding + script or a devcontainer. + +3. Fill in `.env`, and add the compose variables: + ```bash + cp .env.example .env.local + vendor/bin/gf env # does it resolve? names the first thing missing ``` - Tokens you provide include: `{app_guid}` (generate at https://www.guidgenerator.com/), - `{app_title}`, `{app_root_url}`, `{app_base_path}`, `{app_redirect_after_login}`, - `{app_redirect_after_logout}`, the `{app_microsoft_*}` client id/tenant/drive id, and the - matching `{prod_app_*}` values for production. -3. Provide secrets and per-environment values as **environment variables**, not tokens. The - committed root `config.json` references them with `%env(...)%` — e.g. - `"uri": "%env(MONGO_URI)%"`, `"clientSecret": "%env(MICROSOFT_CLIENT_SECRET)%"`, - `"basePath": "%env(default:...:APP_BASE_PATH)%"`. Whichever values the process environment - supplies *are* the environment — there is nothing to activate. Copy `.env.example` to `.env` - for local development; use container env / Docker/Kubernetes secrets in production. See - **[DOCKER.md](DOCKER.md)** and the framework's - [environment-variables guide](https://github.com/gcgov/framework/blob/main/readme/environment-variables.md). + Every reference in `config.json` is **required** — there are no defaults, and a blank value + counts as missing. That is deliberate: a half-configured application should refuse to start + rather than run in some unintended posture. `gf env --list` shows the whole list. + 4. Run it: ```bash - cp .env.example .env - docker compose --profile dev up --build + docker compose up --build # → http://localhost:8080 ``` -5. Test the `widget` module, then create your own models, controllers, and services. -### Working with production data +5. Try the `widget` module, then write your own models, controllers, and routes. + +## Adding what you need -`config.json` has a CLI-only `environments.prod` entry (stripped at runtime) whose Mongo -connection references `PROD_MONGO_URI` / `PROD_MONGO_DATABASE`. To pull prod data locally, -set those two variables in your gitignored `.env` (they are listed, commented, in -`.env.example`), validate with `vendor/bin/gf env prod`, then run `gf db:restore --from=prod` -or `gf db:run --env=prod`. No prod config file ever lives in the repo, and the prefixed names -mean a missing value fails loudly instead of quietly using your local database. +`config.json` ships with five variables and nothing else — no Microsoft, PayJunction, or SMTP +block. Add the section for an integration when the application actually uses one; a section that +is absent hydrates to its defaults. Keeping unused credentials out of the file means the +application never has to be handed a value it does not use in order to boot. ## Documentation -- **[DOCKER.md](DOCKER.md)** — running in Docker, and how to set environment variables securely - (Docker/Swarm/Kubernetes secrets, TLS at the edge, the CLI). -- The `gf` CLI: `vendor/bin/gf` (`gf setup`, `gf env`, `gf cli`, `gf db:*`, …). +- **[DOCKER.md](DOCKER.md)** — the images, secrets as provisioned files, health checks, TLS, and + how a Release reaches a host. +- The `gf` CLI: `vendor/bin/gf` (`gf init`, `gf env`, `gf cli`, `gf db:run`, `gf migrate`, …). +- Framework docs: [environment variables](https://github.com/gcgov/framework/blob/main/readme/environment-variables.md) + · [the gf CLI](https://github.com/gcgov/framework/blob/main/readme/gf.md). ## Running the CI checks (phpstan + phpunit) diff --git a/app/app.php b/app/app.php index 4b756c7..4f82d96 100644 --- a/app/app.php +++ b/app/app.php @@ -5,8 +5,12 @@ use gcgov\framework\config; use OpenApi\Attributes as OA; -#[OA\Info( version: '1.0.0', title: '{app_title}',contact: new OA\Contact(email:'itstaff@garrettcountymd.gov') )] -#[OA\Server(url:'{prod_app_root_url}{prod_app_base_path}')] +// OpenAPI metadata for the generated documentation. Attributes are compile-time +// constants, so these are literals to edit once — unlike config.json, they are not +// environment-driven. A relative server url resolves against whatever host serves the +// document, which is what makes one spec correct in every Environment. +#[OA\Info( version: '1.0.0', title: 'Application', contact: new OA\Contact( email: 'itstaff@garrettcountymd.gov' ) )] +#[OA\Server( url: '/' )] final class app implements \gcgov\framework\interfaces\app { diff --git a/composer.lock b/composer.lock index 41d2dc1..6981125 100644 --- a/composer.lock +++ b/composer.lock @@ -671,13 +671,13 @@ "version": "dev-claude/v7-config-deployment-review-4tgoxl", "source": { "type": "git", - "url": "git@github.com:gcgov/framework.git", - "reference": "c6a1adb7fb8c233d8e7ba2c2c846b37c84692a43" + "url": "https://github.com/gcgov/framework", + "reference": "a9f404c123c061b36b071a868ec5889e9ccaefcc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/gcgov/framework/zipball/c6a1adb7fb8c233d8e7ba2c2c846b37c84692a43", - "reference": "c6a1adb7fb8c233d8e7ba2c2c846b37c84692a43", + "url": "https://api.github.com/repos/gcgov/framework/zipball/a9f404c123c061b36b071a868ec5889e9ccaefcc", + "reference": "a9f404c123c061b36b071a868ec5889e9ccaefcc", "shasum": "" }, "require": { @@ -743,7 +743,7 @@ "MIT" ], "description": "Open source framework for PHP applications. Includes MongoDB modelling system.", - "time": "2026-08-26T21:25:35+00:00" + "time": "2026-08-27T12:20:45+00:00" }, { "name": "gcgov/framework-service-auth-oauth-server", diff --git a/config.json b/config.json index 76f691f..989b633 100644 --- a/config.json +++ b/config.json @@ -1,69 +1,39 @@ { "app": { - "title": "{app_title}", - "guid": "{app_guid}" - }, - "email": { - "fromAddress": "{app_smtp_sendmail_from_address}", - "fromName": "{app_smtp_sendmail_from_name}", - "SMTPUsername": "%env(default::SMTP_USERNAME)%", - "SMTPPassword": "%env(default::SMTP_PASSWORD)%" + "title": "Application", + "guid": "" }, "settings": { "useSession": false }, - "type": "%env(default:local:APP_TYPE)%", - "serverName": "%env(default:app.local:APP_SERVER_NAME)%", - "rootUrl": "%env(default:{app_root_url}:APP_ROOT_URL)%", - "basePath": "%env(default:{app_base_path}:APP_BASE_PATH)%", - "phpPath": "", + "logging": { + "lifecycle": false, + "renderer": false, + "destination": "stderr" + }, + + "type": "%env(APP_TYPE)%", + "rootUrl": "%env(APP_ROOT_URL)%", + "basePath": "%env(APP_BASE_PATH)%", + "mongoDatabases": [ { "default": true, "database": "%env(MONGO_DATABASE)%", - "uri": "%env(MONGO_URI)%", + "uri": "%env(secret:MONGO_URI)%", + "logging": true, "audit": false, "include_meta": false, "include_metaLabels": false, - "include_metaFields": false, - "logging": true, - "auditDatabaseName": "", - "auditDatabaseUri": "" + "include_metaFields": false } ], + "jwtAuth": { - "tokenIssuedBy": "%env(default:{app_root_url}:APP_ROOT_URL)%", - "tokenPermittedFor": "%env(default:{app_base_path}:APP_BASE_PATH)%", - "redirectAfterLoginUrl": "%env(default:{app_redirect_after_login}:APP_REDIRECT_AFTER_LOGIN)%", - "redirectAfterLogoutUrl": "%env(default:{app_redirect_after_logout}:APP_REDIRECT_AFTER_LOGOUT)%" + "redirectAfterLoginUrl": "%env(APP_REDIRECT_AFTER_LOGIN)%", + "redirectAfterLogoutUrl": "%env(APP_REDIRECT_AFTER_LOGOUT)%", + "keyPath": "" }, - "appDictionary": { - "key": "value" - }, - "microsoft": { - "clientId": "%env(default:{app_microsoft_client_id}:MICROSOFT_CLIENT_ID)%", - "clientSecret": "%env(MICROSOFT_CLIENT_SECRET)%", - "tenant": "%env(default:{app_microsoft_tenant}:MICROSOFT_TENANT)%", - "driveId": "%env(default:{app_microsoft_drive_id}:MICROSOFT_DRIVE_ID)%", - "fromAddress": "%env(default:{app_microsoft_default_from_address}:MICROSOFT_FROM_ADDRESS)%" - }, - "payjunction": { - "username": "%env(default:{app_payjunction_username}:PAYJUNCTION_USERNAME)%", - "password": "%env(PAYJUNCTION_PASSWORD)%", - "apiKey": "%env(PAYJUNCTION_API_KEY)%", - "terminalId": "%env(default:{app_payjunction_terminal_id}:PAYJUNCTION_TERMINAL_ID)%", - "merchantId": "%env(default:{app_payjunction_merchant_id}:PAYJUNCTION_MERCHANT_ID)%" - }, - "environments": { - "prod": { - "type": "prod", - "mongoDatabases": [ - { - "default": true, - "database": "%env(PROD_MONGO_DATABASE)%", - "uri": "%env(PROD_MONGO_URI)%" - } - ] - } - } + + "appDictionary": {} } diff --git a/docker-compose.yml b/docker-compose.yml index 9ba36c6..941b8c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,13 @@ -# Local development stack: Nginx + PHP-FPM (+ optional MongoDB). +# Local development stack. Production topology lives in the ops repository +# (gcgov/deploy), not here — this file exists so a developer can run the app. # -# cp .env.example .env -# docker compose --profile dev up --build # starts php, nginx and mongodb +# gf env --init # write .env with the variables config.json needs +# cp .env.example .env.local # and the ones docker compose needs +# docker compose up --build # open http://localhost:8080 # -# Without `--profile dev`, mongodb is not started — point MONGO_URI at an -# external database instead. Secrets come from .env / the environment; never -# put real secret values in this file (see DOCKER.md). +# The php container mounts the working tree, so edits are live; opcache timestamp +# validation is switched back on in docker/php/conf.d/dev.ini to match. services: php: @@ -15,10 +16,14 @@ services: target: dev env_file: - .env + - .env.local volumes: - .:/var/www/app - # keep the image-built vendor/ from being shadowed by the bind mount + # Keep the image's vendor/ from being shadowed by the bind mount. - /var/www/app/vendor + depends_on: + mongodb: + condition: service_started nginx: image: nginx:1.27-alpine @@ -28,19 +33,27 @@ services: - "${HTTP_PORT:-8080}:80" volumes: - ./docker/nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro - - .:/var/www/app:ro + # Static assets come from the working tree in development; the production nginx + # image bakes them in instead. + - ./www:/var/www/app/www:ro environment: PHP_FPM_HOST: "${PHP_FPM_HOST:-php:9000}" CORS_ORIGIN_APP: "${CORS_ORIGIN_APP:-http://localhost:8080}" CORS_ORIGIN_FRONTEND: "${CORS_ORIGIN_FRONTEND:-http://localhost:5173}" CORS_ORIGIN_SWAGGER: "${CORS_ORIGIN_SWAGGER:-http://localhost:8081}" - # Only substitute our own variables; leave nginx's $variables intact. + # Substitute only our own variables; nginx's own $variables must survive. NGINX_ENVSUBST_FILTER: "^(CORS_ORIGIN_|PHP_FPM_HOST)" + healthcheck: + # Matches the production image's check. APP_BASE_PATH is the application's, so + # adjust the path if the app is not served at the domain root. + test: [ "CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost/health || exit 1" ] + interval: 15s + timeout: 3s + start_period: 20s + retries: 3 mongodb: image: mongo:7 - profiles: - - dev ports: - "${MONGO_PORT:-27017}:27017" volumes: diff --git a/docker/php/conf.d/app.ini b/docker/php/conf.d/app.ini new file mode 100644 index 0000000..376a291 --- /dev/null +++ b/docker/php/conf.d/app.ini @@ -0,0 +1,57 @@ +; Production PHP settings for the application image. +; +; The php:8.4-fpm base image ships NO php.ini at all — every value below is otherwise +; a compile default, and opcache is off. This file replaces the four per-environment +; php.ini files the IIS hosting used. +; +; docker/php/conf.d/dev.ini overrides these for local development. + +; --- error handling ------------------------------------------------------------- +; Errors go to the log, never to the response body: an API returns its own JSON error +; shape, and a PHP notice rendered into a response leaks paths and code structure. +display_errors = Off +display_startup_errors = Off +log_errors = On +error_log = /dev/stderr +error_reporting = E_ALL & ~E_DEPRECATED + +; Do not advertise the PHP version in response headers. +expose_php = Off + +; --- opcache -------------------------------------------------------------------- +; The image is immutable, so the filesystem cannot change under a running container: +; timestamp validation is pure overhead. This is safe ONLY because a deploy replaces +; the container rather than the files inside it. +opcache.enable = 1 +opcache.enable_cli = 0 +opcache.validate_timestamps = 0 +opcache.memory_consumption = 192 +opcache.interned_strings_buffer = 16 +opcache.max_accelerated_files = 20000 + +; JIT off. It pays off on CPU-bound numeric code; an API whose time goes on Mongo I/O +; and JSON serialisation sees no benefit from it, and it has a history of subtle bugs. +; Turn it on for a specific workload that measures faster with it, not by default. +opcache.jit = disable + +; --- limits --------------------------------------------------------------------- +; upload_max_filesize and post_max_size must not be smaller than nginx's +; client_max_body_size (1024m), or a large upload is accepted by nginx and then +; rejected by PHP after the whole body has already crossed the wire. +memory_limit = 256M +upload_max_filesize = 1024M +post_max_size = 1024M +max_execution_time = 300 +max_input_time = 300 +max_file_uploads = 50 + +; --- sessions and temp ---------------------------------------------------------- +; Both live inside the container and are lost on redeploy. Applications that need +; durable sessions must not use the filesystem handler. +session.save_path = /var/www/app/srv/tmp/sessions +sys_temp_dir = /var/www/app/srv/tmp/tmp +upload_tmp_dir = /var/www/app/srv/tmp/tmp + +; --- misc ----------------------------------------------------------------------- +date.timezone = America/New_York +zend.assertions = -1 diff --git a/docker/php/conf.d/dev.ini b/docker/php/conf.d/dev.ini new file mode 100644 index 0000000..f08a614 --- /dev/null +++ b/docker/php/conf.d/dev.ini @@ -0,0 +1,14 @@ +; Local development overrides, layered on top of app.ini (loaded after it). +; Copied into the image only by the `dev` target. + +display_errors = On +display_startup_errors = On +error_reporting = E_ALL + +; Pick up edits from the bind mount without restarting the container. +opcache.validate_timestamps = 1 +opcache.revalidate_freq = 0 +opcache.jit = disable + +; A hung request should surface as a timeout, not sit for five minutes. +max_execution_time = 60 diff --git a/docker/php/php-fpm.d/zz-app.conf b/docker/php/php-fpm.d/zz-app.conf new file mode 100644 index 0000000..0e43597 --- /dev/null +++ b/docker/php/php-fpm.d/zz-app.conf @@ -0,0 +1,37 @@ +; The application's FPM pool. This REPLACES the base image's www.conf, which the +; Dockerfile deletes. +; +; Why replace rather than layer: www.conf sets `user`/`group`, which only a root +; master process can act on. This image runs as www-data throughout, so those +; directives would warn on every start and do nothing. Running the master as root to +; keep them would put a root process on a public-facing host to gain privilege +; separation inside what is already a single-purpose isolation boundary. + +[www] +listen = 9000 + +; No listen.allowed_clients: nginx connects from its own container address, not +; 127.0.0.1, so an allowlist here would reject every real request. The port is never +; published to the host and only nginx shares the network — that is the boundary. + +; Static: the container has a fixed memory limit, so sizing the pool dynamically only +; obscures how much of it can actually be used. 12 x 256M leaves headroom under a 4G +; limit — retune this together with the limit, never independently of it. +pm = static +pm.max_children = 12 +pm.max_requests = 1000 + +; Logs are the platform's job. Workers write to the container's stderr so `docker logs` +; and the collector see them, matching the framework's own logging.destination. +catch_workers_output = yes +decorate_workers_output = no +php_admin_value[error_log] = /dev/stderr +php_admin_flag[log_errors] = on + +; Deliberately no slowlog: FPM opens it as a regular file, and pointing it at +; /dev/stderr fails to start on some kernels. Worth adding once someone can verify it +; on a real host — a diagnostic is not worth a container that will not boot. + +; The application reads its configuration from the environment; without this, FPM +; would hide it from PHP. +clear_env = no diff --git a/tests/Unit/ConfigFilesTest.php b/tests/Unit/ConfigFilesTest.php index 477dacf..ce75693 100644 --- a/tests/Unit/ConfigFilesTest.php +++ b/tests/Unit/ConfigFilesTest.php @@ -4,32 +4,41 @@ namespace app\tests\Unit; -use gcgov\framework\models\config\variantEnvironment; use gcgov\framework\models\unifiedConfig; use gcgov\framework\services\environment\configLoader; +use gcgov\framework\services\environment\environmentException; use PHPUnit\Framework\TestCase; -use Symfony\Component\Dotenv\Dotenv; /** - * Pins the completeness contract of the committed root config.json against the committed - * .env.example: after `cp .env.example .env` the active config must fully resolve (every hard - * %env(VAR)% is covered), and the CLI-only `environments.prod` entry must resolve once the - * (commented) PROD_* variables are supplied. A failure here means a clean checkout — or the - * Docker image built from it — would 500. + * Pins the contract of the committed config.json. + * + * Every %env() reference is required, so the failure mode this guards against is a clean + * checkout — or the image built from it — refusing to boot because a variable nobody + * documented is missing. The manifest is derived from config.json itself, so this test + * asserts the derivation rather than a hand-written list. */ final class ConfigFilesTest extends TestCase { private const string ROOT = __DIR__ . '/../..'; + /** The values a developer supplies. Deliberately written out, so adding a required reference fails here first. */ + private const array DEV_ENVIRONMENT = [ + 'APP_TYPE' => 'local', + 'APP_ROOT_URL' => 'http://localhost:8080', + 'APP_BASE_PATH' => '/', + 'APP_REDIRECT_AFTER_LOGIN' => 'http://localhost:5173/auth/sign-in', + 'APP_REDIRECT_AFTER_LOGOUT' => 'http://localhost:5173/auth/sign-out', + 'MONGO_DATABASE' => 'app', + 'MONGO_URI' => 'mongodb://mongodb:27017', + ]; + /** @var array */ private array $envSnapshot = []; protected function setUp(): void { $this->envSnapshot = $_ENV; - // Mirror `cp .env.example .env`: seed the environment from the committed example - // (uncommented entries only — commented PROD_* lines are intentionally absent). - foreach( ( new Dotenv() )->parse( (string)file_get_contents( self::ROOT . '/.env.example' ) ) as $name => $value ) { + foreach( self::DEV_ENVIRONMENT as $name => $value ) { $this->setEnv( $name, $value ); } } @@ -39,6 +48,7 @@ protected function tearDown(): void { foreach( array_keys( $_ENV ) as $key ) { if( !array_key_exists( $key, $this->envSnapshot ) ) { putenv( $key ); + unset( $_SERVER[ $key ] ); } } $_ENV = $this->envSnapshot; @@ -51,38 +61,121 @@ private function setEnv( string $name, string $value ): void { } - public function testActiveConfigResolvesWithDotEnvExample(): void { + private function clearEnv( string $name ): void { + unset( $_ENV[ $name ], $_SERVER[ $name ] ); + putenv( $name ); + } + + + public function testConfigResolvesWithTheDocumentedDeveloperEnvironment(): void { $config = configLoader::load( self::ROOT ); $this->assertInstanceOf( unifiedConfig::class, $config ); $this->assertSame( 'local', $config->type ); $this->assertSame( 'mongodb://mongodb:27017', $config->mongoDatabases[ 0 ]->uri ); $this->assertSame( 'app', $config->mongoDatabases[ 0 ]->database ); - // merged app-side sections hydrate from the same file - $this->assertSame( '{app_title}', $config->app->title ); - $this->assertSame( '', $config->email->SMTPUsername ); + $this->assertSame( 'Application', $config->app->title, 'the placeholder `gf init --title` overwrites' ); $this->assertFalse( $config->settings->useSession ); } - public function testActiveConfigResolvesWithoutProdVariables(): void { - // The environments section is CLI-only; the active config resolves even though - // PROD_MONGO_URI / PROD_MONGO_DATABASE are absent from .env.example (commented out). - $this->assertArrayNotHasKey( 'PROD_MONGO_URI', $_ENV ); - $config = configLoader::load( self::ROOT ); - $this->assertSame( 'local', $config->type ); + /** + * The manifest and the config file cannot drift, because the manifest IS the config + * file. If this list changes, `gf env --init` changes with it automatically. + */ + public function testConfigReferencesExactlyTheDocumentedVariables(): void { + $references = configLoader::references( self::ROOT ); + + // Compared as a set: the order is config.json's key order, which is a formatting + // choice, not a contract. + $referenced = array_keys( $references ); + $documented = array_keys( self::DEV_ENVIRONMENT ); + sort( $referenced ); + sort( $documented ); + $this->assertSame( $documented, $referenced ); + $this->assertTrue( $references[ 'MONGO_URI' ], 'the connection string is a secret' ); + $this->assertFalse( $references[ 'APP_TYPE' ] ); + } + + + /** Each reference is required: dropping any one of them must stop the application. */ + public function testEveryReferenceIsRequired(): void { + foreach( array_keys( self::DEV_ENVIRONMENT ) as $name ) { + $this->clearEnv( $name ); + + try { + configLoader::load( self::ROOT ); + $this->fail( 'config.json resolved without ' . $name . ' — every reference is supposed to be required' ); + } + catch( environmentException $e ) { + $this->assertStringContainsString( $name, $e->getMessage() ); + } + finally { + $this->setEnv( $name, self::DEV_ENVIRONMENT[ $name ] ); + } + } } - public function testProdEnvironmentEntryResolvesWithProdVariables(): void { - $this->setEnv( 'PROD_MONGO_URI', 'mongodb+srv://user:pass@prod-cluster/' ); - $this->setEnv( 'PROD_MONGO_DATABASE', 'app' ); + /** + * The production path: the connection string arrives as a provisioned file rather than + * an environment variable, and the same committed config.json reads it. + */ + public function testMongoUriCanBeSuppliedAsAProvisionedSecretFile(): void { + $secretFile = tempnam( sys_get_temp_dir(), 'mongo' ); + $this->assertIsString( $secretFile ); + file_put_contents( $secretFile, "mongodb+srv://user:pass@cluster/\n" ); - $prod = configLoader::loadVariantEnvironment( self::ROOT, 'prod' ); + $this->clearEnv( 'MONGO_URI' ); + $this->setEnv( 'MONGO_URI_FILE', $secretFile ); + + try { + $config = configLoader::load( self::ROOT ); + $this->assertSame( 'mongodb+srv://user:pass@cluster/', $config->mongoDatabases[ 0 ]->uri ); + } + finally { + $this->clearEnv( 'MONGO_URI_FILE' ); + unlink( $secretFile ); + } + } + + + /** Logging to files would be per-replica and destroyed by every deploy. */ + public function testLoggingGoesToStderr(): void { + $this->assertSame( 'stderr', configLoader::load( self::ROOT )->logging->destination ); + } + + + /** No optional integration blocks: an application adds what it actually uses. */ + public function testTemplateShipsNoUnusedIntegrationBlocks(): void { + $raw = json_decode( (string)file_get_contents( self::ROOT . '/config.json' ), true ); + $this->assertIsArray( $raw ); + + foreach( [ 'microsoft', 'payjunction', 'sqlDatabases', 'environments' ] as $absent ) { + $this->assertArrayNotHasKey( $absent, $raw ); + } + foreach( [ 'serverName', 'cookieUrl', 'phpPath', 'baseUrl' ] as $removed ) { + $this->assertArrayNotHasKey( $removed, $raw ); + } + } + + + /** A missing section must hydrate to its defaults rather than fataling. */ + public function testAbsentSectionsHydrateToDefaults(): void { + $config = configLoader::load( self::ROOT ); + + $this->assertSame( '', $config->microsoft->clientId ); + $this->assertSame( '', $config->payjunction->username ); + $this->assertSame( [], $config->sqlDatabases ); + } + + + /** Issuer and audience are not configured separately — they derive from the app's own urls. */ + public function testJwtIssuerAndAudienceDeriveFromTheApplicationUrls(): void { + $config = configLoader::load( self::ROOT ); - $this->assertInstanceOf( variantEnvironment::class, $prod ); - $this->assertSame( 'prod', $prod->type, 'the prod entry type must be the literal "prod" — the db:restore guard depends on it' ); - $this->assertSame( 'mongodb+srv://user:pass@prod-cluster/', $prod->mongoDatabases[ 0 ]->uri ); + $this->assertSame( 'http://localhost:8080', $config->getTokenIssuedBy() ); + $this->assertSame( '/', $config->getTokenPermittedFor() ); } } From 9c3e29703bb16a17fd07d68621f2b9a6da615629 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 13:06:14 +0000 Subject: [PATCH 08/17] Point release.yml at the workflow that exists, via the right mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The caller named build-and-deploy.yml, which gcgov/deploy does not have, and its comment claimed the deploy step ran on a Zone's self-hosted runner through this call. It does not, and it must not: a called workflow executes in the caller's context, so deploying that way would give every contributor to an application repository code execution inside a network Zone. The ops repo splits it in two — build.yml is callable and GitHub-hosted; deployment arrives separately by repository_dispatch and runs in the ops repo's own context. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012kqJazGHDMq19pQ64732i6 --- .github/workflows/release.yml | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b5eb6b3..706f5fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,15 @@ -# Build a Release and hand it to the ops repository to deploy. -# -# This file is deliberately thin. The real logic lives in a reusable workflow in -# gcgov/deploy so that changing how deployment works is one pull request, not thirty. +# Build a Release and ask gcgov/deploy to deploy it. # # push to main build and publish only -# push of v* build, publish, and deploy +# push of v* build, publish, and request deployment +# +# This file is deliberately thin. The real logic lives in gcgov/deploy, so changing how +# deployment works is one pull request rather than one per application. # -# The deploy step runs on gcgov/deploy's self-hosted runner for the target Zone, which -# is why nothing here needs SSH access or any secret beyond the registry token: this -# repository's contributors never get code execution inside a Zone. +# What it calls only ever runs on GitHub-hosted runners. Deployment itself happens in +# gcgov/deploy's own context, reached by repository_dispatch — a called workflow executes +# in the CALLER's context, so if the deploy step lived here, every contributor to this +# repository would have code execution inside a network Zone. name: Release @@ -23,12 +24,15 @@ permissions: jobs: release: - uses: gcgov/deploy/.github/workflows/build-and-deploy.yml@main + uses: gcgov/deploy/.github/workflows/build.yml@main with: - # Everything else derives from this: image names, ops repo paths, database users. + # Everything derives from this: image names, ops repo paths, database users. app: framework-app-template # Which Zone this application runs in: internal | bridge | isolated. zone: bridge # Only a tag deploys. A push to main publishes an image and stops there. deploy: ${{ startsWith(github.ref, 'refs/tags/v') }} - secrets: inherit + secrets: + # Fine-grained token with contents:write on gcgov/deploy and nothing else. Used + # solely to fire the deploy dispatch; without it, images publish and nothing ships. + OPS_DISPATCH_TOKEN: ${{ secrets.OPS_DISPATCH_TOKEN }} From c42b95c269b743ccfcb29c075aaa0db3b12f9be9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 13:48:29 +0000 Subject: [PATCH 09/17] Add scripts/adopt-framework-release.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves the application off the framework's development branch onto a published release: rewrites the constraint, deletes the temporary vcs bridge, regenerates the lock, and verifies it installs and the suite passes. The bridge exists only because there was no published v7 to depend on. Once one is tagged it becomes the kind of indirection that quietly stays for years, so removing it is part of adopting a release rather than a separate cleanup nobody schedules. Run with '^7.0@RC' at the release candidate and '^7.0' at v7.0.0. config.platform.php stays either way — it pins resolution to the PHP the production image runs. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012kqJazGHDMq19pQ64732i6 --- scripts/adopt-framework-release.sh | 84 ++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100755 scripts/adopt-framework-release.sh diff --git a/scripts/adopt-framework-release.sh b/scripts/adopt-framework-release.sh new file mode 100755 index 0000000..44e6462 --- /dev/null +++ b/scripts/adopt-framework-release.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# +# Move this application off the framework's development branch and onto a published +# release. +# +# scripts/adopt-framework-release.sh '^7.0@RC' # at the release candidate +# scripts/adopt-framework-release.sh '^7.0' # at v7.0.0 +# +# What it does, in order: +# 1. rewrites the gcgov/framework constraint in composer.json +# 2. deletes the `repositories` vcs entry, if the bridge is still there +# 3. regenerates composer.lock against the published package +# 4. verifies the lock installs and the suite passes +# +# The bridge it removes is temporary scaffolding: before v7.0.0-rc.1 existed there was no +# published version to depend on, so composer.json pointed at the framework's development +# branch through a `vcs` repository. Once a release is tagged, that indirection is exactly +# the kind of thing that quietly stays for years. +# +# config.platform.php stays. It pins resolution to the PHP the production image runs, and +# without it, resolving on a newer PHP locks packages that will not install in the image. + +set -euo pipefail + +CONSTRAINT="${1:-}" +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [ -z "$CONSTRAINT" ]; then + sed -n '3,8p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' + exit 1 +fi + +command -v composer >/dev/null || { echo 'composer is not on PATH' >&2; exit 1; } +cd "$ROOT" + +echo "==> setting gcgov/framework to $CONSTRAINT" +python3 - "$CONSTRAINT" <<'PY' +import collections, json, sys + +constraint = sys.argv[1] +with open('composer.json') as handle: + document = json.load(handle, object_pairs_hook=collections.OrderedDict) + +document['require']['gcgov/framework'] = constraint + +# The vcs bridge pointed at the framework's development branch. A published release makes +# it not just unnecessary but harmful: it would keep resolving from a moving branch. +removed = document.pop('repositories', None) +if removed: + print(' removed the vcs repositories entry') + +with open('composer.json', 'w') as handle: + json.dump(document, handle, indent='\t') + handle.write('\n') +PY + +composer validate --no-check-publish --no-check-all + +echo '==> regenerating composer.lock' +composer update gcgov/framework --with-all-dependencies --no-install --no-interaction + +echo '==> verifying' +composer install --no-interaction --no-progress --prefer-dist +composer ci + +installed="$(python3 -c " +import json +lock = json.load(open('composer.lock')) +print(next(p['version'] for p in lock['packages'] if p['name'] == 'gcgov/framework')) +")" + +case "$installed" in + dev-*) echo "still on a development version ($installed) — the constraint did not take" >&2; exit 1 ;; + *) echo "==> locked to gcgov/framework $installed" ;; +esac + +cat < Date: Thu, 27 Aug 2026 14:02:00 +0000 Subject: [PATCH 10/17] Adopt gcgov/framework v7.0.0-rc.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the temporary vcs bridge: the constraint is now ^7.0@RC and the lock pins the published release rather than a commit on a development branch. The `repositories` entry is gone. At v7.0.0 the constraint becomes plain ^7.0 — `scripts/adopt-framework-release.sh '^7.0'` does it. Two fixes to that script, both found by running it: - `composer validate` ran before the lock was regenerated, so it failed on a staleness it had just deliberately caused. It now validates the file alone first and the lock after. - Documented that blanket --ignore-platform-reqs must never be used here. It discards config.platform.php along with the extension checks, which locked symfony/filesystem v8.1.5 (php >=8.4.1) against a pin of 8.4.0 — the subsequent `composer install` refused it, which is exactly what the pin is for. Verified: framework v7.0.0-rc.1 installs from Packagist, every framework and app class autoloads including the new health router, 47 tests pass, PHPStan clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012kqJazGHDMq19pQ64732i6 --- README.md | 17 ++++++++++++----- composer.json | 8 +------- composer.lock | 25 +++++++++---------------- scripts/adopt-framework-release.sh | 12 +++++++++++- 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 834bc00..a8a3011 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,15 @@ application never has to be handed a value it does not use in order to boot. ## Running the CI checks (phpstan + phpunit) -`composer.json` resolves `gcgov/framework` from its v7 development branch through a `vcs` -repository, and `composer.lock` pins the exact revision. This is a temporary bridge: when -`v7.0.0-rc.1` is tagged, the constraint becomes `^7.0`, the `repositories` entry is deleted, and -the lock is regenerated. +`composer.json` requires `gcgov/framework: ^7.0@RC` and `composer.lock` pins the exact release. +The `@RC` stability flag is there because v7 is currently a release candidate; at `v7.0.0` it +becomes plain `^7.0`: + +```bash +scripts/adopt-framework-release.sh '^7.0' +``` + +To run the checks: ```bash composer install --prefer-dist # add --ignore-platform-req=ext-mongodb if the extension isn't loaded @@ -68,7 +73,9 @@ a live MongoDB. `composer.lock` is resolved for PHP 8.4.0 (`config.platform.php`), which is what the production image runs — without that pin, resolving on a newer PHP locks packages that will not install in -the image. Keep the pin, the `php` constraint, and the Dockerfile's base image in step. +the image. Keep the pin, the `php` constraint, and the Dockerfile's base image in step — and +never use blanket `--ignore-platform-reqs`, which discards the pin along with the extension +checks. ## Local development without Docker diff --git a/composer.json b/composer.json index 8a71414..7d8f163 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "mongodb/mongodb": "^2.1", "phpmailer/phpmailer": "^6.2", "zircote/swagger-php": "^6.1", - "gcgov/framework": "dev-claude/v7-config-deployment-review-4tgoxl", + "gcgov/framework": "^7.0@RC", "gcgov/framework-service-gcgov-cron-monitor": "^v1.1", "gcgov/framework-service-documentation": "^1.1", "gcgov/framework-service-auth-oauth-server": "^2.1", @@ -18,12 +18,6 @@ "phpunit/phpunit": "^11.5", "jetbrains/phpstorm-attributes": "^1.0" }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/gcgov/framework" - } - ], "config": { "platform": { "php": "8.4.0" diff --git a/composer.lock b/composer.lock index 6981125..84458cb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "507acfa5108428fd982ac28bdab21449", + "content-hash": "90155cc972ff2aa175decab94235dcec", "packages": [ { "name": "andrewsauder/json-deserialize", @@ -668,10 +668,10 @@ }, { "name": "gcgov/framework", - "version": "dev-claude/v7-config-deployment-review-4tgoxl", + "version": "v7.0.0-rc.1", "source": { "type": "git", - "url": "https://github.com/gcgov/framework", + "url": "https://github.com/gcgov/framework.git", "reference": "a9f404c123c061b36b071a868ec5889e9ccaefcc" }, "dist": { @@ -727,22 +727,15 @@ "gcgov\\framework\\": "src/" } }, - "scripts": { - "phpstan": [ - "phpstan analyse --memory-limit=512M" - ], - "test": [ - "phpunit" - ], - "ci": [ - "@phpstan", - "@test" - ] - }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "Open source framework for PHP applications. Includes MongoDB modelling system.", + "support": { + "issues": "https://github.com/gcgov/framework/issues", + "source": "https://github.com/gcgov/framework/tree/v7.0.0-rc.1" + }, "time": "2026-08-27T12:20:45+00:00" }, { @@ -7371,7 +7364,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "gcgov/framework": 20 + "gcgov/framework": 5 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/scripts/adopt-framework-release.sh b/scripts/adopt-framework-release.sh index 44e6462..98a0b8f 100755 --- a/scripts/adopt-framework-release.sh +++ b/scripts/adopt-framework-release.sh @@ -19,6 +19,11 @@ # # config.platform.php stays. It pins resolution to the PHP the production image runs, and # without it, resolving on a newer PHP locks packages that will not install in the image. +# +# If composer complains about a missing extension here, ignore that extension by name +# (--ignore-platform-req=ext-mongodb). NEVER reach for blanket --ignore-platform-reqs: it +# also discards the php pin, which silently locks packages the production image cannot +# install. The `composer install` below is what catches that, so do not skip it either. set -euo pipefail @@ -54,11 +59,16 @@ with open('composer.json', 'w') as handle: handle.write('\n') PY -composer validate --no-check-publish --no-check-all +# --no-check-lock: the lock is deliberately stale at this point — the constraint has just +# changed and the regeneration is the next step. Only the file's own validity matters here. +composer validate --no-check-lock --no-check-publish --no-check-all echo '==> regenerating composer.lock' composer update gcgov/framework --with-all-dependencies --no-install --no-interaction +# Now the lock must agree with composer.json, so check that too. +composer validate --no-check-publish --no-check-all + echo '==> verifying' composer install --no-interaction --no-progress --prefer-dist composer ci From 6c75844460e20c4ab0ca45535d7ae15cfe610d33 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 16:17:47 +0000 Subject: [PATCH 11/17] Enable Framework Services from config.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The framework no longer has \app\app::registerFrameworkServiceNamespaces(). The five services live inside it and are switched on by a `services` section of config.json, so enabling a service and configuring it are one statement. app.php loses the method and keeps only its lifecycle hooks. The class is still required: config derives every path in the framework by reflecting on its file location. router.php implements the new interfaces\appRouter and answers its providesAuthentication() with false. That is the load-bearing line here. config.json enables services.auth, whose guard establishes identity; authentication() below it returns true for everyone, so claiming to provide authentication would satisfy the framework's boot check while leaving all four widget routes open to anyone. Verified both ways: with services.auth present a request reaches the guard and is refused for a missing Authorization header, and with it removed the application refuses to boot, naming the four routes. The comment block describing how auth wires up was rewritten. It described authoauth "automatically adding our guard" and a getRunFrameworkServiceRouteAuthentication() that was never implemented here and is no longer duck-typed — the per-route opt-out is now implementing interfaces\router\skipsServiceAuthentication. constants.php held DEFAULT_ROLES, which existed only to be passed to the commented-out setBlockNewUsers() calls in app.php. That configuration is now services.auth.defaultNewUserRoles, so the constant had no consumer left. It is replaced by the widget role constants the route table was carrying as string literals, which is the convention the framework documents, and a test asserts every role a route requires is one of them. Two things translate to nothing rather than to something: - cronMonitor was registered but never configured. appDictionary is empty, so the service was constructed with an empty base URI and every ping went nowhere. An empty cronMonitor.url block would be the present-and-blank optional integration ADR 0001 rejects, so it is simply gone. - settings held only useSession, which the framework removed. An empty settings block is the same problem; a missing section hydrates to defaults. Both are now asserted absent alongside microsoft and payjunction. composer.json drops the four service requires. composer.lock is deliberately NOT regenerated: there is no framework release containing these changes to regenerate against, and regenerating against v7.0.0-rc.1 would install cleanly and produce a template whose authenticated routes have no guard and no boot check to catch it. Loudly unbuildable beats silently unprotected. So CI is red on this branch until a release is tagged, at which point scripts/adopt-framework-release.sh clears it. To make that state legible, CI now runs composer validate before composer install: install treats a lock that disagrees with composer.json as a warning and carries on from the stale lock, which would install the wrong framework and fail later as class-not-found errors. validate exits non-zero naming the actual problem. 50 tests pass against the framework branch. One PHPStan finding predates this change: authentication() carries a @throws its body does not use, identical on v7. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KmBBiV3fQaaRdrmspZarS5 --- .github/workflows/ci.yml | 10 +++++-- README.md | 39 ++++++++++++++++++++++++- app/app.php | 24 +++++---------- app/constants.php | 7 ++++- app/router.php | 53 +++++++++++++++++++--------------- composer.json | 6 +--- config.json | 11 +++++-- tests/Unit/AppTest.php | 17 ++++------- tests/Unit/ConfigFilesTest.php | 42 +++++++++++++++++++++++++-- tests/Unit/ConstantsTest.php | 27 +++++++++++++---- tests/Unit/RouterTest.php | 26 +++++++++++++---- 11 files changed, 185 insertions(+), 77 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 214dba5..2e42c6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,8 +25,13 @@ jobs: extensions: mongodb, sodium, fileinfo, pdo, imagick tools: composer:v2 coverage: none - # Installs from the committed composer.lock. gcgov/framework resolves through - # the vcs repository in composer.json until v7.0.0-rc.1 is tagged. + # composer install treats a lock that disagrees with composer.json as a warning and + # carries on from the stale lock — so a drifted lock installs the wrong framework and + # surfaces later as confusing class-not-found errors. validate exits non-zero, here, + # naming the problem. + - run: composer validate --no-check-publish --no-check-all + # Installs from the committed composer.lock, which pins gcgov/framework to a + # published release. - run: composer install --no-interaction --no-progress --prefer-dist - run: composer phpstan @@ -41,6 +46,7 @@ jobs: extensions: mongodb, sodium, fileinfo, pdo, imagick tools: composer:v2 coverage: none + - run: composer validate --no-check-publish --no-check-all - run: composer install --no-interaction --no-progress --prefer-dist - run: composer test diff --git a/README.md b/README.md index a8a3011..3d5b8b3 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,48 @@ container images — nginx and PHP-FPM — and keeps every secret out of the com ## Adding what you need -`config.json` ships with five variables and nothing else — no Microsoft, PayJunction, or SMTP +`config.json` ships with seven variables and nothing else — no Microsoft, PayJunction, or SMTP block. Add the section for an integration when the application actually uses one; a section that is absent hydrates to its defaults. Keeping unused credentials out of the file means the application never has to be handed a value it does not use in order to boot. +## Framework Services + +Framework Services are part of the framework; you switch one on by giving it a block in the +`services` section. Presence enables — a block that is absent means the service is off, a block +that is present (even `{}`) means it is on, and its contents are that service's settings. + +```json +"services": { + "auth": { "provider": "oauth" }, + "userCrud": {}, + "documentation": {} +} +``` + +- **`auth`** — one service, two providers. `"oauth"` is a full OAuth server (password, + third-party and authorization-code grants, plus MFA); `"msFront"` exchanges a Microsoft token + the front end already holds for an application token. Either way you get + `/.well-known/jwks.json`, `/auth/fileToken`, and a JWT guard over every route marked + `authentication: true`. Two providers cannot both be active — there is one `provider` key. +- **`userCrud`** — `/user` CRUD over the resolved user model, gated on `User.Read` / `User.Write`. +- **`documentation`** — `GET /documentation.yaml`, generated from the annotations in this + application and the framework. + +`auth` takes two optional settings, omitted here so they keep their defaults. Add them when the +application wants a different answer: + +```json +"auth": { "provider": "oauth", "blockNewUsers": false, "defaultNewUserRoles": [ "Widget.Read" ] } +``` + +`blockNewUsers` defaults to `true`, so only users already in the database may sign in. Set it +false to provision a user on first successful authentication, carrying `defaultNewUserRoles`. + +Routes that declare `authentication: true` need something to guard them. If no auth service is +enabled and `\app\router::providesAuthentication()` returns false, the framework refuses to +boot rather than serve routes that look protected and are not. + ## Documentation - **[DOCKER.md](DOCKER.md)** — the images, secrets as provisioned files, health checks, TLS, and diff --git a/app/app.php b/app/app.php index 4f82d96..abcaa7d 100644 --- a/app/app.php +++ b/app/app.php @@ -2,7 +2,6 @@ namespace app; -use gcgov\framework\config; use OpenApi\Attributes as OA; // OpenAPI metadata for the generated documentation. Attributes are compile-time @@ -13,6 +12,13 @@ #[OA\Server( url: '/' )] final class app implements \gcgov\framework\interfaces\app { + // Nothing but the two lifecycle hooks — Framework Services are enabled in the + // `services` section of config.json rather than registered here. + // + // The class is still required. \gcgov\framework\config derives every path in the + // framework by reflecting on this file's location, so an application without it + // cannot resolve its own root. + /** * Processed after lifecycle is complete with this instance @@ -28,20 +34,4 @@ public static function _before() : void { } - public function registerFrameworkServiceNamespaces(): array { - //uncomment to auto create new user entries if the user does not have one in the user collection - //$msAuthConfig = \gcgov\framework\services\authmsfront\msAuthConfig::getInstance(); - //$msAuthConfig->setBlockNewUsers( false, constants::DEFAULT_ROLES ); - //$oauthConfig = \gcgov\framework\services\authoauth\oauthConfig::getInstance(); - //$oauthConfig->setBlockNewUsers( false, constants::DEFAULT_ROLES ); - return [ - '\gcgov\framework\services\documentation', - '\gcgov\framework\services\cronMonitor', - '\gcgov\framework\services\usercrud', - //'\gcgov\framework\services\authmsfront', - '\gcgov\framework\services\authoauth', - ]; - } - - } diff --git a/app/constants.php b/app/constants.php index 1222f79..185f42f 100644 --- a/app/constants.php +++ b/app/constants.php @@ -1,8 +1,13 @@ runFrameworkServiceRouteAuthentication = false and add method - // getRunFrameworkServiceRouteAuthentication(): bool -- see below + // to skip the services.auth guard for particular routes, implement + // \gcgov\framework\interfaces\router\skipsServiceAuthentication on this class and + // return false from getRunFrameworkServiceRouteAuthentication() for them - //user has been authenticated return true; } - // To disable the authoauth for a specific request, set $this->runFrameworkServiceRouteAuthentication to false during authentication() method - // private bool $runFrameworkServiceRouteAuthentication = true; - // public function getRunFrameworkServiceRouteAuthentication(): bool { - // return $this->runFrameworkServiceRouteAuthentication; - // } } diff --git a/composer.json b/composer.json index 7d8f163..e2c4cf8 100644 --- a/composer.json +++ b/composer.json @@ -7,11 +7,7 @@ "mongodb/mongodb": "^2.1", "phpmailer/phpmailer": "^6.2", "zircote/swagger-php": "^6.1", - "gcgov/framework": "^7.0@RC", - "gcgov/framework-service-gcgov-cron-monitor": "^v1.1", - "gcgov/framework-service-documentation": "^1.1", - "gcgov/framework-service-auth-oauth-server": "^2.1", - "gcgov/framework-service-user-crud": "^1.1" + "gcgov/framework": "^7.0@RC" }, "require-dev": { "phpstan/phpstan": "^2.1", diff --git a/config.json b/config.json index 989b633..52b798b 100644 --- a/config.json +++ b/config.json @@ -3,9 +3,6 @@ "title": "Application", "guid": "" }, - "settings": { - "useSession": false - }, "logging": { "lifecycle": false, "renderer": false, @@ -35,5 +32,13 @@ "keyPath": "" }, + "services": { + "auth": { + "provider": "oauth" + }, + "userCrud": {}, + "documentation": {} + }, + "appDictionary": {} } diff --git a/tests/Unit/AppTest.php b/tests/Unit/AppTest.php index 5af4290..5ded621 100644 --- a/tests/Unit/AppTest.php +++ b/tests/Unit/AppTest.php @@ -22,17 +22,12 @@ public function testAppClassIsFinal(): void { $this->assertTrue( ( new \ReflectionClass( app::class ) )->isFinal() ); } - public function testRegisterFrameworkServiceNamespacesReturnsArray(): void { - $namespaces = ( new app() )->registerFrameworkServiceNamespaces(); - $this->assertIsArray( $namespaces ); - } - - public function testRegisteredNamespacesIncludeExpectedServices(): void { - $namespaces = ( new app() )->registerFrameworkServiceNamespaces(); - $this->assertContains( '\gcgov\framework\services\documentation', $namespaces ); - $this->assertContains( '\gcgov\framework\services\cronMonitor', $namespaces ); - $this->assertContains( '\gcgov\framework\services\usercrud', $namespaces ); - $this->assertContains( '\gcgov\framework\services\authoauth', $namespaces ); + /** + * Framework Services are enabled in config.json's `services` section. The application + * class no longer names them, and ConfigFilesTest asserts what is actually enabled. + */ + public function testAppNoLongerRegistersServiceNamespaces(): void { + $this->assertFalse( method_exists( app::class, 'registerFrameworkServiceNamespaces' ) ); } public function testLifecycleHooksReturnVoid(): void { diff --git a/tests/Unit/ConfigFilesTest.php b/tests/Unit/ConfigFilesTest.php index ce75693..66ed8a0 100644 --- a/tests/Unit/ConfigFilesTest.php +++ b/tests/Unit/ConfigFilesTest.php @@ -75,7 +75,6 @@ public function testConfigResolvesWithTheDocumentedDeveloperEnvironment(): void $this->assertSame( 'mongodb://mongodb:27017', $config->mongoDatabases[ 0 ]->uri ); $this->assertSame( 'app', $config->mongoDatabases[ 0 ]->database ); $this->assertSame( 'Application', $config->app->title, 'the placeholder `gf init --title` overwrites' ); - $this->assertFalse( $config->settings->useSession ); } @@ -151,7 +150,10 @@ public function testTemplateShipsNoUnusedIntegrationBlocks(): void { $raw = json_decode( (string)file_get_contents( self::ROOT . '/config.json' ), true ); $this->assertIsArray( $raw ); - foreach( [ 'microsoft', 'payjunction', 'sqlDatabases', 'environments' ] as $absent ) { + // settings held only useSession, which the framework removed; cronMonitor was + // registered as a service but never given a url, so it did nothing. Both would now + // be present-and-blank blocks, which is what this test exists to prevent. + foreach( [ 'microsoft', 'payjunction', 'sqlDatabases', 'environments', 'settings', 'cronMonitor' ] as $absent ) { $this->assertArrayNotHasKey( $absent, $raw ); } foreach( [ 'serverName', 'cookieUrl', 'phpPath', 'baseUrl' ] as $removed ) { @@ -160,6 +162,42 @@ public function testTemplateShipsNoUnusedIntegrationBlocks(): void { } + /** + * Which Framework Services this application runs, asserted against the real committed + * config.json — this is the only place that fact is now recorded. + */ + public function testEnabledServicesAreAuthUserCrudAndDocumentation(): void { + $services = configLoader::load( self::ROOT )->services; + + $this->assertNotNull( $services->auth ); + $this->assertSame( 'oauth', $services->auth->provider ); + $this->assertTrue( $services->auth->isOauth() ); + $this->assertNotNull( $services->userCrud ); + $this->assertNotNull( $services->documentation ); + } + + + /** Selecting a provider selects its block and only its block. */ + public function testOnlyTheSelectedAuthProviderIsConfigured(): void { + $auth = configLoader::load( self::ROOT )->services->auth; + + $this->assertNotNull( $auth->oauth, 'the selected provider hydrates to its defaults when omitted' ); + $this->assertNull( $auth->msFront ); + } + + + /** + * Shipped blank, these would be configuration that looks deliberate and is not. They + * take their defaults; README documents them for an application that wants to change them. + */ + public function testNewUserProvisioningTakesItsFailClosedDefaults(): void { + $auth = configLoader::load( self::ROOT )->services->auth; + + $this->assertTrue( $auth->blockNewUsers, 'only users already in the database may sign in' ); + $this->assertSame( [], $auth->defaultNewUserRoles ); + } + + /** A missing section must hydrate to its defaults rather than fataling. */ public function testAbsentSectionsHydrateToDefaults(): void { $config = configLoader::load( self::ROOT ); diff --git a/tests/Unit/ConstantsTest.php b/tests/Unit/ConstantsTest.php index b50ba71..57b167f 100644 --- a/tests/Unit/ConstantsTest.php +++ b/tests/Unit/ConstantsTest.php @@ -7,17 +7,34 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use app\constants; +use app\router; #[CoversClass(constants::class)] final class ConstantsTest extends TestCase { - public function testDefaultRolesConstantExistsAndIsArray(): void { - $this->assertTrue( defined( constants::class . '::DEFAULT_ROLES' ) ); - $this->assertIsArray( constants::DEFAULT_ROLES ); + public function testWidgetRoleConstantsExist(): void { + $this->assertSame( 'Widget.Read', constants::ROLE_WIDGET_READ ); + $this->assertSame( 'Widget.Write', constants::ROLE_WIDGET_WRITE ); } - public function testDefaultRolesIsEmptyByDefault(): void { - $this->assertSame( [], constants::DEFAULT_ROLES ); + + /** + * The point of naming the roles is that the route table uses the names. A constant no + * route references is decoration — which is what DEFAULT_ROLES had become once the + * service configuration it fed moved into config.json. + */ + public function testEveryRoleRequiredByARouteIsANamedConstant(): void { + $named = [ constants::ROLE_WIDGET_READ, constants::ROLE_WIDGET_WRITE ]; + + $required = []; + foreach( ( new router() )->getRoutes() as $route ) { + $required = array_merge( $required, $route->requiredRoles ); + } + + $this->assertNotEmpty( $required ); + foreach( array_unique( $required ) as $role ) { + $this->assertContains( $role, $named, $role . ' is a literal in the route table' ); + } } } diff --git a/tests/Unit/RouterTest.php b/tests/Unit/RouterTest.php index 5d29571..d071d35 100644 --- a/tests/Unit/RouterTest.php +++ b/tests/Unit/RouterTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; +use app\constants; use app\router; use gcgov\framework\models\route; use gcgov\framework\models\routeHandler; @@ -13,13 +14,26 @@ #[CoversClass(router::class)] final class RouterTest extends TestCase { - public function testRouterImplementsFrameworkRouterInterface(): void { + public function testRouterImplementsFrameworkAppRouterInterface(): void { + // appRouter, not router: class_implements reports inherited interfaces, so + // asserting the parent would still pass if this class stopped being an appRouter. $this->assertContains( - \gcgov\framework\interfaces\router::class, + \gcgov\framework\interfaces\appRouter::class, class_implements( router::class ) ?: [] ); } + + /** + * False, and deliberately so. config.json enables services.auth, whose guard + * establishes the caller's identity. authentication() below returns true for + * everyone, so claiming to provide authentication here would satisfy the framework's + * boot check while leaving every widget route open. + */ + public function testRouterDoesNotClaimToProvideAuthentication(): void { + $this->assertFalse( ( new router() )->providesAuthentication() ); + } + public function testGetRoutesReturnsFiveRoutes(): void { $routes = ( new router() )->getRoutes(); $this->assertCount( 5, $routes ); @@ -29,14 +43,14 @@ public function testGetRoutesReturnsFiveRoutes(): void { } public function testWidgetGetAllRoute(): void { - // tests/bootstrap.php seeds environmentConfig with basePath 'api', so the + // tests/bootstrap.php seeds unifiedConfig with basePath 'api', so the // runtime-derived route prefix is '/api' $routes = ( new router() )->getRoutes(); $this->assertSame( 'GET', $routes[0]->httpMethod ); $this->assertSame( '/api/widgets', $routes[0]->route ); $this->assertSame( 'getAll', $routes[0]->method ); $this->assertTrue( $routes[0]->authentication ); - $this->assertSame( [ 'Widget.Read' ], $routes[0]->requiredRoles ); + $this->assertSame( [ constants::ROLE_WIDGET_READ ], $routes[0]->requiredRoles ); } public function testWidgetGetOneRoute(): void { @@ -50,14 +64,14 @@ public function testWidgetSaveRouteRequiresWritePermission(): void { $routes = ( new router() )->getRoutes(); $this->assertSame( 'POST', $routes[2]->httpMethod ); $this->assertSame( 'save', $routes[2]->method ); - $this->assertSame( [ 'Widget.Read', 'Widget.Write' ], $routes[2]->requiredRoles ); + $this->assertSame( [ constants::ROLE_WIDGET_READ, constants::ROLE_WIDGET_WRITE ], $routes[2]->requiredRoles ); } public function testWidgetDeleteRoute(): void { $routes = ( new router() )->getRoutes(); $this->assertSame( 'DELETE', $routes[3]->httpMethod ); $this->assertSame( 'delete', $routes[3]->method ); - $this->assertSame( [ 'Widget.Read', 'Widget.Write' ], $routes[3]->requiredRoles ); + $this->assertSame( [ constants::ROLE_WIDGET_READ, constants::ROLE_WIDGET_WRITE ], $routes[3]->requiredRoles ); } public function testCliRouteIsUnauthenticated(): void { From a228a734acf01f301a603d50dd6868aa45f49a25 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 00:31:48 +0000 Subject: [PATCH 12/17] Point jwtAuth.keyPath at the provisioned key directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config.json hard-coded "keyPath": "" with no %env() reference, so the framework fell back to {root}/srv/jwtCertificates — a directory no built image contains, because the keys are secrets and are gitignored. A container therefore mounted its provisioned keys and then looked somewhere else for them. Nothing catches this at start-up: /api/health and /api/health/ready never construct the JWT service, so the container reports healthy, the deploy health gate passes and the Release is recorded as good. The first symptom is a configException naming a key directory the moment a user tries to sign in. keyPath now reads %env(APP_JWT_KEY_PATH)%, which the value genuinely varies by environment: srv/jwtCertificates locally, /run/secrets//jwt in a Zone. `gf env --list` picks it up from config.json, so the .env manifest cannot drift from it. DOCKER.md gives the value for both cases and explains why the failure looks like success. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018NP3QWawCQVvvzFLQMR5iZ --- DOCKER.md | 17 ++++++++++++++--- config.json | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/DOCKER.md b/DOCKER.md index fd50d51..c28c294 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -79,9 +79,20 @@ key and CI never sees a secret. See `docs/adr/0003-secrets-never-decrypt-in-ci-o ### JWT signing keys These are secrets too, and they are gitignored — so they are **not in the build context and not in -the image**. A container must point `jwtAuth.keyPath` at a provisioned directory -(`/run/secrets//jwt`) or authentication cannot work at all. Every replica must have the same -keys; regenerating them signs every user out. +the image**. `config.json` reads the directory from `%env(APP_JWT_KEY_PATH)%`, which every +environment must set — the two halves are only useful together, and mounting the keys without +pointing the application at them is the failure that looks like success: + +| | `APP_JWT_KEY_PATH` | +|---|---| +| local dev | `/var/www/app/srv/jwtCertificates/` — where `vendor/bin/gf cert:generate-auth` writes | +| a Zone | `/run/secrets//jwt/` — the directory `bin/provision` fills, mounted read-only | + +Get it wrong and nothing complains at start-up. `/api/health` and `/api/health/ready` never +construct the JWT service, so the container reports healthy and a deploy goes green; the first +sign is a `configException` naming a key directory the moment someone tries to sign in. + +Every replica must have the same keys; regenerating them signs every user out. ### Rules diff --git a/config.json b/config.json index 52b798b..8be189c 100644 --- a/config.json +++ b/config.json @@ -29,7 +29,7 @@ "jwtAuth": { "redirectAfterLoginUrl": "%env(APP_REDIRECT_AFTER_LOGIN)%", "redirectAfterLogoutUrl": "%env(APP_REDIRECT_AFTER_LOGOUT)%", - "keyPath": "" + "keyPath": "%env(APP_JWT_KEY_PATH)%" }, "services": { From 4a0b34c10f27d1bc4fe1c6065cd9129b5b69191c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 00:46:35 +0000 Subject: [PATCH 13/17] Pin the container-side APP_JWT_KEY_PATH in docker-compose.yml One .env cannot hold a JWT key path that is right on both filesystems: the host gf CLI (cert:generate-auth) writes the keys on the host, while the container reads them at /var/www/app. DOCKER.md used to document the container path as the .env value, which broke whichever consumer read it second. The compose file now sets the container value itself (environment wins over env_file), and .env carries a root-relative host path for the CLI. DOCKER.md's key-mount section also now describes the framework's readiness check: with services.auth enabled, /api/health/ready fails when the key directory holds no usable keys, so an unmounted key directory stops a deploy at the health gate instead of surfacing at the first production sign-in. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_012SmBhj1hdgf4hFcvCN78m5 --- DOCKER.md | 11 +++++++---- docker-compose.yml | 7 +++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/DOCKER.md b/DOCKER.md index c28c294..0f5cfb0 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -85,12 +85,15 @@ pointing the application at them is the failure that looks like success: | | `APP_JWT_KEY_PATH` | |---|---| -| local dev | `/var/www/app/srv/jwtCertificates/` — where `vendor/bin/gf cert:generate-auth` writes | +| local dev — `.env`, read by the host gf CLI | `srv/jwtCertificates/` — relative to the application root; where `vendor/bin/gf cert:generate-auth` writes | +| local dev — the container | `/var/www/app/srv/jwtCertificates/` — set in `docker-compose.yml` itself, because `.env` is shared with the host CLI and one value cannot be right on both filesystems; the bind mount exposes the same directory | | a Zone | `/run/secrets//jwt/` — the directory `bin/provision` fills, mounted read-only | -Get it wrong and nothing complains at start-up. `/api/health` and `/api/health/ready` never -construct the JWT service, so the container reports healthy and a deploy goes green; the first -sign is a `configException` naming a key directory the moment someone tries to sign in. +Get it wrong and `/api/health/ready` fails: when `services.auth` is enabled, readiness checks +that the key directory holds usable signing keys, precisely so an unmounted or empty key mount +stops a deploy at the health gate instead of surfacing as a `configException` the moment +someone tries to sign in. Plain `/api/health` stays I/O-free and keeps the container alive — +missing keys are a readiness problem, not a liveness one. Every replica must have the same keys; regenerating them signs every user out. diff --git a/docker-compose.yml b/docker-compose.yml index 941b8c0..b30bf09 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,13 @@ services: env_file: - .env - .env.local + environment: + # The container-side path for the JWT signing keys. Set HERE rather than in .env: + # that file is shared with the host gf CLI — `gf cert:generate-auth` writes the + # keys on the host — and one variable cannot hold a value that is right on both + # filesystems. `environment` wins over env_file for the container, and the bind + # mount below exposes the same srv/jwtCertificates directory at this path. + APP_JWT_KEY_PATH: /var/www/app/srv/jwtCertificates/ volumes: - .:/var/www/app # Keep the image's vendor/ from being shadowed by the bind mount. From f2ab8c694114820344f354f7ae8a4adba1dc59d4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 12:25:42 +0000 Subject: [PATCH 14/17] Make the local stack actually run, and document it end to end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following this repository's own instructions did not produce a working application. Four blockers, each fixed here. - MongoDB ran as a standalone, so every write failed. The framework opens a transaction for every save and delete it is not handed a session for, and MongoDB offers transactions only on a replica set or a sharded cluster. The compose stack now runs a single-member set and initiates it from the mongodb healthcheck, with php waiting on service_healthy rather than service_started — the healthcheck is what initiates the set, so waiting for the process is waiting for a node that cannot accept writes. Reads worked throughout, which is why this survived a smoke test: /widgets listed, and saving one did not. - MONGO_URI now joins APP_JWT_KEY_PATH in the compose file's environment block. .env is shared by the host gf CLI and the container, and one variable cannot name both localhost and the compose service. The host's value needs directConnection=true, because the set advertises a member name only the compose network resolves. - One .env, not .env + .env.local. Compose interpolates ${HTTP_PORT} and the CORS origins from .env and never reads .env.local, so the documented `cp .env.example .env.local` left every one of them silently on its default — and .env.example's own header disagreed with both READMEs anyway. Both env_file entries are now required:false, so `docker compose run` works before .env exists, which is when the bootstrap needs it. - No way to create the first user. Now `gf user:create`, which the framework gained for this. LOCAL-DEVELOPMENT.md is the walkthrough: prerequisites through a signed-in request that writes a document, the two variables that differ by side, and a troubleshooting table keyed on the message you actually see. Bootstrap is container-first, so Docker and git are the whole prerequisite list and a host PHP toolchain is optional. ConfigFilesTest gains guards for the three invariants that broke silently: the replica-set flag, the pinned container variables, and .env.example declaring every variable compose interpolates. Its DEV_ENVIRONMENT was also missing APP_JWT_KEY_PATH, added to config.json earlier without it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014PQDyvhd1iz7g1AF6RsCdT --- .env.example | 31 +++- DOCKER.md | 38 +++-- LOCAL-DEVELOPMENT.md | 302 +++++++++++++++++++++++++++++++++ README.md | 44 +++-- docker-compose.yml | 70 ++++++-- tests/Unit/ConfigFilesTest.php | 97 ++++++++++- 6 files changed, 541 insertions(+), 41 deletions(-) create mode 100644 LOCAL-DEVELOPMENT.md diff --git a/.env.example b/.env.example index 2f8390e..f70fbbf 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,19 @@ -# Variables that docker compose needs, which config.json knows nothing about. -# Copy to .env alongside the application variables. +# Copy this to `.env` — not `.env.local`. # -# The APPLICATION's variables are not listed here — `gf env --init` generates those -# from config.json itself, so the two cannot drift. The split is deliberate: +# cp .env.example .env +# docker compose run --rm php vendor/bin/gf env --init # appends the app's own variables # -# gf env --init writes what config.json references (APP_TYPE, MONGO_URI, …) -# this file everything else the local stack needs +# One file, two halves. This file holds what DOCKER COMPOSE needs; `gf env --init` +# appends what CONFIG.JSON needs (APP_TYPE, MONGO_URI, …) to the same file, deriving +# them from config.json itself so the two cannot drift. --init only appends variables +# the file does not already declare, so it is safe to re-run after adding a config +# reference. +# +# Why one file rather than .env + .env.local: docker compose interpolates ${HTTP_PORT} +# and the CORS origins below from `.env` and from nothing else. Put them in .env.local +# and compose silently uses its defaults instead — no warning, no error. (The framework +# does read .env.local, and it wins over .env, so it remains a fine place for a personal +# override of an application variable.) # # Never commit .env. @@ -21,3 +29,14 @@ PHP_FPM_HOST=php:9000 CORS_ORIGIN_APP=http://localhost:8080 CORS_ORIGIN_FRONTEND=http://localhost:5173 CORS_ORIGIN_SWAGGER=http://localhost:8081 + +# ---- MONGO_URI, for the host ---- +# gf env --init will append MONGO_URI blank. The value below is the one the HOST gf +# CLI needs; the php container gets its own from docker-compose.yml, because one +# variable cannot name both `localhost` and the compose service. +# +# directConnection=true is required from the host: the replica set advertises its +# member as "mongodb:27017", a name only the compose network resolves, so without it +# the driver discovers that member and then cannot reach it. +# +# MONGO_URI=mongodb://localhost:27017/?directConnection=true diff --git a/DOCKER.md b/DOCKER.md index 0f5cfb0..ea5493f 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -16,14 +16,22 @@ updated on a production host. ## Local development +**[LOCAL-DEVELOPMENT.md](LOCAL-DEVELOPMENT.md) is the walkthrough** — bootstrap through a +signed-in request, with a troubleshooting table. The short version: + ```bash -vendor/bin/gf env --init # write .env from what config.json references -cp .env.example .env.local # and the variables docker compose needs +cp .env.example .env # what docker compose needs +docker compose build php +docker compose run --rm php vendor/bin/gf init --title="…" # appends what config.json needs # fill in the blanks in .env, then: docker compose up --build -open http://localhost:8080 +docker compose exec php vendor/bin/gf user:create --email=… --roles="User.Read,User.Write" +open http://localhost:8080/health ``` +One `.env`, not two: docker compose interpolates `${HTTP_PORT}` and the CORS origins from `.env` +and never reads `.env.local`, so a compose variable put there is ignored silently. + The working tree is bind-mounted into the php container and opcache revalidates on every request (`docker/php/conf.d/dev.ini`), so edits are live. @@ -33,6 +41,11 @@ docker compose exec php vendor/bin/gf cli:list docker compose exec php composer ci ``` +The compose stack runs MongoDB as a **single-member replica set**, not a standalone. That is a +correctness requirement: every write the framework makes runs in a transaction, which MongoDB +offers only on a replica set or a sharded cluster, so a standalone serves every read and fails +every write. + --- ## Configuration @@ -76,18 +89,23 @@ Zone plus an offline break-glass key. An operator decrypts on their own workstat the files to the host — a step deliberately separate from deploying, so no host holds a decryption key and CI never sees a secret. See `docs/adr/0003-secrets-never-decrypt-in-ci-or-on-hosts.md`. +### The two variables that differ by side + +`.env` is read by both the host `gf` CLI and the php container, and two variables cannot hold one +value correct for both. Both are pinned in `docker-compose.yml`'s `environment:` block, which wins +over `env_file:`, so `.env` carries the host's value and the container quietly gets its own: + +| | local dev — `.env`, read by the host gf CLI | local dev — the container | a Zone | +|---|---|---|---| +| `APP_JWT_KEY_PATH` | `srv/jwtCertificates/`, relative to the application root; where `gf cert:generate-auth` writes | `/var/www/app/srv/jwtCertificates/` — the same directory through the bind mount | `/run/secrets//jwt/`, the directory `bin/provision` fills, mounted read-only | +| `MONGO_URI` | `mongodb://localhost:27017/?directConnection=true` — the published port. `directConnection` because the set advertises `mongodb:27017`, which the host cannot resolve | `mongodb://mongodb:27017/?replicaSet=rs0` — the compose service name | the provisioned secret file, via `MONGO_URI_FILE` | + ### JWT signing keys These are secrets too, and they are gitignored — so they are **not in the build context and not in the image**. `config.json` reads the directory from `%env(APP_JWT_KEY_PATH)%`, which every environment must set — the two halves are only useful together, and mounting the keys without -pointing the application at them is the failure that looks like success: - -| | `APP_JWT_KEY_PATH` | -|---|---| -| local dev — `.env`, read by the host gf CLI | `srv/jwtCertificates/` — relative to the application root; where `vendor/bin/gf cert:generate-auth` writes | -| local dev — the container | `/var/www/app/srv/jwtCertificates/` — set in `docker-compose.yml` itself, because `.env` is shared with the host CLI and one value cannot be right on both filesystems; the bind mount exposes the same directory | -| a Zone | `/run/secrets//jwt/` — the directory `bin/provision` fills, mounted read-only | +pointing the application at them is the failure that looks like success. Get it wrong and `/api/health/ready` fails: when `services.auth` is enabled, readiness checks that the key directory holds usable signing keys, precisely so an unmounted or empty key mount diff --git a/LOCAL-DEVELOPMENT.md b/LOCAL-DEVELOPMENT.md new file mode 100644 index 0000000..201cf42 --- /dev/null +++ b/LOCAL-DEVELOPMENT.md @@ -0,0 +1,302 @@ +# Running this application on a development computer + +From an empty checkout to a signed-in request against your own data. Nine steps, all of which +should succeed; if one does not, [Troubleshooting](#troubleshooting) names the cause. + +> This is the local story. `DOCKER.md` covers the images, secrets and deployment; +> `README.md` is the short version of this page. + +--- + +## 1. Prerequisites + +- **Docker Desktop** (or Docker Engine plus the Compose plugin) and **git**. That is the whole list. +- **A host PHP toolchain is optional.** PHP 8.4 with `ext-mongodb`, `ext-sodium`, `ext-zip` and + `ext-openssl` gives you a faster inner loop and a `vendor/bin/gf` you can run without a container, + but nothing below requires it — every command runs inside the stack. Installing PHP on Windows + used to be the bulk of setting this up, and it no longer has to be. + +The stack publishes two ports on the host: **8080** (the API) and **27017** (MongoDB). Change either +in `.env` if something already has them. + +--- + +## 2. Get the code and the compose variables + +```bash +git clone https://github.com//.git +cd +cp .env.example .env +``` + +`.env` is gitignored and holds every local value. It has two halves: the compose variables you just +copied, and the application variables step 3 appends. They share one file because **docker compose +interpolates `${HTTP_PORT}` and the CORS origins from `.env` and from nothing else** — it never +reads `.env.local`, so a compose variable put there is ignored with no warning. + +--- + +## 3. Bootstrap + +```bash +docker compose build php +docker compose run --rm php vendor/bin/gf init --title="My API" +``` + +`gf init` is non-interactive and does four things: + +| | | +|---|---| +| **Identity** | Writes `app.title` and a freshly minted `app.guid` into `config.json`. The guid is the OAuth `client_id`; re-running `init` keeps an existing one rather than invalidating every registered client. | +| **`.env`** | Appends every variable `config.json` references, derived from `config.json` itself so the list cannot drift. Values are left blank for you. | +| **JWT keys** | Generates 5 RSA keypairs plus `guids.json` into `srv/jwtCertificates/`. Gitignored — they are secrets, and they never enter a build context or an image. | +| **chrome-headless-shell** | Downloads it into `srv/chrome/`. Add `--skip-chrome` if the application does not render PDFs; it is ~150 MB. | + +The working tree is bind-mounted, so all of that lands on your host filesystem even though the +command ran in a container. + +--- + +## 4. Fill in `.env` + +Open it. The bottom half is what `gf init` appended — eight variables, all required. There are no +defaults and a variable set to the empty string counts as unset, deliberately: a half-configured +application should refuse to start rather than run in some posture nobody chose. + +```bash +APP_TYPE=local +APP_ROOT_URL=http://localhost:8080 +APP_BASE_PATH=/ +APP_REDIRECT_AFTER_LOGIN=http://localhost:5173 +APP_REDIRECT_AFTER_LOGOUT=http://localhost:5173 +APP_JWT_KEY_PATH=srv/jwtCertificates/ +MONGO_DATABASE=myapp +MONGO_URI=mongodb://localhost:27017/?directConnection=true +``` + +`APP_BASE_PATH` is `/` at the domain root, **not blank** — blank counts as unset and is a startup +failure like any other missing reference. Set it to `/api` when the application is served under a +prefix, and change the nginx healthcheck in `docker-compose.yml` to match. + +Then check it: + +```bash +docker compose run --rm php vendor/bin/gf env # resolve it, or name the first thing missing +docker compose run --rm php vendor/bin/gf env --list # every variable, and whether it is set +``` + +### The two variables that differ by side + +`.env` is read by both the host `gf` CLI and the container, and two of its variables cannot hold one +value that is right for both. Those two are **pinned in `docker-compose.yml`**, where `environment:` +overrides `env_file:`, so the value in `.env` is the host's and the container quietly gets its own: + +| Variable | In `.env` (the host) | In the container | Why | +|---|---|---|---| +| `APP_JWT_KEY_PATH` | `srv/jwtCertificates/` | `/var/www/app/srv/jwtCertificates/` | Different filesystems, same directory through the bind mount. `gf cert:generate-auth` writes on the host. | +| `MONGO_URI` | `mongodb://localhost:27017/?directConnection=true` | `mongodb://mongodb:27017/?replicaSet=rs0` | Different networks. The host sees a published port; the container resolves the service name. | + +`directConnection=true` is not optional from the host. The replica set advertises its one member as +`mongodb:27017` — a name only the compose network resolves — so without it the driver discovers that +member and then cannot reach it. In a Zone, `APP_JWT_KEY_PATH` is a mounted secret directory and +`MONGO_URI` comes from a file; see `DOCKER.md`. + +--- + +## 5. Start it + +```bash +docker compose up --build +``` + +Three containers come up: **nginx** on 8080, **php** (PHP-FPM), and **mongodb**. Mongo takes a few +seconds longer than it used to — its healthcheck initiates the replica set, and php waits for it. + +```bash +curl -s localhost:8080/health # {"status":"ok","version":"unknown"} +curl -s localhost:8080/health/ready # every dependency, one line each +``` + +The two are deliberately different. `/health` is liveness and does no I/O, so a database blip cannot +restart every container. `/health/ready` is readiness: it pings each configured database and, when +`services.auth` is enabled, checks that the key directory holds usable signing keys. It answers +`503` with the failing check named, which is a much better first stop than the application logs: + +```json +{ "status": "ok", "version": "unknown", "checks": { "mongo:myapp": "ok", "jwtKeys": "ok" } } +``` + +--- + +## 6. Why MongoDB is a replica set + +`docker-compose.yml` starts mongo with `--replSet rs0` and initiates a single-member set. That is a +correctness requirement, not production fidelity. + +Every write the framework makes runs in a transaction — `save()` alone is several writes (the +document, its auto-increment counters, and the embedded copies pushed into other collections), and a +half-applied save is a corrupt denormalisation rather than a failed request. MongoDB offers +transactions only on a replica set or a sharded cluster. + +A standalone `mongod` therefore serves every **read** perfectly and fails every **write** with: + +``` +Transaction numbers are only allowed on a replica set member or mongos +``` + +which is exactly the shape of bug that survives a smoke test: the list endpoints work and only +saving fails. + +--- + +## 7. Create the first user + +```bash +docker compose exec php vendor/bin/gf user:create \ + --email=dev@example.test \ + --roles="User.Read,User.Write,Widget.Read,Widget.Write" +``` + +It prints a generated password once — pass `--password=…` if you would rather choose it. + +You cannot skip this, and there is no way around it from outside. `config.json` enables +`services.auth`, whose `blockNewUsers` defaults to true, so only users already in the database may +sign in. Every `/user` route requires a caller already holding `User.Write`. Nothing can +authenticate, so nothing can create the first user. Inserting the document with `mongosh` does not +help either: the user model hashes the password as it writes, so a hand-written document has no +password anyone can sign in with. `gf user:create` saves through that same model, which is what +makes the account usable. + +Roles are plain strings that routes compare against — nothing validates them. Give the first user +whatever its routes name in `requiredRoles` (this template ships `Widget.Read` / `Widget.Write` in +`app/constants.php`), plus `User.Read` and `User.Write` to administer other users through +`services.userCrud`. + +Adding a role later is the same command with `--force`, which updates in place and leaves the +password alone: + +```bash +docker compose exec php vendor/bin/gf user:create --email=dev@example.test --force --roles="User.Read,Widget.Read" +``` + +--- + +## 8. Sign in and call a route + +Every route this template ships requires authentication and a role, so you need a token. Take +`client_id` from `app.guid` in `config.json` — the value `gf init` minted. + +```bash +CLIENT_ID=$(python3 -c "import json;print(json.load(open('config.json'))['app']['guid'])") + +TOKEN=$(curl -s -X POST localhost:8080/auth/authorize \ + -H 'Content-Type: application/json' \ + -d "{\"grant_type\":\"password\",\"scope\":\"login\",\"client_id\":\"$CLIENT_ID\",\"username\":\"dev@example.test\",\"password\":\"…\"}" \ + | python3 -c "import json,sys;print(json.load(sys.stdin)['access_token'])") + +curl -s localhost:8080/widgets -H "Authorization: Bearer $TOKEN" +``` + +`grant_type=password` and `scope=login` are both checked exactly, and `client_id` must equal +`app.guid` — a mismatch is a 401 reading "Invalid client id", not a hint that the guid is wrong. + +Then write something, which is the step that proves the replica set is doing its job: + +```bash +curl -s -X POST localhost:8080/widgets/new \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + -d '{"name":"First widget"}' +``` + +Other routes worth knowing: `GET /.well-known/jwks.json` (the public keys, unauthenticated), +`GET /documentation.yaml` (OpenAPI, generated from the annotations in this application and the +framework), and `GET /user` (`services.userCrud`). + +--- + +## 9. The day-to-day loop + +**Edits are live.** The working tree is bind-mounted into the php container and +`docker/php/conf.d/dev.ini` turns opcache timestamp validation back on, so a saved file is picked up +on the next request. No restart, no rebuild. Rebuild only when `composer.json`, the `Dockerfile` or +anything under `docker/` changes. + +```bash +docker compose logs -f php # logs go to stderr, not logs/*.log +docker compose exec php vendor/bin/gf cli:list # the application's CLI routes +docker compose exec php vendor/bin/gf cli /cli/widgets +docker compose exec php composer ci # phpstan + phpunit, the same checks CI runs +docker compose exec php vendor/bin/gf env # after editing config.json +docker compose exec mongodb mongosh myapp # a shell on the database +``` + +Set `logging.lifecycle: true` in `config.json` to trace routing and the auth guard end to end when a +request is answered by something you did not expect. + +`docker compose down` stops the stack and keeps the data; `docker compose down -v` also drops the +Mongo volume, which is how you start over (the replica set re-initiates on the next boot, and you +create the first user again). + +### Adding a configuration variable + +Add the `%env(...)%` reference to `config.json`, then: + +```bash +docker compose run --rm php vendor/bin/gf env --init # appends only what .env lacks +``` + +It leaves every value you have filled in, and every variable `config.json` knows nothing about, +alone. `--force` rewrites from `config.json` and discards both — you rarely want it. + +--- + +## Running without Docker + +The stack is the supported path, but nothing requires it. Serve `www/` with any PHP 8.4 SAPI that +has `ext-mongodb`, point `MONGO_URI` at a MongoDB you run yourself, and use `vendor/bin/gf` directly. + +The one thing that is easy to get wrong: **that MongoDB must also be a replica set** (§6). A +`mongod` started with no arguments will not do. + +```bash +mongod --replSet rs0 --dbpath /path/to/data +mongosh --eval 'rs.initiate()' +``` + +With mongo on the host under its own name, `directConnection=true` is unnecessary — set +`MONGO_URI=mongodb://localhost:27017/?replicaSet=rs0`. + +--- + +## Troubleshooting + +| Symptom | Cause | +|---|---| +| `Transaction numbers are only allowed on a replica set member or mongos` | MongoDB is a standalone. Reads work, writes cannot. See §6. | +| A write hangs, then fails with a server-selection timeout | `MONGO_URI` names `replicaSet=rs0` from the host. The set advertises `mongodb:27017`, which the host cannot resolve — use `directConnection=true` instead (§4). | +| `configException` naming a variable, at startup | That `%env()` reference has no value. Every one is required and blank counts as unset. `gf env --list` shows the whole list. | +| `configException` about `APP_BASE_PATH`, which you did set | You set it to nothing. At the domain root it is `/`, not blank (§4). | +| `HTTP_PORT` / a CORS origin appears to do nothing | It is in `.env.local`. Docker compose interpolates only from `.env` (§2). | +| `/health/ready` returns 503 on `jwtKeys` | The key directory is empty or `APP_JWT_KEY_PATH` points at the wrong side (§4). Run `gf cert:generate-auth`. | +| `/health/ready` returns 503 on `mongo:*` | Mongo is not up or `MONGO_URI` is wrong. `docker compose ps` shows whether its healthcheck ever went green. | +| 401 on every application route | No token, or no user. Steps 7 and 8. | +| 401 "Invalid client id" | `client_id` does not equal `app.guid` in `config.json`. | +| 403 on a route that authenticated fine | The user lacks the role the route declares. Re-run `user:create --force --roles="…"`. | +| nginx exits immediately at startup | Two of `CORS_ORIGIN_APP` / `_FRONTEND` / `_SWAGGER` are equal. They become keys in an nginx `map`, and a duplicate key is fatal. | +| `docker compose` errors that `.env` is missing | Both env files are declared `required: false`, so this means a Compose too old to understand that (pre-2.24). Upgrade, or `touch .env .env.local` and carry on. | +| The framework refuses to boot over authenticated routes | `services.auth` is absent from `config.json` while routes declare `authentication: true`. That combination would serve them to anyone. | +| `composer install` complains about `ext-mongodb` on the host | Use `--ignore-platform-req=ext-mongodb`, never blanket `--ignore-platform-reqs` — that also discards the `config.platform.php` pin and locks packages the production image cannot install. | + +`db/local-createuser.js` creates a **MongoDB account**, not an application user. The compose stack +runs `mongod` without authentication, so you do not need it locally. + +--- + +## Where to look next + +- **[README.md](README.md)** — the short bootstrap, and how Framework Services are enabled. +- **[DOCKER.md](DOCKER.md)** — the two images, secrets as provisioned files, TLS, and how a Release + reaches a host. +- **[the gf CLI](https://github.com/gcgov/framework/blob/main/readme/gf.md)** · + **[environment variables](https://github.com/gcgov/framework/blob/main/readme/environment-variables.md)** · + **[MongoDB](https://github.com/gcgov/framework/blob/main/readme/mongodb.md)** diff --git a/README.md b/README.md index 3d5b8b3..6aa63a8 100644 --- a/README.md +++ b/README.md @@ -9,36 +9,47 @@ container images — nginx and PHP-FPM — and keeps every secret out of the com 1. [Use this template](https://github.com/gcgov/framework-app-template/generate) to generate a new repository for your application. -2. Bootstrap it: +2. Bootstrap it. Docker and git are the only prerequisites — PHP on the host is optional: ```bash - composer install - vendor/bin/gf init --title="Permits API" + cp .env.example .env # the variables docker compose itself needs + docker compose build php + docker compose run --rm php vendor/bin/gf init --title="Permits API" ``` - `gf init` writes the title and a freshly minted guid into `config.json`, writes a `.env` - skeleton from the variables `config.json` references, generates JWT signing keypairs, and - installs chrome-headless-shell. It is non-interactive, so it also works from a scaffolding - script or a devcontainer. + `gf init` writes the title and a freshly minted guid into `config.json`, appends the variables + `config.json` references to `.env`, generates JWT signing keypairs, and installs + chrome-headless-shell. It is non-interactive, so it also works from a scaffolding script or a + devcontainer. The working tree is bind-mounted, so all of that lands on your host. -3. Fill in `.env`, and add the compose variables: +3. Fill in `.env`, then check it: ```bash - cp .env.example .env.local - vendor/bin/gf env # does it resolve? names the first thing missing + docker compose run --rm php vendor/bin/gf env # does it resolve? names the first thing missing ``` Every reference in `config.json` is **required** — there are no defaults, and a blank value counts as missing. That is deliberate: a half-configured application should refuse to start rather than run in some unintended posture. `gf env --list` shows the whole list. -4. Run it: + One `.env`, not two: docker compose interpolates its own variables from `.env` and never reads + `.env.local`, so the two halves share a file. + +4. Run it, and create the account you sign in as: ```bash docker compose up --build - # → http://localhost:8080 + # → http://localhost:8080/health + + docker compose exec php vendor/bin/gf user:create \ + --email=dev@example.test --roles="User.Read,User.Write,Widget.Read,Widget.Write" ``` + Every route below requires a token, and `blockNewUsers` defaults to true, so this step is how + an application gets its first user — there is no way in from outside. 5. Try the `widget` module, then write your own models, controllers, and routes. +**[LOCAL-DEVELOPMENT.md](LOCAL-DEVELOPMENT.md) is the full walkthrough**, through signing in and +writing a document, with a troubleshooting table. + ## Adding what you need -`config.json` ships with seven variables and nothing else — no Microsoft, PayJunction, or SMTP +`config.json` ships with eight variables and nothing else — no Microsoft, PayJunction, or SMTP block. Add the section for an integration when the application actually uses one; a section that is absent hydrates to its defaults. Keeping unused credentials out of the file means the application never has to be handed a value it does not use in order to boot. @@ -82,6 +93,9 @@ boot rather than serve routes that look protected and are not. ## Documentation +- **[LOCAL-DEVELOPMENT.md](LOCAL-DEVELOPMENT.md)** — running this application on a development + computer, end to end: bootstrap, configuration, the first user, a signed-in request, and what + each failure means. - **[DOCKER.md](DOCKER.md)** — the images, secrets as provisioned files, health checks, TLS, and how a Release reaches a host. - The `gf` CLI: `vendor/bin/gf` (`gf init`, `gf env`, `gf cli`, `gf db:run`, `gf migrate`, …). @@ -119,3 +133,7 @@ checks. You can still run the app under any PHP 8.4+ SAPI with `ext-mongodb`. Point the web root at `/www/`, resolve config secrets through your shell environment or a `.env` file at the project root, and use `vendor/bin/gf` for CLI tasks. The Docker stack is the supported, reproducible path. + +The MongoDB you point it at must be a **replica set** — a `mongod` started with no arguments will +not do. Every write the framework makes runs in a transaction, so a standalone serves every read +and fails every write. One member is enough: `mongod --replSet rs0`, then `rs.initiate()`. diff --git a/docker-compose.yml b/docker-compose.yml index b30bf09..5499e5c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,19 @@ # Local development stack. Production topology lives in the ops repository # (gcgov/deploy), not here — this file exists so a developer can run the app. # -# gf env --init # write .env with the variables config.json needs -# cp .env.example .env.local # and the ones docker compose needs +# cp .env.example .env # the variables docker compose itself needs +# docker compose build php +# docker compose run --rm php vendor/bin/gf init --title="My API" # docker compose up --build # open http://localhost:8080 # +# Full walkthrough: LOCAL-DEVELOPMENT.md +# +# One .env, not two. Compose interpolates ${HTTP_PORT} and friends from `.env` and +# from nothing else — it never reads `.env.local` — so a compose variable put there +# is silently ignored. `gf env --init` appends the application's variables to the +# same file rather than replacing it, so both halves live together. +# # The php container mounts the working tree, so edits are live; opcache timestamp # validation is switched back on in docker/php/conf.d/dev.ini to match. @@ -15,22 +23,36 @@ services: context: . target: dev env_file: - - .env - - .env.local + # required:false so `docker compose run` works before `.env` exists — which is + # exactly when you need it, since `gf init` is what writes the file. + - path: .env + required: false + - path: .env.local + required: false environment: - # The container-side path for the JWT signing keys. Set HERE rather than in .env: - # that file is shared with the host gf CLI — `gf cert:generate-auth` writes the - # keys on the host — and one variable cannot hold a value that is right on both - # filesystems. `environment` wins over env_file for the container, and the bind - # mount below exposes the same srv/jwtCertificates directory at this path. + # The two variables whose correct value differs between the host and the + # container. Both are pinned HERE rather than in .env, because that file is + # shared with the host gf CLI and one variable cannot hold a value that is + # right on both filesystems and both networks. `environment` wins over + # env_file for the container. + # + # The JWT signing keys: `gf cert:generate-auth` writes them on the host, and + # the bind mount below exposes that same srv/jwtCertificates directory here. APP_JWT_KEY_PATH: /var/www/app/srv/jwtCertificates/ + # MongoDB: the host reaches the published port as localhost, the container + # reaches the service by name. replicaSet=rs0 because every write this + # framework makes runs in a transaction — see the mongodb service below. + MONGO_URI: mongodb://mongodb:27017/?replicaSet=rs0 volumes: - .:/var/www/app # Keep the image's vendor/ from being shadowed by the bind mount. - /var/www/app/vendor depends_on: mongodb: - condition: service_started + # service_healthy, not service_started: the healthcheck below is what + # initiates the replica set, and an application that connects first sees a + # node that cannot accept writes. + condition: service_healthy nginx: image: nginx:1.27-alpine @@ -61,10 +83,38 @@ services: mongodb: image: mongo:7 + # A single-member replica set, not a standalone. This is a correctness + # requirement rather than production fidelity: the framework's save(), delete() + # and their -Many siblings each open a transaction when not handed a session, and + # MongoDB only offers transactions on a replica set or a sharded cluster. A + # standalone mongod serves every read and fails every write with "Transaction + # numbers are only allowed on a replica set member or mongos" — so the app looks + # like it works until the first thing you try to save. + command: [ "--replSet", "rs0", "--bind_ip_all" ] ports: - "${MONGO_PORT:-27017}:27017" volumes: - mongo-data:/data/db + healthcheck: + # Initiates the set on first boot and reports healthy once it has a primary. + # Idempotent, because a healthcheck runs forever: rs.initiate() throws + # AlreadyInitialized on every run after the first, and hello().isWritablePrimary + # is the condition that actually matters either way. + # + # The member is named "mongodb" — the name the php container resolves. A host + # connecting to the published port must therefore pass directConnection=true + # (see .env.example), because it cannot resolve the name the set advertises. + test: + - CMD-SHELL + - > + mongosh --quiet --eval ' + try { rs.initiate({_id:"rs0",members:[{_id:0,host:"mongodb:27017"}]}) } catch (e) {} + quit(db.hello().isWritablePrimary ? 0 : 1) + ' + interval: 5s + timeout: 5s + start_period: 40s + retries: 12 volumes: mongo-data: diff --git a/tests/Unit/ConfigFilesTest.php b/tests/Unit/ConfigFilesTest.php index 66ed8a0..c78323a 100644 --- a/tests/Unit/ConfigFilesTest.php +++ b/tests/Unit/ConfigFilesTest.php @@ -29,7 +29,19 @@ final class ConfigFilesTest extends TestCase { 'APP_REDIRECT_AFTER_LOGIN' => 'http://localhost:5173/auth/sign-in', 'APP_REDIRECT_AFTER_LOGOUT' => 'http://localhost:5173/auth/sign-out', 'MONGO_DATABASE' => 'app', - 'MONGO_URI' => 'mongodb://mongodb:27017', + 'MONGO_URI' => 'mongodb://mongodb:27017/?replicaSet=rs0', + 'APP_JWT_KEY_PATH' => '/var/www/app/srv/jwtCertificates/', + ]; + + /** + * The variables whose correct value differs between the host gf CLI and the php + * container, so docker-compose.yml pins them and .env carries the host's value. + * + * @var array + */ + private const array CONTAINER_PINNED = [ + 'APP_JWT_KEY_PATH' => '/var/www/app/srv/jwtCertificates/', + 'MONGO_URI' => 'mongodb://mongodb:27017/?replicaSet=rs0', ]; /** @var array */ @@ -72,7 +84,7 @@ public function testConfigResolvesWithTheDocumentedDeveloperEnvironment(): void $this->assertInstanceOf( unifiedConfig::class, $config ); $this->assertSame( 'local', $config->type ); - $this->assertSame( 'mongodb://mongodb:27017', $config->mongoDatabases[ 0 ]->uri ); + $this->assertSame( 'mongodb://mongodb:27017/?replicaSet=rs0', $config->mongoDatabases[ 0 ]->uri ); $this->assertSame( 'app', $config->mongoDatabases[ 0 ]->database ); $this->assertSame( 'Application', $config->app->title, 'the placeholder `gf init --title` overwrites' ); } @@ -216,4 +228,85 @@ public function testJwtIssuerAndAudienceDeriveFromTheApplicationUrls(): void { $this->assertSame( '/', $config->getTokenPermittedFor() ); } + + /** + * MongoDB must run as a replica set, and nothing else in the stack says so out loud. + * + * Every write the framework makes opens a transaction, which MongoDB offers only on a + * replica set or a sharded cluster. A standalone mongod serves every read and fails + * every write — so dropping this flag leaves a stack that starts, answers /health, lists + * widgets, and cannot save one. That is a regression a smoke test does not catch, which + * is what earns it a test. + */ + public function testTheLocalStackRunsMongoAsAReplicaSet(): void { + $mongo = $this->composeService( 'mongodb' ); + + $this->assertContains( '--replSet', $mongo[ 'command' ] ?? [], 'a standalone mongod cannot serve any write this framework makes' ); + $this->assertSame( 'service_healthy', $this->composeService( 'php' )[ 'depends_on' ][ 'mongodb' ][ 'condition' ] ?? null, 'the healthcheck is what initiates the set, so php must wait for it rather than for the process' ); + } + + + /** + * The variables one .env cannot get right for both sides are pinned in the compose file. + * + * .env is read by the host gf CLI and by the container. MONGO_URI must name localhost on + * one and the compose service on the other; APP_JWT_KEY_PATH must name a host-relative + * and a container-absolute path. Pinning them under `environment:` (which beats + * `env_file:`) is what lets one file serve both — and losing either pin is silent, since + * the host's value resolves perfectly well inside the container and simply points nowhere. + */ + public function testTheContainerPinsTheVariablesThatDifferBySide(): void { + $environment = $this->composeService( 'php' )[ 'environment' ] ?? []; + + foreach( self::CONTAINER_PINNED as $name => $value ) { + $this->assertSame( $value, $environment[ $name ] ?? null, $name . ' must be pinned in docker-compose.yml: .env cannot hold a value correct on both the host and the container' ); + } + } + + + /** Every pinned variable is one config.json actually reads. */ + public function testEveryPinnedVariableIsReferencedByConfig(): void { + $references = configLoader::references( self::ROOT ); + + foreach( array_keys( self::CONTAINER_PINNED ) as $name ) { + $this->assertArrayHasKey( $name, $references ); + } + } + + + /** + * .env.example must declare every variable docker compose interpolates. + * + * Compose reads `.env` and only `.env`, so `cp .env.example .env` is the whole supply + * chain for these. A ${VAR} the example file does not declare silently falls back to its + * default — no warning, no error — which is how a published port or a CORS origin ends up + * ignored. + */ + public function testEnvExampleDeclaresEveryVariableComposeInterpolates(): void { + $compose = (string)file_get_contents( self::ROOT . '/docker-compose.yml' ); + $example = (string)file_get_contents( self::ROOT . '/.env.example' ); + + preg_match_all( '/\$\{([A-Z0-9_]+)(?::-[^}]*)?}/', $compose, $matches ); + $interpolated = array_unique( $matches[ 1 ] ); + $this->assertNotEmpty( $interpolated ); + + foreach( $interpolated as $name ) { + $this->assertMatchesRegularExpression( '/^' . preg_quote( $name, '/' ) . '=/m', $example, $name . ' is interpolated by docker-compose.yml but .env.example does not declare it' ); + } + } + + + /** + * Read one service out of the committed compose file. + * + * @return array + */ + private function composeService( string $name ): array { + /** @var array $parsed */ + $parsed = \Symfony\Component\Yaml\Yaml::parseFile( self::ROOT . '/docker-compose.yml' ); + $this->assertArrayHasKey( $name, $parsed[ 'services' ] ); + + return $parsed[ 'services' ][ $name ]; + } + } From 3bd354f870bb8aa2080fc106e8a75ba5eb34e0dc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 13:59:55 +0000 Subject: [PATCH 15/17] Cut the local-dev guide at the framework seam, and declare symfony/yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up from a design review of the local development work. - LOCAL-DEVELOPMENT.md keeps only what is genuinely this stack's: the runnable sequence, the concrete values (mongodb:27017, rs0, HTTP_PORT, /widgets, Widget.Read), and the troubleshooting table. Every "why" now links to the framework's readme/local-development.md. The seam falls where the drift danger is: this repository is a GitHub template, so an application's copy of this file is frozen the moment it is scaffolded, while the rules behind it reach that application through Composer and stay current. - A suspected Linux-only bootstrap failure is documented rather than fixed. The dev image ends USER www-data (uid 33), so `docker compose run --rm php vendor/bin/gf init` writes into a bind mount as uid 33 and should fail on config.json, which the host owns. Docker Desktop's file-sharing layer masks this on macOS and Windows, which is why it went unnoticed. It has NOT been reproduced — there was no Docker daemon available — so the row is worded as suspected and carries the --user workaround. A developer who hits it gets a path forward; if the diagnosis is wrong the row simply never matches. - symfony/yaml moves from a transitive dependency to a declared one. ConfigFilesTest parses docker-compose.yml with it, which worked only because the framework happens to pull it in — the day that stops being true the test breaks for a reason nothing points at. composer.lock is refreshed for the new content hash only; no resolved version changed, and `composer validate`, which CI runs first, passes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014PQDyvhd1iz7g1AF6RsCdT --- DOCKER.md | 4 +- LOCAL-DEVELOPMENT.md | 280 ++++++++++++++----------------------------- README.md | 6 +- composer.json | 3 +- composer.lock | 2 +- 5 files changed, 102 insertions(+), 193 deletions(-) diff --git a/DOCKER.md b/DOCKER.md index ea5493f..f32d0f7 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -17,7 +17,9 @@ updated on a production host. ## Local development **[LOCAL-DEVELOPMENT.md](LOCAL-DEVELOPMENT.md) is the walkthrough** — bootstrap through a -signed-in request, with a troubleshooting table. The short version: +signed-in request, with a troubleshooting table; the framework's +[local-development.md](https://github.com/gcgov/framework/blob/main/readme/local-development.md) has the rules behind it. The short +version: ```bash cp .env.example .env # what docker compose needs diff --git a/LOCAL-DEVELOPMENT.md b/LOCAL-DEVELOPMENT.md index 201cf42..76d6273 100644 --- a/LOCAL-DEVELOPMENT.md +++ b/LOCAL-DEVELOPMENT.md @@ -1,67 +1,45 @@ # Running this application on a development computer -From an empty checkout to a signed-in request against your own data. Nine steps, all of which -should succeed; if one does not, [Troubleshooting](#troubleshooting) names the cause. +The commands, for this stack. Every *why* behind them — the replica set, the required Config +References, the signing keys, the Bootstrap User — is in the framework's +**[local-development.md](https://github.com/gcgov/framework/blob/main/readme/local-development.md)**, which reaches you through Composer and +so stays current; this page is a copy frozen when your application was scaffolded, and is yours to +edit as your stack changes. -> This is the local story. `DOCKER.md` covers the images, secrets and deployment; -> `README.md` is the short version of this page. +> `DOCKER.md` covers the images, secrets and deployment. `README.md` is the short version of this +> page. --- ## 1. Prerequisites -- **Docker Desktop** (or Docker Engine plus the Compose plugin) and **git**. That is the whole list. -- **A host PHP toolchain is optional.** PHP 8.4 with `ext-mongodb`, `ext-sodium`, `ext-zip` and - `ext-openssl` gives you a faster inner loop and a `vendor/bin/gf` you can run without a container, - but nothing below requires it — every command runs inside the stack. Installing PHP on Windows - used to be the bulk of setting this up, and it no longer has to be. +**Docker Desktop** (or Docker Engine plus the Compose plugin) and **git**. That is the whole list. -The stack publishes two ports on the host: **8080** (the API) and **27017** (MongoDB). Change either -in `.env` if something already has them. +A host PHP toolchain is optional — PHP 8.4 with `ext-mongodb`, `ext-sodium`, `ext-zip` and +`ext-openssl` gives you a faster inner loop and a `vendor/bin/gf` you can run without a container, +but nothing below needs it. ---- - -## 2. Get the code and the compose variables - -```bash -git clone https://github.com//.git -cd -cp .env.example .env -``` - -`.env` is gitignored and holds every local value. It has two halves: the compose variables you just -copied, and the application variables step 3 appends. They share one file because **docker compose -interpolates `${HTTP_PORT}` and the CORS origins from `.env` and from nothing else** — it never -reads `.env.local`, so a compose variable put there is ignored with no warning. - ---- +The stack publishes **8080** (the API) and **27017** (MongoDB). Change either in `.env`. -## 3. Bootstrap +## 2. Bootstrap ```bash +git clone https://github.com//.git && cd +cp .env.example .env # the variables docker compose itself needs docker compose build php docker compose run --rm php vendor/bin/gf init --title="My API" ``` -`gf init` is non-interactive and does four things: +`gf init` writes the title and a minted guid into `config.json`, appends the variables +`config.json` references to `.env`, generates JWT signing keypairs into `srv/jwtCertificates/`, and +installs chrome-headless-shell (`--skip-chrome` to skip ~150 MB). The working tree is bind-mounted, +so all of it lands on your host. It is idempotent — re-run it as your `config.json` grows. -| | | -|---|---| -| **Identity** | Writes `app.title` and a freshly minted `app.guid` into `config.json`. The guid is the OAuth `client_id`; re-running `init` keeps an existing one rather than invalidating every registered client. | -| **`.env`** | Appends every variable `config.json` references, derived from `config.json` itself so the list cannot drift. Values are left blank for you. | -| **JWT keys** | Generates 5 RSA keypairs plus `guids.json` into `srv/jwtCertificates/`. Gitignored — they are secrets, and they never enter a build context or an image. | -| **chrome-headless-shell** | Downloads it into `srv/chrome/`. Add `--skip-chrome` if the application does not render PDFs; it is ~150 MB. | - -The working tree is bind-mounted, so all of that lands on your host filesystem even though the -command ran in a container. - ---- +**One `.env`, not two.** Docker compose interpolates `${HTTP_PORT}` and the CORS origins from +`.env` and never reads `.env.local`, so a compose variable put there is ignored with no warning. +`gf env --init` appends to the same file rather than replacing it. -## 4. Fill in `.env` - -Open it. The bottom half is what `gf init` appended — eight variables, all required. There are no -defaults and a variable set to the empty string counts as unset, deliberately: a half-configured -application should refuse to start rather than run in some posture nobody chose. +## 3. Fill in `.env` ```bash APP_TYPE=local @@ -74,82 +52,52 @@ MONGO_DATABASE=myapp MONGO_URI=mongodb://localhost:27017/?directConnection=true ``` -`APP_BASE_PATH` is `/` at the domain root, **not blank** — blank counts as unset and is a startup -failure like any other missing reference. Set it to `/api` when the application is served under a -prefix, and change the nginx healthcheck in `docker-compose.yml` to match. - -Then check it: - ```bash docker compose run --rm php vendor/bin/gf env # resolve it, or name the first thing missing docker compose run --rm php vendor/bin/gf env --list # every variable, and whether it is set ``` +`APP_BASE_PATH` is `/` at the domain root, **not blank** — blank counts as unset and is a startup +failure. Set it to `/api` when the application is served under a prefix, and change the nginx +healthcheck in `docker-compose.yml` to match. + ### The two variables that differ by side -`.env` is read by both the host `gf` CLI and the container, and two of its variables cannot hold one -value that is right for both. Those two are **pinned in `docker-compose.yml`**, where `environment:` -overrides `env_file:`, so the value in `.env` is the host's and the container quietly gets its own: +`.env` is read by the host `gf` CLI *and* by the php container, and these two cannot hold one value +correct for both — so `docker-compose.yml` pins the container's value in `environment:`, which beats +`env_file:`, and `.env` carries the host's. ([Why](https://github.com/gcgov/framework/blob/main/readme/local-development.md#variables-whose-correct-value-depends-on-who-is-reading)) -| Variable | In `.env` (the host) | In the container | Why | -|---|---|---|---| -| `APP_JWT_KEY_PATH` | `srv/jwtCertificates/` | `/var/www/app/srv/jwtCertificates/` | Different filesystems, same directory through the bind mount. `gf cert:generate-auth` writes on the host. | -| `MONGO_URI` | `mongodb://localhost:27017/?directConnection=true` | `mongodb://mongodb:27017/?replicaSet=rs0` | Different networks. The host sees a published port; the container resolves the service name. | +| Variable | In `.env` (the host) | In the container | +|---|---|---| +| `APP_JWT_KEY_PATH` | `srv/jwtCertificates/` | `/var/www/app/srv/jwtCertificates/` — the same directory through the bind mount | +| `MONGO_URI` | `mongodb://localhost:27017/?directConnection=true` | `mongodb://mongodb:27017/?replicaSet=rs0` | -`directConnection=true` is not optional from the host. The replica set advertises its one member as -`mongodb:27017` — a name only the compose network resolves — so without it the driver discovers that -member and then cannot reach it. In a Zone, `APP_JWT_KEY_PATH` is a mounted secret directory and -`MONGO_URI` comes from a file; see `DOCKER.md`. +`directConnection=true` is not optional from the host: the replica set advertises its member as +`mongodb:27017`, a name only the compose network resolves. In a Zone both variables come from +provisioned files instead — see `DOCKER.md`. ---- - -## 5. Start it +## 4. Start it ```bash docker compose up --build -``` - -Three containers come up: **nginx** on 8080, **php** (PHP-FPM), and **mongodb**. Mongo takes a few -seconds longer than it used to — its healthcheck initiates the replica set, and php waits for it. - -```bash -curl -s localhost:8080/health # {"status":"ok","version":"unknown"} +curl -s localhost:8080/health # liveness, no I/O curl -s localhost:8080/health/ready # every dependency, one line each ``` -The two are deliberately different. `/health` is liveness and does no I/O, so a database blip cannot -restart every container. `/health/ready` is readiness: it pings each configured database and, when -`services.auth` is enabled, checks that the key directory holds usable signing keys. It answers -`503` with the failing check named, which is a much better first stop than the application logs: +Three containers: **nginx** on 8080, **php**, and **mongodb**. Mongo takes a few seconds longer than +a plain image would — its healthcheck initiates the replica set, and php waits for it. ```json { "status": "ok", "version": "unknown", "checks": { "mongo:myapp": "ok", "jwtKeys": "ok" } } ``` ---- - -## 6. Why MongoDB is a replica set +**Why a replica set:** every write the framework makes runs in a transaction, which MongoDB offers +only on a replica set or a sharded cluster — so a standalone serves every read and fails every +write. That is the failure this stack exists to prevent, and it is worth recognising on sight. +([Detail](https://github.com/gcgov/framework/blob/main/readme/local-development.md#1-the-database-must-be-a-replica-set) · +[ADR](https://github.com/gcgov/framework/blob/main/docs/adr/0008-writes-are-transactional-so-mongodb-is-a-replica-set.md)) -`docker-compose.yml` starts mongo with `--replSet rs0` and initiates a single-member set. That is a -correctness requirement, not production fidelity. - -Every write the framework makes runs in a transaction — `save()` alone is several writes (the -document, its auto-increment counters, and the embedded copies pushed into other collections), and a -half-applied save is a corrupt denormalisation rather than a failed request. MongoDB offers -transactions only on a replica set or a sharded cluster. - -A standalone `mongod` therefore serves every **read** perfectly and fails every **write** with: - -``` -Transaction numbers are only allowed on a replica set member or mongos -``` - -which is exactly the shape of bug that survives a smoke test: the list endpoints work and only -saving fails. - ---- - -## 7. Create the first user +## 5. Create the Bootstrap User ```bash docker compose exec php vendor/bin/gf user:create \ @@ -157,34 +105,18 @@ docker compose exec php vendor/bin/gf user:create \ --roles="User.Read,User.Write,Widget.Read,Widget.Write" ``` -It prints a generated password once — pass `--password=…` if you would rather choose it. - -You cannot skip this, and there is no way around it from outside. `config.json` enables -`services.auth`, whose `blockNewUsers` defaults to true, so only users already in the database may -sign in. Every `/user` route requires a caller already holding `User.Write`. Nothing can -authenticate, so nothing can create the first user. Inserting the document with `mongosh` does not -help either: the user model hashes the password as it writes, so a hand-written document has no -password anyone can sign in with. `gf user:create` saves through that same model, which is what -makes the account usable. +It prints a generated password once; pass `--password=…` to choose your own. You cannot skip this +and there is no way in from outside — `blockNewUsers` defaults true, every `/user` route needs +`User.Write`, and a hand-written `mongosh` document has no usable password. +([Why](https://github.com/gcgov/framework/blob/main/readme/local-development.md#4-the-bootstrap-user)) -Roles are plain strings that routes compare against — nothing validates them. Give the first user -whatever its routes name in `requiredRoles` (this template ships `Widget.Read` / `Widget.Write` in -`app/constants.php`), plus `User.Read` and `User.Write` to administer other users through -`services.userCrud`. +This application's roles are in `app/constants.php`. Adding one later is the same command with +`--force`, which leaves the password alone. -Adding a role later is the same command with `--force`, which updates in place and leaves the -password alone: +## 6. Sign in and call a route -```bash -docker compose exec php vendor/bin/gf user:create --email=dev@example.test --force --roles="User.Read,Widget.Read" -``` - ---- - -## 8. Sign in and call a route - -Every route this template ships requires authentication and a role, so you need a token. Take -`client_id` from `app.guid` in `config.json` — the value `gf init` minted. +Every route this template ships requires authentication and a role. `client_id` is `app.guid` from +`config.json`. ```bash CLIENT_ID=$(python3 -c "import json;print(json.load(open('config.json'))['app']['guid'])") @@ -195,75 +127,46 @@ TOKEN=$(curl -s -X POST localhost:8080/auth/authorize \ | python3 -c "import json,sys;print(json.load(sys.stdin)['access_token'])") curl -s localhost:8080/widgets -H "Authorization: Bearer $TOKEN" -``` -`grant_type=password` and `scope=login` are both checked exactly, and `client_id` must equal -`app.guid` — a mismatch is a 401 reading "Invalid client id", not a hint that the guid is wrong. - -Then write something, which is the step that proves the replica set is doing its job: - -```bash curl -s -X POST localhost:8080/widgets/new \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"name":"First widget"}' ``` -Other routes worth knowing: `GET /.well-known/jwks.json` (the public keys, unauthenticated), -`GET /documentation.yaml` (OpenAPI, generated from the annotations in this application and the -framework), and `GET /user` (`services.userCrud`). +That last call is the one that proves the replica set is doing its job. `grant_type=password` and +`scope=login` are checked exactly, and `client_id` must equal `app.guid`. ---- +Also available: `GET /.well-known/jwks.json`, `GET /documentation.yaml`, `GET /user`. -## 9. The day-to-day loop +## 7. The day-to-day loop -**Edits are live.** The working tree is bind-mounted into the php container and -`docker/php/conf.d/dev.ini` turns opcache timestamp validation back on, so a saved file is picked up -on the next request. No restart, no rebuild. Rebuild only when `composer.json`, the `Dockerfile` or -anything under `docker/` changes. +Edits are live — the working tree is bind-mounted and `docker/php/conf.d/dev.ini` turns opcache +timestamp validation back on. Rebuild only when `composer.json`, the `Dockerfile` or `docker/` +changes. ```bash docker compose logs -f php # logs go to stderr, not logs/*.log -docker compose exec php vendor/bin/gf cli:list # the application's CLI routes +docker compose exec php vendor/bin/gf cli:list docker compose exec php vendor/bin/gf cli /cli/widgets -docker compose exec php composer ci # phpstan + phpunit, the same checks CI runs -docker compose exec php vendor/bin/gf env # after editing config.json -docker compose exec mongodb mongosh myapp # a shell on the database -``` - -Set `logging.lifecycle: true` in `config.json` to trace routing and the auth guard end to end when a -request is answered by something you did not expect. - -`docker compose down` stops the stack and keeps the data; `docker compose down -v` also drops the -Mongo volume, which is how you start over (the replica set re-initiates on the next boot, and you -create the first user again). - -### Adding a configuration variable - -Add the `%env(...)%` reference to `config.json`, then: - -```bash -docker compose run --rm php vendor/bin/gf env --init # appends only what .env lacks +docker compose exec php composer ci +docker compose exec php vendor/bin/gf env --init # after adding a %env() reference to config.json +docker compose exec mongodb mongosh myapp ``` -It leaves every value you have filled in, and every variable `config.json` knows nothing about, -alone. `--force` rewrites from `config.json` and discards both — you rarely want it. - ---- +`docker compose down` keeps the data; `down -v` drops the Mongo volume and starts over — the replica +set re-initiates on the next boot and you create the Bootstrap User again. ## Running without Docker -The stack is the supported path, but nothing requires it. Serve `www/` with any PHP 8.4 SAPI that -has `ext-mongodb`, point `MONGO_URI` at a MongoDB you run yourself, and use `vendor/bin/gf` directly. - -The one thing that is easy to get wrong: **that MongoDB must also be a replica set** (§6). A -`mongod` started with no arguments will not do. +Serve `www/` with any PHP 8.4 SAPI that has `ext-mongodb`, and use `vendor/bin/gf` directly. The +MongoDB you point it at must still be a replica set: ```bash mongod --replSet rs0 --dbpath /path/to/data mongosh --eval 'rs.initiate()' ``` -With mongo on the host under its own name, `directConnection=true` is unnecessary — set +With mongo under its own name on the host, drop `directConnection=true` and use `MONGO_URI=mongodb://localhost:27017/?replicaSet=rs0`. --- @@ -272,20 +175,22 @@ With mongo on the host under its own name, `directConnection=true` is unnecessar | Symptom | Cause | |---|---| -| `Transaction numbers are only allowed on a replica set member or mongos` | MongoDB is a standalone. Reads work, writes cannot. See §6. | -| A write hangs, then fails with a server-selection timeout | `MONGO_URI` names `replicaSet=rs0` from the host. The set advertises `mongodb:27017`, which the host cannot resolve — use `directConnection=true` instead (§4). | -| `configException` naming a variable, at startup | That `%env()` reference has no value. Every one is required and blank counts as unset. `gf env --list` shows the whole list. | -| `configException` about `APP_BASE_PATH`, which you did set | You set it to nothing. At the domain root it is `/`, not blank (§4). | -| `HTTP_PORT` / a CORS origin appears to do nothing | It is in `.env.local`. Docker compose interpolates only from `.env` (§2). | -| `/health/ready` returns 503 on `jwtKeys` | The key directory is empty or `APP_JWT_KEY_PATH` points at the wrong side (§4). Run `gf cert:generate-auth`. | -| `/health/ready` returns 503 on `mongo:*` | Mongo is not up or `MONGO_URI` is wrong. `docker compose ps` shows whether its healthcheck ever went green. | -| 401 on every application route | No token, or no user. Steps 7 and 8. | -| 401 "Invalid client id" | `client_id` does not equal `app.guid` in `config.json`. | -| 403 on a route that authenticated fine | The user lacks the role the route declares. Re-run `user:create --force --roles="…"`. | -| nginx exits immediately at startup | Two of `CORS_ORIGIN_APP` / `_FRONTEND` / `_SWAGGER` are equal. They become keys in an nginx `map`, and a duplicate key is fatal. | -| `docker compose` errors that `.env` is missing | Both env files are declared `required: false`, so this means a Compose too old to understand that (pre-2.24). Upgrade, or `touch .env .env.local` and carry on. | -| The framework refuses to boot over authenticated routes | `services.auth` is absent from `config.json` while routes declare `authentication: true`. That combination would serve them to anyone. | -| `composer install` complains about `ext-mongodb` on the host | Use `--ignore-platform-req=ext-mongodb`, never blanket `--ignore-platform-reqs` — that also discards the `config.platform.php` pin and locks packages the production image cannot install. | +| `Transaction numbers are only allowed on a replica set member or mongos` | MongoDB is a standalone. Reads work, writes cannot. §4. | +| **`gf init` fails with "Failed writing config.json"** — *suspected, on Linux hosts only* | The `dev` image runs as `www-data` (uid 33) and cannot write files a bind mount says you own. Docker Desktop masks this on macOS and Windows. Workaround: `docker compose run --rm --user "$(id -u):$(id -g)" php …`. Please report it if you hit this — it has not yet been confirmed on a real Linux host. | +| A write hangs, then a server-selection timeout | `MONGO_URI` names `replicaSet=rs0` from the host. Use `directConnection=true` instead. §3. | +| `configException` naming a variable, at startup | That Config Reference has no value. Every one is required and blank counts as unset. `gf env --list`. | +| `configException` about `APP_BASE_PATH`, which you did set | You set it to nothing. At the domain root it is `/`, not blank. §3. | +| `HTTP_PORT` / a CORS origin appears to do nothing | It is in `.env.local`. Compose interpolates only from `.env`. §2. | +| `/health/ready` 503 on `jwtKeys` | The key directory is empty, or `APP_JWT_KEY_PATH` points at the wrong side. §3. Run `gf cert:generate-auth`. | +| `/health/ready` 503 on `mongo:*` | Mongo is not up or `MONGO_URI` is wrong. `docker compose ps` shows whether its healthcheck went green. | +| 401 on every application route | No token, or no user. §5 and §6. | +| 401 "Invalid client id" | `client_id` does not equal `app.guid`. | +| 403 after authenticating fine | The user lacks the role the route declares. `user:create --force --roles="…"`. | +| Sign-in returns an MFA challenge and a token with no roles | `settings.forceMfaForPasswordUsers` is on. Complete `POST /auth/verifyMfaSecret` then `POST /auth/verifyMfaCode`. | +| nginx exits immediately | Two of the three `CORS_ORIGIN_*` are equal. They become keys in an nginx `map`; a duplicate key is fatal. | +| `docker compose` errors that `.env` is missing | Both env files are `required: false`, so this is a Compose older than 2.24. Upgrade, or `touch .env .env.local`. | +| The framework refuses to boot over authenticated routes | `services.auth` is absent while routes declare `authentication: true`. | +| `composer install` complains about `ext-mongodb` on the host | Use `--ignore-platform-req=ext-mongodb`, never blanket `--ignore-platform-reqs` — that also discards the `config.platform.php` pin. | `db/local-createuser.js` creates a **MongoDB account**, not an application user. The compose stack runs `mongod` without authentication, so you do not need it locally. @@ -294,9 +199,8 @@ runs `mongod` without authentication, so you do not need it locally. ## Where to look next -- **[README.md](README.md)** — the short bootstrap, and how Framework Services are enabled. -- **[DOCKER.md](DOCKER.md)** — the two images, secrets as provisioned files, TLS, and how a Release - reaches a host. -- **[the gf CLI](https://github.com/gcgov/framework/blob/main/readme/gf.md)** · - **[environment variables](https://github.com/gcgov/framework/blob/main/readme/environment-variables.md)** · +- **[The framework's local-development guide](https://github.com/gcgov/framework/blob/main/readme/local-development.md)** — the rules behind + every step above, and the version that stays current. +- **[README.md](README.md)** · **[DOCKER.md](DOCKER.md)** +- **[the gf CLI](https://github.com/gcgov/framework/blob/main/readme/gf.md)** · **[environment variables](https://github.com/gcgov/framework/blob/main/readme/environment-variables.md)** · **[MongoDB](https://github.com/gcgov/framework/blob/main/readme/mongodb.md)** diff --git a/README.md b/README.md index 6aa63a8..d1d305c 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,10 @@ boot rather than serve routes that look protected and are not. ## Documentation - **[LOCAL-DEVELOPMENT.md](LOCAL-DEVELOPMENT.md)** — running this application on a development - computer, end to end: bootstrap, configuration, the first user, a signed-in request, and what - each failure means. + computer: the commands for this stack, end to end, and what each failure means. The rules behind + them live in the framework, at + [readme/local-development.md](https://github.com/gcgov/framework/blob/main/readme/local-development.md) — that copy stays current, this one + is frozen at Scaffold time. - **[DOCKER.md](DOCKER.md)** — the images, secrets as provisioned files, health checks, TLS, and how a Release reaches a host. - The `gf` CLI: `vendor/bin/gf` (`gf init`, `gf env`, `gf cli`, `gf db:run`, `gf migrate`, …). diff --git a/composer.json b/composer.json index e2c4cf8..5c52b17 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "require-dev": { "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^11.5", - "jetbrains/phpstorm-attributes": "^1.0" + "jetbrains/phpstorm-attributes": "^1.0", + "symfony/yaml": "^7.1 || ^8.0" }, "config": { "platform": { diff --git a/composer.lock b/composer.lock index 84458cb..627b260 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "90155cc972ff2aa175decab94235dcec", + "content-hash": "0521117a478e6293806c6270f1090ffd", "packages": [ { "name": "andrewsauder/json-deserialize", From e9fd285934587359b9e24d53f03ec332e845712c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 17:26:26 +0000 Subject: [PATCH 16/17] Restore the development database from a backup in db/backup/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A developer working on an application with years of documents in it wants a copy of real data, and until now this stack offered no way to load one. `gf db:restore` was the v6 answer and was removed in v7, because it put production credentials on every workstation. A dump file is the replacement, and this is the half that runs it: mongodump --uri="" --db=myapp --out=db/backup docker compose run --rm mongo-restore `mongo-restore` is a one-shot container from the same mongo:7 image as the database, held out of `docker compose up` by a profile, so a restore never happens as a side effect of a boot. It restores db/backup/{DatabaseName} into MONGO_DATABASE with --drop, so it replaces every collection the backup holds and leaves anything else alone. It builds its connection from the compose service name instead of reading MONGO_URI. The only database a restore can write to is therefore the one beside it, whatever .env happens to hold — which is the property that made db:restore worth removing in the first place. db/backup/ is git-ignored and excluded from the build context: the php stage does `COPY . /var/www/app`, so a dump left there would otherwise ship inside a Release. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Rw19sb82Jy2GQTLdd7N8f7 --- .dockerignore | 4 ++ .env.example | 7 +++ LOCAL-DEVELOPMENT.md | 52 +++++++++++++++++- README.md | 5 ++ db/backup/.gitignore | 6 +++ db/backup/README.md | 15 ++++++ docker-compose.yml | 30 +++++++++++ docker/mongodb/restore.sh | 110 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 db/backup/.gitignore create mode 100644 db/backup/README.md create mode 100755 docker/mongodb/restore.sh diff --git a/.dockerignore b/.dockerignore index ab41934..68aa3b3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,3 +15,7 @@ srv/tmp/**/* srv/profile/* logs/* !**/.gitignore + +# A restore source is a copy of a database, and the php stage does `COPY . /var/www/app`. +# Keep backups out of the build context entirely — the local stack bind-mounts them. +db/backup/* diff --git a/.env.example b/.env.example index f70fbbf..2a36822 100644 --- a/.env.example +++ b/.env.example @@ -40,3 +40,10 @@ CORS_ORIGIN_SWAGGER=http://localhost:8081 # the driver discovers that member and then cannot reach it. # # MONGO_URI=mongodb://localhost:27017/?directConnection=true + +# ---- Restoring the development database from db/backup/ (optional) ---- +# `docker compose run --rm mongo-restore` restores db/backup/${MONGO_DATABASE} into the +# development database. Set this when the backup directory is named for a different +# database — the dump is restored into MONGO_DATABASE either way. +# +# MONGO_RESTORE_FROM=SomeOtherDatabase diff --git a/LOCAL-DEVELOPMENT.md b/LOCAL-DEVELOPMENT.md index 76d6273..ee5a5be 100644 --- a/LOCAL-DEVELOPMENT.md +++ b/LOCAL-DEVELOPMENT.md @@ -138,7 +138,54 @@ That last call is the one that proves the replica set is doing its job. `grant_t Also available: `GET /.well-known/jwks.json`, `GET /documentation.yaml`, `GET /user`. -## 7. The day-to-day loop +## 7. Restore a backup into the development database + +A `mongodump` of another database goes in `db/backup/`, in a directory named for the database it +came from. One command restores it: + +```bash +mongodump --uri="" --db=myapp --out=db/backup # writes db/backup/myapp/ +docker compose run --rm mongo-restore +``` + +`mongo-restore` is a one-shot container from the same `mongo:7` image as the database, so your host +needs no MongoDB tools at all. A compose profile keeps it out of `docker compose up`, so a restore +is never a side effect of a boot. + +| | | +|---|---| +| Reads | `db/backup/${MONGO_DATABASE}`, mounted read-only | +| Writes | the `mongodb` service in this stack, and nothing else | +| Replaces | every collection the backup holds; a collection it does not hold stays as it is | +| Restores into | `MONGO_DATABASE`, whatever the backup directory is called | + +That third row is worth reading twice: a restore is not a reset. `docker compose down -v` is the +reset. + +Point it at a directory named for a different database with `MONGO_RESTORE_FROM`. Anything after +the service name reaches `mongorestore` unchanged: + +```bash +docker compose run --rm -e MONGO_RESTORE_FROM=myapp-prod mongo-restore +docker compose run --rm mongo-restore --numParallelCollections=1 +``` + +**The connection is not `MONGO_URI`.** `docker/mongodb/restore.sh` builds it from the compose +service name, so the only database a restore can write to is the one beside it. That is deliberate. +A workstation that could reach another Environment's database is what retired `gf db:restore` in +v7, and the backup file is the seam that replaced it. + +**A restored account keeps its own password hash**, so you can sign in as a user whose password you +already know — and as nobody else. Set a password on an account you want to use: + +```bash +docker compose exec php vendor/bin/gf user:create --force --email=dev@example.test --roles="…" +``` + +`db/backup/` is git-ignored, and a backup holds whatever the source database holds. Treat the +directory the way you treat that database. + +## 8. The day-to-day loop Edits are live — the working tree is bind-mounted and `docker/php/conf.d/dev.ini` turns opcache timestamp validation back on. Rebuild only when `composer.json`, the `Dockerfile` or `docker/` @@ -184,6 +231,9 @@ With mongo under its own name on the host, drop `directConnection=true` and use | `/health/ready` 503 on `jwtKeys` | The key directory is empty, or `APP_JWT_KEY_PATH` points at the wrong side. §3. Run `gf cert:generate-auth`. | | `/health/ready` 503 on `mongo:*` | Mongo is not up or `MONGO_URI` is wrong. `docker compose ps` shows whether its healthcheck went green. | | 401 on every application route | No token, or no user. §5 and §6. | +| `mongo-restore` reports "No backup at db/backup/…" | The directory is named for the database the dump came from, and `mongodump --out=db/backup` names it for you. §7. | +| `mongorestore` does not know what to do with file `.gitignore` | Expected. It reads `db/backup` as a dump root, and that file is what keeps the directory in git. §7. | +| Sign-in fails for a user the restore brought in | A restore does not hand you anyone's password, only their hash. Sign in as an account you know, or `gf user:create --force`. §7. | | 401 "Invalid client id" | `client_id` does not equal `app.guid`. | | 403 after authenticating fine | The user lacks the role the route declares. `user:create --force --roles="…"`. | | Sign-in returns an MFA challenge and a token with no roles | `settings.forceMfaForPasswordUsers` is on. Complete `POST /auth/verifyMfaSecret` then `POST /auth/verifyMfaCode`. | diff --git a/README.md b/README.md index d1d305c..0ba6a01 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,11 @@ container images — nginx and PHP-FPM — and keeps every secret out of the com 5. Try the `widget` module, then write your own models, controllers, and routes. +To work against real data rather than an empty database, put a `mongodump` in +`db/backup/{DatabaseName}/` and run `docker compose run --rm mongo-restore`. It restores over the +compose network, so your host needs no MongoDB tools, and it can write to no database but this +stack's. + **[LOCAL-DEVELOPMENT.md](LOCAL-DEVELOPMENT.md) is the full walkthrough**, through signing in and writing a document, with a troubleshooting table. diff --git a/db/backup/.gitignore b/db/backup/.gitignore new file mode 100644 index 0000000..06dea2d --- /dev/null +++ b/db/backup/.gitignore @@ -0,0 +1,6 @@ +# Backups are real data. Nothing in here is ever committed. +* + +# But not these files... +!.gitignore +!README.md diff --git a/db/backup/README.md b/db/backup/README.md new file mode 100644 index 0000000..d58cff5 --- /dev/null +++ b/db/backup/README.md @@ -0,0 +1,15 @@ +# db/backup + +A `mongodump` of a database goes here, in a directory named for the database it came +from, and `docker compose run --rm mongo-restore` restores it into the development +database: + +```bash +mongodump --uri="" --db=myapp --out=db/backup # writes db/backup/myapp/ +docker compose run --rm mongo-restore +``` + +The restore is described in [LOCAL-DEVELOPMENT.md](../../LOCAL-DEVELOPMENT.md). + +Everything in this directory is git-ignored, and it stays that way. A backup carries the +same data the database carries — treat the directory as you treat the database. diff --git a/docker-compose.yml b/docker-compose.yml index 5499e5c..9791cfc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -116,5 +116,35 @@ services: start_period: 40s retries: 12 + # Restores the development database from a mongodump backup in db/backup/ — a one-shot + # container, run when you want it: + # + # docker compose run --rm mongo-restore + # + # The profile keeps it out of `docker compose up`, so a restore is never something a + # boot does to you; `docker compose run` enables the profile of the service it names, + # so there is nothing to activate by hand. LOCAL-DEVELOPMENT.md §7 has the walkthrough. + mongo-restore: + # The same image as the database it restores into, so the tools match the server. + image: mongo:7 + profiles: [ "restore" ] + env_file: + # MONGO_DATABASE — which database to restore into. The connection itself is NOT + # read from here: restore.sh builds it from the compose service name, so this + # container can reach no database but the one beside it. + - path: .env + required: false + - path: .env.local + required: false + entrypoint: [ "bash", "/usr/local/bin/restore.sh" ] + volumes: + - ./docker/mongodb/restore.sh:/usr/local/bin/restore.sh:ro + # Read-only: this container restores backups and must never write one. + - ./db/backup:/backup:ro + depends_on: + mongodb: + # A restore is a write, and writes need the primary the healthcheck establishes. + condition: service_healthy + volumes: mongo-data: diff --git a/docker/mongodb/restore.sh b/docker/mongodb/restore.sh new file mode 100755 index 0000000..5e3f5d8 --- /dev/null +++ b/docker/mongodb/restore.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# +# Restore the development database from a mongodump backup in db/backup/. +# +# It runs in the same mongo image as the database it restores into, beside the dev +# replica set, so a developer needs no MongoDB tools on the host. +# +# It reaches that database by compose service name, built here rather than read from +# MONGO_URI. The only database this script can write to is therefore the one in this +# stack, whatever `.env` happens to hold — which is the point. A workstation restore +# that could be aimed at another Environment is what retired `gf db:restore` in v7. +# +# docker compose run --rm mongo-restore # db/backup/$MONGO_DATABASE +# docker compose run --rm -e MONGO_RESTORE_FROM=other mongo-restore # a directory under another name +# docker compose run --rm mongo-restore --numParallelCollections=1 # extra mongorestore arguments +# +# Arguments after the service name reach mongorestore unchanged. + +set -euo pipefail +shopt -s nullglob + +# The dump root, as docker-compose.yml mounts db/backup. Each subdirectory in it is one +# database named for that database, which is the layout `mongodump --out` writes. +readonly BACKUP_ROOT=/backup + +# The compose service that runs mongod, and the connection to it. 27017 is the port +# inside the compose network; MONGO_PORT publishes it to the host and does not apply +# here. replicaSet=rs0 for the same reason every other client uses it — see the mongodb +# service in docker-compose.yml. +readonly MONGO_SERVICE="${MONGO_SERVICE:-mongodb}" +readonly MONGO_URI="mongodb://${MONGO_SERVICE}:27017/?replicaSet=rs0" + +log() { printf '==> %s\n' "$*"; } +die() { printf 'mongo-restore: %s\n' "$*" >&2; exit 1; } + + +# What db/backup holds right now, so an error answers the question it raises. +describe_backup_root() { + local directories=( "$BACKUP_ROOT"/*/ ) + local names=() + + local directory + for directory in "${directories[@]}"; do + directory="${directory%/}" + names+=( "${directory##*/}" ) + done + + if [[ ${#names[@]} -eq 0 ]]; then + printf 'db/backup holds no database directories.' + return + fi + + printf 'db/backup holds: %s.' "${names[*]}" +} + + +main() { + local target_database="${MONGO_DATABASE:-}" + if [[ -z $target_database ]]; then + die 'MONGO_DATABASE is not set. It is one of the variables `gf env --init` appends to .env; fill it in and run this again.' + fi + + # Which directory to read. It defaults to the database being restored into, because a + # dump of that database is what a developer has nine times out of ten. It differs when + # the backup came from an Environment that names the database something else. + local source_database="${MONGO_RESTORE_FROM:-$target_database}" + local source_directory="$BACKUP_ROOT/$source_database" + + if [[ ! -d $source_directory ]]; then + die "No backup at db/backup/$source_database. $(describe_backup_root) Produce one with: mongodump --uri=\"\" --db=$source_database --out=db/backup" + fi + + local dump_files=( "$source_directory"/*.bson "$source_directory"/*.bson.gz ) + if [[ ${#dump_files[@]} -eq 0 ]]; then + die "db/backup/$source_database holds no .bson files, so it is not a mongodump of a database. Produce one with: mongodump --uri=\"\" --db=$source_database --out=db/backup" + fi + + # --drop replaces each collection the backup carries. Collections the backup does not + # carry are left alone: this is a restore of what was dumped, not a reset of the + # database. `docker compose down -v` is the reset. + # + # --nsInclude confines the run to one database even though mongorestore is pointed at + # the whole dump root, which is what lets several databases sit in db/backup side by + # side. Pointing it at the root rather than at the database directory is also what + # keeps the namespaces coming from the directory names. mongorestore logs "don't know + # what to do with file .gitignore, skipping..." for the file that keeps db/backup in + # git — it is reading the root, and that line is expected. + local restore_arguments=( --drop --nsInclude="$source_database.*" ) + + local gzipped_files=( "$source_directory"/*.bson.gz ) + if [[ ${#gzipped_files[@]} -gt 0 ]]; then + restore_arguments+=( --gzip ) + fi + + if [[ $source_database != "$target_database" ]]; then + restore_arguments+=( --nsFrom="$source_database.*" --nsTo="$target_database.*" ) + fi + + log "Restoring db/backup/$source_database into $target_database on $MONGO_SERVICE" + log 'Every collection the backup holds is dropped and rewritten.' + + mongorestore --uri="$MONGO_URI" "${restore_arguments[@]}" "$@" "$BACKUP_ROOT" + + log "Restored $target_database." + log 'A restored account keeps its own password hash, so you sign in as a user whose password you know.' + log 'For any other account: docker compose exec php vendor/bin/gf user:create --force --email=… --roles="…"' +} + + +main "$@" From 09ce84e3c8fa84f283dadf9edc5daa951feb67db Mon Sep 17 00:00:00 2001 From: gcgov deploy Date: Tue, 8 Sep 2026 11:57:43 +0000 Subject: [PATCH 17/17] docs: correct SOPS backend wording and ADR citations The v7 review moved the operational ADRs into gcgov/deploy and renumbered them. Point DOCKER.md at the moved secrets ADR (gcgov/deploy 0001), replace the stale "GCP KMS key per Zone" wording with Azure Key Vault (one vault per Zone, gcgov/deploy 0004), and update the replica-set ADR link in LOCAL-DEVELOPMENT.md to framework's renumbered 0004. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NzByhoDp7hsD39aoThJ9rv --- DOCKER.md | 10 ++++++---- LOCAL-DEVELOPMENT.md | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/DOCKER.md b/DOCKER.md index f32d0f7..6979e44 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -86,10 +86,12 @@ Why files rather than environment variables: a value in the environment is visib `docker inspect`, readable from `/proc//environ`, and inherited by every child process. A mounted file is none of those things. -**Where the values come from.** Encrypted with SOPS in `gcgov/deploy`, against a GCP KMS key per -Zone plus an offline break-glass key. An operator decrypts on their own workstation and provisions -the files to the host — a step deliberately separate from deploying, so no host holds a decryption -key and CI never sees a secret. See `docs/adr/0003-secrets-never-decrypt-in-ci-or-on-hosts.md`. +**Where the values come from.** Encrypted with SOPS in `gcgov/deploy`, against an Azure Key Vault +key — one vault per Zone — plus an offline break-glass key. An operator decrypts on their own +workstation and provisions the files to the host — a step deliberately separate from deploying, so +no host holds a decryption key and CI never sees a secret. See +`gcgov/deploy docs/adr/0001-secrets-never-decrypt-in-ci-or-on-hosts.md`, and +`gcgov/deploy docs/adr/0004-azure-key-vault-per-zone-for-deployment-secrets.md` for why Azure Key Vault. ### The two variables that differ by side diff --git a/LOCAL-DEVELOPMENT.md b/LOCAL-DEVELOPMENT.md index ee5a5be..ec12232 100644 --- a/LOCAL-DEVELOPMENT.md +++ b/LOCAL-DEVELOPMENT.md @@ -95,7 +95,7 @@ a plain image would — its healthcheck initiates the replica set, and php waits only on a replica set or a sharded cluster — so a standalone serves every read and fails every write. That is the failure this stack exists to prevent, and it is worth recognising on sight. ([Detail](https://github.com/gcgov/framework/blob/main/readme/local-development.md#1-the-database-must-be-a-replica-set) · -[ADR](https://github.com/gcgov/framework/blob/main/docs/adr/0008-writes-are-transactional-so-mongodb-is-a-replica-set.md)) +[ADR](https://github.com/gcgov/framework/blob/main/docs/adr/0004-writes-are-transactional-so-mongodb-is-a-replica-set.md)) ## 5. Create the Bootstrap User