Compare commits

...

114 Commits

Author SHA1 Message Date
Alliballibaba
fd40e62cb8 cleanup. 2025-10-26 21:25:19 +01:00
Alliballibaba
acf423f9f0 adds comments. 2025-10-26 20:57:00 +01:00
Alliballibaba
02c27fc2a9 Fixes library tests. 2025-10-26 20:56:08 +01:00
Alliballibaba
d54f736db7 simplifications 2025-10-26 20:52:19 +01:00
Alliballibaba
694b6188c0 Formatting. 2025-10-26 20:39:31 +01:00
Alliballibaba
b275cd58f8 Merge branch 'main' into feat/task-threads
# Conflicts:
#	types_test.go
2025-10-26 20:24:42 +01:00
Alliballibaba
3f63a4d137 Combines frankenphp_handle_task() and frankenphp_handle_request(). 2025-10-26 20:21:19 +01:00
Michal Kleiner
fb1f46808e fix: typo in method name in example usage (#1935) 2025-10-22 09:14:36 +02:00
Florent Drousset
7f64673495 Fix small typo (#1934) 2025-10-21 17:50:00 +02:00
Kévin Dunglas
f7756717b5 feat: allow creating strongly typed slices and maps from PHP values with type utilities (#1933)
* feat: use generics in type functions for better type support

* various improvements

* better docs

* update docs
2025-10-21 11:20:54 +02:00
Damien Calesse
9aee496b96 Add patchelf installation in static-gnu Dockerfile (#1899)
* Add patchelf installation in static-gnu Dockerfile

* reduce static-builder-gnu.Dockerfile layers

---------

Co-authored-by: henderkes <m@pyc.ac>
2025-10-20 18:08:40 +02:00
Alexander Stecher
45823c51b2 fix: catches panic on invalid status code (#1920) 2025-10-16 11:35:35 +02:00
Kévin Dunglas
f8ea48c3b1 chore(caddy): better error handling 2025-10-15 11:12:34 +02:00
Laury S.
1fbd619597 fix: remove BOM on config fr doc file (#1924) 2025-10-14 17:50:51 +02:00
Kévin Dunglas
d52ce94341 docs: improve Mercure documentation and various other parts 2025-10-14 14:32:38 +02:00
Kévin Dunglas
b749f52ae5 chore: simplify string using backticks
# Conflicts:
#	internal/extgen/classparser.go
#	internal/extgen/gofile_test.go
2025-10-14 14:09:17 +02:00
Alliballibaba
e801a49f3f Prevents test race condition. 2025-10-11 23:36:57 +02:00
Alliballibaba
12b6aaeac6 Prevents test race condition. 2025-10-11 23:36:48 +02:00
Alliballibaba
a5a9351020 Adds sleep back in. 2025-10-11 23:29:13 +02:00
Alliballibaba
03d886d32e Removes more code. 2025-10-11 23:23:58 +02:00
Alliballibaba
8144a06ebb clang-format. 2025-10-11 22:55:31 +02:00
Alliballibaba
8a5d489c85 Returns error messages directly to PHP. 2025-10-11 22:53:44 +02:00
Alliballibaba
6b9c236d9a Removes docs (still experimental) 2025-10-11 22:41:15 +02:00
Alliballibaba
02a3b3f56d Fixes build error. 2025-10-11 22:38:51 +02:00
Alliballibaba
117b415e8b Merge branch 'main' into feat/task-threads 2025-10-11 22:37:20 +02:00
Alliballibaba
7565628516 Allows setting queue len. 2025-10-11 22:21:02 +02:00
Alliballibaba
05bf065a1b Uses goValue and phpValue for task dispatching. 2025-10-11 22:13:03 +02:00
Alliballibaba
58d1761fe8 Simplifies by removing args. 2025-10-11 21:11:14 +02:00
Kévin Dunglas
e917ab7974 fix: callback parameters handling in worker extensions 2025-10-09 15:42:29 +02:00
Kévin Dunglas
5514491a18 feat(extgen): support for mixed type (#1913)
* feat(extgent): support for mixed type

* refactor: use unsafe.Pointer

* Revert "refactor: use unsafe.Pointer"

This reverts commit 8a0b9c1beb.

* fix docs

* fix docs

* cleanup template

* fix template

* fix tests
2025-10-09 14:10:45 +02:00
Kévin Dunglas
c42d287138 refactor: extension worker (#1910)
* refactor: extension worker

* feat: optional HTTP request

* allow passing unsafe.Pointer to the extension callback

* lint

* simplify
2025-10-09 14:10:09 +02:00
SpencerMalone
1f6f768c97 fix: release but don't free CLI streams when executing cli scripts (#1906)
* Bring upstream commit 0a4a55fd44 into cli_register_file_handles to release but not free stdout/in/err.

Fixes being unable to log to stdout or error after using frankenphp.ExecutePHPCode

* chore: clang-format

---------

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
2025-10-08 17:07:54 +02:00
Kévin Dunglas
a4596b7767 ci: fix Biome linter issue (#1911) 2025-10-08 08:38:00 +02:00
Alliballibaba
b23f3f833e Foxes pinning. 2025-10-07 22:20:27 +02:00
Alliballibaba
268d294313 Fixes pinning. 2025-10-07 21:29:29 +02:00
Alliballibaba
77fec2b4a7 Adds docs. 2025-10-07 21:23:40 +02:00
Alliballibaba
df7e77d3a6 Adjusts naming. 2025-10-07 21:08:16 +02:00
Alliballibaba
0dff2a2790 Adjusts naming. 2025-10-07 21:04:32 +02:00
Alliballibaba
0c0a0cb19b Adds more tests. 2025-10-07 20:39:54 +02:00
Rob Landers
fa3a7032a4 refactor: make Worker an embeddable struct (#1884)
* make WorkerExtension embeddable

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* change names

Signed-off-by: Robert Landers <landers.robert@gmail.com>

---------

Signed-off-by: Robert Landers <landers.robert@gmail.com>
2025-10-07 16:56:56 +02:00
Filippo Tessarotto
ab1ec71d24 docs(worker): Prefer try-catch over set_exception_handler (#1897) 2025-10-06 09:01:49 +02:00
Alliballibaba
99bb21f646 Allows setting args with task-workers. 2025-10-05 11:33:19 +02:00
Alliballibaba
b8addd76e7 Merge branch 'main' into feat/task-threads 2025-10-01 22:56:25 +02:00
dependabot[bot]
219a5407ff chore: bump the go-modules group with 2 updates (#1903) 2025-09-29 17:56:28 +02:00
Marc
76511c2e19 fix(packages): frankenphp trust failing because admin API isn't started #1846 (#1870) 2025-09-27 15:50:29 +02:00
Alexandre Daubois
7668a27d4b chore: bump /caddy sub-group deps (#1863) 2025-09-23 10:26:13 +02:00
Alliballibaba
639817ebef Merge branch 'main' into feat/task-threads
# Conflicts:
#	worker.go
2025-09-22 21:06:16 +02:00
Artur Melanchyk
e4c1801c25 fix: added missing decrement for the "ready" WaitGroup counter (#1890)
Co-authored-by: Artur Melanchyk <13834276+arturmelanchyk@users.noreply.github.com>
2025-09-22 19:57:01 +02:00
Alliballibaba
9c36ed4624 Merge branch 'main' into feat/task-threads 2025-09-20 22:28:10 +02:00
Alliballibaba
83c7a88ec7 Cleanup. 2025-09-20 22:27:30 +02:00
Alliballibaba
c16665ae78 Allows direct execution on tasks and correctly frees in types_test. 2025-09-18 23:35:26 +02:00
Alliballibaba
0d43efff35 Adjusts name. 2025-09-18 22:35:57 +02:00
Alliballibaba
eb2b575cbc Fixes thread attaching. 2025-09-18 22:35:08 +02:00
Alliballibaba
f5e6a045b9 Fixes small issues. 2025-09-18 22:28:54 +02:00
Alliballibaba
7a2bb89c9b Waits briefly to ensure logs are flushed 2025-09-18 22:09:18 +02:00
Alliballibaba
2297616552 Adjusts queue len. 2025-09-18 21:53:14 +02:00
Alliballibaba
7982b3af59 Adds max queue len and more tests. 2025-09-18 21:50:01 +02:00
Rob Landers
52df300f86 feat: custom workers initial support (#1795)
* create a simple thread framework

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* add tests

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* fix comment

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* remove mention of an old function that no longer exists

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* simplify providing a request

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* satisfy linter

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* add error handling and handle shutdowns

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* add tests

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* pipes are tied to workers, not threads

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* fix test

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* add a way to detect when a request is completed

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* we never shutdown workers or remove them, so we do not need this

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* add more comments

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* Simplify modular threads (#1874)

* Simplify

* remove unused variable

* log thread index

* feat: allow passing parameters to the PHP callback and accessing its return value (#1881)

* fix formatting

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* fix test compilation

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* fix segfaults

Signed-off-by: Robert Landers <landers.robert@gmail.com>

* Update frankenphp.c

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>

---------

Signed-off-by: Robert Landers <landers.robert@gmail.com>
Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
2025-09-18 09:21:49 +02:00
Alliballibaba
6c3e1d6af6 Fixes race. 2025-09-17 23:04:37 +02:00
Alliballibaba
2387a9d74c Allows prepared env. 2025-09-17 22:53:03 +02:00
Alliballibaba
7438edd676 Adds direct dispatching test. 2025-09-17 22:39:59 +02:00
Alliballibaba
9c4cf7e2d8 Throws on task handling failure. 2025-09-17 22:10:39 +02:00
Alliballibaba
7f52e2d116 Allows watching with threads. 2025-09-17 21:49:10 +02:00
Alliballibaba
f43c8bb1bf Removes log. 2025-09-16 20:50:51 +02:00
Florent Drousset
fe7aa2cae4 docs: fix small typo in x-sendfile.md (#1882)
Just fixing a small typo (double "à") that I've seen in the doc.
2025-09-16 20:02:43 +02:00
Alliballibaba
6e79380ddc formatting 2025-09-16 18:25:07 +02:00
Alliballibaba
a102da8171 Adds tests and optimizations. 2025-09-16 18:24:33 +02:00
Kévin Dunglas
7754abcbd0 fix: PHPValue() and GoValue() types (#1879)
* fix: PHPValue() return type

* fix: GoValue() argument type
2025-09-15 17:04:06 +02:00
Kévin Dunglas
52a0be5728 feat(ext): expose GoValue() and PHPValue() functions (#1877)
* feat(ext): expose a GoValue function

* GoValue()
2025-09-15 16:25:11 +02:00
Alliballibaba
a6999209d3 test 2025-09-15 00:21:58 +02:00
Alliballibaba
65e11372c1 test 2025-09-14 23:50:48 +02:00
Alliballibaba
8ad2351abc FOrmatting. 2025-09-14 23:46:53 +02:00
Alliballibaba
abdb279bd3 Initial testing. 2025-09-14 23:45:40 +02:00
Alliballibaba
d852f1cd4b test123 2025-09-09 22:26:47 +02:00
Alexander Stecher
960dd209f7 feat: multiple workers with same file (#1856)
* Allow multiple workers with the same file.

* Fix formatting of duplicate filename check

* Adds docs.

* suggestions by @alexandre-daubois.

* Update performance.md

---------

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
2025-09-09 14:27:00 +02:00
Youenn Le Gouedec
984f0a0211 docs: replace GEN_STUB_FILE by GEN_STUB_SCRIPT (#1849) 2025-09-08 14:13:16 +02:00
dependabot[bot]
86a2d27c01 ci: bump actions/setup-go from 5 to 6 in the github-actions group
Bumps the github-actions group with 1 update: [actions/setup-go](https://github.com/actions/setup-go).


Updates `actions/setup-go` from 5 to 6
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 13:58:05 +02:00
dependabot[bot]
0f942c8601 chore(caddy): bump github.com/spf13/cobra
Bumps the go-modules group in /caddy with 1 update: [github.com/spf13/cobra](https://github.com/spf13/cobra).


Updates `github.com/spf13/cobra` from 1.9.1 to 1.10.0
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.9.1...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-01 23:36:48 +02:00
dependabot[bot]
8c501a8d98 ci: bump actions/attest-build-provenance in the github-actions group (#1852) 2025-09-01 23:10:01 +02:00
Adiel Cristo
c564c3ffb9 fix: minor docs fixes 2025-08-29 12:46:40 +02:00
Adiel Cristo
6ab8350561 feat: translate file docs/extensions.md (#1843) 2025-08-29 09:17:49 +02:00
Kévin Dunglas
ad86bf49c2 chore: prepare release 1.9.1 2025-08-28 19:18:41 +02:00
Kévin Dunglas
1030e4ceb4 ci: fix issues and lint with zizmor 2025-08-28 19:16:32 +02:00
Kévin Dunglas
460d63e436 chore: bump deps and check go.mod files are clean in CI 2025-08-28 19:16:01 +02:00
Alexander Stecher
78bc5c87d8 fix: free request context if php_request_startup() errors (#1842) 2025-08-28 17:29:10 +02:00
Alexandre Daubois
99bb87167e chore: bump deps 2025-08-27 15:56:40 +02:00
Alexandre Daubois
c14d771fdf feat(docker): add support for Debian Trixie (#1777)
* ci: add support for Debian Trixie

* nit

* add Trixie to bug_report.yaml

---------

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
2025-08-27 08:32:05 +02:00
Alexander Stecher
952754db27 fix: don't flush env between requests (#1814) 2025-08-27 08:30:40 +02:00
Marc
9b851bf53e fix: add WorkingDirectory to ReadHat service file to make mercure.db files work without absolute paths (#1835)
* add WorkingDirectory to make mercure.db files work without absolut paths

* make linter happy
2025-08-26 16:39:02 +02:00
Alexander Stecher
d540727369 feat:(extgen) make Go arrays more consistent with PHP arrays (#1800)
* Makes go arrays more consistent with PHP arrays.

* NewAssociativeArray.

* linting

* go linting

* Exposes all primitive types.

* Removes pointer alias

* linting

* Optimizes hash update.

* Fixes extgen tests.

* Moves file to tests.

* Fixes suggested by @dunglas.

* Replaces 'interface{}' with 'any'.

* Panics on wrong zval.

* interface improvements as suggested by @dunglas.

* Adjusts docs.

* Adjusts docs.

* Removes PackedArray alias and adjusts docs.

* Updates docs.
2025-08-25 16:24:15 +02:00
Alexander Stecher
c10e85b905 refactor: cleanup context (#1816)
* Removes NewRequestWithContext.

* Moves cgi logic to cgi.go

* Calls 'update_request_info' from the C side.

* Calls 'update_request_info' from the C side.

* clang-format

* Removes unnecessary export.

* Adds TODO.

* Adds TODO.

* Removes 'is_worker_thread'

* Shortens return statement.

* Removes the context refactor.

* adjusts comment.

* Skips parsing cgi path variables on explicitly assigned worker.

* suggesions by @dunglas.

* Re-introduces 'is_worker_thread'.

* More formatting.
2025-08-25 16:18:20 +02:00
Alexandre Daubois
4dd6b5ea16 fix: support filename other than ext.go and keep local vars on generation 2025-08-25 16:17:01 +02:00
Alexander Stecher
2b78ffe15c tests: make caddy_tests faster (#1823) 2025-08-25 16:16:32 +02:00
demouth
53e6d1897d docs: add Japanese translation for documentation (#1740)
* docs(ja): add Japanese documentation translation

* docs(ja): fix invalid link fragments and improve section heading
2025-08-25 16:15:10 +02:00
Adiel Cristo
1eb16f1434 feat: add Brazilian Portuguese translation (#1645)
* feat: add Brazilian Portuguese

* Translate file README.md

* Update file README.md

* Translate file docs/classic.md

* Translate file docs/worker.md

* Translate file docs/early-hints.md

* Translate file docs/mercure.md

* Translate file docs/x-sendfile.md

* Translate file docs/config.md

* Translate file docs/docker.md

* Minor fixes

* Translate file docs/production.md

* Translate file CONTRIBUTING.md

* Minor fixes

* Translate file docs/performance.md

* Minor fixes

* Translate file docs/embed.md

* Minor fixes

* Minor fixes

* Translate file docs/static.md

* Translate file docs/compile.md

* Minor fixes

* Translate file docs/metrics.md

* Translate file docs/laravel.md

* Translate file docs/known-issues.md

* Minor fixes

* Translate file docs/github-actions.md

* Fix build

* Fix build

* fix: remove text already translated

* feat: update translation

* fix: format comments based on other translations
2025-08-25 16:13:04 +02:00
Marc
34005da9f8 feat(static): add memcache and memcached (#1825) 2025-08-25 10:35:07 +02:00
Alexandre Daubois
b3fb8dfaba chore: try running tests against PHP 8.5 as an experimental job (#1700) 2025-08-22 13:43:12 +02:00
Alexandre Daubois
d7aebedd2d chore: bump golang.org/x/net to v0.43.0 2025-08-18 11:27:19 +02:00
Kévin Dunglas
5f153e06d6 chore: upgrade to Go 1.25 (#1811)
* chore: upgrade to Go 1.25

* free all interned strings (test)

* Revert "free all interned strings (test)"

This reverts commit 34823baadb.

* Another test.

* Another test

* ASAN_OPTIONS: detect_leaks=0

* Update sanitizers.yaml

* Update sanitizers.yaml

* Update sanitizers.yaml

---------

Co-authored-by: Alliballibaba <alliballibaba@gmail.com>
2025-08-18 10:46:03 +02:00
Alexandre Daubois
555c613669 chore: bump super-linter/super-linter/slim to 8.0.0 2025-08-18 10:45:38 +02:00
Alliballibaba
e34b82b425 refactor: removes 'Max Threads' 2025-08-17 21:01:45 +02:00
Alexandre Daubois
af057a93a9 chore: bump actions/download-artifact (#1812) 2025-08-15 19:30:07 +02:00
Kévin Dunglas
a1ae2692e1 chore: modernize Go code 2025-08-15 00:22:44 +02:00
Alexandre Daubois
6ad34b1cb3 chore: bump deps 2025-08-14 15:27:29 +02:00
Pierre Clavequin
f2e9217bfc docs: update Chinese documentation to latest version 2025-08-13 20:52:42 +02:00
Bram
1f6d6bde92 Simplify chown command (#1802)
* Simplify chown command

* Fix whitespace and also modify other chown command
2025-08-12 09:36:46 +02:00
Rob Landers
c7bc5a3778 handle extensions in cli mode (#1798)
Signed-off-by: Robert Landers <landers.robert@gmail.com>
2025-08-11 11:00:13 +02:00
Alexander Stecher
9e4a6b789b refactor: remove some duplications in tests (#1783)
* Removes test duplications.

* Adds t.Helper().

* Fixes tests.

* UUpdate frankenphp_test.go

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>

---------

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
2025-08-09 22:45:56 +02:00
Marc
8d148a16e2 feat(static): add iconv extension (#1793) 2025-08-07 12:50:47 +02:00
Alexandre Daubois
1d0169d321 fix(types): better zval handling to avoid leaks with arrays (#1780) 2025-08-04 19:00:13 +02:00
WeidiDeng
365eae1a99 fix(caddy): check if http app fails to provision due to not configured or invalid configuration (#1774) 2025-07-30 11:43:48 +02:00
Alexander Stecher
2a41fc183a fix: dev Docker image build (#1769)
* Fixes build command.

* Rests ti go.sh
2025-07-27 15:31:23 +02:00
Alexandre Daubois
8175ae7e8c chore: miscellaneous fix in C code (#1766) 2025-07-24 10:24:38 +02:00
Cthulhux
cd16da248a Update feature_request.yaml (#1765)
docs: fix typo in issue template
2025-07-23 16:23:56 +02:00
177 changed files with 11674 additions and 1965 deletions

View File

@@ -26,6 +26,7 @@ body:
label: Build Type
description: What build of FrankenPHP do you use?
options:
- Docker (Debian Trixie)
- Docker (Debian Bookworm)
- Docker (Alpine)
- Official static build

View File

@@ -6,7 +6,7 @@ body:
- type: textarea
id: description
attributes:
label: Describe you feature request
label: Describe your feature request
value: |
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is.

View File

@@ -85,10 +85,11 @@ jobs:
echo ref="${FRANKENPHP_LATEST_TAG}"
echo skip=false
} >> "${GITHUB_OUTPUT}"
- uses: actions/checkout@v4
- uses: actions/checkout@v5
if: ${{ !fromJson(steps.check.outputs.skip) }}
with:
ref: ${{ steps.check.outputs.ref }}
persist-credentials: false
- name: Set up Docker Buildx
if: ${{ !fromJson(steps.check.outputs.skip) }}
uses: docker/setup-buildx-action@v3
@@ -124,6 +125,10 @@ jobs:
race: "-race" # The Go race detector is only supported on amd64
exclude:
# arm/v6 is only available for Alpine: https://github.com/docker-library/golang/issues/502
- variant: php-${{ needs.prepare.outputs.php82_version }}-trixie
platform: linux/arm/v6
- variant: php-${{ needs.prepare.outputs.php83_version }}-trixie
platform: linux/arm/v6
- variant: php-${{ needs.prepare.outputs.php82_version }}-bookworm
platform: linux/arm/v6
- variant: php-${{ needs.prepare.outputs.php83_version }}-bookworm
@@ -131,12 +136,13 @@ jobs:
steps:
- name: Prepare
id: prepare
run: |
platform=${{ matrix.platform }}
echo "sanitized_platform=${platform//\//-}" >> "${GITHUB_OUTPUT}"
- uses: actions/checkout@v4
run: echo "sanitized_platform=${PLATFORM//\//-}" >> "${GITHUB_OUTPUT}"
env:
PLATFORM: ${{ matrix.platform }}
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.ref }}
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
@@ -178,13 +184,14 @@ jobs:
run: |
mkdir -p /tmp/metadata/builder /tmp/metadata/runner
builderDigest=$(jq -r '."builder-${{ matrix.variant }}"."containerimage.digest"' <<< "${METADATA}")
builderDigest=$(jq -r ".\"builder-${VARIANT}\".\"containerimage.digest\"" <<< "${METADATA}")
touch "/tmp/metadata/builder/${builderDigest#sha256:}"
runnerDigest=$(jq -r '."runner-${{ matrix.variant }}"."containerimage.digest"' <<< "${METADATA}")
runnerDigest=$(jq -r ".\"runner-${VARIANT}\".\"containerimage.digest\"" <<< "${METADATA}")
touch "/tmp/metadata/runner/${runnerDigest#sha256:}"
env:
METADATA: ${{ steps.build.outputs.metadata }}
VARIANT: ${{ matrix.variant }}
- name: Upload builder metadata
if: fromJson(needs.prepare.outputs.push)
uses: actions/upload-artifact@v4
@@ -204,11 +211,15 @@ jobs:
- name: Run tests
if: ${{ !fromJson(needs.prepare.outputs.push) }}
run: |
docker run --platform=${{ matrix.platform }} --rm \
"$(jq -r '."builder-${{ matrix.variant }}"."containerimage.config.digest"' <<< "${METADATA}")" \
sh -c './go.sh test -tags ${{ matrix.race }} -v $(./go.sh list ./... | grep -v github.com/dunglas/frankenphp/internal/testext | grep -v github.com/dunglas/frankenphp/internal/extgen) && cd caddy && ../go.sh test ${{ matrix.race }} -v ./...'
docker run --platform="${PLATFORM}" --rm \
"$(jq -r ".\"builder-${VARIANT}\".\"containerimage.config.digest\"" <<< "${METADATA}")" \
sh -c "./go.sh test ${RACE} -v $(./go.sh list ./... | grep -v github.com/dunglas/frankenphp/internal/testext | grep -v github.com/dunglas/frankenphp/internal/extgen | tr '\n' ' ') && cd caddy && ../go.sh test ${RACE} -v ./..."
env:
METADATA: ${{ steps.build.outputs.metadata }}
PLATFORM: ${{ matrix.platform }}
VARIANT: ${{ matrix.variant }}
RACE: ${{ matrix.race }}
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
push:
runs-on: ubuntu-24.04
@@ -223,7 +234,7 @@ jobs:
target: ["builder", "runner"]
steps:
- name: Download metadata
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
pattern: metadata-${{ matrix.target }}-${{ matrix.variant }}-*
path: /tmp/metadata
@@ -241,13 +252,17 @@ jobs:
run: |
set -x
# shellcheck disable=SC2046,SC2086
docker buildx imagetools create $(jq -cr '.target."${{ matrix.target }}-${{ matrix.variant }}".tags | map("-t " + .) | join(" ")' <<< ${METADATA}) \
docker buildx imagetools create $(jq -cr ".target.\"${TARGET}-${VARIANT}\".tags | map(\"-t \" + .) | join(\" \")" <<< ${METADATA}) \
$(printf "${IMAGE_NAME}@sha256:%s " *)
env:
METADATA: ${{ needs.prepare.outputs.metadata }}
TARGET: ${{ matrix.target }}
VARIANT: ${{ matrix.variant }}
- name: Inspect image
run: |
# shellcheck disable=SC2046,SC2086
docker buildx imagetools inspect $(jq -cr '.target."${{ matrix.target }}-${{ matrix.variant }}".tags | first' <<< ${METADATA})
docker buildx imagetools inspect $(jq -cr ".target.\"${TARGET}-${VARIANT}\".tags | first" <<< ${METADATA})
env:
METADATA: ${{ needs.prepare.outputs.metadata }}
TARGET: ${{ matrix.target }}
VARIANT: ${{ matrix.variant }}

View File

@@ -20,11 +20,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false
- name: Lint Code Base
uses: super-linter/super-linter/slim@v7.4.0
uses: super-linter/super-linter/slim@v8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LINTER_RULES_PATH: /
@@ -38,8 +39,11 @@ jobs:
VALIDATE_PHP_PSALM: false
VALIDATE_TERRAGRUNT: false
VALIDATE_DOCKERFILE_HADOLINT: false
# Prettier and StandardJS are incompatible
VALIDATE_TRIVY: false
# Prettier, Biome and StandardJS are incompatible
VALIDATE_JAVASCRIPT_PRETTIER: false
VALIDATE_TYPESCRIPT_PRETTIER: false
VALIDATE_BIOME_FORMAT: false
VALIDATE_BIOME_LINT: false
# Conflicts with MARKDOWN
VALIDATE_MARKDOWN_PRETTIER: false

View File

@@ -35,13 +35,17 @@ jobs:
USE_ZEND_ALLOC: 0
LIBRARY_PATH: ${{ github.workspace }}/php/target/lib:${{ github.workspace }}/watcher/target/lib
LD_LIBRARY_PATH: ${{ github.workspace }}/php/target/lib
# PHP doesn't free some memory on purpose, we have to disable leaks detection: https://go.dev/doc/go1.25#go-command
ASAN_OPTIONS: detect_leaks=0
steps:
- name: Remove local PHP
run: sudo apt-get remove --purge --autoremove 'php*' 'libmemcached*'
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
with:
go-version: "1.24"
persist-credentials: false
- uses: actions/setup-go@v6
with:
go-version: "1.25"
cache-dependency-path: |
go.sum
caddy/go.sum
@@ -61,7 +65,7 @@ jobs:
name: Compile PHP
run: |
mkdir php/
curl -fsSL "${{ steps.determine-php-version.outputs.archive }}" | tar -Jx -C php --strip-components=1
curl -fsSL "${URL}" | tar -Jx -C php --strip-components=1
cd php/
./configure \
CFLAGS="$CFLAGS" \
@@ -91,6 +95,8 @@ jobs:
--prefix="$(pwd)/target/"
make -j"$(getconf _NPROCESSORS_ONLN)"
make install
env:
URL: ${{ steps.determine-php-version.outputs.archive }}
- name: Add PHP to the PATH
run: echo "$(pwd)/php/target/bin" >> "$GITHUB_PATH"
- name: Install e-dant/watcher
@@ -102,6 +108,6 @@ jobs:
echo "CGO_LDFLAGS=$LDFLAGS $(php-config --ldflags) $(php-config --libs)"
} >> "$GITHUB_ENV"
- name: Compile tests
run: go test ${{ matrix.sanitizer == 'msan' && '-tags=nowatcher' || '' }} -${{ matrix.sanitizer }} -v -x -c
run: go test ${{ matrix.sanitizer == 'msan' && '-tags=nowatcher' || '' }} -${{ matrix.sanitizer }} -v -x -c
- name: Run tests
run: ./frankenphp.test -test.v

View File

@@ -3,6 +3,7 @@ name: Build binary releases
concurrency:
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}
on:
pull_request:
branches:
@@ -30,13 +31,14 @@ on:
type: string
schedule:
- cron: "0 0 * * *"
permissions:
contents: write
id-token: write
attestations: write
contents: read
env:
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
GOTOOLCHAIN: local
jobs:
prepare:
runs-on: ubuntu-24.04
@@ -51,7 +53,7 @@ jobs:
id: check
if: github.event_name == 'schedule'
run: |
ref="${{ (github.ref_type == 'tag' && github.ref_name) || (github.event_name == 'workflow_dispatch' && inputs.version) || '' }}"
ref="${REF}"
if [[ -z "${ref}" ]]; then
ref="$(gh release view --repo dunglas/frankenphp --json tagName --jq '.tagName')"
fi
@@ -59,9 +61,11 @@ jobs:
echo "ref=${ref}" >> "${GITHUB_OUTPUT}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v4
REF: ${{ (github.ref_type == 'tag' && github.ref_name) || (github.event_name == 'workflow_dispatch' && inputs.version) || '' }}
- uses: actions/checkout@v5
with:
ref: ${{ steps.check.outputs.ref }}
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Create platforms matrix
@@ -77,7 +81,12 @@ jobs:
env:
SHA: ${{ github.sha }}
VERSION: ${{ steps.check.outputs.ref || 'dev' }}
build-linux-musl:
permissions:
contents: write
id-token: write
attestations: write
strategy:
fail-fast: false
matrix:
@@ -96,12 +105,13 @@ jobs:
steps:
- name: Prepare
id: prepare
run: |
platform=${{ matrix.platform }}
echo "sanitized_platform=${platform//\//-}" >> "${GITHUB_OUTPUT}"
- uses: actions/checkout@v4
run: echo "sanitized_platform=${PLATFORM//\//-}" >> "${GITHUB_OUTPUT}"
env:
PLATFORM: ${{ matrix.platform }}
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.ref }}
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
@@ -156,11 +166,12 @@ jobs:
run: |
# shellcheck disable=SC2034
digest=$(jq -r '."static-builder-musl"."${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && 'containerimage.digest' || 'containerimage.config.digest' }}"' <<< "${METADATA}")
docker create --platform=${{ matrix.platform }} --name static-builder-musl "${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && '${IMAGE_NAME}@${digest}' || '${digest}' }}"
docker create --platform="${PLATFORM}" --name static-builder-musl "${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && '${IMAGE_NAME}@${digest}' || '${digest}' }}"
docker cp "static-builder-musl:/go/src/app/dist/${BINARY}" "${BINARY}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}"
env:
METADATA: ${{ steps.build.outputs.metadata }}
BINARY: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}
PLATFORM: ${{ matrix.platform }}
- name: Upload artifact
if: ${{ !fromJson(needs.prepare.outputs.push) }}
uses: actions/upload-artifact@v4
@@ -169,11 +180,12 @@ jobs:
path: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
- name: Upload assets
if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
run: gh release upload "${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }}" frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} --repo dunglas/frankenphp --clobber
run: gh release upload "${REF}" frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} --repo dunglas/frankenphp --clobber
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REF: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }}
- if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
uses: actions/attest-build-provenance@v2
uses: actions/attest-build-provenance@v3
with:
subject-path: ${{ github.workspace }}/frankenphp-linux-*
- name: Run sanity checks
@@ -190,6 +202,10 @@ jobs:
BINARY: ./frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
build-linux-gnu:
permissions:
contents: write
id-token: write
attestations: write
strategy:
fail-fast: false
matrix:
@@ -200,12 +216,13 @@ jobs:
steps:
- name: Prepare
id: prepare
run: |
platform=${{ matrix.platform }}
echo "sanitized_platform=${platform//\//-}" >> "${GITHUB_OUTPUT}"
- uses: actions/checkout@v4
run: echo "sanitized_platform=${PLATFORM//\//-}" >> "${GITHUB_OUTPUT}"
env:
PLATFORM: ${{ matrix.platform }}
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.ref }}
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
@@ -259,7 +276,7 @@ jobs:
run: |
# shellcheck disable=SC2034
digest=$(jq -r '."static-builder-gnu"."${{ fromJson(needs.prepare.outputs.push) && 'containerimage.digest' || 'containerimage.config.digest' }}"' <<< "${METADATA}")
container_id=$(docker create --platform=${{ matrix.platform }} "${{ fromJson(needs.prepare.outputs.push) && '${IMAGE_NAME}@${digest}' || '${digest}' }}")
container_id=$(docker create --platform="${PLATFORM}" "${{ fromJson(needs.prepare.outputs.push) && '${IMAGE_NAME}@${digest}' || '${digest}' }}")
mkdir -p gh-output
cd gh-output
for file in $(docker run --rm "${{ fromJson(needs.prepare.outputs.push) && '${IMAGE_NAME}@${digest}' || '${digest}' }}" sh -c "ls /go/src/app/dist | grep '^frankenphp'"); do
@@ -270,6 +287,7 @@ jobs:
env:
METADATA: ${{ steps.build.outputs.metadata }}
BINARY: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}
PLATFORM: ${{ matrix.platform }}
- name: Upload artifact
if: ${{ !fromJson(needs.prepare.outputs.push) }}
uses: actions/upload-artifact@v4
@@ -278,11 +296,12 @@ jobs:
path: gh-output/*
- name: Upload assets
if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
run: gh release upload "${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }}" gh-output/* --repo dunglas/frankenphp --clobber
run: gh release upload "${REF}" gh-output/* --repo dunglas/frankenphp --clobber
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REF: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }}
- if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
uses: actions/attest-build-provenance@v2
uses: actions/attest-build-provenance@v3
with:
subject-path: ${{ github.workspace }}/gh-output/frankenphp-linux-*-gnu
- name: Run sanity checks
@@ -307,13 +326,13 @@ jobs:
if: fromJson(needs.prepare.outputs.push)
steps:
- name: Download metadata
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
pattern: metadata-static-builder-musl-*
path: /tmp/metadata
merge-multiple: true
- name: Download GNU metadata
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
pattern: metadata-static-builder-gnu-*
path: /tmp/metadata-gnu
@@ -356,6 +375,10 @@ jobs:
GNU_METADATA: ${{ needs.prepare.outputs.gnu_metadata }}
build-mac:
permissions:
contents: write
id-token: write
attestations: write
strategy:
fail-fast: false
matrix:
@@ -366,26 +389,30 @@ jobs:
env:
HOMEBREW_NO_AUTO_UPDATE: 1
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.ref }}
- uses: actions/setup-go@v5
persist-credentials: false
- uses: actions/setup-go@v6
with:
go-version: "1.24"
go-version: "1.25"
cache-dependency-path: |
go.sum
caddy/go.sum
cache: false
- name: Set FRANKENPHP_VERSION
run: |
if [ "${GITHUB_REF_TYPE}" == "tag" ]; then
export FRANKENPHP_VERSION=${GITHUB_REF_NAME:1}
elif [ "${GITHUB_EVENT_NAME}" == "schedule" ]; then
export FRANKENPHP_VERSION="${{ needs.prepare.outputs.ref }}"
export FRANKENPHP_VERSION="${REF}"
else
export FRANKENPHP_VERSION=${GITHUB_SHA}
fi
echo "FRANKENPHP_VERSION=${FRANKENPHP_VERSION}" >> "${GITHUB_ENV}"
env:
REF: ${{ needs.prepare.outputs.ref }}
- name: Build FrankenPHP
run: ./build-static.sh
env:
@@ -393,7 +420,7 @@ jobs:
RELEASE: ${{ (needs.prepare.outputs.ref || github.ref_type == 'tag') && '1' || '' }}
NO_COMPRESS: ${{ github.event_name == 'pull_request' && '1' || '' }}
- if: needs.prepare.outputs.ref || github.ref_type == 'tag'
uses: actions/attest-build-provenance@v2
uses: actions/attest-build-provenance@v3
with:
subject-path: ${{ github.workspace }}/dist/frankenphp-mac-*
- name: Upload artifact

View File

@@ -23,18 +23,26 @@ jobs:
tests-linux:
name: Tests (Linux, PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental || false }}
strategy:
fail-fast: false
matrix:
php-versions: ["8.2", "8.3", "8.4"]
include:
- php-versions: "8.2"
- php-versions: "8.3"
- php-versions: "8.4"
- php-versions: "8.5"
experimental: true
env:
GOMAXPROCS: 10
LIBRARY_PATH: ${{ github.workspace }}/watcher/target/lib
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
with:
go-version: "1.24"
persist-credentials: false
- uses: actions/setup-go@v6
with:
go-version: "1.25"
cache-dependency-path: |
go.sum
caddy/go.sum
@@ -79,16 +87,25 @@ jobs:
if: matrix.php-versions == '8.4'
with:
version: latest
- name: Ensure go.mod is tidy
if: matrix.php-versions == '8.4'
run: go mod tidy -diff
- name: Ensure caddy/go.mod is tidy
if: matrix.php-versions == '8.4'
run: go mod tidy -diff
working-directory: caddy/
tests-mac:
name: Tests (macOS, PHP 8.4)
runs-on: macos-latest
env:
HOMEBREW_NO_AUTO_UPDATE: 1
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v5
with:
go-version: "1.24"
persist-credentials: false
- uses: actions/setup-go@v6
with:
go-version: "1.25"
cache-dependency-path: |
go.sum
caddy/go.sum

1
.gitleaksignore Normal file
View File

@@ -0,0 +1 @@
/github/workspace/docs/mercure.md:jwt:65

View File

@@ -17,7 +17,7 @@ The image contains the usual development tools (Go, GDB, Valgrind, Neovim...) an
- additional configuration files: `/etc/frankenphp/php.d/*.ini`
- php extensions: `/usr/lib/frankenphp/modules/`
If your docker version is lower than 23.0, the build will fail due to dockerignore [pattern issue](https://github.com/moby/moby/pull/42676). Add directories to `.dockerignore`.
If your Docker version is lower than 23.0, the build will fail due to dockerignore [pattern issue](https://github.com/moby/moby/pull/42676). Add directories to `.dockerignore`.
```patch
!testdata/*.php
@@ -195,7 +195,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
## Docker-Related Resources
- [Bake file definition](https://docs.docker.com/build/customize/bake/file-definition/)
- [docker buildx build](https://docs.docker.com/engine/reference/commandline/buildx_build/)
- [`docker buildx build`](https://docs.docker.com/engine/reference/commandline/buildx_build/)
## Useful Command

View File

@@ -110,7 +110,7 @@ RUN go mod download
WORKDIR /go/src/app
COPY --link . ./
# See https://github.com/docker-library/php/blob/master/8.3/bookworm/zts/Dockerfile#L57-L59 for PHP values
# See https://github.com/docker-library/php/blob/master/8.4/trixie/zts/Dockerfile#L57-L59 for PHP values
ENV CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $PHP_CFLAGS"
ENV CGO_CPPFLAGS=$PHP_CPPFLAGS
ENV CGO_LDFLAGS="-L/usr/local/lib -lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS"

View File

@@ -73,7 +73,7 @@ if [ -z "${PHP_VERSION}" ]; then
export PHP_VERSION
fi
# default extension set
defaultExtensions="amqp,apcu,ast,bcmath,brotli,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,lz4,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,password-argon2,parallel,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pdo_sqlsrv,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,ssh2,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,xlswriter,xml,xmlreader,xmlwriter,xz,zip,zlib,yaml,zstd"
defaultExtensions="amqp,apcu,ast,bcmath,brotli,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,lz4,mbregex,mbstring,memcache,memcached,mysqli,mysqlnd,opcache,openssl,password-argon2,parallel,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pdo_sqlsrv,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,ssh2,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,xlswriter,xml,xmlreader,xmlwriter,xz,zip,zlib,yaml,zstd"
defaultExtensionLibs="libavif,nghttp2,nghttp3,ngtcp2"
md5binary="md5sum"

View File

@@ -109,12 +109,12 @@ func TestAutoScaleWorkerThreads(t *testing.T) {
// spam an endpoint that simulates IO
endpoint := "http://localhost:" + testPort + "/?sleep=2&work=1000"
amountOfThreads := len(getDebugState(t, tester).ThreadDebugStates)
amountOfThreads := getNumThreads(t, tester)
// try to spawn the additional threads by spamming the server
for tries := 0; tries < maxTries; tries++ {
for range maxTries {
wg.Add(requestsPerTry)
for i := 0; i < requestsPerTry; i++ {
for range requestsPerTry {
go func() {
tester.AssertGetResponse(endpoint, http.StatusOK, "slept for 2 ms and worked for 1000 iterations")
wg.Done()
@@ -122,7 +122,7 @@ func TestAutoScaleWorkerThreads(t *testing.T) {
}
wg.Wait()
amountOfThreads = len(getDebugState(t, tester).ThreadDebugStates)
amountOfThreads = getNumThreads(t, tester)
if amountOfThreads > 2 {
break
}
@@ -161,12 +161,12 @@ func TestAutoScaleRegularThreadsOnAutomaticThreadLimit(t *testing.T) {
// spam an endpoint that simulates IO
endpoint := "http://localhost:" + testPort + "/sleep.php?sleep=2&work=1000"
amountOfThreads := len(getDebugState(t, tester).ThreadDebugStates)
amountOfThreads := getNumThreads(t, tester)
// try to spawn the additional threads by spamming the server
for tries := 0; tries < maxTries; tries++ {
for range maxTries {
wg.Add(requestsPerTry)
for i := 0; i < requestsPerTry; i++ {
for range requestsPerTry {
go func() {
tester.AssertGetResponse(endpoint, http.StatusOK, "slept for 2 ms and worked for 1000 iterations")
wg.Done()
@@ -174,7 +174,7 @@ func TestAutoScaleRegularThreadsOnAutomaticThreadLimit(t *testing.T) {
}
wg.Wait()
amountOfThreads = len(getDebugState(t, tester).ThreadDebugStates)
amountOfThreads = getNumThreads(t, tester)
if amountOfThreads > 1 {
break
}
@@ -208,6 +208,7 @@ func getAdminResponseBody(t *testing.T, tester *caddytest.Tester, method string,
}
func getDebugState(t *testing.T, tester *caddytest.Tester) frankenphp.FrankenPHPDebugState {
t.Helper()
threadStates := getAdminResponseBody(t, tester, "GET", "threads")
var debugStates frankenphp.FrankenPHPDebugState
@@ -217,6 +218,11 @@ func getDebugState(t *testing.T, tester *caddytest.Tester) frankenphp.FrankenPHP
return debugStates
}
func getNumThreads(t *testing.T, tester *caddytest.Tester) int {
t.Helper()
return len(getDebugState(t, tester).ThreadDebugStates)
}
func TestAddModuleWorkerViaAdminApi(t *testing.T) {
// Initialize a server with admin API enabled
tester := caddytest.NewTester(t)

View File

@@ -33,6 +33,8 @@ type FrankenPHPApp struct {
MaxThreads int `json:"max_threads,omitempty"`
// Workers configures the worker scripts to start.
Workers []workerConfig `json:"workers,omitempty"`
// TaskWorkers configures the task worker scripts to start.
TaskWorkers []workerConfig `json:"task_workers,omitempty"`
// Overwrites the default php ini configuration
PhpIni map[string]string `json:"php_ini,omitempty"`
// The maximum amount of time a request may be stalled waiting for a thread
@@ -42,7 +44,7 @@ type FrankenPHPApp struct {
logger *slog.Logger
}
var iniError = errors.New("'php_ini' must be in the format: php_ini \"<key>\" \"<value>\"")
var iniError = errors.New(`"php_ini" must be in the format: php_ini "<key>" "<value>"`)
// CaddyModule returns the Caddy module information.
func (f FrankenPHPApp) CaddyModule() caddy.ModuleInfo {
@@ -62,7 +64,12 @@ func (f *FrankenPHPApp) Provision(ctx caddy.Context) error {
}
} else {
// if the http module is not configured (this should never happen) then collect the metrics by default
f.metrics = frankenphp.NewPrometheusMetrics(ctx.GetMetricsRegistry())
if errors.Is(err, caddy.ErrNotConfigured) {
f.metrics = frankenphp.NewPrometheusMetrics(ctx.GetMetricsRegistry())
} else {
// the http module failed to provision due to invalid configuration
return fmt.Errorf("failed to provision caddy http: %w", err)
}
}
return nil
@@ -122,6 +129,15 @@ func (f *FrankenPHPApp) Start() error {
opts = append(opts, frankenphp.WithWorkers(w.Name, repl.ReplaceKnown(w.FileName, ""), w.Num, workerOpts...))
}
for _, tw := range f.TaskWorkers {
workerOpts := []frankenphp.WorkerOption{
frankenphp.WithWorkerEnv(tw.Env),
frankenphp.WithWorkerWatchMode(tw.Watch),
frankenphp.AsTaskWorker(true, 0), // TODO: maxQueueLen configurable here?
}
opts = append(opts, frankenphp.WithWorkers(tw.Name, repl.ReplaceKnown(tw.FileName, ""), tw.Num, workerOpts...))
}
frankenphp.Shutdown()
if err := frankenphp.Init(opts...); err != nil {
@@ -189,7 +205,7 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
v, err := time.ParseDuration(d.Val())
if err != nil {
return errors.New("max_wait_time must be a valid duration (example: 10s)")
return d.Err("max_wait_time must be a valid duration (example: 10s)")
}
f.MaxWaitTime = v
@@ -197,14 +213,14 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
parseIniLine := func(d *caddyfile.Dispenser) error {
key := d.Val()
if !d.NextArg() {
return iniError
return d.WrapErr(iniError)
}
if f.PhpIni == nil {
f.PhpIni = make(map[string]string)
}
f.PhpIni[key] = d.Val()
if d.NextArg() {
return iniError
return d.WrapErr(iniError)
}
return nil
@@ -221,7 +237,7 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if !isBlock {
if !d.NextArg() {
return iniError
return d.WrapErr(iniError)
}
err := parseIniLine(d)
if err != nil {
@@ -229,6 +245,13 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
}
case "task_worker":
twc, err := parseWorkerConfig(d)
if err != nil {
return err
}
f.TaskWorkers = append(f.TaskWorkers, twc)
case "worker":
wc, err := parseWorkerConfig(d)
if err != nil {
@@ -238,12 +261,12 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
wc.FileName = filepath.Join(frankenphp.EmbeddedAppPath, wc.FileName)
}
if strings.HasPrefix(wc.Name, "m#") {
return fmt.Errorf(`global worker names must not start with "m#": %q`, wc.Name)
return d.Errf(`global worker names must not start with "m#": %q`, wc.Name)
}
// check for duplicate workers
for _, existingWorker := range f.Workers {
if existingWorker.FileName == wc.FileName {
return fmt.Errorf("global workers must not have duplicate filenames: %q", wc.FileName)
return d.Errf("global workers must not have duplicate filenames: %q", wc.FileName)
}
}
@@ -256,13 +279,13 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
if f.MaxThreads > 0 && f.NumThreads > 0 && f.MaxThreads < f.NumThreads {
return errors.New(`"max_threads"" must be greater than or equal to "num_threads"`)
return d.Err(`"max_threads"" must be greater than or equal to "num_threads"`)
}
return nil
}
func parseGlobalOption(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
func parseGlobalOption(d *caddyfile.Dispenser, _ any) (any, error) {
app := &FrankenPHPApp{}
if err := app.UnmarshalCaddyfile(d); err != nil {
return nil, err
@@ -274,3 +297,8 @@ func parseGlobalOption(d *caddyfile.Dispenser, _ interface{}) (interface{}, erro
Value: caddyconfig.JSON(app, nil),
}, nil
}
var (
_ caddy.App = (*FrankenPHPApp)(nil)
_ caddy.Provisioner = (*FrankenPHPApp)(nil)
)

View File

@@ -7,9 +7,7 @@ import (
"fmt"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
const (
@@ -35,12 +33,3 @@ func init() {
func wrongSubDirectiveError(module string, allowedDriectives string, wrongValue string) error {
return fmt.Errorf("unknown '%s' subdirective: '%s' (allowed directives are: %s)", module, wrongValue, allowedDriectives)
}
// Interface guards
var (
_ caddy.App = (*FrankenPHPApp)(nil)
_ caddy.Provisioner = (*FrankenPHPApp)(nil)
_ caddy.Provisioner = (*FrankenPHPModule)(nil)
_ caddyhttp.MiddlewareHandler = (*FrankenPHPModule)(nil)
_ caddyfile.Unmarshaler = (*FrankenPHPModule)(nil)
)

View File

@@ -14,7 +14,6 @@ import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddytest"
"github.com/dunglas/frankenphp"
"github.com/dunglas/frankenphp/internal/fastabs"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/require"
@@ -42,7 +41,7 @@ func TestPHP(t *testing.T) {
}
`, "caddyfile")
for i := 0; i < 100; i++ {
for i := range 100 {
wg.Add(1)
go func(i int) {
@@ -105,7 +104,7 @@ func TestWorker(t *testing.T) {
}
`, "caddyfile")
for i := 0; i < 100; i++ {
for i := range 100 {
wg.Add(1)
go func(i int) {
@@ -157,7 +156,7 @@ func TestGlobalAndModuleWorker(t *testing.T) {
}
`, "caddyfile")
for i := 0; i < 10; i++ {
for i := range 10 {
wg.Add(1)
go func(i int) {
@@ -209,7 +208,7 @@ func TestNamedModuleWorkers(t *testing.T) {
}
`, "caddyfile")
for i := 0; i < 10; i++ {
for i := range 10 {
wg.Add(1)
go func(i int) {
@@ -456,7 +455,7 @@ func TestMetrics(t *testing.T) {
`, "caddyfile")
// Make some requests
for i := 0; i < 10; i++ {
for i := range 10 {
wg.Add(1)
go func(i int) {
tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/index.php?i=%d", i), http.StatusOK, fmt.Sprintf("I am by birth a Genevese (%d)", i))
@@ -475,7 +474,7 @@ func TestMetrics(t *testing.T) {
_, err = metrics.ReadFrom(resp.Body)
require.NoError(t, err, "failed to read metrics")
cpus := fmt.Sprintf("%d", frankenphp.MaxThreads)
cpus := strconv.Itoa(getNumThreads(t, tester))
// Check metrics
expectedMetrics := `
@@ -529,7 +528,7 @@ func TestWorkerMetrics(t *testing.T) {
workerName, _ := fastabs.FastAbs("../testdata/index.php")
// Make some requests
for i := 0; i < 10; i++ {
for i := range 10 {
wg.Add(1)
go func(i int) {
tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/index.php?i=%d", i), http.StatusOK, fmt.Sprintf("I am by birth a Genevese (%d)", i))
@@ -548,7 +547,7 @@ func TestWorkerMetrics(t *testing.T) {
_, err = metrics.ReadFrom(resp.Body)
require.NoError(t, err, "failed to read metrics")
cpus := fmt.Sprintf("%d", frankenphp.MaxThreads)
cpus := strconv.Itoa(getNumThreads(t, tester))
// Check metrics
expectedMetrics := `
@@ -621,7 +620,7 @@ func TestNamedWorkerMetrics(t *testing.T) {
`, "caddyfile")
// Make some requests
for i := 0; i < 10; i++ {
for i := range 10 {
wg.Add(1)
go func(i int) {
tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/index.php?i=%d", i), http.StatusOK, fmt.Sprintf("I am by birth a Genevese (%d)", i))
@@ -640,7 +639,7 @@ func TestNamedWorkerMetrics(t *testing.T) {
_, err = metrics.ReadFrom(resp.Body)
require.NoError(t, err, "failed to read metrics")
cpus := fmt.Sprintf("%d", frankenphp.MaxThreads)
cpus := strconv.Itoa(getNumThreads(t, tester))
// Check metrics
expectedMetrics := `
@@ -712,7 +711,7 @@ func TestAutoWorkerConfig(t *testing.T) {
workerName, _ := fastabs.FastAbs("../testdata/index.php")
// Make some requests
for i := 0; i < 10; i++ {
for i := range 10 {
wg.Add(1)
go func(i int) {
tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/index.php?i=%d", i), http.StatusOK, fmt.Sprintf("I am by birth a Genevese (%d)", i))
@@ -731,8 +730,9 @@ func TestAutoWorkerConfig(t *testing.T) {
_, err = metrics.ReadFrom(resp.Body)
require.NoError(t, err, "failed to read metrics")
cpus := fmt.Sprintf("%d", frankenphp.MaxThreads)
workers := fmt.Sprintf("%d", frankenphp.MaxThreads-1)
numThreads := getNumThreads(t, tester)
cpus := strconv.Itoa(numThreads)
workers := strconv.Itoa(numThreads - 1)
// Check metrics
expectedMetrics := `
@@ -872,7 +872,7 @@ func TestPHPIniBlockConfiguration(t *testing.T) {
func testSingleIniConfiguration(tester *caddytest.Tester, key string, value string) {
// test twice to ensure the ini setting is not lost
for i := 0; i < 2; i++ {
for range 2 {
tester.AssertGetResponse(
"http://localhost:"+testPort+"/ini.php?key="+key,
http.StatusOK,
@@ -940,7 +940,7 @@ func TestMaxWaitTime(t *testing.T) {
wg := sync.WaitGroup{}
success := atomic.Bool{}
wg.Add(10)
for i := 0; i < 10; i++ {
for range 10 {
go func() {
statusCode := getStatusCode("http://localhost:"+testPort+"/sleep.php?sleep=10", t)
if statusCode == http.StatusGatewayTimeout {
@@ -987,9 +987,9 @@ func TestMaxWaitTimeWorker(t *testing.T) {
wg := sync.WaitGroup{}
success := atomic.Bool{}
wg.Add(10)
for i := 0; i < 10; i++ {
for range 10 {
go func() {
statusCode := getStatusCode("http://localhost:"+testPort+"/sleep.php?sleep=10000&iteration=1", t)
statusCode := getStatusCode("http://localhost:"+testPort+"/sleep.php?sleep=10&iteration=1", t)
if statusCode == http.StatusGatewayTimeout {
success.Store(true)
}
@@ -1074,7 +1074,7 @@ func TestMultiWorkersMetrics(t *testing.T) {
`, "caddyfile")
// Make some requests
for i := 0; i < 10; i++ {
for i := range 10 {
wg.Add(1)
go func(i int) {
tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/index.php?i=%d", i), http.StatusOK, fmt.Sprintf("I am by birth a Genevese (%d)", i))
@@ -1093,7 +1093,7 @@ func TestMultiWorkersMetrics(t *testing.T) {
_, err = metrics.ReadFrom(resp.Body)
require.NoError(t, err, "failed to read metrics")
cpus := fmt.Sprintf("%d", frankenphp.MaxThreads)
cpus := strconv.Itoa(getNumThreads(t, tester))
// Check metrics
expectedMetrics := `
@@ -1180,7 +1180,7 @@ func TestDisabledMetrics(t *testing.T) {
`, "caddyfile")
// Make some requests
for i := 0; i < 10; i++ {
for i := range 10 {
wg.Add(1)
go func(i int) {
tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/index.php?i=%d", i), http.StatusOK, fmt.Sprintf("I am by birth a Genevese (%d)", i))
@@ -1285,7 +1285,7 @@ func TestWorkerRestart(t *testing.T) {
))
// Make some requests
for i := 0; i < 10; i++ {
for i := range 10 {
wg.Add(1)
go func(i int) {
tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/worker-restart.php?i=%d", i), http.StatusOK, fmt.Sprintf("Counter (%d)", i))
@@ -1423,3 +1423,31 @@ func TestWorkerMatchDirectiveWithoutFileServer(t *testing.T) {
// the request should completely fall through the php_server module
tester.AssertGetResponse("http://localhost:"+testPort+"/static.txt", http.StatusNotFound, "Request falls through")
}
func TestServerWithTaskWorker(t *testing.T) {
tester := caddytest.NewTester(t)
taskWorker, err := fastabs.FastAbs("../testdata/tasks/task-worker.php")
require.NoError(t, err)
tester.InitServer(`
{
skip_install_trust
admin localhost:2999
frankenphp {
num_threads 2
task_worker `+taskWorker+` {
num 1
}
}
}
`, "caddyfile")
debugState := getDebugState(t, tester)
require.Len(t, debugState.ThreadDebugStates, 2, "there should be 3 threads")
require.Equal(
t,
debugState.ThreadDebugStates[1].Name,
"Task Worker PHP Thread - "+taskWorker,
"the second spawned thread should be the task worker",
)
}

View File

@@ -2,12 +2,13 @@ package caddy
import (
"errors"
"github.com/dunglas/frankenphp/internal/extgen"
"log"
"os"
"path/filepath"
"strings"
"github.com/dunglas/frankenphp/internal/extgen"
caddycmd "github.com/caddyserver/caddy/v2/cmd"
"github.com/spf13/cobra"
)
@@ -16,7 +17,7 @@ func init() {
caddycmd.RegisterCommand(caddycmd.Command{
Name: "extension-init",
Usage: "go_extension.go [--verbose]",
Short: "(Experimental) Initializes a PHP extension from a Go file",
Short: "Initializes a PHP extension from a Go file (EXPERIMENTAL)",
Long: `
Initializes a PHP extension from a Go file. This command generates the necessary C files for the extension, including the header and source files, as well as the arginfo file.`,
CobraFunc: func(cmd *cobra.Command) {
@@ -27,27 +28,21 @@ Initializes a PHP extension from a Go file. This command generates the necessary
})
}
func cmdInitExtension(fs caddycmd.Flags) (int, error) {
func cmdInitExtension(_ caddycmd.Flags) (int, error) {
if len(os.Args) < 3 {
return 1, errors.New("the path to the Go source is required")
}
sourceFile := os.Args[2]
baseName := extgen.SanitizePackageName(strings.TrimSuffix(filepath.Base(sourceFile), ".go"))
baseName := strings.TrimSuffix(filepath.Base(sourceFile), ".go")
baseName = extgen.SanitizePackageName(baseName)
sourceDir := filepath.Dir(sourceFile)
buildDir := filepath.Join(sourceDir, "build")
generator := extgen.Generator{BaseName: baseName, SourceFile: sourceFile, BuildDir: buildDir}
generator := extgen.Generator{BaseName: baseName, SourceFile: sourceFile, BuildDir: filepath.Dir(sourceFile)}
if err := generator.Generate(); err != nil {
return 1, err
}
log.Printf("PHP extension %q initialized successfully in %q", baseName, generator.BuildDir)
log.Printf("PHP extension %q initialized successfully in directory %q", baseName, generator.BuildDir)
return 0, nil
}

View File

@@ -1,30 +1,30 @@
module github.com/dunglas/frankenphp/caddy
go 1.24.0
go 1.25.0
replace github.com/dunglas/frankenphp => ../
retract v1.0.0-rc.1 // Human error
require (
github.com/caddyserver/caddy/v2 v2.10.0
github.com/caddyserver/certmagic v0.23.0
github.com/caddyserver/caddy/v2 v2.10.2
github.com/caddyserver/certmagic v0.25.0
github.com/dunglas/caddy-cbrotli v1.0.1
github.com/dunglas/frankenphp v1.9.0
github.com/dunglas/mercure/caddy v0.20.0
github.com/dunglas/vulcain/caddy v1.2.0
github.com/prometheus/client_golang v1.22.0
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/dunglas/frankenphp v1.9.1
github.com/dunglas/mercure/caddy v0.20.2
github.com/dunglas/vulcain/caddy v1.2.1
github.com/prometheus/client_golang v1.23.2
github.com/spf13/cobra v1.10.1
github.com/stretchr/testify v1.11.1
)
require github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca // indirect
require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/auth v0.16.3 // indirect
cloud.google.com/go/auth v0.16.5 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/compute/metadata v0.8.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
@@ -35,22 +35,22 @@ require (
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 // indirect
github.com/MicahParks/jwkset v0.9.6 // indirect
github.com/MicahParks/keyfunc/v3 v3.4.0 // indirect
github.com/MicahParks/keyfunc/v3 v3.6.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring v1.9.4 // indirect
github.com/alecthomas/chroma/v2 v2.19.0 // indirect
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/bits-and-blooms/bitset v1.24.0 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/ccoveille/go-safecast v1.6.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
github.com/coreos/go-oidc/v3 v3.15.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgraph-io/badger v1.6.2 // indirect
@@ -60,43 +60,41 @@ require (
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dolthub/maphash v0.1.0 // indirect
github.com/dunglas/httpsfv v1.1.0 // indirect
github.com/dunglas/mercure v0.20.0 // indirect
github.com/dunglas/vulcain v1.2.0 // indirect
github.com/dunglas/mercure v0.20.2 // indirect
github.com/dunglas/vulcain v1.2.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gammazero/deque v1.1.0 // indirect
github.com/getkin/kin-openapi v0.132.0 // indirect
github.com/go-chi/chi/v5 v5.2.2 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
github.com/go-chi/chi/v5 v5.2.3 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonpointer v0.21.2 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gofrs/uuid/v5 v5.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/brotli/go/cbrotli v1.1.0 // indirect
github.com/google/cel-go v0.26.0 // indirect
github.com/google/cel-go v0.26.1 // indirect
github.com/google/certificate-transparency-go v1.3.2 // indirect
github.com/google/go-tpm v0.9.5 // indirect
github.com/google/go-tspi v0.3.0 // indirect
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -109,15 +107,15 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/libdns/libdns v1.1.0 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/maypok86/otter v1.2.4 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect
github.com/miekg/dns v1.1.67 // indirect
github.com/mholt/acmez/v3 v3.1.3 // indirect
github.com/miekg/dns v1.1.68 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
@@ -126,7 +124,6 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
@@ -134,17 +131,17 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.52.0 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/sagikazarmark/locafero v0.10.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/slackhq/nebula v1.9.5 // indirect
github.com/slackhq/nebula v1.9.6 // indirect
github.com/smallstep/certificates v0.28.4 // indirect
github.com/smallstep/cli-utils v0.12.1 // indirect
github.com/smallstep/linkedca v0.23.0 // indirect
@@ -152,10 +149,10 @@ require (
github.com/smallstep/pkcs7 v0.2.1 // indirect
github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492 // indirect
github.com/smallstep/truststore v0.13.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.9.2 // indirect
github.com/spf13/pflag v1.0.7 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.20.1 // indirect
github.com/stoewer/go-strcase v1.3.1 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
@@ -166,12 +163,13 @@ require (
github.com/tidwall/sjson v1.2.5 // indirect
github.com/unrolled/secure v1.17.0 // indirect
github.com/urfave/cli v1.22.17 // indirect
github.com/woodsbury/decimal128 v1.4.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/yuin/goldmark v1.7.12 // indirect
github.com/yuin/goldmark v1.7.13 // indirect
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.4.2 // indirect
go.etcd.io/bbolt v1.4.3 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/contrib/propagators/autoprop v0.62.0 // indirect
@@ -185,31 +183,32 @@ require (
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.step.sm/crypto v0.67.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.step.sm/crypto v0.70.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/mock v0.5.2 // indirect
go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20250711192710-b903b535d3ef // indirect
golang.org/x/exp v0.0.0-20250717185816-542afb5b7346 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/net v0.42.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20250826074233-8f580defa01d // indirect
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.35.0 // indirect
google.golang.org/api v0.242.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 // indirect
google.golang.org/grpc v1.73.0 // indirect
golang.org/x/tools v0.37.0 // indirect
google.golang.org/api v0.248.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.1 // indirect

View File

@@ -6,12 +6,12 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
cloud.google.com/go/auth v0.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc=
cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA=
cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk=
@@ -45,8 +45,8 @@ github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 h1:1yw6O62BReQ
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145/go.mod h1:877WBceefKn14QwVVn4xRFUsHsZb9clICgdeTj4XsUg=
github.com/MicahParks/jwkset v0.9.6 h1:Tf8l2/MOby5Kh3IkrqzThPQKfLytMERoAsGZKlyYZxg=
github.com/MicahParks/jwkset v0.9.6/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0=
github.com/MicahParks/keyfunc/v3 v3.4.0 h1:g03TXq6NjhZyO/UkODl//abm4KiLLNRi0VhW7vGOHyg=
github.com/MicahParks/keyfunc/v3 v3.4.0/go.mod h1:y6Ed3dMgNKTcpxbaQHD8mmrYDUZWJAxteddA6OQj+ag=
github.com/MicahParks/keyfunc/v3 v3.6.1 h1:A8A5zGZ8XmRyxizSY7s5FLY/aSplrnEBLCOrC0D1ojM=
github.com/MicahParks/keyfunc/v3 v3.6.1/go.mod h1:y6Ed3dMgNKTcpxbaQHD8mmrYDUZWJAxteddA6OQj+ag=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
@@ -58,64 +58,64 @@ github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhve
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4=
github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-sdk-go-v2 v1.36.4 h1:GySzjhVvx0ERP6eyfAbAuAXLtAda5TEy19E5q5W8I9E=
github.com/aws/aws-sdk-go-v2 v1.36.4/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/config v1.29.16 h1:XkruGnXX1nEZ+Nyo9v84TzsX+nj86icbFAeust6uo8A=
github.com/aws/aws-sdk-go-v2/config v1.29.16/go.mod h1:uCW7PNjGwZ5cOGZ5jr8vCWrYkGIhPoTNV23Q/tpHKzg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.69 h1:8B8ZQboRc3uaIKjshve/XlvJ570R7BKNy3gftSbS178=
github.com/aws/aws-sdk-go-v2/credentials v1.17.69/go.mod h1:gPME6I8grR1jCqBFEGthULiolzf/Sexq/Wy42ibKK9c=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31 h1:oQWSGexYasNpYp4epLGZxxjsDo8BMBh6iNWkTXQvkwk=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31/go.mod h1:nc332eGUU+djP3vrMI6blS0woaCfHTe3KiSQUVTMRq0=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35 h1:o1v1VFfPcDVlK3ll1L5xHsaQAFdNtZ5GXnNR7SwueC4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35/go.mod h1:rZUQNYMNG+8uZxz9FOerQJ+FceCiodXvixpeRtdESrU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35 h1:R5b82ubO2NntENm3SAm0ADME+H630HomNJdgv+yZ3xw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35/go.mod h1:FuA+nmgMRfkzVKYDNEqQadvEMxtxl9+RLT9ribCwEMs=
github.com/aws/aws-sdk-go-v2 v1.38.0 h1:UCRQ5mlqcFk9HJDIqENSLR3wiG1VTWlyUfLDEvY7RxU=
github.com/aws/aws-sdk-go-v2 v1.38.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
github.com/aws/aws-sdk-go-v2/config v1.31.0 h1:9yH0xiY5fUnVNLRWO0AtayqwU1ndriZdN78LlhruJR4=
github.com/aws/aws-sdk-go-v2/config v1.31.0/go.mod h1:VeV3K72nXnhbe4EuxxhzsDc/ByrCSlZwUnWH52Nde/I=
github.com/aws/aws-sdk-go-v2/credentials v1.18.4 h1:IPd0Algf1b+Qy9BcDp0sCUcIWdCQPSzDoMK3a8pcbUM=
github.com/aws/aws-sdk-go-v2/credentials v1.18.4/go.mod h1:nwg78FjH2qvsRM1EVZlX9WuGUJOL5od+0qvm0adEzHk=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 h1:GicIdnekoJsjq9wqnvyi2elW6CGMSYKhdozE7/Svh78=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3/go.mod h1:R7BIi6WNC5mc1kfRM7XM/VHC3uRWkjc396sfabq4iOo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 h1:o9RnO+YZ4X+kt5Z7Nvcishlz0nksIt2PIzDglLMP0vA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3/go.mod h1:+6aLJzOG1fvMOyzIySYjOFjcguGvVRL68R+uoRencN4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 h1:joyyUFhiTQQmVK6ImzNU9TQSNRNeD9kOklqTzyk5v6s=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3/go.mod h1:+vNIyZQP3b3B1tSLI0lxvrU9cfM7gpdRXMFfm67ZcPc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.16 h1:/ldKrPPXTC421bTNWrUIpq3CxwHwRI/kpc+jPUTJocM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.16/go.mod h1:5vkf/Ws0/wgIMJDQbjI4p2op86hNW6Hie5QtebrDgT8=
github.com/aws/aws-sdk-go-v2/service/kms v1.41.0 h1:2jKyib9msVrAVn+lngwlSplG13RpUZmzVte2yDao5nc=
github.com/aws/aws-sdk-go-v2/service/kms v1.41.0/go.mod h1:RyhzxkWGcfixlkieewzpO3D4P4fTMxhIDqDZWsh0u/4=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.4 h1:EU58LP8ozQDVroOEyAfcq0cGc5R/FTZjVoYJ6tvby3w=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.4/go.mod h1:CrtOgCcysxMvrCoHnvNAD7PHWclmoFG78Q2xLK0KKcs=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.2 h1:XB4z0hbQtpmBnb1FQYvKaCM7UsS6Y/u8jVBwIUGeCTk=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.2/go.mod h1:hwRpqkRxnQ58J9blRDrB4IanlXCpcKmsC83EhG77upg=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.21 h1:nyLjs8sYJShFYj6aiyjCBI3EcLn1udWrQTjEF+SOXB0=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.21/go.mod h1:EhdxtZ+g84MSGrSrHzZiUm9PYiZkrADNja15wtRJSJo=
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 h1:ieRzyHXypu5ByllM7Sp4hC5f/1Fy5wqxqY0yB85hC7s=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3/go.mod h1:O5ROz8jHiOAKAwx179v+7sHMhfobFVi6nZt8DEyiYoM=
github.com/aws/aws-sdk-go-v2/service/kms v1.44.0 h1:Z95XCqqSnwXr0AY7PgsiOUBhUG2GoDM5getw6RfD1Lg=
github.com/aws/aws-sdk-go-v2/service/kms v1.44.0/go.mod h1:DqcSngL7jJeU1fOzh5Ll5rSvX/MlMV6OZlE4mVdFAQc=
github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 h1:Mc/MKBf2m4VynyJkABoVEN+QzkfLqGj0aiJuEe7cMeM=
github.com/aws/aws-sdk-go-v2/service/sso v1.28.0/go.mod h1:iS5OmxEcN4QIPXARGhavH7S8kETNL11kym6jhoS7IUQ=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 h1:6csaS/aJmqZQbKhi1EyEMM7yBW653Wy/B9hnBofW+sw=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0/go.mod h1:59qHWaY5B+Rs7HGTuVGaC32m0rdpQ68N8QCN3khYiqs=
github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 h1:MG9VFW43M4A8BYeAfaJJZWrroinxeTi2r3+SnmLQfSA=
github.com/aws/aws-sdk-go-v2/service/sts v1.37.0/go.mod h1:JdeBDPgpJfuS6rU/hNglmOigKhyEZtBmbraLE4GK1J8=
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM=
github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/caddyserver/caddy/v2 v2.10.0 h1:fonubSaQKF1YANl8TXqGcn4IbIRUDdfAkpcsfI/vX5U=
github.com/caddyserver/caddy/v2 v2.10.0/go.mod h1:q+dgBS3xtIJJGYI2H5Nyh9+4BvhQQ9yCGmECv4Ubdjo=
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
github.com/caddyserver/caddy/v2 v2.10.2 h1:g/gTYjGMD0dec+UgMw8SnfmJ3I9+M2TdvoRL/Ovu6U8=
github.com/caddyserver/caddy/v2 v2.10.2/go.mod h1:TXLQHx+ev4HDpkO6PnVVHUbL6OXt6Dfe7VcIBdQnPL0=
github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/ccoveille/go-safecast v1.6.1 h1:Nb9WMDR8PqhnKCVs2sCB+OqhohwO5qaXtCviZkIff5Q=
github.com/ccoveille/go-safecast v1.6.1/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -134,8 +134,8 @@ github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg=
github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
@@ -167,14 +167,14 @@ github.com/dunglas/caddy-cbrotli v1.0.1 h1:mkg7EB1GmoyfBt3kY3mq4o/0bfnBeq7ZLQjmV
github.com/dunglas/caddy-cbrotli v1.0.1/go.mod h1:uXABy3tjy1FABF+3JWKVh1ajFvIO/kfpwHaeZGSBaAY=
github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54=
github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
github.com/dunglas/mercure v0.20.0 h1:BcgHjdZc7oWapXWHv8RSStz3HvuWallJ0KQ2yejgzgc=
github.com/dunglas/mercure v0.20.0/go.mod h1:pswrX6EiPpYq0I1UZCxGZtw4zIPwYCCcGnN40ZSe6iU=
github.com/dunglas/mercure/caddy v0.20.0 h1:I4Oy4YFDrv63pssf6fdoHez76aH9Q8cugB42RXJr9yE=
github.com/dunglas/mercure/caddy v0.20.0/go.mod h1:vZdsT+3Kr8/fBNDr8vehMYwQsxGCMdD5zwGXc+hd9pM=
github.com/dunglas/vulcain v1.2.0 h1:RPNYuTe0woh4bGfIMAJ3dCgJDN8VJwhDjucQiCOoUsE=
github.com/dunglas/vulcain v1.2.0/go.mod h1:LhyYeqSAEw9P65l25CIzS1sRwJxkP75Qa7p8lIHZPsc=
github.com/dunglas/vulcain/caddy v1.2.0 h1:2O2R7Hn+kkInv6mrmOk5LLDtgRdPKGlXzdFJUKrb/jE=
github.com/dunglas/vulcain/caddy v1.2.0/go.mod h1:RAbiewGNIyWCmT37C2k4O1hs4IF+QIGVVgSNQuW2FH4=
github.com/dunglas/mercure v0.20.2 h1:zQRJ7QgjtQafOc9q3XtYVDgsfbUokvHeBhiFBpNTiIs=
github.com/dunglas/mercure v0.20.2/go.mod h1:sJtQEtezUTKlbDCK1Sqgt9sUrcwBNZJxVc6oHkqvt3w=
github.com/dunglas/mercure/caddy v0.20.2 h1:CGpcT+arSzxr39s2qRvPpS2/yjZV26cElgee+9Au7lg=
github.com/dunglas/mercure/caddy v0.20.2/go.mod h1:96MOkU7u42rFRIQSpzQibC2LT1vwo/AgLA5jivuw7aE=
github.com/dunglas/vulcain v1.2.1 h1:pkPwvIfoa/xmWSVUyhntbIKT+XO2VFMyhLKv1gA61O8=
github.com/dunglas/vulcain v1.2.1/go.mod h1:Qv1hxHP8rMDp6ZrpQQPSOh1OibuAYScNNCL/46sCRXU=
github.com/dunglas/vulcain/caddy v1.2.1 h1:Bh5awZ9WoNqsBv7OZfs1SktJqRgJaF5avI5oDmxs6lI=
github.com/dunglas/vulcain/caddy v1.2.1/go.mod h1:8QrmLTfURmW2VgjTR6Gb9a53FrZjspFQfX5FTy/f6dw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
@@ -192,30 +192,28 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gammazero/deque v1.1.0 h1:OyiyReBbnEG2PP0Bnv1AASLIYvyKqIFN5xfl1t8oGLo=
github.com/gammazero/deque v1.1.0/go.mod h1:JVrR+Bj1NMQbPnYclvDlvSX0nVGReLrQZ0aUMuWLctg=
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA=
github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
@@ -225,8 +223,8 @@ github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -243,8 +241,8 @@ github.com/google/brotli/go/cbrotli v1.1.0/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ=
github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A=
github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs=
@@ -263,8 +261,6 @@ github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -282,8 +278,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
@@ -328,8 +324,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU=
github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -346,11 +342,11 @@ github.com/maypok86/otter v1.2.4 h1:HhW1Pq6VdJkmWwcZZq19BlEQkHtI8xgsQzBVXJU0nfc=
github.com/maypok86/otter v1.2.4/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/mholt/acmez/v3 v3.1.3 h1:gUl789rjbJSuM5hYzOFnNaGgWPV1xVfnOs59o0dZEcc=
github.com/mholt/acmez/v3 v3.1.3/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -373,10 +369,6 @@ github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//J
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
@@ -398,21 +390,21 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA=
github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
@@ -420,8 +412,8 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc=
github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw=
github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E=
github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -454,8 +446,8 @@ github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5k
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/slackhq/nebula v1.9.5 h1:ZrxcvP/lxwFglaijmiwXLuCSkybZMJnqSYI1S8DtGnY=
github.com/slackhq/nebula v1.9.5/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ=
github.com/slackhq/nebula v1.9.6 h1:Fl0LE2dHDeVEK3R+un59Z3V4ZzbZ6q2e/zF4ClaD5yo=
github.com/slackhq/nebula v1.9.6/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
github.com/smallstep/certificates v0.28.4 h1:JTU6/A5Xes6m+OsR6fw1RACSA362vJc9SOFVG7poBEw=
@@ -475,8 +467,8 @@ github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492/go.mod h1:QQhwLqCS1
github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
@@ -488,13 +480,13 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
@@ -512,8 +504,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ=
@@ -538,6 +531,8 @@ github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc=
github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@@ -545,8 +540,8 @@ github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zI
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY=
github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
@@ -555,8 +550,8 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
@@ -588,22 +583,24 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
go.step.sm/crypto v0.67.0 h1:1km9LmxMKG/p+mKa1R4luPN04vlJYnRLlLQrWv7egGU=
go.step.sm/crypto v0.67.0/go.mod h1:+AoDpB0mZxbW/PmOXuwkPSpXRgaUaoIK+/Wx/HGgtAU=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.step.sm/crypto v0.70.0 h1:Q9Ft7N637mucyZcHZd1+0VVQJVwDCKqcb9CYcYi7cds=
go.step.sm/crypto v0.70.0/go.mod h1:pzfUhS5/ue7ev64PLlEgXvhx1opwbhFCjkvlhsxVds0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -615,13 +612,13 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto/x509roots/fallback v0.0.0-20250711192710-b903b535d3ef h1:EJekzaXZlPQg739ghq7w/XWZVcuAOY6mh35JX2D+7Gc=
golang.org/x/crypto/x509roots/fallback v0.0.0-20250711192710-b903b535d3ef/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/crypto/x509roots/fallback v0.0.0-20250826074233-8f580defa01d h1:earVicuMOnaYH+t9T1PeRhC0LHHPGXXTDD7NEEP/Pb4=
golang.org/x/crypto/x509roots/fallback v0.0.0-20250826074233-8f580defa01d/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250717185816-542afb5b7346 h1:vuCObX8mQzik1tfEcYxWZBuVsmQtD1IjxCyPKM18Bh4=
golang.org/x/exp v0.0.0-20250717185816-542afb5b7346/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -630,8 +627,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -648,8 +645,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -668,8 +665,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -692,8 +689,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -703,8 +700,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -715,8 +712,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
@@ -731,14 +728,16 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg=
google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/api v0.248.0 h1:hUotakSkcwGdYUqzCRc5yGYsg4wXxpkKlW5ryVqvC1Y=
google.golang.org/api v0.248.0/go.mod h1:yAFUAF56Li7IuIQbTFoLwXTCI6XCFKueOlS7S9e4F9k=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -750,20 +749,20 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 h1:iOye66xuaAK0WnkPuhQPUFy8eJcmwUXqGGP3om6IxX8=
google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79/go.mod h1:HKJDgKsFUnv5VAGeQjz8kxcgDP0HoE0iZNp0OdZNlhE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 h1:1ZwqphdOdWYXsUHgMpU/101nCtf/kSp9hOrcvFsnl10=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 h1:APHvLLYBhtZvsbnpkfknDZ7NyH4z5+ub/I0u8L3Oz6g=
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1/go.mod h1:xUjFWUnWDpZ/C0Gu0qloASKFb6f8/QXiiXhSPFsD668=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

View File

@@ -6,6 +6,7 @@ import (
"log/slog"
"net/http"
"path/filepath"
"slices"
"strconv"
"strings"
@@ -70,9 +71,8 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
}
for i, wc := range f.Workers {
// make the file path absolute from the public directory
// this can only be done if the root is definied inside php_server
// this can only be done if the root is defined inside php_server
if !filepath.IsAbs(wc.FileName) && f.Root != "" {
wc.FileName = filepath.Join(f.Root, wc.FileName)
}
@@ -263,7 +263,10 @@ func (f *FrankenPHPModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if _, ok := fileNames[w.FileName]; ok {
return fmt.Errorf(`workers in a single "php_server" block must not have duplicate filenames: %q`, w.FileName)
}
fileNames[w.FileName] = struct{}{}
if len(w.MatchPath) == 0 {
fileNames[w.FileName] = struct{}{}
}
}
return nil
@@ -450,12 +453,8 @@ func parsePhpServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
tryPolicy = ""
}
for _, tf := range tryFiles {
if tf == dirIndex {
dirRedir = true
break
}
if slices.Contains(tryFiles, dirIndex) {
dirRedir = true
}
}
@@ -602,7 +601,7 @@ func prependWorkerRoutes(routes caddyhttp.RouteList, h httpcaddyfile.Helper, f F
// forward matching routes to the PHP handler
routes = append(routes, caddyhttp.Route{
MatcherSetsRaw: []caddy.ModuleMap{
caddy.ModuleMap{"path": h.JSON(allWorkerMatches)},
{"path": h.JSON(allWorkerMatches)},
},
HandlersRaw: []json.RawMessage{
caddyconfig.JSONModuleObject(f, "handler", "php", nil),
@@ -611,3 +610,10 @@ func prependWorkerRoutes(routes caddyhttp.RouteList, h httpcaddyfile.Helper, f F
return routes
}
// Interface guards
var (
_ caddy.Provisioner = (*FrankenPHPModule)(nil)
_ caddyhttp.MiddlewareHandler = (*FrankenPHPModule)(nil)
_ caddyfile.Unmarshaler = (*FrankenPHPModule)(nil)
)

View File

@@ -59,7 +59,7 @@ func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
}
if d.NextArg() {
return wc, errors.New(`FrankenPHP: too many "worker" arguments: ` + d.Val())
return wc, d.Errf(`FrankenPHP: too many "worker" arguments: %s`, d.Val())
}
for d.NextBlock(1) {

98
cgi.go
View File

@@ -21,6 +21,16 @@ import (
"github.com/dunglas/frankenphp/internal/phpheaders"
)
// Protocol versions, in Apache mod_ssl format: https://httpd.apache.org/docs/current/mod/mod_ssl.html
// Note that these are slightly different from SupportedProtocols in caddytls/config.go
var tlsProtocolStrings = map[uint16]string{
tls.VersionTLS10: "TLSv1",
tls.VersionTLS11: "TLSv1.1",
tls.VersionTLS12: "TLSv1.2",
tls.VersionTLS13: "TLSv1.3",
}
// Known $_SERVER keys
var knownServerKeys = []string{
"CONTENT_LENGTH",
"DOCUMENT_ROOT",
@@ -204,25 +214,58 @@ func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
thread := phpThreads[threadIndex]
fc := thread.getRequestContext()
addKnownVariablesToServer(thread, fc, trackVarsArray)
addHeadersToServer(fc, trackVarsArray)
if fc.request != nil {
addKnownVariablesToServer(thread, fc, trackVarsArray)
addHeadersToServer(fc, trackVarsArray)
}
// The Prepared Environment is registered last and can overwrite any previous values
addPreparedEnvToServer(fc, trackVarsArray)
}
// splitCgiPath splits the request path into SCRIPT_NAME, SCRIPT_FILENAME, PATH_INFO, DOCUMENT_URI
func splitCgiPath(fc *frankenPHPContext) {
path := fc.request.URL.Path
splitPath := fc.splitPath
if splitPath == nil {
splitPath = []string{".php"}
}
if splitPos := splitPos(path, splitPath); splitPos > -1 {
fc.docURI = path[:splitPos]
fc.pathInfo = path[splitPos:]
// Strip PATH_INFO from SCRIPT_NAME
fc.scriptName = strings.TrimSuffix(path, fc.pathInfo)
// Ensure the SCRIPT_NAME has a leading slash for compliance with RFC3875
// Info: https://tools.ietf.org/html/rfc3875#section-4.1.13
if fc.scriptName != "" && !strings.HasPrefix(fc.scriptName, "/") {
fc.scriptName = "/" + fc.scriptName
}
}
// TODO: is it possible to delay this and avoid saving everything in the context?
// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
fc.scriptFilename = sanitizedPathJoin(fc.documentRoot, fc.scriptName)
fc.worker = getWorkerByPath(fc.scriptFilename)
}
// splitPos returns the index where path should
// be split based on SplitPath.
// example: if splitPath is [".php"]
// "/path/to/script.php/some/path": ("/path/to/script.php", "/some/path")
//
// Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
// Copyright 2015 Matthew Holt and The Caddy Authors
func splitPos(fc *frankenPHPContext, path string) int {
if len(fc.splitPath) == 0 {
func splitPos(path string, splitPath []string) int {
if len(splitPath) == 0 {
return 0
}
lowerPath := strings.ToLower(path)
for _, split := range fc.splitPath {
for _, split := range splitPath {
if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
return idx + len(split)
}
@@ -230,13 +273,44 @@ func splitPos(fc *frankenPHPContext, path string) int {
return -1
}
// Map of supported protocols to Apache ssl_mod format
// Note that these are slightly different from SupportedProtocols in caddytls/config.go
var tlsProtocolStrings = map[uint16]string{
tls.VersionTLS10: "TLSv1",
tls.VersionTLS11: "TLSv1.1",
tls.VersionTLS12: "TLSv1.2",
tls.VersionTLS13: "TLSv1.3",
// go_update_request_info updates the sapi_request_info struct
// See: https://github.com/php/php-src/blob/345e04b619c3bc11ea17ee02cdecad6ae8ce5891/main/SAPI.h#L72
//
//export go_update_request_info
func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info) {
thread := phpThreads[threadIndex]
fc := thread.getRequestContext()
request := fc.request
if request == nil {
return
}
authUser, authPassword, ok := request.BasicAuth()
if ok {
if authPassword != "" {
info.auth_password = thread.pinCString(authPassword)
}
if authUser != "" {
info.auth_user = thread.pinCString(authUser)
}
}
info.request_method = thread.pinCString(request.Method)
info.query_string = thread.pinCString(request.URL.RawQuery)
info.content_length = C.zend_long(request.ContentLength)
if contentType := request.Header.Get("Content-Type"); contentType != "" {
info.content_type = thread.pinCString(contentType)
}
if fc.pathInfo != "" {
info.path_translated = thread.pinCString(sanitizedPathJoin(fc.documentRoot, fc.pathInfo)) // See: http://www.oreilly.com/openbook/cgi/ch02_04.html
}
info.request_uri = thread.pinCString(request.URL.RequestURI())
info.proto_num = C.int(request.ProtoMajor*1000 + request.ProtoMinor)
}
// SanitizedPathJoin performs filepath.Join(root, reqPath) that

View File

@@ -5,6 +5,7 @@ import (
"log/slog"
"net/http"
"os"
"strconv"
"strings"
"time"
)
@@ -27,9 +28,11 @@ type frankenPHPContext struct {
// Whether the request is already closed by us
isDone bool
responseWriter http.ResponseWriter
responseWriter http.ResponseWriter
handlerParameters any
handlerReturn any
done chan interface{}
done chan any
startedAt time.Time
}
@@ -39,13 +42,18 @@ func fromContext(ctx context.Context) (fctx *frankenPHPContext, ok bool) {
return
}
func newFrankenPHPContext() *frankenPHPContext {
return &frankenPHPContext{
done: make(chan any),
startedAt: time.Now(),
}
}
// NewRequestWithContext creates a new FrankenPHP request context.
func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Request, error) {
fc := &frankenPHPContext{
done: make(chan interface{}),
startedAt: time.Now(),
request: r,
}
fc := newFrankenPHPContext()
fc.request = r
for _, o := range opts {
if err := o(fc); err != nil {
return nil, err
@@ -67,36 +75,13 @@ func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Reques
}
}
if fc.splitPath == nil {
fc.splitPath = []string{".php"}
}
if fc.env == nil {
fc.env = make(map[string]string)
}
if splitPos := splitPos(fc, r.URL.Path); splitPos > -1 {
fc.docURI = r.URL.Path[:splitPos]
fc.pathInfo = r.URL.Path[splitPos:]
// Strip PATH_INFO from SCRIPT_NAME
fc.scriptName = strings.TrimSuffix(r.URL.Path, fc.pathInfo)
// Ensure the SCRIPT_NAME has a leading slash for compliance with RFC3875
// Info: https://tools.ietf.org/html/rfc3875#section-4.1.13
if fc.scriptName != "" && !strings.HasPrefix(fc.scriptName, "/") {
fc.scriptName = "/" + fc.scriptName
}
}
// if a worker is assigned explicitly, use its filename
// determine if the filename belongs to a worker otherwise
// If a worker is already assigned explicitly, use its filename and skip parsing path variables
if fc.worker != nil {
fc.scriptFilename = fc.worker.fileName
} else {
// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
fc.scriptFilename = sanitizedPathJoin(fc.documentRoot, fc.scriptName)
fc.worker = getWorkerByPath(fc.scriptFilename)
// If no worker was assigned, split the path into the "traditional" CGI path variables.
// This needs to already happen here in case a worker script still matches the path.
splitCgiPath(fc)
}
c := context.WithValue(r.Context(), contextKey, fc)
@@ -104,6 +89,7 @@ func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Reques
return r.WithContext(c), nil
}
// newDummyContext creates a fake context from a request path
func newDummyContext(requestPath string, opts ...RequestOption) (*frankenPHPContext, error) {
r, err := http.NewRequest(http.MethodGet, requestPath, nil)
if err != nil {
@@ -132,16 +118,29 @@ func (fc *frankenPHPContext) closeContext() {
// validate checks if the request should be outright rejected
func (fc *frankenPHPContext) validate() bool {
if !strings.Contains(fc.request.URL.Path, "\x00") {
return true
if strings.Contains(fc.request.URL.Path, "\x00") {
fc.rejectBadRequest("Invalid request path")
return false
}
fc.rejectBadRequest("Invalid request path")
contentLengthStr := fc.request.Header.Get("Content-Length")
if contentLengthStr != "" {
if contentLength, err := strconv.Atoi(contentLengthStr); err != nil || contentLength < 0 {
fc.rejectBadRequest("invalid Content-Length header: " + contentLengthStr)
return false
return false
}
}
return true
}
func (fc *frankenPHPContext) clientHasClosed() bool {
if fc.request == nil {
return false
}
select {
case <-fc.request.Context().Done():
return true

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
#checkov:skip=CKV_DOCKER_2
#checkov:skip=CKV_DOCKER_3
FROM golang:1.24-alpine
FROM golang:1.25-alpine
ENV GOTOOLCHAIN=local
ENV CFLAGS="-ggdb3"
@@ -79,7 +79,7 @@ WORKDIR /go/src/app
COPY . .
WORKDIR /go/src/app/caddy/frankenphp
RUN go build
RUN ../../go.sh build -buildvcs=false
WORKDIR /go/src/app
CMD [ "zsh" ]

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
#checkov:skip=CKV_DOCKER_2
#checkov:skip=CKV_DOCKER_3
FROM golang:1.24
FROM golang:1.25
ENV GOTOOLCHAIN=local
ENV CFLAGS="-ggdb3"

View File

@@ -11,7 +11,7 @@ variable "PHP_VERSION" {
}
variable "GO_VERSION" {
default = "1.24"
default = "1.25"
}
variable "SHA" {}
@@ -32,9 +32,9 @@ function "tag" {
params = [version, os, php-version, tgt]
result = [
version == "" ? "" : "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}-php${php-version}-${os}", "latest-")}",
php-version == DEFAULT_PHP_VERSION && os == "bookworm" && version != "" ? "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}", "latest-")}" : "",
php-version == DEFAULT_PHP_VERSION && os == "trixie" && version != "" ? "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}", "latest-")}" : "",
php-version == DEFAULT_PHP_VERSION && version != "" ? "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}-${os}", "latest-")}" : "",
os == "bookworm" && version != "" ? "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}-php${php-version}", "latest-")}" : "",
os == "trixie" && version != "" ? "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}-php${php-version}", "latest-")}" : "",
]
}
@@ -76,7 +76,7 @@ function "_php_version" {
target "default" {
name = "${tgt}-php-${replace(php-version, ".", "-")}-${os}"
matrix = {
os = ["bookworm", "alpine"]
os = ["trixie", "bookworm", "alpine"]
php-version = split(",", PHP_VERSION)
tgt = ["builder", "runner"]
}

View File

@@ -17,7 +17,7 @@ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 -
- 附加配置文件: `/etc/frankenphp/php.d/*.ini`
- php 扩展: `/usr/lib/frankenphp/modules/`
如果你的 docker 版本低于 23.0,则会因为 dockerignore [pattern issue](https://github.com/moby/moby/pull/42676) 而导致构建失败。将目录添加到 `.dockerignore`
如果你的 Docker 版本低于 23.0,则会因为 dockerignore [pattern issue](https://github.com/moby/moby/pull/42676) 而导致构建失败。将目录添加到 `.dockerignore`
```patch
!testdata/*.php
@@ -42,7 +42,7 @@ go test -tags watcher -race -v ./...
```console
cd caddy/frankenphp/
go build
go build -tags watcher,brotli,nobadger,nomysql,nopgx
cd ../../
```
@@ -53,10 +53,13 @@ cd testdata/
../caddy/frankenphp/frankenphp run
```
服务器正在监听 `127.0.0.1:8080`
服务器正在监听 `127.0.0.1:80`
> [!NOTE]
> 如果您正在使用 Docker您必须绑定容器的 80 端口或者在容器内部执行命令。
```console
curl -vk https://localhost/phpinfo.php
curl -vk http://127.0.0.1/phpinfo.php
```
## 最小测试服务器
@@ -112,22 +115,22 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
1. 从 GitHub 下载 FrankenPHP 二进制文件的调试版本或创建包含调试符号的自定义静态构建:
```console
docker buildx bake \
--load \
--set static-builder.args.DEBUG_SYMBOLS=1 \
--set "static-builder.platform=linux/amd64" \
static-builder
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
```
```console
docker buildx bake \
--load \
--set static-builder.args.DEBUG_SYMBOLS=1 \
--set "static-builder.platform=linux/amd64" \
static-builder
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
```
2. 将当前版本的 `frankenphp` 替换为 debug FrankenPHP 可执行文件
3. 照常启动 FrankenPHP或者你可以直接使用 GDB 启动 FrankenPHP `gdb --args frankenphp run`
4. 使用 GDB 附加到进程:
```console
gdb -p `pidof frankenphp`
```
```console
gdb -p `pidof frankenphp`
```
5. 如有必要,请在 GDB shell 中输入 `continue`
6. 使 FrankenPHP 崩溃
@@ -139,45 +142,42 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
1. 打开 `.github/workflows/tests.yml`
2. 启用 PHP 调试符号
```patch
- uses: shivammathur/setup-php@v2
# ...
env:
phpts: ts
+ debug: true
```
```patch
- uses: shivammathur/setup-php@v2
# ...
env:
phpts: ts
+ debug: true
```
3. 启用 `tmate` 以连接到容器
```patch
-
name: Set CGO flags
run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
+ -
+ run: |
+ sudo apt install gdb
+ mkdir -p /home/runner/.config/gdb/
+ printf "set auto-load safe-path /\nhandle SIG34 nostop noprint pass" > /home/runner/.config/gdb/gdbinit
+ -
+ uses: mxschmitt/action-tmate@v3
```
```patch
- name: Set CGO flags
run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
+ - run: |
+ sudo apt install gdb
+ mkdir -p /home/runner/.config/gdb/
+ printf "set auto-load safe-path /\nhandle SIG34 nostop noprint pass" > /home/runner/.config/gdb/gdbinit
+ - uses: mxschmitt/action-tmate@v3
```
4. 连接到容器
5. 打开 `frankenphp.go`
6. 启用 `cgosymbolizer`
```patch
- //_ "github.com/ianlancetaylor/cgosymbolizer"
+ _ "github.com/ianlancetaylor/cgosymbolizer"
```
```patch
- //_ "github.com/ianlancetaylor/cgosymbolizer"
+ _ "github.com/ianlancetaylor/cgosymbolizer"
```
7. 下载模块: `go get`
8. 在容器中,可以使用 GDB 和以下:
```console
go test -tags watcher -c -ldflags=-w
gdb --args frankenphp.test -test.run ^MyTest$
```
```console
go test -tags watcher -c -ldflags=-w
gdb --args frankenphp.test -test.run ^MyTest$
```
9. 当错误修复后,恢复所有这些更改
@@ -190,13 +190,12 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
- [PHP 嵌入 C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
- [扩展和嵌入 PHP 作者Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
- [TSRMLS_CC到底是什么](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
- [Mac 上的 PHP 嵌入](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4)
- [SDL 绑定](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
## Docker 相关资源
- [Bake 文件定义](https://docs.docker.com/build/customize/bake/file-definition/)
- [docker buildx 构建](https://docs.docker.com/engine/reference/commandline/buildx_build/)
- [`docker buildx build`](https://docs.docker.com/engine/reference/commandline/buildx_build/)
## 有用的命令

View File

@@ -8,7 +8,7 @@ FrankenPHP 凭借其令人惊叹的功能为你的 PHP 应用程序提供了超
FrankenPHP 可与任何 PHP 应用程序一起使用,并且由于提供了与 worker 模式的集成,使你的 Symfony 和 Laravel 项目比以往任何时候都更快。
FrankenPHP 也可以用作独立的 Go 库,将 PHP 嵌入到任何使用 net/http 的应用程序中。
FrankenPHP 也可以用作独立的 Go 库,将 PHP 嵌入到任何使用 `net/http` 的应用程序中。
[**了解更多** _frankenphp.dev_](https://frankenphp.dev/cn/) 以及查看此演示文稿:
@@ -56,7 +56,7 @@ docker run -v .:/app/public \
> [!TIP]
>
> 不要尝试使用 `https://127.0.0.1`。使用 `https://localhost` 并接受自签名证书。
> 使用 [`SERVER_NAME` 环境变量](config.md#环境变量) 更改要使用的域。
> 使用 [`SERVER_NAME` 环境变量](config.md#environment-variables) 更改要使用的域。
### Homebrew
@@ -76,12 +76,16 @@ frankenphp php-server
## 文档
- [Classic 模式](classic.md)
- [worker 模式](worker.md)
- [早期提示支持(103 HTTP status code)](early-hints.md)
- [实时功能](mercure.md)
- [高效地服务大型静态文件](x-sendfile.md)
- [配置](config.md)
- [用 Go 编写 PHP 扩展](extensions.md)
- [Docker 镜像](docker.md)
- [在生产环境中部署](production.md)
- [性能优化](performance.md)
- [创建独立、可自行执行的 PHP 应用程序](embed.md)
- [创建静态二进制文件](static.md)
- [从源代码编译](compile.md)

11
docs/cn/classic.md Normal file
View File

@@ -0,0 +1,11 @@
# 使用经典模式
在没有任何额外配置的情况下FrankenPHP 以经典模式运行。在此模式下FrankenPHP 的功能类似于传统的 PHP 服务器,直接提供 PHP 文件服务。这使其成为 PHP-FPM 或 Apache with mod_php 的无缝替代品。
与 Caddy 类似FrankenPHP 接受无限数量的连接,并使用[固定数量的线程](config.md#caddyfile-配置)来为它们提供服务。接受和排队的连接数量仅受可用系统资源的限制。
PHP 线程池使用在启动时初始化的固定数量的线程运行,类似于 PHP-FPM 的静态模式。也可以让线程在[运行时自动扩展](performance.md#max_threads),类似于 PHP-FPM 的动态模式。
排队的连接将无限期等待,直到有 PHP 线程可以为它们提供服务。为了避免这种情况,你可以在 FrankenPHP 的全局配置中使用 max_wait_time [配置](config.md#caddyfile-配置)来限制请求可以等待空闲的 PHP 线程的时间,超时后将被拒绝。
此外,你还可以在 Caddy 中设置合理的[写超时](https://caddyserver.com/docs/caddyfile/options#timeouts)。
每个 Caddy 实例只会启动一个 FrankenPHP 线程池,该线程池将在所有 `php_server` 块之间共享。

View File

@@ -9,6 +9,23 @@
FrankenPHP 支持 PHP 8.2 及更高版本。
### 使用 Homebrew (Linux 和 Mac)
安装与 FrankenPHP 兼容的 libphp 版本的最简单方法是使用 [Homebrew PHP](https://github.com/shivammathur/homebrew-php) 提供的 ZTS 包。
首先,如果尚未安装,请安装 [Homebrew](https://brew.sh)。
然后,安装 PHP 的 ZTS 变体、Brotli可选用于压缩支持和 watcher可选用于文件更改检测
```console
brew install shivammathur/php/php-zts brotli watcher
brew link --overwrite --force shivammathur/php/php-zts
```
### 通过编译 PHP
或者,你可以按照以下步骤,使用 FrankenPHP 所需的选项从源代码编译 PHP。
首先,[获取 PHP 源代码](https://www.php.net/downloads.php) 并提取它们:
```console
@@ -16,11 +33,10 @@ tar xf php-*
cd php-*/
```
然后,为你的平台配置 PHP.
然后,运行适用于你平台的 `configure` 脚本。
以下 `./configure` 标志是必需的,但你可以添加其他标志,例如编译扩展或附加功能。
这些参数是必需的,但你也可以添加其他编译参数(例如额外的扩展)。
### Linux
#### Linux
```console
./configure \
@@ -30,12 +46,12 @@ cd php-*/
--enable-zend-max-execution-timers
```
### Mac
#### Mac
使用 [Homebrew](https://brew.sh/) 包管理器安装 `libiconv``bison``re2c``pkg-config`
使用 [Homebrew](https://brew.sh/) 包管理器安装所需的和可选的依赖项
```console
brew install libiconv bison re2c pkg-config
brew install libiconv bison brotli re2c pkg-config watcher
echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
```
@@ -43,16 +59,13 @@ echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
```console
./configure \
--enable-embed=static \
--enable-embed \
--enable-zts \
--disable-zend-signals \
--disable-opcache-jit \
--enable-static \
--enable-shared=no \
--with-iconv=/opt/homebrew/opt/libiconv/
```
## 编译并安装 PHP
#### 编译 PHP
最后,编译并安装 PHP
@@ -61,30 +74,36 @@ make -j"$(getconf _NPROCESSORS_ONLN)"
sudo make install
```
## 安装可选依赖项
某些 FrankenPHP 功能依赖于必须安装的可选系统依赖项。
或者,可以通过向 Go 编译器传递构建标签来禁用这些功能。
| 功能 | 依赖项 | 用于禁用的构建标签 |
| --------------------- | --------------------------------------------------------------------- | ------------------ |
| Brotli 压缩 | [Brotli](https://github.com/google/brotli) | nobrotli |
| 文件更改时重启 worker | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
## 编译 Go 应用
你现在可以使用 Go 库并编译我们的 Caddy 构建:
```console
curl -L https://github.com/php/frankenphp/archive/refs/heads/main.tar.gz | tar xz
cd frankenphp-main/caddy/frankenphp
CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build
```
你现在可以构建最终的二进制文件。
### 使用 xcaddy
你可以使用 [xcaddy](https://github.com/caddyserver/xcaddy) 来编译 [自定义 Caddy 模块](https://caddyserver.com/docs/modules/) 的 FrankenPHP
推荐的方法是使用 [xcaddy](https://github.com/caddyserver/xcaddy) 来编译 FrankenPHP
`xcaddy` 还允许轻松添加 [自定义 Caddy 模块](https://caddyserver.com/docs/modules/) 和 FrankenPHP 扩展:
```console
CGO_ENABLED=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags '-w -s'" \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output frankenphp \
--with github.com/dunglas/frankenphp/caddy \
--with github.com/dunglas/caddy-cbrotli \
--with github.com/dunglas/mercure/caddy \
--with github.com/dunglas/vulcain/caddy
# Add extra Caddy modules here
# 在这里添加额外的 Caddy 模块和 FrankenPHP 扩展
```
> [!TIP]
@@ -96,3 +115,13 @@ xcaddy build \
> 请将 `XCADDY_GO_BUILD_FLAGS` 环境变量更改为如下类似的值
> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'`
> (根据你的应用需求更改堆栈大小)。
### 不使用 xcaddy
或者,可以通过直接使用 `go` 命令来编译 FrankenPHP 而不使用 `xcaddy`
```console
curl -L https://github.com/php/frankenphp/archive/refs/heads/main.tar.gz | tar xz
cd frankenphp-main/caddy/frankenphp
CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build -tags=nobadger,nomysql,nopgx
```

View File

@@ -1,41 +1,44 @@
# 配置
# 配置
FrankenPHPCaddy 以及 Mercure 和 Vulcain 模块可以使用 [Caddy 支持的格式](https://caddyserver.com/docs/getting-started#your-first-config) 进行配置。
FrankenPHPCaddy 以及 Mercure 和 Vulcain 模块可以使用 [Caddy 支持的格式](https://caddyserver.com/docs/getting-started#your-first-config) 进行配置。
在 [Docker 镜像](docker.md) 中,`Caddyfile` 位于 `/etc/frankenphp/Caddyfile`
静态二进制文件也会在执行 `frankenphp run` 命令的目录中查找 `Caddyfile`
你可以使用 `-c``--config` 选项指定自定义路径。
在[Docker 映像](docker.md) 中,`Caddyfile` 位于 `/etc/frankenphp/Caddyfile`
静态二进制文件会在启动时所在的目录中查找 `Caddyfile`
PHP 本身可以[使用 `php.ini` 文件](https://www.php.net/manual/zh/configuration.file.php)进行配置。
PHP 解释器将在以下位置查找:
Docker:
根据你的安装方法PHP 解释器将在上述位置查找配置文件。
- php.ini: `/usr/local/etc/php/php.ini` 默认情况下不提供 php.ini。
## Docker
- `php.ini`: `/usr/local/etc/php/php.ini`(默认情况下不提供 `php.ini`
- 附加配置文件: `/usr/local/etc/php/conf.d/*.ini`
- php 扩展: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
- PHP 扩展: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
- 你应该复制 PHP 项目提供的官方模板:
```dockerfile
FROM dunglas/frankenphp
# 生产:
# 生产环境:
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
# 开发:
# 开发环境:
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
```
FrankenPHP 安装 (.rpm 或 .deb):
## RPM 和 Debian 包
- php.ini: `/etc/frankenphp/php.ini` 默认情况下提供带有生产预设的 php.ini 文件
- `php.ini`: `/etc/frankenphp/php.ini`默认情况下提供带有生产预设的 `php.ini` 文件
- 附加配置文件: `/etc/frankenphp/php.d/*.ini`
- php 扩展: `/usr/lib/frankenphp/modules/`
- PHP 扩展: `/usr/lib/frankenphp/modules/`
静态二进制:
## 静态二进制文件
- php.ini: 执行 `frankenphp run``frankenphp php-server` 的目录,然后是 `/etc/frankenphp/php.ini`
- `php.ini`: 执行 `frankenphp run``frankenphp php-server` 的目录,然后是 `/etc/frankenphp/php.ini`
- 附加配置文件: `/etc/frankenphp/php.d/*.ini`
- php 扩展: 无法加载
- 复制[PHP 源代码](https://github.com/php/php-src/)中提供的`php.ini-production``php.ini-development`中的一个。
- PHP 扩展: 无法加载,将它们打包在二进制文件本身中
- 复制 [PHP 源代码](https://github.com/php/php-src/) 中提供的 `php.ini-production``php.ini-development` 中的一个。
## Caddyfile 配置
@@ -47,22 +50,28 @@ FrankenPHP 安装 (.rpm 或 .deb):
localhost {
# 启用压缩(可选)
encode zstd br gzip
# 执行当前目录中 PHP 文件并提供资
# 当前目录中执行 PHP 文件并提供资源服务
php_server
}
```
可以使用全局选项显式配置 FrankenPHP
可以使用全局选项显式配置 FrankenPHP:
`frankenphp` [全局选项](https://caddyserver.com/docs/caddyfile/concepts#global-options) 可用于配置 FrankenPHP。
```caddyfile
{
frankenphp {
num_threads <num_threads> # 设置要启动的 PHP 线程数。默认:可用 CPU 数量的 2 倍。
num_threads <num_threads> # 设置要启动的 PHP 线程数。默认:可用 CPU 数量的 2 倍。
max_threads <max_threads> # 限制可以在运行时启动的额外 PHP 线程的数量。默认值num_threads。可以设置为 'auto'。
max_wait_time <duration> # 设置请求在超时之前可以等待的最大时间,直到找到一个空闲的 PHP 线程。 默认:禁用。
php_ini <key> <value> # 设置一个 php.ini 指令。可以多次使用以设置多个指令。
worker {
file <path> # 设置 worker 脚本的路径。
num <num> # 设置要启动的 PHP 线程数,默认为可用 CPU 数的 2 倍。
env <key> <value> # 额外的环境变量设置为给定值。可以多个环境变量多次指定
file <path> # 设置工作脚本的路径。
num <num> # 设置要启动的 PHP 线程数,默认为可用 CPU 数的 2 倍。
env <key> <value> # 设置一个额外的环境变量为给定值。可以多次指定以设置多个环境变量。
watch <path> # 设置要监视文件更改的路径。可以为多个路径多次指定。
name <name> # 设置worker的名称用于日志和指标。默认值worker文件的绝对路径。
max_consecutive_failures <num> # 设置在工人被视为不健康之前的最大连续失败次数,-1意味着工人将始终重新启动。默认值6。
}
}
}
@@ -70,7 +79,7 @@ localhost {
# ...
```
或者,可以使用 `worker` 选项的一行缩写形式
或者,可以使用 `worker` 选项的一行简短形式
```caddyfile
{
@@ -82,11 +91,11 @@ localhost {
# ...
```
如果在同一服务器上运行多个应用还可以定义多个 worker
如果在同一服务器上服务多个应用程序,您还可以定义多个工作线程:
```caddyfile
app.example.com {
root /path/to/app/public
root /path/to/app/public
php_server {
root /path/to/app/public # 允许更好的缓存
worker index.php <num>
@@ -94,23 +103,26 @@ app.example.com {
}
other.example.com {
root /path/to/other/public
root /path/to/other/public
php_server {
root /path/to/other/public
worker index.php <num>
}
}
# ...
```
通常你只需要 `php_server` 指令
但如果要完全控制,可以使用低级`php` 指令
使用 `php_server` 指令通常是您需要的。
如果你需要完全控制,可以使用低级的 `php` 指令
`php` 指令将所有输入传递给 PHP而不是先检查是否
是一个PHP文件。在[性能页面](performance.md#try_files)中了解更多关于它的信息。
使用 `php_server` 指令等于以下配置
使用 `php_server` 指令等于以下配置:
```caddyfile
route {
# 为目录请求添加尾斜杠
# 为目录请求添加尾斜杠
@canonicalPath {
file {path}/index.php
not path */
@@ -129,43 +141,161 @@ route {
}
```
`php_server``php` 指令有以下选项:
`php_server``php` 指令有以下选项:
```caddyfile
php_server [<matcher>] {
root <directory> # 设置站点的根目录。默认值:`root` 指令。
split_path <delim...> # 设置用于将 URI 拆分为两部分的子字符串。第一个匹配的子字符串将用于从路径中拆分"路径信息"。第一部分匹配的子字符串为后缀,并将假定为实际资源(CGI 脚本)名称。第二部分将设置为PATH_INFO,供脚本使用。默认值:`.php`
resolve_root_symlink false # 禁用将 `root` 目录在符号链接时将其解析为实际值(默认启用)。
env <key> <value> # 设置额外的环境变量,可以设置多个环境变量。
root <directory> # 将根文件夹设置站点。默认值:`root` 指令。
split_path <delim...> # 设置用于将 URI 分割成两部分的子字符串。第一个匹配的子字符串将用来将 "路径信息" 与路径分开。第一部分后缀为匹配的子字符串,并将被视为实际资源CGI 脚本名称。第二部分将设置为脚本使用的 PATH_INFO。默认值`.php`
resolve_root_symlink false # 禁用通过评估符号链接(如果存在)将 `root` 目录解析为实际值(默认启用)。
env <key> <value> # 设置一个额外的环境变量为给定的值。可以多次指定以设置多个环境变量。
file_server off # 禁用内置的 file_server 指令。
worker { # 创建特定于此服务器的 worker。可以多个 worker 多次指定
file <path> # 设置 worker 脚本的路径,可以相对于 php_server 根目录
num <num> # 设置要启动的 PHP 线程数,默认为可用 CPU 数的 2 倍
name <name> # 为 worker 设置名称用于日志和指标。默认值worker 文件的绝对路径。在 php_server 块中定义时始终以 m# 开头。
worker { # 为此服务器创建特定的worker。可以多次指定以创建多个workers
file <path> # 设置工作脚本的路径,可以相对于 php_server 根目录
num <num> # 设置要启动的 PHP 线程数,默认为可用数的 2 倍
name <name> # 为worker设置名称用于日志和指标。默认值worker文件的绝对路径。定义在 php_server 块中时始终以 m# 开头。
watch <path> # 设置要监视文件更改的路径。可以为多个路径多次指定。
env <key> <value> # 额外的环境变量设置为给定值。可以为多个环境变量多次指定。此 worker 的环境变量也从 php_server 父继承,但可以在此处覆盖。
env <key> <value> # 设置一个额外的环境变量为给定值。可以多次指定以设置多个环境变量。此工作进程的环境变量也从 php_server 父进程继承,但可以在此处覆盖。
match <path> # 将worker匹配到路径模式。覆盖 try_files并且只能在 php_server 指令中使用。
}
worker <other_file> <num> # 也可以像在全局 frankenphp 块中样使用简短形式。
worker <other_file> <num> # 也可以像在全局 frankenphp 块中样使用简短形式。
}
```
### 监控文件变化
由于 workers 只会启动您的应用程序一次并将其保留在内存中,
因此对您的 PHP 文件的任何更改不会立即反映出来。
Wworkers 可以通过 `watch` 指令在文件更改时重新启动。
这对开发环境很有用。
```caddyfile
{
frankenphp {
worker {
file /path/to/app/public/worker.php
watch
}
}
}
```
如果没有指定 `watch` 目录,它将回退到 `./**/*.{php,yaml,yml,twig,env}`
这将监视启动 FrankenPHP 进程的目录及其子目录中的所有 `.php``.yaml``.yml``.twig``.env` 文件。
你也可以通过 [shell 文件名模式](https://pkg.go.dev/path/filepath#Match) 指定一个或多个目录:
```caddyfile
{
frankenphp {
worker {
file /path/to/app/public/worker.php
watch /path/to/app # 监视 /path/to/app 所有子目录中的所有文件
watch /path/to/app/*.php # 监视位于/path/to/app中的以.php结尾的文件
watch /path/to/app/**/*.php # 监视 /path/to/app 及子目录中的 PHP 文件
watch /path/to/app/**/*.{php,twig} # 在/path/to/app及其子目录中监视PHP和Twig文件
}
}
}
```
- `**` 模式表示递归监视
- 目录也可以是相对的相对于FrankenPHP进程启动的位置
- 如果您定义了多个workers当文件发生更改时将重新启动所有workers。
- 小心查看在运行时创建的文件(如日志),因为它们可能导致不必要的工作进程重启。
文件监视器基于[e-dant/watcher](https://github.com/e-dant/watcher)。
## 将 worker 匹配到一条路径
在传统的PHP应用程序中脚本总是放在公共目录中。
这对于工作脚本也是如此这些脚本被视为任何其他PHP脚本。
如果您想将工作脚本放在公共目录外,可以通过 `match` 指令来实现。
`match` 指令是 `try_files` 的一种优化替代方案,仅在 `php_server``php` 内部可用。
以下示例将在公共目录中提供文件(如果存在)
并将请求转发给与路径模式匹配的 worker。
```caddyfile
{
frankenphp {
php_server {
worker {
file /path/to/worker.php # 文件可以在公共路径之外
match /api/* # 所有以 /api/ 开头的请求将由此 worker 处理
}
}
}
}
```
### 全双工 (HTTP/1)
在使用HTTP/1.x时可能希望启用全双工模式以便在完整主体之前写入响应。
已被阅读。(例如WebSocket、服务器发送事件等。)
这是一个可选配置,需要添加到 `Caddyfile` 中的全局选项中:
```caddyfile
{
servers {
enable_full_duplex
}
}
```
> [!CAUTION]
>
> 启用此选项可能导致不支持全双工的旧HTTP/1.x客户端死锁。
> 这也可以通过配置 `CADDY_GLOBAL_OPTIONS` 环境配置来实现:
```sh
CADDY_GLOBAL_OPTIONS="servers {
enable_full_duplex
}"
```
您可以在[Caddy文档](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex)中找到有关此设置的更多信息。
## 环境变量
以下环境变量可用于`Caddyfile` 注入 Caddy 指令,而无需对其进行修改
可以使用以下环境变量在不修改 `Caddyfile` 的情况下注入 Caddy 指令:
- `SERVER_NAME`: 更改 [监听的地址](https://caddyserver.com/docs/caddyfile/concepts#addresses),提供的主名也将用于生成的 TLS 证书
- `CADDY_GLOBAL_OPTIONS`: 注入 [全局选项](https://caddyserver.com/docs/caddyfile/options)
- `SERVER_NAME`: 更改[监听的地址](https://caddyserver.com/docs/caddyfile/concepts#addresses),提供的宿主名也将用于生成的TLS证书
- `SERVER_ROOT`: 更改网站的根目录,默认为 `public/`
- `CADDY_GLOBAL_OPTIONS`: 注入[全局选项](https://caddyserver.com/docs/caddyfile/options)
- `FRANKENPHP_CONFIG`: 在 `frankenphp` 指令下注入配置
至于 FPM 和 CLI SAPIs环境变量默认在 `$_SERVER` 超全局中暴露。
[the `variables_order` PHP 指令](https://www.php.net/manual/en/ini.core.php#ini.variables-order) 的 `S` 值始终等于 `ES`,无论 `E` 在该指令中的其他位置如何。
## PHP 配置
加载 [其他 PHP INI 配置文件](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan)
可以使用 `PHP_INI_SCAN_DIR` 环境变量。
设置后PHP 将加载给定目录中存在 `.ini` 扩展名的所有文件。
加载[附加的 PHP 配置文件](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan)
`PHP_INI_SCAN_DIR`环境变量可以被使用
设置后PHP 将加载给定目录中所有带有 `.ini` 扩展名的文件。
您还可以通过在 `Caddyfile` 中使用 `php_ini` 指令来更改 PHP 配置:
```caddyfile
{
frankenphp {
php_ini memory_limit 256M
# 或者
php_ini {
memory_limit 256M
max_execution_time 15
}
}
}
```
## 启用调试模式
使用 Docker 镜像时,将 `CADDY_GLOBAL_OPTIONS` 环境变量设置为 `debug` 以启用调试模式
使用Docker镜像时`CADDY_GLOBAL_OPTIONS`环境变量设置为`debug`以启用调试模式:
```console
docker run -v $PWD:/app/public \

View File

@@ -1,7 +1,15 @@
# 构建自定义 Docker 镜像
[FrankenPHP Docker 镜像](https://hub.docker.com/r/dunglas/frankenphp) 基于 [官方 PHP 镜像](https://hub.docker.com/_/php/)。
Alpine Linux 和 Debian 衍生版适用于常见的处理器架构,支持 PHP 8.2、8.3 和 8.4。。[查看 Tags](https://hub.docker.com/r/dunglas/frankenphp/tags)。
[FrankenPHP Docker 镜像](https://hub.docker.com/r/dunglas/frankenphp) 基于 [官方 PHP 镜像](https://hub.docker.com/_/php/)。提供适用于流行架构的 Debian 和 Alpine Linux 变体。推荐使用 Debian 变体。
提供 PHP 8.2、8.3 和 8.4 的变体。
标签遵循此模式:`dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
- `<frankenphp-version>``<php-version>` 分别是 FrankenPHP 和 PHP 的版本号,范围从主版本(例如 `1`)、次版本(例如 `1.2`)到补丁版本(例如 `1.2.3`)。
- `<os>` 要么是 `bookworm`(用于 Debian Bookworm要么是 `alpine`(用于 Alpine 的最新稳定版本)。
[浏览标签](https://hub.docker.com/r/dunglas/frankenphp/tags)。
## 如何使用镜像
@@ -71,13 +79,13 @@ FROM dunglas/frankenphp AS runner
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
```
FrankenPHP 提供的 `builder` 镜像包含 libphp 的编译版本。
FrankenPHP 提供的 `builder` 镜像包含 `libphp` 的编译版本。
[用于构建的镜像](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) 适用于所有版本的 FrankenPHP 和 PHP包括 Alpine 和 Debian。
> [!TIP]
>
> 如果你的系统基于 musl libcAlpine Linux 上默认使用)并搭配 Symfony 使用,
> 你可能需要 [增加默认堆栈大小](compile.md#使用-xcaddy)。
> 你可能需要 [增加默认堆栈大小](compile.md#using-xcaddy)。
## 默认启用 worker 模式
@@ -136,7 +144,7 @@ volumes:
FrankenPHP 可以在 Docker 中以非 root 用户身份运行。
下面是一个示例 Dockerfile
下面是一个示例 `Dockerfile`
```dockerfile
FROM dunglas/frankenphp
@@ -148,18 +156,45 @@ RUN \
useradd ${USER}; \
# 需要开放80和443端口的权限
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
# 需要 /data/caddy 和 /config/caddy 目录的写入权限
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy;
# 需要 /config/caddy 和 /data/caddy 目录的写入权限
chown -R ${USER}:${USER} /config/caddy /data/caddy
USER ${USER}
```
### 无权限运行
即使在无根运行时FrankenPHP 也需要 `CAP_NET_BIND_SERVICE` 权限来将
Web 服务器绑定到特权端口80 和 443
如果你在非特权端口1024 及以上)上公开 FrankenPHP则可以以非 root 用户身份运行
Web 服务器,并且不需要任何权限:
```dockerfile
FROM dunglas/frankenphp
ARG USER=appuser
RUN \
# 在基于 alpine 的发行版使用 "adduser -D ${USER}"
useradd ${USER}; \
# 移除默认权限
setcap -r /usr/local/bin/frankenphp; \
# 给予 /config/caddy 和 /data/caddy 写入权限
chown -R ${USER}:${USER} /config/caddy /data/caddy
USER ${USER}
```
接下来,设置 `SERVER_NAME` 环境变量以使用非特权端口。
示例:`:8000`
## 更新
Docker 镜像会按照以下条件更新:
* 发布新的版本后
* 每日 4:00UTC 时间)检查新的 PHP 镜像
- 发布新的版本后
- 每日 4:00UTC 时间)检查新的 PHP 镜像
## 开发版本

View File

@@ -6,16 +6,18 @@ FrankenPHP 能够将 PHP 应用程序的源代码和资源文件嵌入到静态
了解有关此功能的更多信息 [Kévin 在 SymfonyCon 上的演讲](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/)。
有关嵌入 Laravel 应用程序,请[阅读此特定文档条目](laravel.md#laravel-apps-as-standalone-binaries)。
## 准备你的应用
在创建独立二进制文件之前,请确保应用已准备好进行打包。
例如,你可能希望:
* 给应用安装生产环境的依赖
* 导出 autoloader
* 如果可能,为应用启用生产模式
* 丢弃不需要的文件,例如 `.git` 或测试文件,以减小最终二进制文件的大小
- 给应用安装生产环境的依赖
- 导出 autoloader
- 如果可能,为应用启用生产模式
- 丢弃不需要的文件,例如 `.git` 或测试文件,以减小最终二进制文件的大小
例如,对于 Symfony 应用程序,你可以使用以下命令:
@@ -29,7 +31,8 @@ cd $TMPDIR/my-prepared-app
echo APP_ENV=prod > .env.local
echo APP_DEBUG=0 >> .env.local
# 删除测试文件
# 删除测试和其他不需要的文件以节省空间
# 或者,将这些文件添加到您的 .gitattributes 文件中,并设置 export-ignore 属性
rm -Rf tests/
# 安装依赖项
@@ -39,42 +42,45 @@ composer install --ignore-platform-reqs --no-dev -a
composer dump-env prod
```
### 自定义配置
要自定义[配置](config.md),您可以放置一个 `Caddyfile` 以及一个 `php.ini` 文件
在应用程序的主目录中嵌入(在之前的示例中是`$TMPDIR/my-prepared-app`)。
## 创建 Linux 二进制文件
创建 Linux 二进制文件的最简单方法是使用我们提供的基于 Docker 的构建器。
1. 在准备好的应用的存储库中创建一个名为 `static-build.Dockerfile` 的文件。
```dockerfile
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
```dockerfile
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
# 复制应用代码
WORKDIR /go/src/app/dist/app
COPY . .
# 复制应用代码
WORKDIR /go/src/app/dist/app
COPY . .
# 构建静态二进制文件,只选择你需要的 PHP 扩展
WORKDIR /go/src/app/
RUN EMBED=dist/app/ \
PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \
./build-static.sh
```
# 构建静态二进制文件
WORKDIR /go/src/app/
RUN EMBED=dist/app/ ./build-static.sh
```
> [!CAUTION]
>
> 某些 .dockerignore 文件(例如默认的 [symfony-docker .dockerignore](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore)会忽略 vendor
> 文件夹和环境文件。在构建之前,请务必调整或删除 .dockerignore 文件。
> [!CAUTION]
>
> 某些 `.dockerignore` 文件(例如默认的 [Symfony Docker `.dockerignore`](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore)
> 会忽略 `vendor/` 文件夹和 `.env` 文件。在构建之前,请务必调整或删除 `.dockerignore` 文件。
2. 构建:
```console
docker build -t static-app -f static-build.Dockerfile .
```
```console
docker build -t static-app -f static-build.Dockerfile .
```
3. 提取二进制文件
```console
docker cp $(docker create --name static-app-tmp static-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-app-tmp
```
```console
docker cp $(docker create --name static-app-tmp static-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-app-tmp
```
生成的二进制文件是当前目录中名为 `my-app` 的文件。
@@ -85,9 +91,7 @@ composer dump-env prod
```console
git clone https://github.com/php/frankenphp
cd frankenphp
EMBED=/path/to/your/app \
PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \
./build-static.sh
EMBED=/path/to/your/app ./build-static.sh
```
在 `dist/` 目录中生成的二进制文件名称为 `frankenphp-<os>-<arch>`。
@@ -120,13 +124,20 @@ EMBED=/path/to/your/app \
./my-app php-cli bin/console
```
## PHP Extensions
默认情况下,脚本将构建您项目的 `composer.json` 文件中所需的扩展(如果有的话)。
如果 `composer.json` 文件不存在,将构建默认扩展,如 [静态构建条目](static.md) 中所述。
要自定义扩展,请使用 `PHP_EXTENSIONS` 环境变量。
## 自定义构建
[阅读静态构建文档](static.md) 查看如何自定义二进制文件扩展、PHP 版本等)。
## 分发二进制文件
创建的二进制文件不会被压缩。
若要在发送文件之前减小文件的大小,可以对其进行压缩。
在Linux上创建的二进制文件使用[UPX](https://upx.github.io)进行压缩。
在Mac上您可以在发送文件之前压缩它以减小文件大小。
我们推荐使用 `xz`。

747
docs/cn/extensions.md Normal file
View File

@@ -0,0 +1,747 @@
# 使用 Go 编写 PHP 扩展
使用 FrankenPHP你可以**使用 Go 编写 PHP 扩展**,这允许你创建**高性能的原生函数**,可以直接从 PHP 调用。你的应用程序可以利用任何现有或新的 Go 库,以及直接从你的 PHP 代码中使用**协程goroutines的并发模型**。
编写 PHP 扩展通常使用 C 语言完成但通过一些额外的工作也可以使用其他语言编写。PHP 扩展允许你利用底层语言的强大功能来扩展 PHP 的功能,例如,通过添加原生函数或优化特定操作。
借助 Caddy 模块,你可以使用 Go 编写 PHP 扩展,并将其快速集成到 FrankenPHP 中。
## 两种方法
FrankenPHP 提供两种方式来创建 Go 语言的 PHP 扩展:
1. **使用扩展生成器** - 推荐的方法,为大多数用例生成所有必要的样板代码,让你专注于编写 Go 代码
2. **手动实现** - 对于高级用例,完全控制扩展结构
我们将从生成器方法开始,因为这是最简单的入门方式,然后为那些需要完全控制的人展示手动实现。
## 使用扩展生成器
FrankenPHP 捆绑了一个工具,允许你**仅使用 Go 创建 PHP 扩展**。**无需编写 C 代码**或直接使用 CGOFrankenPHP 还包含一个**公共类型 API**,帮助你在 Go 中编写扩展,而无需担心**PHP/C 和 Go 之间的类型转换**。
> [!TIP]
> 如果你想了解如何从头开始在 Go 中编写扩展,可以阅读下面的手动实现部分,该部分演示了如何在不使用生成器的情况下在 Go 中编写 PHP 扩展。
请记住,此工具**不是功能齐全的扩展生成器**。它旨在帮助你在 Go 中编写简单的扩展,但它不提供 PHP 扩展的最高级功能。如果你需要编写更**复杂和优化**的扩展,你可能需要编写一些 C 代码或直接使用 CGO。
### 先决条件
正如下面的手动实现部分所涵盖的,你需要[获取 PHP 源代码](https://www.php.net/downloads.php)并创建一个新的 Go 模块。
#### 创建新模块并获取 PHP 源代码
在 Go 中编写 PHP 扩展的第一步是创建一个新的 Go 模块。你可以使用以下命令:
```console
go mod init github.com/my-account/my-module
```
第二步是为后续步骤[获取 PHP 源代码](https://www.php.net/downloads.php)。获取后,将它们解压到你选择的目录中,不要放在你的 Go 模块内:
```console
tar xf php-*
```
### 编写扩展
现在一切都设置好了,可以在 Go 中编写你的原生函数。创建一个名为 `stringext.go` 的新文件。我们的第一个函数将接受一个字符串作为参数,重复次数,一个布尔值来指示是否反转字符串,并返回结果字符串。这应该看起来像这样:
```go
import (
"C"
"github.com/dunglas/frankenphp"
"strings"
)
//export_php:function repeat_this(string $str, int $count, bool $reverse): string
func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(s))
result := strings.Repeat(str, int(count))
if reverse {
runes := []rune(result)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
result = string(runes)
}
return frankenphp.PHPString(result, false)
}
```
这里有两个重要的事情要注意:
- 指令注释 `//export_php:function` 定义了 PHP 中的函数签名。这是生成器知道如何使用正确的参数和返回类型生成 PHP 函数的方式;
- 函数必须返回 `unsafe.Pointer`。FrankenPHP 提供了一个 API 来帮助你在 C 和 Go 之间进行类型转换。
虽然第一点不言自明,但第二点可能更难理解。让我们在下一节中深入了解类型转换。
### 类型转换
虽然一些变量类型在 C/PHP 和 Go 之间具有相同的内存表示,但某些类型需要更多逻辑才能直接使用。这可能是编写扩展时最困难的部分,因为它需要了解 Zend 引擎的内部结构以及变量在 PHP 中的内部存储方式。此表总结了你需要知道的内容:
| PHP 类型 | Go 类型 | 直接转换 | C 到 Go 助手 | Go 到 C 助手 | 类方法支持 |
| ------------------ | ------------------- | -------- | --------------------- | ---------------------- | ---------- |
| `int` | `int64` | ✅ | - | - | ✅ |
| `?int` | `*int64` | ✅ | - | - | ✅ |
| `float` | `float64` | ✅ | - | - | ✅ |
| `?float` | `*float64` | ✅ | - | - | ✅ |
| `bool` | `bool` | ✅ | - | - | ✅ |
| `?bool` | `*bool` | ✅ | - | - | ✅ |
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
| `object` | `struct` | ❌ | _尚未实现_ | _尚未实现_ | ❌ |
> [!NOTE]
> 此表尚不详尽,将随着 FrankenPHP 类型 API 变得更加完整而完善。
>
> 特别是对于类方法,目前支持原始类型和数组。对象尚不能用作方法参数或返回类型。
如果你参考上一节的代码片段,你可以看到助手用于转换第一个参数和返回值。我们的 `repeat_this()` 函数的第二和第三个参数不需要转换,因为底层类型的内存表示对于 C 和 Go 都是相同的。
#### 处理数组
FrankenPHP 通过 `frankenphp.Array` 类型为 PHP 数组提供原生支持。此类型表示 PHP 索引数组(列表)和关联数组(哈希映射),具有有序的键值对。
**在 Go 中创建和操作数组:**
```go
//export_php:function process_data(array $input): array
func process_data(arr *C.zval) unsafe.Pointer {
// 将 PHP 数组转换为 Go
goArray := frankenphp.GoArray(unsafe.Pointer(arr))
result := &frankenphp.Array{}
result.SetInt(0, "first")
result.SetInt(1, "second")
result.Append("third") // 自动分配下一个整数键
result.SetString("name", "John")
result.SetString("age", int64(30))
for i := uint32(0); i < goArray.Len(); i++ {
key, value := goArray.At(i)
if key.Type == frankenphp.PHPStringKey {
result.SetString("processed_"+key.Str, value)
} else {
result.SetInt(key.Int+100, value)
}
}
// 转换回 PHP 数组
return frankenphp.PHPArray(result)
}
```
**`frankenphp.Array` 的关键特性:**
- **有序键值对** - 像 PHP 数组一样维护插入顺序
- **混合键类型** - 在同一数组中支持整数和字符串键
- **类型安全** - `PHPKey` 类型确保正确的键处理
- **自动列表检测** - 转换为 PHP 时,自动检测数组应该是打包列表还是哈希映射
- **不支持对象** - 目前,只有标量类型和数组可以用作值。提供对象将导致 PHP 数组中的 `null` 值。
**可用方法:**
- `SetInt(key int64, value any)` - 使用整数键设置值
- `SetString(key string, value any)` - 使用字符串键设置值
- `Append(value any)` - 使用下一个可用整数键添加值
- `Len() uint32` - 获取元素数量
- `At(index uint32) (PHPKey, any)` - 获取索引处的键值对
- `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - 转换为 PHP 数组
### 声明原生 PHP 类
生成器支持将 Go 结构体声明为**不透明类**,可用于创建 PHP 对象。你可以使用 `//export_php:class` 指令注释来定义 PHP 类。例如:
```go
//export_php:class User
type UserStruct struct {
Name string
Age int
}
```
#### 什么是不透明类?
**不透明类**是内部结构(属性)对 PHP 代码隐藏的类。这意味着:
- **无直接属性访问**:你不能直接从 PHP 读取或写入属性(`$user->name` 不起作用)
- **仅方法接口** - 所有交互必须通过你定义的方法进行
- **更好的封装** - 内部数据结构完全由 Go 代码控制
- **类型安全** - 没有 PHP 代码使用错误类型破坏内部状态的风险
- **更清晰的 API** - 强制设计适当的公共接口
这种方法提供了更好的封装,并防止 PHP 代码意外破坏 Go 对象的内部状态。与对象的所有交互都必须通过你明确定义的方法进行。
#### 为类添加方法
由于属性不能直接访问,你**必须定义方法**来与不透明类交互。使用 `//export_php:method` 指令来定义行为:
```go
//export_php:class User
type UserStruct struct {
Name string
Age int
}
//export_php:method User::getName(): string
func (us *UserStruct) GetUserName() unsafe.Pointer {
return frankenphp.PHPString(us.Name, false)
}
//export_php:method User::setAge(int $age): void
func (us *UserStruct) SetUserAge(age int64) {
us.Age = int(age)
}
//export_php:method User::getAge(): int
func (us *UserStruct) GetUserAge() int64 {
return int64(us.Age)
}
//export_php:method User::setNamePrefix(string $prefix = "User"): void
func (us *UserStruct) SetNamePrefix(prefix *C.zend_string) {
us.Name = frankenphp.GoString(unsafe.Pointer(prefix)) + ": " + us.Name
}
```
#### 可空参数
生成器支持在 PHP 签名中使用 `?` 前缀的可空参数。当参数可空时,它在你的 Go 函数中变成指针,允许你检查值在 PHP 中是否为 `null`
```go
//export_php:method User::updateInfo(?string $name, ?int $age, ?bool $active): void
func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool) {
// 检查是否提供了 name不为 null
if name != nil {
us.Name = frankenphp.GoString(unsafe.Pointer(name))
}
// 检查是否提供了 age不为 null
if age != nil {
us.Age = int(*age)
}
// 检查是否提供了 active不为 null
if active != nil {
us.Active = *active
}
}
```
**关于可空参数的要点:**
- **可空原始类型**`?int``?float``?bool`)在 Go 中变成指针(`*int64``*float64``*bool`
- **可空字符串**`?string`)仍然是 `*C.zend_string`,但可以是 `nil`
- **在解引用指针值之前检查 `nil`**
- **PHP `null` 变成 Go `nil`** - 当 PHP 传递 `null` 时,你的 Go 函数接收 `nil` 指针
> [!WARNING]
> 目前,类方法有以下限制。**不支持对象**作为参数类型或返回类型。**完全支持数组**作为参数和返回类型。支持的类型:`string`、`int`、`float`、`bool`、`array` 和 `void`(用于返回类型)。**完全支持可空参数类型**,适用于所有标量类型(`?string`、`?int`、`?float`、`?bool`)。
生成扩展后,你将被允许在 PHP 中使用类及其方法。请注意,你**不能直接访问属性**
```php
<?php
$user = new User();
// ✅ 这可以工作 - 使用方法
$user->setAge(25);
echo $user->getName(); // 输出:(空,默认值)
echo $user->getAge(); // 输出25
$user->setNamePrefix("Employee");
// ✅ 这也可以工作 - 可空参数
$user->updateInfo("John", 30, true); // 提供所有参数
$user->updateInfo("Jane", null, false); // Age 为 null
$user->updateInfo(null, 25, null); // Name 和 active 为 null
// ❌ 这不会工作 - 直接属性访问
// echo $user->name; // 错误:无法访问私有属性
// $user->age = 30; // 错误:无法访问私有属性
```
这种设计确保你的 Go 代码完全控制如何访问和修改对象的状态,提供更好的封装和类型安全。
### 声明常量
生成器支持使用两个指令将 Go 常量导出到 PHP`//export_php:const` 用于全局常量,`//export_php:classconstant` 用于类常量。这允许你在 Go 和 PHP 代码之间共享配置值、状态代码和其他常量。
#### 全局常量
使用 `//export_php:const` 指令创建全局 PHP 常量:
```go
//export_php:const
const MAX_CONNECTIONS = 100
//export_php:const
const API_VERSION = "1.2.3"
//export_php:const
const STATUS_OK = iota
//export_php:const
const STATUS_ERROR = iota
```
#### 类常量
使用 `//export_php:classconstant ClassName` 指令创建属于特定 PHP 类的常量:
```go
//export_php:classconstant User
const STATUS_ACTIVE = 1
//export_php:classconstant User
const STATUS_INACTIVE = 0
//export_php:classconstant User
const ROLE_ADMIN = "admin"
//export_php:classconstant Order
const STATE_PENDING = iota
//export_php:classconstant Order
const STATE_PROCESSING = iota
//export_php:classconstant Order
const STATE_COMPLETED = iota
```
类常量在 PHP 中使用类名作用域访问:
```php
<?php
// 全局常量
echo MAX_CONNECTIONS; // 100
echo API_VERSION; // "1.2.3"
// 类常量
echo User::STATUS_ACTIVE; // 1
echo User::ROLE_ADMIN; // "admin"
echo Order::STATE_PENDING; // 0
```
该指令支持各种值类型,包括字符串、整数、布尔值、浮点数和 iota 常量。使用 `iota`生成器自动分配顺序值0、1、2 等)。全局常量在你的 PHP 代码中作为全局常量可用,而类常量使用公共可见性限定在各自的类中。使用整数时,支持不同的可能记法(二进制、十六进制、八进制)并在 PHP 存根文件中按原样转储。
你可以像在 Go 代码中习惯的那样使用常量。例如,让我们采用我们之前声明的 `repeat_this()` 函数,并将最后一个参数更改为整数:
```go
import (
"C"
"github.com/dunglas/frankenphp"
"strings"
)
//export_php:const
const STR_REVERSE = iota
//export_php:const
const STR_NORMAL = iota
//export_php:classconstant StringProcessor
const MODE_LOWERCASE = 1
//export_php:classconstant StringProcessor
const MODE_UPPERCASE = 2
//export_php:function repeat_this(string $str, int $count, int $mode): string
func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(s))
result := strings.Repeat(str, int(count))
if mode == STR_REVERSE {
// 反转字符串
}
if mode == STR_NORMAL {
// 无操作,只是为了展示常量
}
return frankenphp.PHPString(result, false)
}
//export_php:class StringProcessor
type StringProcessorStruct struct {
// 内部字段
}
//export_php:method StringProcessor::process(string $input, int $mode): string
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(input))
switch mode {
case MODE_LOWERCASE:
str = strings.ToLower(str)
case MODE_UPPERCASE:
str = strings.ToUpper(str)
}
return frankenphp.PHPString(str, false)
}
```
### 使用命名空间
生成器支持使用 `//export_php:namespace` 指令将 PHP 扩展的函数、类和常量组织在命名空间下。这有助于避免命名冲突,并为扩展的 API 提供更好的组织。
#### 声明命名空间
在你的 Go 文件顶部使用 `//export_php:namespace` 指令,将所有导出的符号放在特定命名空间下:
```go
//export_php:namespace My\Extension
package main
import "C"
//export_php:function hello(): string
func hello() string {
return "Hello from My\\Extension namespace!"
}
//export_php:class User
type UserStruct struct {
// 内部字段
}
//export_php:method User::getName(): string
func (u *UserStruct) GetName() unsafe.Pointer {
return frankenphp.PHPString("John Doe", false)
}
//export_php:const
const STATUS_ACTIVE = 1
```
#### 在 PHP 中使用命名空间扩展
当声明命名空间时,所有函数、类和常量都放在 PHP 中的该命名空间下:
```php
<?php
echo My\Extension\hello(); // "Hello from My\Extension namespace!"
$user = new My\Extension\User();
echo $user->getName(); // "John Doe"
echo My\Extension\STATUS_ACTIVE; // 1
```
#### 重要说明
- 每个文件只允许**一个**命名空间指令。如果找到多个命名空间指令,生成器将返回错误。
- 命名空间适用于文件中的**所有**导出符号:函数、类、方法和常量。
- 命名空间名称遵循 PHP 命名空间约定,使用反斜杠(`\`)作为分隔符。
- 如果没有声明命名空间,符号将照常导出到全局命名空间。
### 生成扩展
这就是魔法发生的地方,现在可以生成你的扩展。你可以使用以下命令运行生成器:
```console
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
```
> [!NOTE]
> 不要忘记将 `GEN_STUB_SCRIPT` 环境变量设置为你之前下载的 PHP 源代码中 `gen_stub.php` 文件的路径。这是在手动实现部分中提到的同一个 `gen_stub.php` 脚本。
如果一切顺利,应该创建了一个名为 `build` 的新目录。此目录包含扩展的生成文件,包括带有生成的 PHP 函数存根的 `my_extension.go` 文件。
### 将生成的扩展集成到 FrankenPHP 中
我们的扩展现在已准备好编译并集成到 FrankenPHP 中。为此,请参阅 FrankenPHP [编译文档](compile.md)以了解如何编译 FrankenPHP。使用 `--with` 标志添加模块,指向你的模块路径:
```console
CGO_ENABLED=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output frankenphp \
--with github.com/my-account/my-module/build
```
请注意,你指向在生成步骤中创建的 `/build` 子目录。但是,这不是强制性的:你也可以将生成的文件复制到你的模块目录并直接指向它。
### 测试你的生成扩展
你可以创建一个 PHP 文件来测试你创建的函数和类。例如,创建一个包含以下内容的 `index.php` 文件:
```php
<?php
// 使用全局常量
var_dump(repeat_this('Hello World', 5, STR_REVERSE));
// 使用类常量
$processor = new StringProcessor();
echo $processor->process('Hello World', StringProcessor::MODE_LOWERCASE); // "hello world"
echo $processor->process('Hello World', StringProcessor::MODE_UPPERCASE); // "HELLO WORLD"
```
一旦你按照上一节所示将扩展集成到 FrankenPHP 中,你就可以使用 `./frankenphp php-server` 运行此测试文件,你应该看到你的扩展正在工作。
## 手动实现
如果你想了解扩展的工作原理或需要完全控制你的扩展,你可以手动编写它们。这种方法给你完全的控制,但需要更多的样板代码。
### 基本函数
我们将看到如何在 Go 中编写一个简单的 PHP 扩展,定义一个新的原生函数。此函数将从 PHP 调用,并将触发一个在 Caddy 日志中记录消息的协程。此函数不接受任何参数并且不返回任何内容。
#### 定义 Go 函数
在你的模块中,你需要定义一个新的原生函数,该函数将从 PHP 调用。为此,创建一个你想要的名称的文件,例如 `extension.go`,并添加以下代码:
```go
package ext_go
//#include "extension.h"
import "C"
import (
"unsafe"
"github.com/caddyserver/caddy/v2"
"github.com/dunglas/frankenphp"
)
func init() {
frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry))
}
//export go_print_something
func go_print_something() {
go func() {
caddy.Log().Info("Hello from a goroutine!")
}()
}
```
`frankenphp.RegisterExtension()` 函数通过处理内部 PHP 注册逻辑简化了扩展注册过程。`go_print_something` 函数使用 `//export` 指令表示它将在我们将编写的 C 代码中可访问,这要归功于 CGO。
在此示例中,我们的新函数将触发一个在 Caddy 日志中记录消息的协程。
#### 定义 PHP 函数
为了允许 PHP 调用我们的函数,我们需要定义相应的 PHP 函数。为此,我们将创建一个存根文件,例如 `extension.stub.php`,其中包含以下代码:
```php
<?php
/** @generate-class-entries */
function go_print(): void {}
```
此文件定义了 `go_print()` 函数的签名,该函数将从 PHP 调用。`@generate-class-entries` 指令允许 PHP 自动为我们的扩展生成函数条目。
这不是手动完成的,而是使用 PHP 源代码中提供的脚本(确保根据你的 PHP 源代码所在位置调整 `gen_stub.php` 脚本的路径):
```bash
php ../php-src/build/gen_stub.php extension.stub.php
```
此脚本将生成一个名为 `extension_arginfo.h` 的文件,其中包含 PHP 知道如何定义和调用我们函数所需的信息。
#### 编写 Go 和 C 之间的桥梁
现在,我们需要编写 Go 和 C 之间的桥梁。在你的模块目录中创建一个名为 `extension.h` 的文件,内容如下:
```c
#ifndef _EXTENSION_H
#define _EXTENSION_H
#include <php.h>
extern zend_module_entry ext_module_entry;
#endif
```
接下来,创建一个名为 `extension.c` 的文件,该文件将执行以下步骤:
- 包含 PHP 头文件;
- 声明我们的新原生 PHP 函数 `go_print()`
- 声明扩展元数据。
让我们首先包含所需的头文件:
```c
#include <php.h>
#include "extension.h"
#include "extension_arginfo.h"
// 包含 Go 导出的符号
#include "_cgo_export.h"
```
然后我们将 PHP 函数定义为原生语言函数:
```c
PHP_FUNCTION(go_print)
{
ZEND_PARSE_PARAMETERS_NONE();
go_print_something();
}
zend_module_entry ext_module_entry = {
STANDARD_MODULE_HEADER,
"ext_go",
ext_functions, /* Functions */
NULL, /* MINIT */
NULL, /* MSHUTDOWN */
NULL, /* RINIT */
NULL, /* RSHUTDOWN */
NULL, /* MINFO */
"0.1.1",
STANDARD_MODULE_PROPERTIES
};
```
在这种情况下,我们的函数不接受参数并且不返回任何内容。它只是调用我们之前定义的 Go 函数,使用 `//export` 指令导出。
最后,我们在 `zend_module_entry` 结构中定义扩展的元数据,例如其名称、版本和属性。这些信息对于 PHP 识别和加载我们的扩展是必需的。请注意,`ext_functions` 是指向我们定义的 PHP 函数的指针数组,它由 `gen_stub.php` 脚本在 `extension_arginfo.h` 文件中自动生成。
扩展注册由我们在 Go 代码中调用的 FrankenPHP 的 `RegisterExtension()` 函数自动处理。
### 高级用法
现在我们知道了如何在 Go 中创建基本的 PHP 扩展,让我们复杂化我们的示例。我们现在将创建一个 PHP 函数,该函数接受一个字符串作为参数并返回其大写版本。
#### 定义 PHP 函数存根
为了定义新的 PHP 函数,我们将修改我们的 `extension.stub.php` 文件以包含新的函数签名:
```php
<?php
/** @generate-class-entries */
/**
* 将字符串转换为大写。
*
* @param string $string 要转换的字符串。
* @return string 字符串的大写版本。
*/
function go_upper(string $string): string {}
```
> [!TIP]
> 不要忽视函数的文档!你可能会与其他开发人员共享扩展存根,以记录如何使用你的扩展以及哪些功能可用。
通过使用 `gen_stub.php` 脚本重新生成存根文件,`extension_arginfo.h` 文件应该如下所示:
```c
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_go_upper, 0, 1, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0)
ZEND_END_ARG_INFO()
ZEND_FUNCTION(go_upper);
static const zend_function_entry ext_functions[] = {
ZEND_FE(go_upper, arginfo_go_upper)
ZEND_FE_END
};
```
我们可以看到 `go_upper` 函数定义了一个 `string` 类型的参数和一个 `string` 的返回类型。
#### Go 和 PHP/C 之间的类型转换
你的 Go 函数不能直接接受 PHP 字符串作为参数。你需要将其转换为 Go 字符串。幸运的是FrankenPHP 提供了助手函数来处理 PHP 字符串和 Go 字符串之间的转换,类似于我们在生成器方法中看到的。
头文件保持简单:
```c
#ifndef _EXTENSION_H
#define _EXTENSION_H
#include <php.h>
extern zend_module_entry ext_module_entry;
#endif
```
我们现在可以在我们的 `extension.c` 文件中编写 Go 和 C 之间的桥梁。我们将 PHP 字符串直接传递给我们的 Go 函数:
```c
PHP_FUNCTION(go_upper)
{
zend_string *str;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STR(str)
ZEND_PARSE_PARAMETERS_END();
zend_string *result = go_upper(str);
RETVAL_STR(result);
}
```
你可以在 [PHP 内部手册](https://www.phpinternalsbook.com/php7/extensions_design/php_functions.html#parsing-parameters-zend-parse-parameters) 的专门页面中了解更多关于 `ZEND_PARSE_PARAMETERS_START` 和参数解析的信息。在这里,我们告诉 PHP 我们的函数接受一个 `string` 类型的强制参数作为 `zend_string`。然后我们将此字符串直接传递给我们的 Go 函数,并使用 `RETVAL_STR` 返回结果。
只剩下一件事要做:在 Go 中实现 `go_upper` 函数。
#### 实现 Go 函数
我们的 Go 函数将接受 `*C.zend_string` 作为参数,使用 FrankenPHP 的助手函数将其转换为 Go 字符串,处理它,并将结果作为新的 `*C.zend_string` 返回。助手函数为我们处理所有内存管理和转换复杂性。
```go
import "strings"
//export go_upper
func go_upper(s *C.zend_string) *C.zend_string {
str := frankenphp.GoString(unsafe.Pointer(s))
upper := strings.ToUpper(str)
return (*C.zend_string)(frankenphp.PHPString(upper, false))
}
```
这种方法比手动内存管理更清洁、更安全。FrankenPHP 的助手函数自动处理 PHP 的 `zend_string` 格式和 Go 字符串之间的转换。`PHPString()` 中的 `false` 参数表示我们想要创建一个新的非持久字符串(在请求结束时释放)。
> [!TIP]
> 在此示例中,我们不执行任何错误处理,但你应该始终检查指针不是 `nil` 并且数据在 Go 函数中使用之前是有效的。
### 将扩展集成到 FrankenPHP 中
我们的扩展现在已准备好编译并集成到 FrankenPHP 中。为此,请参阅 FrankenPHP [编译文档](compile.md)以了解如何编译 FrankenPHP。使用 `--with` 标志添加模块,指向你的模块路径:
```console
CGO_ENABLED=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output frankenphp \
--with github.com/my-account/my-module
```
就是这样!你的扩展现在集成到 FrankenPHP 中,可以在你的 PHP 代码中使用。
### 测试你的扩展
将扩展集成到 FrankenPHP 后,你可以为你实现的函数创建一个包含示例的 `index.php` 文件:
```php
<?php
// 测试基本函数
go_print();
// 测试高级函数
echo go_upper("hello world") . "\n";
```
你现在可以使用 `./frankenphp php-server` 运行带有此文件的 FrankenPHP你应该看到你的扩展正在工作。

View File

@@ -7,7 +7,7 @@
在存储库设置中的 `secrets` 下,添加以下字段:
- `REGISTRY_LOGIN_SERVER`: 要使用的 docker registry`docker.io`)。
- `REGISTRY_LOGIN_SERVER`: 要使用的 Docker registry`docker.io`)。
- `REGISTRY_USERNAME`: 用于登录 registry 的用户名(如 `dunglas`)。
- `REGISTRY_PASSWORD`: 用于登录 registry 的密码(如 `access key`)。
- `IMAGE_NAME`: 镜像的名称(如 `dunglas/frankenphp`)。

View File

@@ -4,9 +4,18 @@
已知以下扩展与 FrankenPHP 不兼容:
| 名称 | 原因 | 替代方案 |
|-------------------------------------------------------------|-------|----------------------------------------------------------------------------------------------------------------------|
| [imap](https://www.php.net/manual/en/imap.installation.php) | 非线程安全 | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
| 名称 | 原因 | 替代方案 |
| ----------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------- |
| [imap](https://www.php.net/manual/en/imap.installation.php) | 不安全的线程 | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | 不安全的线程 | - |
## 有缺陷的 PHP 扩展
以下扩展在与 FrankenPHP 一起使用时已知存在错误和意外行为:
| 名称 | 问题 |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | 在使用静态构建的 FrankenPHP使用 musl libc 构建)时,在重负载下 OpenSSL 扩展可能会崩溃。一个解决方法是使用动态链接的构建(如 Docker 镜像中使用的版本)。此错误正在由 PHP 跟踪。[查看问题](https://github.com/php/php-src/issues/13648)。 |
## get_browser
@@ -14,8 +23,7 @@
## 独立的二进制和基于 Alpine 的 Docker 镜像
独立的二进制文件和基于 Alpine 的 docker 镜像 (`dunglas/frankenphp:*-alpine`) 使用的是 [musl libc](https://musl.libc.org/) 而不是 [glibc and friends](https://www.etalabs.net/compare_libcs.html),为的是保持较小的二进制大小。
这可能会导致一些兼容性问题。特别是glob 标志 `GLOB_BRACE` [不可用](https://www.php.net/manual/en/function.glob.php)。
独立的二进制文件和基于 Alpine 的 Docker 镜像 (`dunglas/frankenphp:*-alpine`) 使用的是 [musl libc](https://musl.libc.org/) 而不是 [glibc and friends](https://www.etalabs.net/compare_libcs.html),为的是保持较小的二进制大小。这可能会导致一些兼容性问题。特别是glob 标志 `GLOB_BRACE` [不可用](https://www.php.net/manual/en/function.glob.php)。
## 在 Docker 中使用 `https://127.0.0.1`
@@ -67,3 +75,69 @@ docker run \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
## Composer 脚本引用 `@php`
[Composer 脚本](https://getcomposer.org/doc/articles/scripts.md) 可能想要执行一个 PHP 二进制文件来完成一些任务,例如在 [Laravel 项目](laravel.md) 中运行 `@php artisan package:discover --ansi`。这 [目前失败](https://github.com/php/frankenphp/issues/483#issuecomment-1899890915) 的原因有两个:
- Composer 不知道如何调用 FrankenPHP 二进制文件;
- Composer 可以在命令中使用 `-d` 标志添加 PHP 设置,而 FrankenPHP 目前尚不支持。
作为一种变通方法,我们可以在 `/usr/local/bin/php` 中创建一个 Shell 脚本,该脚本会去掉不支持的参数,然后调用 FrankenPHP:
```bash
#!/usr/bin/env bash
args=("$@")
index=0
for i in "$@"
do
if [ "$i" == "-d" ]; then
unset 'args[$index]'
unset 'args[$index+1]'
fi
index=$((index+1))
done
/usr/local/bin/frankenphp php-cli ${args[@]}
```
然后将环境变量 `PHP_BINARY` 设置为我们 `php` 脚本的路径,并运行 Composer
```console
export PHP_BINARY=/usr/local/bin/php
composer install
```
## 使用静态二进制文件排查 TLS/SSL 问题
在使用静态二进制文件时您可能会遇到以下与TLS相关的错误例如在使用STARTTLS发送电子邮件时
```text
Unable to connect with STARTTLS: stream_socket_enable_crypto(): SSL operation failed with code 5. OpenSSL Error messages:
error:80000002:system library::No such file or directory
error:80000002:system library::No such file or directory
error:80000002:system library::No such file or directory
error:0A000086:SSL routines::certificate verify failed
```
由于静态二进制不捆绑 TLS 证书,因此您需要将 OpenSSL 指向本地 CA 证书安装。
检查 [`openssl_get_cert_locations()`](https://www.php.net/manual/en/function.openssl-get-cert-locations.php) 的输出,
以找到 CA 证书必须安装的位置,并将它们存储在该位置。
> [!WARNING]
>
> Web 和命令行界面可能有不同的设置。
> 确保在适当的上下文中运行 `openssl_get_cert_locations()`。
[从Mozilla提取的CA证书可以在curl网站上下载](https://curl.se/docs/caextract.html)。
或者,许多发行版,包括 Debian、Ubuntu 和 Alpine提供名为 `ca-certificates` 的软件包,其中包含这些证书。
还可以使用 `SSL_CERT_FILE``SSL_CERT_DIR` 来提示 OpenSSL 在哪里查找 CA 证书:
```console
# Set TLS certificates environment variables
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
export SSL_CERT_DIR=/etc/ssl/certs
```

View File

@@ -19,21 +19,23 @@ docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
1. [下载与你的系统相对应的二进制文件](https://github.com/php/frankenphp/releases)
2. 将以下配置添加到 Laravel 项目根目录中名为 `Caddyfile` 的文件中:
```caddyfile
{
frankenphp
}
```caddyfile
{
frankenphp
}
# 服务器的域名
localhost {
# 将 webroot 设置为 public/ 目录
root public/
# 启用压缩(可选)
encode zstd br gzip
# 执行当前目录中的 PHP 文件并提供资
php_server
}
```
# 服务器的域名
localhost {
# 将 webroot 设置为 public/ 目录
root public/
# 启用压缩(可选)
encode zstd br gzip
# 执行当前目录中的 PHP 文件并提供资
php_server {
try_files {path} index.php
}
}
```
3. 从 Laravel 项目的根目录启动 FrankenPHP`frankenphp run`
@@ -59,16 +61,124 @@ php artisan octane:frankenphp
`octane:frankenphp` 命令可以采用以下选项:
* `--host`: 服务器应绑定到的 IP 地址(默认值: `127.0.0.1`
* `--port`: 服务器应可用的端口(默认值: `8000`
* `--admin-port`: 管理服务器应可用的端口(默认值: `2019`
* `--workers`: 应可用于处理请求的 worker 数(默认值: `auto`
* `--max-requests`: 在 worker 重启之前要处理的请求数(默认值: `500`
* `--caddyfile`: FrankenPHP `Caddyfile` 文件的路径
* `--https`: 开启 HTTPS、HTTP/2 和 HTTP/3自动生成和延长证书
* `--http-redirect`: 启用 HTTP 到 HTTPS 重定向(仅在使用 `--https` 时启用)
* `--watch`: 修改应用程序时自动重新加载服务器
* `--poll`: 在监视时使用文件系统轮询,以便通过网络监视文件
* `--log-level`: 在指定日志级别或高于指定日志级别的日志消息
- `--host`: 服务器应绑定到的 IP 地址(默认值: `127.0.0.1`
- `--port`: 服务器应可用的端口(默认值: `8000`
- `--admin-port`: 管理服务器应可用的端口(默认值: `2019`
- `--workers`: 应可用于处理请求的 worker 数(默认值: `auto`
- `--max-requests`: 在 worker 重启之前要处理的请求数(默认值: `500`
- `--caddyfile`FrankenPHP `Caddyfile` 文件的路径(默认: [Laravel Octane 中的存根 `Caddyfile`](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile)
- `--https`: 开启 HTTPS、HTTP/2 和 HTTP/3自动生成和延长证书
- `--http-redirect`: 启用 HTTP 到 HTTPS 重定向(仅在使用 `--https` 时启用)
- `--watch`: 修改应用程序时自动重新加载服务器
- `--poll`: 在监视时使用文件系统轮询,以便通过网络监视文件
- `--log-level`: 在指定日志级别或高于指定日志级别的日志消息
> [!TIP]
> 要获取结构化的 JSON 日志(在使用日志分析解决方案时非常有用),请明确传递 `--log-level` 选项。
你可以了解更多关于 [Laravel Octane 官方文档](https://laravel.com/docs/octane)。
## Laravel 应用程序作为独立的可执行文件
使用[FrankenPHP 的应用嵌入功能](embed.md),可以将 Laravel 应用程序作为
独立的二进制文件分发。
按照以下步骤将您的Laravel应用程序打包为Linux的独立二进制文件
1. 在您的应用程序的存储库中创建一个名为 `static-build.Dockerfile` 的文件:
```dockerfile
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
# 复制你的应用
WORKDIR /go/src/app/dist/app
COPY . .
# 删除测试和其他不必要的文件以节省空间
# 或者,将这些文件添加到 .dockerignore 文件中
RUN rm -Rf tests/
# 复制 .env 文件
RUN cp .env.example .env
# 将 APP_ENV 和 APP_DEBUG 更改为适合生产环境
RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env
# 根据需要对您的 .env 文件进行其他更改
# 安装依赖项
RUN composer install --ignore-platform-reqs --no-dev -a
# 构建静态二进制文件
WORKDIR /go/src/app/
RUN EMBED=dist/app/ ./build-static.sh
```
> [!CAUTION]
>
> 一些 `.dockerignore` 文件
> 将忽略 `vendor/` 目录和 `.env` 文件。在构建之前,请确保调整或删除 `.dockerignore` 文件。
2. 构建:
```console
docker build -t static-laravel-app -f static-build.Dockerfile .
```
3. 提取二进制:
```console
docker cp $(docker create --name static-laravel-app-tmp static-laravel-app):/go/src/app/dist/frankenphp-linux-x86_64 frankenphp ; docker rm static-laravel-app-tmp
```
4. 填充缓存:
```console
frankenphp php-cli artisan optimize
```
5. 运行数据库迁移(如果有的话):
```console
frankenphp php-cli artisan migrate
```
6. 生成应用程序的密钥:
```console
frankenphp php-cli artisan key:generate
```
7. 启动服务器:
```console
frankenphp php-server
```
您的应用程序现在准备好了!
了解有关可用选项的更多信息,以及如何为其他操作系统构建二进制文件,请参见 [应用程序嵌入](embed.md)
文档。
### 更改存储路径
默认情况下Laravel 将上传的文件、缓存、日志等存储在应用程序的 `storage/` 目录中。
这不适合嵌入式应用,因为每个新版本将被提取到不同的临时目录中。
设置 `LARAVEL_STORAGE_PATH` 环境变量(例如,在 `.env` 文件中)或调用 `Illuminate\Foundation\Application::useStoragePath()` 方法以使用临时目录之外的目录。
### 使用独立二进制文件运行 Octane
甚至可以将 Laravel Octane 应用打包为独立的二进制文件!
为此,[正确安装 Octane](#laravel-octane) 并遵循 [前一部分](#laravel-应用程序作为独立的可执行文件) 中描述的步骤。
然后,通过 Octane 在工作模式下启动 FrankenPHP运行
```console
PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp
```
> [!CAUTION]
>
> 为了使命令有效,独立二进制文件**必须**命名为 `frankenphp`
> 因为 Octane 需要一个名为 `frankenphp` 的程序在路径中可用。

View File

@@ -1,6 +1,6 @@
# 实时
FrankenPHP 带有一个内置的 Mercure Hub
FrankenPHP 配备了内置的 [Mercure](https://mercure.rocks) 中心
Mercure 允许将事件实时推送到所有连接的设备:它们将立即收到 JavaScript 事件。
无需 JS 库或 SDK
@@ -9,4 +9,7 @@ Mercure 允许将事件实时推送到所有连接的设备:它们将立即收
要启用 Mercure Hub请按照 [Mercure 网站](https://mercure.rocks/docs/hub/config) 中的说明更新 `Caddyfile`
Mercure hub 的路径是`/.well-known/mercure`.
在 Docker 中运行 FrankenPHP 时,完整的发送 URL 将类似于 `http://php/.well-known/mercure` (其中 `php` 是运行 FrankenPHP 的容器名称)。
要从你的代码中推送 Mercure 更新,我们推荐 [Symfony Mercure Component](https://symfony.com/components/Mercure)(不需要 Symfony 框架来使用)。

17
docs/cn/metrics.md Normal file
View File

@@ -0,0 +1,17 @@
# 指标
当启用 [Caddy 指标](https://caddyserver.com/docs/metrics) 时FrankenPHP 公开以下指标:
- `frankenphp_total_threads`PHP 线程的总数。
- `frankenphp_busy_threads`:当前正在处理请求的 PHP 线程数(运行中的 worker 始终占用一个线程)。
- `frankenphp_queue_depth`:常规排队请求的数量
- `frankenphp_total_workers{worker="[worker_name]"}`worker 的总数。
- `frankenphp_busy_workers{worker="[worker_name]"}`:当前正在处理请求的 worker 数量。
- `frankenphp_worker_request_time{worker="[worker_name]"}`:所有 worker 处理请求所花费的时间。
- `frankenphp_worker_request_count{worker="[worker_name]"}`:所有 worker 处理的请求数量。
- `frankenphp_ready_workers{worker="[worker_name]"}`:至少调用过一次 `frankenphp_handle_request` 的 worker 数量。
- `frankenphp_worker_crashes{worker="[worker_name]"}`worker 意外终止的次数。
- `frankenphp_worker_restarts{worker="[worker_name]"}`worker 被故意重启的次数。
- `frankenphp_worker_queue_depth{worker="[worker_name]"}`:排队请求的数量。
对于 worker 指标,`[worker_name]` 占位符被 Caddyfile 中的 worker 名称替换,否则将使用 worker 文件的绝对路径。

157
docs/cn/performance.md Normal file
View File

@@ -0,0 +1,157 @@
# 性能
默认情况下FrankenPHP 尝试在性能和易用性之间提供良好的折衷。
但是,通过使用适当的配置,可以大幅提高性能。
## 线程和 Worker 数量
默认情况下FrankenPHP 启动的线程和 worker在 worker 模式下)数量是可用 CPU 数量的 2 倍。
适当的值很大程度上取决于你的应用程序是如何编写的、它做什么以及你的硬件。
我们强烈建议更改这些值。为了获得最佳的系统稳定性,建议 `num_threads` x `memory_limit` < `available_memory`
要找到正确的值,最好运行模拟真实流量的负载测试。
[k6](https://k6.io) 和 [Gatling](https://gatling.io) 是很好的工具。
要配置线程数,请使用 `php_server``php` 指令的 `num_threads` 选项。
要更改 worker 数量,请使用 `frankenphp` 指令的 `worker` 部分的 `num` 选项。
### `max_threads`
虽然准确了解你的流量情况总是更好,但现实应用往往更加
不可预测。`max_threads` [配置](config.md#caddyfile-config) 允许 FrankenPHP 在运行时自动生成额外线程,直到指定的限制。
`max_threads` 可以帮助你确定需要多少线程来处理你的流量,并可以使服务器对延迟峰值更具弹性。
如果设置为 `auto`,限制将基于你的 `php.ini` 中的 `memory_limit` 进行估算。如果无法这样做,
`auto` 将默认为 2x `num_threads`。请记住,`auto` 可能会严重低估所需的线程数。
`max_threads` 类似于 PHP FPM 的 [pm.max_children](https://www.php.net/manual/en/install.fpm.configuration.php#pm.max-children)。主要区别是 FrankenPHP 使用线程而不是
进程,并根据需要自动在不同的 worker 脚本和"经典模式"之间委派它们。
## Worker 模式
启用 [worker 模式](worker.md) 大大提高了性能,
但你的应用必须适配以兼容此模式:
你需要创建一个 worker 脚本并确保应用不会泄漏内存。
## 不要使用 musl
官方 Docker 镜像的 Alpine Linux 变体和我们提供的默认二进制文件使用 [musl libc](https://musl.libc.org)。
众所周知,当使用这个替代 C 库而不是传统的 GNU 库时PHP [更慢](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381)
特别是在以 ZTS 模式(线程安全)编译时,这是 FrankenPHP 所必需的。在大量线程环境中,差异可能很显著。
另外,[一些错误只在使用 musl 时发生](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl)。
在生产环境中,我们建议使用链接到 glibc 的 FrankenPHP。
这可以通过使用 Debian Docker 镜像(默认)、从我们的 [Releases](https://github.com/php/frankenphp/releases) 下载 -gnu 后缀二进制文件,或通过[从源代码编译 FrankenPHP](compile.md) 来实现。
或者,我们提供使用 [mimalloc 分配器](https://github.com/microsoft/mimalloc) 编译的静态 musl 二进制文件,这缓解了线程场景中的问题。
## Go 运行时配置
FrankenPHP 是用 Go 编写的。
一般来说Go 运行时不需要任何特殊配置,但在某些情况下,
特定的配置可以提高性能。
你可能想要将 `GODEBUG` 环境变量设置为 `cgocheck=0`FrankenPHP Docker 镜像中的默认值)。
如果你在容器Docker、Kubernetes、LXC...)中运行 FrankenPHP 并限制容器的可用内存,
请将 `GOMEMLIMIT` 环境变量设置为可用内存量。
有关更多详细信息,[专门针对此主题的 Go 文档页面](https://pkg.go.dev/runtime#hdr-Environment_Variables) 是充分利用运行时的必读内容。
## `file_server`
默认情况下,`php_server` 指令自动设置文件服务器来
提供存储在根目录中的静态文件(资产)。
此功能很方便,但有成本。
要禁用它,请使用以下配置:
```caddyfile
php_server {
file_server off
}
```
## `try_files`
除了静态文件和 PHP 文件外,`php_server` 还会尝试提供你应用程序的索引
和目录索引文件(`/path/` -> `/path/index.php`)。如果你不需要目录索引,
你可以通过明确定义 `try_files` 来禁用它们,如下所示:
```caddyfile
php_server {
try_files {path} index.php
root /root/to/your/app # 在这里明确添加根目录允许更好的缓存
}
```
这可以显著减少不必要的文件操作数量。
另一种具有 0 个不必要文件系统操作的方法是改用 `php` 指令并按路径将
文件与 PHP 分开。如果你的整个应用程序由一个入口文件提供服务,这种方法效果很好。
一个在 `/assets` 文件夹后面提供静态文件的示例[配置](config.md#caddyfile-config)可能如下所示:
```caddyfile
route {
@assets {
path /assets/*
}
# /assets 后面的所有内容都由文件服务器处理
file_server @assets {
root /root/to/your/app
}
# 不在 /assets 中的所有内容都由你的索引或 worker PHP 文件处理
rewrite index.php
php {
root /root/to/your/app # 在这里明确添加根目录允许更好的缓存
}
}
```
## 占位符
你可以在 `root``env` 指令中使用[占位符](https://caddyserver.com/docs/conventions#placeholders)。
但是,这会阻止缓存这些值,并带来显著的性能成本。
如果可能,请避免在这些指令中使用占位符。
## `resolve_root_symlink`
默认情况下如果文档根目录是符号链接FrankenPHP 会自动解析它(这对于 PHP 正常工作是必要的)。
如果文档根目录不是符号链接,你可以禁用此功能。
```caddyfile
php_server {
resolve_root_symlink false
}
```
如果 `root` 指令包含[占位符](https://caddyserver.com/docs/conventions#placeholders),这将提高性能。
在其他情况下,收益将可以忽略不计。
## 日志
日志显然非常有用,但根据定义,
它需要 I/O 操作和内存分配,这会大大降低性能。
确保你[正确设置日志级别](https://caddyserver.com/docs/caddyfile/options#log)
并且只记录必要的内容。
## PHP 性能
FrankenPHP 使用官方 PHP 解释器。
所有常见的 PHP 相关性能优化都适用于 FrankenPHP。
特别是:
- 检查 [OPcache](https://www.php.net/manual/zh/book.opcache.php) 是否已安装、启用并正确配置
- 启用 [Composer 自动加载器优化](https://getcomposer.org/doc/articles/autoloader-optimization.md)
- 确保 `realpath` 缓存对于你的应用程序需求足够大
- 使用[预加载](https://www.php.net/manual/zh/opcache.preloading.php)
有关更多详细信息,请阅读[专门的 Symfony 文档条目](https://symfony.com/doc/current/performance.html)
(即使你不使用 Symfony大多数提示也很有用

View File

@@ -18,6 +18,9 @@ ENV SERVER_NAME=your-domain-name.example.com
# 如果要禁用 HTTPS请改用以下值
#ENV SERVER_NAME=:80
# 如果你的项目不使用 "public" 目录作为 web 根目录,你可以在这里设置:
# ENV SERVER_ROOT=web/
# 启用 PHP 生产配置
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
@@ -124,7 +127,7 @@ git clone git@github.com:<username>/<project-name>.git
进入包含项目 (`<project-name>`) 的目录,并在生产模式下启动应用:
```console
docker compose up -d --wait
docker compose up --wait
```
你的服务器已启动并运行,并且已自动为你生成 HTTPS 证书。
@@ -132,7 +135,7 @@ docker compose up -d --wait
> [!CAUTION]
>
> Docker 有一个缓存层,请确保每个部署都有正确的构建,或者使用 --no-cache 选项重新构建项目以避免缓存问题。
> Docker 有一个缓存层,请确保每个部署都有正确的构建,或者使用 `--no-cache` 选项重新构建项目以避免缓存问题。
## 在多个节点上部署

View File

@@ -1,22 +1,50 @@
# 创建静态构建
基于 [static-php-cli](https://github.com/crazywhalecc/static-php-cli) 项目(这个项目支持所有 SAPI不仅仅是 `cli`
FrankenPHP 已支持创建静态二进制,无需安装本地 PHP
与其使用本地安装的PHP库
由于伟大的 [static-php-cli 项目](https://github.com/crazywhalecc/static-php-cli),创建一个静态或基本静态的 FrankenPHP 构建是可能的(尽管它的名字,这个项目支持所有的 SAPI而不仅仅是 CLI
使用这种方法,我们可构建一个包含 PHP 解释器、Caddy Web 服务器和 FrankenPHP 的可移植二进制文件!
完全静态的本地可执行文件不需要任何依赖,并且可以在 [`scratch` Docker 镜像](https://docs.docker.com/build/building/base-images/#create-a-minimal-base-image-using-scratch) 上运行。
然而,它们无法加载动态 PHP 扩展(例如 Xdebug并且由于使用了 musl libc有一些限制。
大多数静态二进制文件只需要 `glibc` 并且可以加载动态扩展。
在可能的情况下我们建议使用基于glibc的、主要是静态构建的版本。
FrankenPHP 还支持 [将 PHP 应用程序嵌入到静态二进制文件中](embed.md)。
## Linux
我们提供了一个 Docker 镜像来构建 Linux 静态二进制文件:
### 基于musl的完全静态构建
对于一个在任何Linux发行版上运行且不需要依赖项的完全静态二进制文件但不支持动态加载扩展
```console
docker buildx bake --load static-builder
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder
docker buildx bake --load static-builder-musl
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-musl
```
生成的静态二进制文件名为 `frankenphp`,可在当前目录中找到
为了在高度并发的场景中获得更好的性能,请考虑使用 [mimalloc](https://github.com/microsoft/mimalloc) 分配器
```console
docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl
```
### 基于glibc的主要静态构建支持动态扩展
对于一个支持动态加载 PHP 扩展的二进制文件,同时又将所选扩展静态编译:
```console
docker buildx bake --load static-builder-gnu
docker cp $(docker create --name static-builder-gnu dunglas/frankenphp:static-builder-gnu):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-gnu
```
该二进制文件支持所有glibc版本2.17及以上但不支持基于musl的系统如Alpine Linux
生成的主要是静态的(除了 `glibc`)二进制文件名为 `frankenphp`,并且可以在当前目录中找到。
如果你想在没有 Docker 的情况下构建静态二进制文件,请查看 macOS 说明,它也适用于 Linux。
@@ -24,12 +52,12 @@ docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-b
默认情况下,大多数流行的 PHP 扩展都会被编译。
若要减小二进制文件的大小减少攻击面,可以选择使用 `PHP_EXTENSIONS` Docker 参数来自定义构建的扩展。
为了减少二进制文件的大小减少攻击面,可以选择使用 `PHP_EXTENSIONS` Docker ARG 构建的扩展列表
例如,运行以下命令以生成仅包含 `opcache,pdo_sqlite` 扩展的二进制
例如,运行以下命令仅构建 `opcache` 扩展:
```console
docker buildx bake --load --set static-builder.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder
docker buildx bake --load --set static-builder-musl.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder-musl
# ...
```
@@ -38,9 +66,9 @@ docker buildx bake --load --set static-builder.args.PHP_EXTENSIONS=opcache,pdo_s
```console
docker buildx bake \
--load \
--set static-builder.args.PHP_EXTENSIONS=gd \
--set static-builder.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \
static-builder
--set static-builder-musl.args.PHP_EXTENSIONS=gd \
--set static-builder-musl.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \
static-builder-musl
```
### 额外的 Caddy 模块
@@ -50,8 +78,8 @@ docker buildx bake \
```console
docker buildx bake \
--load \
--set static-builder.args.XCADDY_ARGS="--with github.com/darkweak/souin/plugins/caddy --with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" \
static-builder
--set static-builder-musl.args.XCADDY_ARGS="--with github.com/darkweak/souin/plugins/caddy --with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" \
static-builder-musl
```
在本例中,我们为 Caddy 添加了 [Souin](https://souin.io) HTTP 缓存模块,以及 [cbrotli](https://github.com/dunglas/caddy-cbrotli)、[Mercure](https://mercure.rocks) 和 [Vulcain](https://vulcain.rocks) 模块。
@@ -68,7 +96,7 @@ docker buildx bake \
如果遇到了 GitHub API 速率限制,请在 `GITHUB_TOKEN` 的环境变量中设置 GitHub Personal Access Token
```console
GITHUB_TOKEN="xxx" docker --load buildx bake static-builder
GITHUB_TOKEN="xxx" docker --load buildx bake static-builder-musl
# ...
```
@@ -89,12 +117,45 @@ cd frankenphp
以下环境变量可以传递给 `docker build``build-static.sh`
脚本来自定义静态构建:
* `FRANKENPHP_VERSION`: 要使用的 FrankenPHP 版本
* `PHP_VERSION`: 要使用的 PHP 版本
* `PHP_EXTENSIONS`: 要构建的 PHP 扩展([支持的扩展列表](https://static-php.dev/zh/guide/extensions.html)
* `PHP_EXTENSION_LIBS`: 要构建的额外库,为扩展添加额外的功能
* `XCADDY_ARGS`:传递给 [xcaddy](https://github.com/caddyserver/xcaddy) 的参数,例如用于添加额外的 Caddy 模块
* `EMBED`: 要嵌入二进制文件的 PHP 应用程序的路径
* `CLEAN`: 设置后libphp 及其所有依赖项都是重新构建的(不使用缓存)
* `DEBUG_SYMBOLS`: 设置后,调试符号将被保留在二进制文件
* `RELEASE`: (仅限维护者)设置后,生成的二进制文件将上传到 GitHub 上
- `FRANKENPHP_VERSION`: 要使用的 FrankenPHP 版本
- `PHP_VERSION`: 要使用的 PHP 版本
- `PHP_EXTENSIONS`: 要构建的 PHP 扩展([支持的扩展列表](https://static-php.dev/zh/guide/extensions.html)
- `PHP_EXTENSION_LIBS`: 要构建的额外库,为扩展添加额外的功能
- `XCADDY_ARGS`:传递给 [xcaddy](https://github.com/caddyserver/xcaddy) 的参数,例如用于添加额外的 Caddy 模块
- `EMBED`: 要嵌入二进制文件的 PHP 应用程序的路径
- `CLEAN`: 设置后libphp 及其所有依赖项都是重新构建的(不使用缓存)
- `NO_COMPRESS`: 不要使用UPX压缩生成的二进制文件
- `DEBUG_SYMBOLS`: 设置后,调试符号将被保留在二进制文件内
- `MIMALLOC`: (实验性仅限Linux) 用[mimalloc](https://github.com/microsoft/mimalloc)替换musl的mallocng以提高性能。我们仅建议在musl目标构建中使用此选项对于glibc建议禁用此选项并在运行二进制文件时使用[`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html)。
- `RELEASE`: (仅限维护者)设置后,生成的二进制文件将上传到 GitHub 上
## 扩展
使用glibc或基于macOS的二进制文件您可以动态加载PHP扩展。然而这些扩展必须使用ZTS支持进行编译。
由于大多数软件包管理器目前不提供其扩展的 ZTS 版本,因此您必须自己编译它们。
为此,您可以构建并运行 `static-builder-gnu` Docker 容器,远程进入它,并使用 `./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config` 编译扩展。
关于 [Xdebug 扩展](https://xdebug.org) 的示例步骤:
```console
docker build -t gnu-ext -f static-builder-gnu.Dockerfile --build-arg FRANKENPHP_VERSION=1.0 .
docker create --name static-builder-gnu -it gnu-ext /bin/sh
docker start static-builder-gnu
docker exec -it static-builder-gnu /bin/sh
cd /go/src/app/dist/static-php-cli/buildroot/bin
git clone https://github.com/xdebug/xdebug.git && cd xdebug
source scl_source enable devtoolset-10
../phpize
./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config
make
exit
docker cp static-builder-gnu:/go/src/app/dist/static-php-cli/buildroot/bin/xdebug/modules/xdebug.so xdebug-zts.so
docker cp static-builder-gnu:/go/src/app/dist/frankenphp-linux-$(uname -m) ./frankenphp
docker stop static-builder-gnu
docker rm static-builder-gnu
docker rmi gnu-ext
```
这将在当前目录中创建 `frankenphp``xdebug-zts.so`
如果你将 `xdebug-zts.so` 移动到你的扩展目录中,添加 `zend_extension=xdebug-zts.so` 到你的 php.ini 并运行 FrankenPHP它将加载 Xdebug。

View File

@@ -1,7 +1,7 @@
# 使用 FrankenPHP Workers
启动应用程序一次并将其保存在内存中。
FrankenPHP 将在几毫秒内处理传入请求。
启动一次应用程序并将其保存在内存中。
FrankenPHP 将在几毫秒内处理传入请求。
## 启动 Worker 脚本
@@ -17,24 +17,34 @@ docker run \
dunglas/frankenphp
```
### 独立二进制
### 独立二进制文件
使用 `php-server` 命令的 `--worker` 选项 执行命令使当前目录的内容使用 worker
使用 `php-server` 命令的 `--worker` 选项通过 worker 为当前目录的内容提供服务
```console
frankenphp php-server --worker /path/to/your/worker/script.php
```
如果你的 PHP 应用程序已[嵌入到二进制文件中](embed.md),你可以在应用程序的根目录中添加自定义的 `Caddyfile`
它将被自动使用。
还可以使用 `--watch` 选项在[文件更改时重启 worker](config.md#watching-for-file-changes)。
如果 `/path/to/your/app/` 目录或子目录中任何以 `.php` 结尾的文件被修改,以下命令将触发重启:
```console
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
```
## Symfony Runtime
FrankenPHP 的 worker 模式由 [Symfony Runtime 组件](https://symfony.com/doc/current/components/runtime.html) 支持。
要在 worker 中启动任何 Symfony 应用程序,请安装 [PHP Runtime](https://github.com/php-runtime/runtime) 的 FrankenPHP 软件包:
FrankenPHP 的 worker 模式由 [Symfony Runtime Component](https://symfony.com/doc/current/components/runtime.html) 支持。
要在 worker 中启动任何 Symfony 应用程序,请安装 [PHP Runtime](https://github.com/php-runtime/runtime) 的 FrankenPHP 包:
```console
composer require runtime/frankenphp-symfony
```
通过定义 `APP_RUNTIME` 环境变量来启动你的应用服务器,以使用 FrankenPHP Symfony Runtime
通过定义 `APP_RUNTIME` 环境变量来使用 FrankenPHP Symfony Runtime 启动你的应用服务器
```console
docker run \
@@ -47,45 +57,50 @@ docker run \
## Laravel Octane
请参阅 [文档](laravel.md#laravel-octane)。
请参阅[专门的文档](laravel.md#laravel-octane)。
## 自定义应用程序
以下示例演示如何在不依赖第三方库的情况下创建自己的 worker 脚本:
以下示例展示了如何创建自己的 worker 脚本而不依赖第三方库
```php
<?php
// public/index.php
// 防止客户端连接中断时 worker 线程脚本终止
// 防止客户端连接中断时 worker 脚本终止
ignore_user_abort(true);
// 启动应用
// 启动你的应用程序
require __DIR__.'/vendor/autoload.php';
$myApp = new \App\Kernel();
$myApp->boot();
// 循环外的处理程序以获得更好的性能(减少工作量)
// 循环外的处理以获得更好的性能(减少工作量)
$handler = static function () use ($myApp) {
// 收到请求时调用
// 超全局变量 php://input
// 收到请求时调用
// 超全局变量php://input 等都会被重置
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
};
for ($nbRequests = 0, $running = true; isset($_SERVER['MAX_REQUESTS']) && ($nbRequests < ((int)$_SERVER['MAX_REQUESTS'])) && $running; ++$nbRequests) {
$running = \frankenphp_handle_request($handler);
// 发送 HTTP 响应后执行某些操作
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
$keepRunning = \frankenphp_handle_request($handler);
// 在发送 HTTP 响应后做一些事情
$myApp->terminate();
// 调用垃圾收器以减少在页面生成过程中触发垃圾回收器的几率
// 调用垃圾收器以减少在页面生成过程中触发垃圾收集的可能性
gc_collect_cycles();
if (!$keepRunning) break;
}
// 结束清理
// 清理
$myApp->shutdown();
```
然后,启动应用并使用 `FRANKENPHP_CONFIG` 环境变量配置你的 worker
然后,启动你的应用程序并使用 `FRANKENPHP_CONFIG` 环境变量配置你的 worker
```console
docker run \
@@ -95,8 +110,8 @@ docker run \
dunglas/frankenphp
```
默认情况下,每个 CPU 启动个 worker。
可以配置要启动的 worker 数:
默认情况下,每个 CPU 启动 2 个 worker。
可以配置要启动的 worker 数
```console
docker run \
@@ -106,9 +121,59 @@ docker run \
dunglas/frankenphp
```
### 在一定数量的请求后重新启动 Worker
### 在处理一定数量的请求后重 Worker
由于 PHP 最初不是为长时间运行的进程而设计的,因此仍然有许多库和遗留代码会发生内存泄露
在 worker 模式下使用此类代码的解决方法是在处理一定数量的请求后重新启动 worker 程序脚本:
由于 PHP 最初不是为长时间运行的进程而设计的,有许多库和传统代码会泄漏内存
在 worker 模式下使用此类代码的一个解决方法是在处理一定数量的请求后重 worker 脚本:
前面的 worker 代码段允许通过设置名为 `MAX_REQUESTS` 的环境变量来配置要处理的最大请求数。
前面的 worker 代码段允许通过设置名为 `MAX_REQUESTS` 的环境变量来配置要处理的最大请求数。
### 手动重启 Workers
虽然可以在[文件更改时重启 workers](config.md#watching-for-file-changes),但也可以通过 [Caddy admin API](https://caddyserver.com/docs/api) 优雅地重启所有 workers。如果在你的 [Caddyfile](config.md#caddyfile-config) 中启用了 admin你可以通过简单的 POST 请求 ping 重启端点,如下所示:
```console
curl -X POST http://localhost:2019/frankenphp/workers/restart
```
### Worker 故障
如果 worker 脚本因非零退出代码而崩溃FrankenPHP 将使用指数退避策略重启它。
如果 worker 脚本保持运行的时间超过上次退避 × 2
它将不会惩罚 worker 脚本并再次重启它。
但是,如果 worker 脚本在短时间内继续以非零退出代码失败
例如脚本中有拼写错误FrankenPHP 将崩溃并出现错误:`too many consecutive failures`
可以在你的 [Caddyfile](config.md#caddyfile-配置) 中使用 `max_consecutive_failures` 选项配置连续失败的次数:
```caddyfile
frankenphp {
worker {
# ...
max_consecutive_failures 10
}
}
```
## 超全局变量行为
[PHP 超全局变量](https://www.php.net/manual/zh/language.variables.superglobals.php)`$_SERVER``$_ENV``$_GET`...
行为如下:
- 在第一次调用 `frankenphp_handle_request()` 之前,超全局变量包含绑定到 worker 脚本本身的值
- 在调用 `frankenphp_handle_request()` 期间和之后,超全局变量包含从处理的 HTTP 请求生成的值,每次调用 `frankenphp_handle_request()` 都会更改超全局变量的值
要在回调内访问 worker 脚本的超全局变量,必须复制它们并将副本导入到回调的作用域中:
```php
<?php
// 在第一次调用 frankenphp_handle_request() 之前复制 worker 的 $_SERVER 超全局变量
$workerServer = $_SERVER;
$handler = static function () use ($workerServer) {
var_dump($_SERVER); // 与请求绑定的 $_SERVER
var_dump($workerServer); // worker 脚本的 $_SERVER
};
// ...
```

69
docs/cn/x-sendfile.md Normal file
View File

@@ -0,0 +1,69 @@
# 高效服务大型静态文件 (`X-Sendfile`/`X-Accel-Redirect`)
通常,静态文件可以直接由 Web 服务器提供服务,
但有时在发送它们之前需要执行一些 PHP 代码:
访问控制、统计、自定义 HTTP 头...
不幸的是,与直接使用 Web 服务器相比,使用 PHP 服务大型静态文件效率低下
(内存过载、性能降低...)。
FrankenPHP 让你在执行自定义 PHP 代码**之后**将静态文件的发送委托给 Web 服务器。
为此,你的 PHP 应用程序只需定义一个包含要服务的文件路径的自定义 HTTP 头。FrankenPHP 处理其余部分。
此功能在 Apache 中称为 **`X-Sendfile`**,在 NGINX 中称为 **`X-Accel-Redirect`**。
在以下示例中,我们假设项目的文档根目录是 `public/` 目录,
并且我们想要使用 PHP 来服务存储在 `public/` 目录外的文件,
来自名为 `private-files/` 的目录。
## 配置
首先,将以下配置添加到你的 `Caddyfile` 以启用此功能:
```patch
root public/
# ...
+ # Symfony、Laravel 和其他使用 Symfony HttpFoundation 组件的项目需要
+ request_header X-Sendfile-Type x-accel-redirect
+ request_header X-Accel-Mapping ../private-files=/private-files
+
+ intercept {
+ @accel header X-Accel-Redirect *
+ handle_response @accel {
+ root private-files/
+ rewrite * {resp.header.X-Accel-Redirect}
+ method * GET
+
+ # 删除 PHP 设置的 X-Accel-Redirect 头以提高安全性
+ header -X-Accel-Redirect
+
+ file_server
+ }
+ }
php_server
```
## 纯 PHP
将相对文件路径(从 `private-files/`)设置为 `X-Accel-Redirect` 头的值:
```php
header('X-Accel-Redirect: file.txt');
```
## 使用 Symfony HttpFoundation 组件的项目Symfony、Laravel、Drupal...
Symfony HttpFoundation [原生支持此功能](https://symfony.com/doc/current/components/http_foundation.html#serving-files)。
它将自动确定 `X-Accel-Redirect` 头的正确值并将其添加到响应中。
```php
use Symfony\Component\HttpFoundation\BinaryFileResponse;
BinaryFileResponse::trustXSendfileTypeHeader();
$response = new BinaryFileResponse(__DIR__.'/../private-files/file.txt');
// ...
```

View File

@@ -25,8 +25,8 @@ brew link --overwrite --force shivammathur/php/php-zts
### By Compiling PHP
Alternatively, you can compile PHP from sources with the options needed by FrankenPHP by following these steps.
~~
~~First, [get the PHP sources](https://www.php.net/downloads.php) and extract them:
First, [get the PHP sources](https://www.php.net/downloads.php) and extract them:
```console
tar xf php-*
@@ -80,7 +80,7 @@ Some FrankenPHP features depend on optional system dependencies that must be ins
Alternatively, these features can be disabled by passing build tags to the Go compiler.
| Feature | Dependency | Build tag to disable it |
|--------------------------------|-----------------------------------------------------------------------|-------------------------|
| ------------------------------ | --------------------------------------------------------------------- | ----------------------- |
| Brotli compression | [Brotli](https://github.com/google/brotli) | nobrotli |
| Restart workers on file change | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |

View File

@@ -1,17 +1,38 @@
# Configuration
FrankenPHP, Caddy as well as the Mercure and Vulcain modules can be configured using [the formats supported by Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
FrankenPHP, Caddy as well as the [Mercure](mercure.md) and [Vulcain](https://vulcain.rocks) modules can be configured using [the formats supported by Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
In [the Docker images](docker.md), the `Caddyfile` is located at `/etc/frankenphp/Caddyfile`.
The static binary will also look for the `Caddyfile` in the directory where the `frankenphp run` command is executed.
The most common format is the `Caddyfile`, which is a simple, human-readable text format.
By default, FrankenPHP will look for a `Caddyfile` in the current directory.
You can specify a custom path with the `-c` or `--config` option.
A minimal `Caddyfile` to serve a PHP application is shown below:
```caddyfile
# The hostname to respond to
localhost
# Optionaly, the directory to serve files from, otherwise defaults to the current directory
#root public/
php_server
```
A more advanced `Caddyfile` enabling more features and providing convenient environment variables is provided [in the FrankenPHP repository](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile),
and with Docker images.
PHP itself can be configured [using a `php.ini` file](https://www.php.net/manual/en/configuration.file.php).
Depending on your installation method, the PHP interpreter will look for configuration files in locations described above.
Depending on your installation method, FrankenPHP and the PHP interpreter will look for configuration files in locations described below.
## Docker
FrankenPHP:
- `/etc/frankenphp/Caddyfile`: the main configuration file
- `/etc/frankenphp/caddy.d/*.caddy`: additional configuration files that are loaded automatically
PHP:
- `php.ini`: `/usr/local/etc/php/php.ini` (no `php.ini` is provided by default)
- additional configuration files: `/usr/local/etc/php/conf.d/*.ini`
- PHP extensions: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
@@ -29,12 +50,25 @@ RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
## RPM and Debian packages
FrankenPHP:
- `/etc/frankenphp/Caddyfile`: the main configuration file
- `/etc/frankenphp/caddy.d/*.caddy`: additional configuration files that are loaded automatically
PHP:
- `php.ini`: `/etc/frankenphp/php.ini` (a `php.ini` file with production presets is provided by default)
- additional configuration files: `/etc/frankenphp/php.d/*.ini`
- PHP extensions: `/usr/lib/frankenphp/modules/`
## Static binary
FrankenPHP:
- In the current working directory: `Caddyfile`
PHP:
- `php.ini`: The directory in which `frankenphp run` or `frankenphp php-server` is executed, then `/etc/frankenphp/php.ini`
- additional configuration files: `/etc/frankenphp/php.d/*.ini`
- PHP extensions: cannot be loaded, bundle them in the binary itself
@@ -55,8 +89,7 @@ localhost {
}
```
You can also explicitly configure FrankenPHP using the global option:
The `frankenphp` [global option](https://caddyserver.com/docs/caddyfile/concepts#global-options) can be used to configure FrankenPHP.
You can also explicitly configure FrankenPHP using the [global option](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp`:
```caddyfile
{
@@ -230,34 +263,6 @@ and otherwise forward the request to the worker matching the path pattern.
}
```
### Full Duplex (HTTP/1)
When using HTTP/1.x, it may be desirable to enable full-duplex mode to allow writing a response before the entire body
has been read. (for example: WebSocket, Server-Sent Events, etc.)
This is an opt-in configuration that needs to be added to the global options in the `Caddyfile`:
```caddyfile
{
servers {
enable_full_duplex
}
}
```
> [!CAUTION]
>
> Enabling this option may cause old HTTP/1.x clients that don't support full-duplex to deadlock.
> This can also be configured using the `CADDY_GLOBAL_OPTIONS` environment config:
```sh
CADDY_GLOBAL_OPTIONS="servers {
enable_full_duplex
}"
```
You can find more information about this setting in the [Caddy documentation](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
## Environment Variables
The following environment variables can be used to inject Caddy directives in the `Caddyfile` without modifying it:
@@ -294,6 +299,43 @@ You can also change the PHP configuration using the `php_ini` directive in the `
}
```
### Disabling HTTPS
By default, FrankenPHP will automatically enable HTTPS using for all the hostnames, including `localhost`.
If you want to disable HTTPS (for example in a development environment), you can set the `SERVER_NAME` environment variable to `http://` or `:80`:
Alternatively, you can use all other methods described in the [Caddy documentation](https://caddyserver.com/docs/automatic-https#activation).
If you want to use HTTPS with the `127.0.0.1` IP address instead of the `localhost` hostname, please read the [known issues](known-issues.md#using-https127001-with-docker) section.
### Full Duplex (HTTP/1)
When using HTTP/1.x, it may be desirable to enable full-duplex mode to allow writing a response before the entire body
has been read. (for example: [Mercure](mercure.md), WebSocket, Server-Sent Events, etc.)
This is an opt-in configuration that needs to be added to the global options in the `Caddyfile`:
```caddyfile
{
servers {
enable_full_duplex
}
}
```
> [!CAUTION]
>
> Enabling this option may cause old HTTP/1.x clients that don't support full-duplex to deadlock.
> This can also be configured using the `CADDY_GLOBAL_OPTIONS` environment config:
```sh
CADDY_GLOBAL_OPTIONS="servers {
enable_full_duplex
}"
```
You can find more information about this setting in the [Caddy documentation](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
## Enable the Debug Mode
When using the Docker image, set the `CADDY_GLOBAL_OPTIONS` environment variable to `debug` to enable the debug mode:

View File

@@ -1,13 +1,15 @@
# Building Custom Docker Image
[FrankenPHP Docker images](https://hub.docker.com/r/dunglas/frankenphp) are based on [official PHP images](https://hub.docker.com/_/php/). Debian and Alpine Linux variants are provided for popular architectures. Debian variants are recommended.
[FrankenPHP Docker images](https://hub.docker.com/r/dunglas/frankenphp) are based on [official PHP images](https://hub.docker.com/_/php/).
Debian and Alpine Linux variants are provided for popular architectures.
Debian variants are recommended.
Variants for PHP 8.2, 8.3 and 8.4 are provided.
The tags follow this pattern: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
- `<frankenphp-version>` and `<php-version>` are version numbers of FrankenPHP and PHP respectively, ranging from major (e.g. `1`), minor (e.g. `1.2`) to patch versions (e.g. `1.2.3`).
- `<os>` is either `bookworm` (for Debian Bookworm) or `alpine` (for the latest stable version of Alpine).
- `<os>` is either `trixie` (for Debian Trixie), `bookworm` (for Debian Bookworm), or `alpine` (for the latest stable version of Alpine).
[Browse tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
@@ -28,6 +30,11 @@ docker build -t my-php-app .
docker run -it --rm --name my-running-app my-php-app
```
## How to Tweak the Configuration
For convenience, [a default `Caddyfile`](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) containing
useful environment variables is provided in the image.
## How to Install More PHP Extensions
The [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) script is provided in the base image.
@@ -156,8 +163,8 @@ RUN \
useradd ${USER}; \
# Add additional capability to bind to port 80 and 443
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
# Give write access to /data/caddy and /config/caddy
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
# Give write access to /config/caddy and /data/caddy
chown -R ${USER}:${USER} /config/caddy /data/caddy
USER ${USER}
```
@@ -180,8 +187,8 @@ RUN \
useradd ${USER}; \
# Remove default capability
setcap -r /usr/local/bin/frankenphp; \
# Give write access to /data/caddy and /config/caddy
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
# Give write access to /config/caddy and /data/caddy
chown -R ${USER}:${USER} /config/caddy /data/caddy
USER ${USER}
```

View File

@@ -33,7 +33,7 @@ As covered in the manual implementation section below as well, you need to [get
The first step to writing a PHP extension in Go is to create a new Go module. You can use the following command for this:
```console
go mod init github.com/my-account/my-module
go mod init example.com/example
```
The second step is to [get the PHP sources](https://www.php.net/downloads.php) for the next steps. Once you have them, decompress them into the directory of your choice, not inside your Go module:
@@ -47,10 +47,15 @@ tar xf php-*
Everything is now setup to write your native function in Go. Create a new file named `stringext.go`. Our first function will take a string as an argument, the number of times to repeat it, a boolean to indicate whether to reverse the string, and return the resulting string. This should look like this:
```go
package example
// #include <Zend/zend_types.h>
import "C"
import (
"C"
"github.com/dunglas/frankenphp"
"strings"
"unsafe"
"github.com/dunglas/frankenphp"
)
//export_php:function repeat_this(string $str, int $count, bool $reverse): string
@@ -72,28 +77,33 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
There are two important things to note here:
* A directive comment `//export_php:function` defines the function signature in PHP. This is how the generator knows how to generate the PHP function with the right parameters and return type;
* The function must return an `unsafe.Pointer`. FrankenPHP provides an API to help you with type juggling between C and Go.
- A directive comment `//export_php:function` defines the function signature in PHP. This is how the generator knows how to generate the PHP function with the right parameters and return type;
- The function must return an `unsafe.Pointer`. FrankenPHP provides an API to help you with type juggling between C and Go.
While the first point speaks for itself, the second may be harder to apprehend. Let's take a deeper dive to type juggling in the next section.
### Type Juggling
While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding internals of the Zend Engine and how variables are stored internally in PHP. This table summarizes what you need to know:
While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding internals of the Zend Engine and how variables are stored internally in PHP.
This table summarizes what you need to know:
| PHP type | Go type | Direct conversion | C to Go helper | Go to C helper | Class Methods Support |
|--------------------|---------------------|-------------------|-----------------------|------------------------|-----------------------|
| `int` | `int64` | ✅ | - | - | ✅ |
| `?int` | `*int64` | ✅ | - | - | ✅ |
| `float` | `float64` | ✅ | - | - | ✅ |
| `?float` | `*float64` | ✅ | - | - | ✅ |
| `bool` | `bool` | ✅ | - | - | ✅ |
| `?bool` | `*bool` | ✅ | - | - | ✅ |
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
| `object` | `struct` | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ |
| PHP type | Go type | Direct conversion | C to Go helper | Go to C helper | Class Methods Support |
| ------------------ | ----------------------------- | ----------------- | --------------------------------- | ---------------------------------- | --------------------- |
| `int` | `int64` | ✅ | - | - | ✅ |
| `?int` | `*int64` | ✅ | - | - | ✅ |
| `float` | `float64` | ✅ | - | - | ✅ |
| `?float` | `*float64` | ✅ | - | - | ✅ |
| `bool` | `bool` | ✅ | - | - | ✅ |
| `?bool` | `*bool` | ✅ | - | - | ✅ |
| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ |
| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ |
| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ |
| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ |
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
| `object` | `struct` | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ |
> [!NOTE]
>
> This table is not exhaustive yet and will be completed as the FrankenPHP types API gets more complete.
>
> For class methods specifically, primitive types and arrays are currently supported. Objects cannot be used as method parameters or return types yet.
@@ -102,61 +112,113 @@ If you refer to the code snippet of the previous section, you can see that helpe
#### Working with Arrays
FrankenPHP provides native support for PHP arrays through the `frankenphp.Array` type. This type represents both PHP indexed arrays (lists) and associative arrays (hashmaps) with ordered key-value pairs.
FrankenPHP provides native support for PHP arrays through `frankenphp.AssociativeArray` or direct conversion to a map or slice.
`AssociativeArray` represents a [hash map](https://en.wikipedia.org/wiki/Hash_table) composed of a `Map: map[string]any`field and an optional `Order: []string` field (unlike PHP "associative arrays", Go maps aren't ordered).
If order or association are not needed, it's also possible to directly convert to a slice `[]any` or unordered map `map[string]any`.
**Creating and manipulating arrays in Go:**
```go
//export_php:function process_data(array $input): array
func process_data(arr *C.zval) unsafe.Pointer {
// Convert PHP array to Go
goArray := frankenphp.GoArray(unsafe.Pointer(arr))
result := &frankenphp.Array{}
result.SetInt(0, "first")
result.SetInt(1, "second")
result.Append("third") // Automatically assigns next integer key
result.SetString("name", "John")
result.SetString("age", int64(30))
for i := uint32(0); i < goArray.Len(); i++ {
key, value := goArray.At(i)
if key.Type == frankenphp.PHPStringKey {
result.SetString("processed_"+key.Str, value)
} else {
result.SetInt(key.Int+100, value)
}
package example
// #include <Zend/zend_types.h>
import "C"
import (
"unsafe"
"github.com/dunglas/frankenphp"
)
// export_php:function process_data_ordered(array $input): array
func process_data_ordered_map(arr *C.zval) unsafe.Pointer {
// Convert PHP associative array to Go while keeping the order
associativeArray, err := frankenphp.GoAssociativeArray[any](unsafe.Pointer(arr))
if err != nil {
// handle error
}
// Convert back to PHP array
return frankenphp.PHPArray(result)
// loop over the entries in order
for _, key := range associativeArray.Order {
value, _ = associativeArray.Map[key]
// do something with key and value
}
// return an ordered array
// if 'Order' is not empty, only the key-value pairs in 'Order' will be respected
return frankenphp.PHPAssociativeArray[string](frankenphp.AssociativeArray[string]{
Map: map[string]string{
"key1": "value1",
"key2": "value2",
},
Order: []string{"key1", "key2"},
})
}
// export_php:function process_data_unordered(array $input): array
func process_data_unordered_map(arr *C.zval) unsafe.Pointer {
// Convert PHP associative array to a Go map without keeping the order
// ignoring the order will be more performant
goMap, err := frankenphp.GoMap[any](unsafe.Pointer(arr))
if err != nil {
// handle error
}
// loop over the entries in no specific order
for key, value := range goMap {
// do something with key and value
}
// return an unordered array
return frankenphp.PHPMap(map[string]string {
"key1": "value1",
"key2": "value2",
})
}
// export_php:function process_data_packed(array $input): array
func process_data_packed(arr *C.zval) unsafe.Pointer {
// Convert PHP packed array to Go
goSlice, err := frankenphp.GoPackedArray(unsafe.Pointer(arr), false)
if err != nil {
// handle error
}
// loop over the slice in order
for index, value := range goSlice {
// do something with index and value
}
// return a packed array
return frankenphp.PHPPackedArray([]string{"value1", "value2", "value3"})
}
```
**Key features of `frankenphp.Array`:**
**Key features of array conversion:**
* **Ordered key-value pairs** - Maintains insertion order like PHP arrays
* **Mixed key types** - Supports both integer and string keys in the same array
* **Type safety** - The `PHPKey` type ensures proper key handling
* **Automatic list detection** - When converting to PHP, automatically detects if array should be a packed list or hashmap
* **Objects are not supported** - Currently, only scalar types and arrays can be used as values. Providing an object will result in a `null` value in the PHP array.
- **Ordered key-value pairs** - Option to keep the order of the associative array
- **Optimized for multiple cases** - Option to ditch the order for better performance or convert straight to a slice
- **Automatic list detection** - When converting to PHP, automatically detects if array should be a packed list or hashmap
- **Nested Arrays** - Arrays can be nested and will convert all support types automatically (`int64`,`float64`,`string`,`bool`,`nil`,`AssociativeArray`,`map[string]any`,`[]any`)
- **Objects are not supported** - Currently, only scalar types and arrays can be used as values. Providing an object will result in a `null` value in the PHP array.
**Available methods:**
##### Available methods: Packed and Associative
* `SetInt(key int64, value interface{})` - Set value with integer key
* `SetString(key string, value interface{})` - Set value with string key
* `Append(value interface{})` - Add value with next available integer key
* `Len() uint32` - Get number of elements
* `At(index uint32) (PHPKey, interface{})` - Get key-value pair at index
* `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - Convert to PHP array
- `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convert to an ordered PHP array with key-value pairs
- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convert a map to an unordered PHP array with key-value pairs
- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convert a slice to a PHP packed array with indexed values only
- `frankenphp.GoAssociativeArray(arr unsafe.Pointer, ordered bool) frankenphp.AssociativeArray` - Convert a PHP array to an ordered Go `AssociativeArray` (map with order)
- `frankenphp.GoMap(arr unsafe.Pointer) map[string]any` - Convert a PHP array to an unordered Go map
- `frankenphp.GoPackedArray(arr unsafe.Pointer) []any` - Convert a PHP array to a Go slice
### Declaring a Native PHP Class
The generator supports declaring **opaque classes** as Go structs, which can be used to create PHP objects. You can use the `//export_php:class` directive comment to define a PHP class. For example:
```go
package example
//export_php:class User
type UserStruct struct {
Name string
@@ -168,11 +230,11 @@ type UserStruct struct {
**Opaque classes** are classes where the internal structure (properties) is hidden from PHP code. This means:
* **No direct property access**: You cannot read or write properties directly from PHP (`$user->name` won't work)
* **Method-only interface** - All interactions must go through methods you define
* **Better encapsulation** - Internal data structure is completely controlled by Go code
* **Type safety** - No risk of PHP code corrupting internal state with wrong types
* **Cleaner API** - Forces to design a proper public interface
- **No direct property access**: You cannot read or write properties directly from PHP (`$user->name` won't work)
- **Method-only interface** - All interactions must go through methods you define
- **Better encapsulation** - Internal data structure is completely controlled by Go code
- **Type safety** - No risk of PHP code corrupting internal state with wrong types
- **Cleaner API** - Forces to design a proper public interface
This approach provides better encapsulation and prevents PHP code from accidentally corrupting the internal state of your Go objects. All interactions with the object must go through the methods you explicitly define.
@@ -181,6 +243,16 @@ This approach provides better encapsulation and prevents PHP code from accidenta
Since properties are not directly accessible, you **must define methods** to interact with your opaque classes. Use the `//export_php:method` directive to define behavior:
```go
package example
// #include <Zend/zend_types.h>
import "C"
import (
"unsafe"
"github.com/dunglas/frankenphp"
)
//export_php:class User
type UserStruct struct {
Name string
@@ -213,18 +285,28 @@ func (us *UserStruct) SetNamePrefix(prefix *C.zend_string) {
The generator supports nullable parameters using the `?` prefix in PHP signatures. When a parameter is nullable, it becomes a pointer in your Go function, allowing you to check if the value was `null` in PHP:
```go
package example
// #include <Zend/zend_types.h>
import "C"
import (
"unsafe"
"github.com/dunglas/frankenphp"
)
//export_php:method User::updateInfo(?string $name, ?int $age, ?bool $active): void
func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool) {
// Check if name was provided (not null)
if name != nil {
us.Name = frankenphp.GoString(unsafe.Pointer(name))
}
// Check if age was provided (not null)
if age != nil {
us.Age = int(*age)
}
// Check if active was provided (not null)
if active != nil {
us.Active = *active
@@ -234,12 +316,13 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
**Key points about nullable parameters:**
* **Nullable primitive types** (`?int`, `?float`, `?bool`) become pointers (`*int64`, `*float64`, `*bool`) in Go
* **Nullable strings** (`?string`) remain as `*C.zend_string` but can be `nil`
* **Check for `nil`** before dereferencing pointer values
* **PHP `null` becomes Go `nil`** - when PHP passes `null`, your Go function receives a `nil` pointer
- **Nullable primitive types** (`?int`, `?float`, `?bool`) become pointers (`*int64`, `*float64`, `*bool`) in Go
- **Nullable strings** (`?string`) remain as `*C.zend_string` but can be `nil`
- **Check for `nil`** before dereferencing pointer values
- **PHP `null` becomes Go `nil`** - when PHP passes `null`, your Go function receives a `nil` pointer
> [!WARNING]
>
> Currently, class methods have the following limitations. **Objects are not supported** as parameter types or return types. **Arrays are fully supported** for both parameters and return types. Supported types: `string`, `int`, `float`, `bool`, `array`, and `void` (for return type). **Nullable parameter types are fully supported** for all scalar types (`?string`, `?int`, `?float`, `?bool`).
After generating the extension, you will be allowed to use the class and its methods in PHP. Note that you **cannot access properties directly**:
@@ -276,6 +359,8 @@ The generator supports exporting Go constants to PHP using two directives: `//ex
Use the `//export_php:const` directive to create global PHP constants:
```go
package example
//export_php:const
const MAX_CONNECTIONS = 100
@@ -294,6 +379,8 @@ const STATUS_ERROR = iota
Use the `//export_php:classconstant ClassName` directive to create constants that belong to a specific PHP class:
```go
package example
//export_php:classconstant User
const STATUS_ACTIVE = 1
@@ -333,10 +420,15 @@ The directive supports various value types including strings, integers, booleans
You can use constants just like you are used to in the Go code. For example, let's take the `repeat_this()` function we declared earlier and change the last argument to an integer:
```go
package example
// #include <Zend/zend_types.h>
import "C"
import (
"C"
"github.com/dunglas/frankenphp"
"strings"
"strings"
"unsafe"
"github.com/dunglas/frankenphp"
)
//export_php:const
@@ -353,37 +445,37 @@ const MODE_UPPERCASE = 2
//export_php:function repeat_this(string $str, int $count, int $mode): string
func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(s))
str := frankenphp.GoString(unsafe.Pointer(s))
result := strings.Repeat(str, int(count))
if mode == STR_REVERSE {
// reverse the string
}
result := strings.Repeat(str, int(count))
if mode == STR_REVERSE {
// reverse the string
}
if mode == STR_NORMAL {
// no-op, just to showcase the constant
}
if mode == STR_NORMAL {
// no-op, just to showcase the constant
}
return frankenphp.PHPString(result, false)
return frankenphp.PHPString(result, false)
}
//export_php:class StringProcessor
type StringProcessorStruct struct {
// internal fields
// internal fields
}
//export_php:method StringProcessor::process(string $input, int $mode): string
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(input))
switch mode {
case MODE_LOWERCASE:
str = strings.ToLower(str)
case MODE_UPPERCASE:
str = strings.ToUpper(str)
}
return frankenphp.PHPString(str, false)
str := frankenphp.GoString(unsafe.Pointer(input))
switch mode {
case MODE_LOWERCASE:
str = strings.ToLower(str)
case MODE_UPPERCASE:
str = strings.ToUpper(str)
}
return frankenphp.PHPString(str, false)
}
```
@@ -397,9 +489,13 @@ Use the `//export_php:namespace` directive at the top of your Go file to place a
```go
//export_php:namespace My\Extension
package main
package example
import "C"
import (
"unsafe"
"github.com/dunglas/frankenphp"
)
//export_php:function hello(): string
func hello() string {
@@ -437,21 +533,21 @@ echo My\Extension\STATUS_ACTIVE; // 1
#### Important Notes
* Only **one** namespace directive is allowed per file. If multiple namespace directives are found, the generator will return an error.
* The namespace applies to **all** exported symbols in the file: functions, classes, methods, and constants.
* Namespace names follow PHP namespace conventions using backslashes (`\`) as separators.
* If no namespace is declared, symbols are exported to the global namespace as usual.
- Only **one** namespace directive is allowed per file. If multiple namespace directives are found, the generator will return an error.
- The namespace applies to **all** exported symbols in the file: functions, classes, methods, and constants.
- Namespace names follow PHP namespace conventions using backslashes (`\`) as separators.
- If no namespace is declared, symbols are exported to the global namespace as usual.
### Generating the Extension
This is where the magic happens, and your extension can now be generated. You can run the generator with the following command:
```console
GEN_STUB_FILE=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
```
> [!NOTE]
> Don't forget to set the `GEN_STUB_FILE` environment variable to the path of the `gen_stub.php` file in the PHP sources you downloaded earlier. This is the same `gen_stub.php` script mentioned in the manual implementation section.
> Don't forget to set the `GEN_STUB_SCRIPT` environment variable to the path of the `gen_stub.php` file in the PHP sources you downloaded earlier. This is the same `gen_stub.php` script mentioned in the manual implementation section.
If everything went well, a new directory named `build` should have been created. This directory contains the generated files for your extension, including the `my_extension.go` file with the generated PHP function stubs.
@@ -502,25 +598,26 @@ We'll see how to write a simple PHP extension in Go that defines a new native fu
In your module, you need to define a new native function that will be called from PHP. To do this, create a file with the name you want, for example, `extension.go`, and add the following code:
```go
package ext_go
package example
//#include "extension.h"
// #include "extension.h"
import "C"
import (
"unsafe"
"github.com/caddyserver/caddy/v2"
"github.com/dunglas/frankenphp"
"log/slog"
"unsafe"
"github.com/dunglas/frankenphp"
)
func init() {
frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry))
frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry))
}
//export go_print_something
func go_print_something() {
go func() {
caddy.Log().Info("Hello from a goroutine!")
}()
go func() {
slog.Info("Hello from a goroutine!")
}()
}
```
@@ -567,9 +664,9 @@ extern zend_module_entry ext_module_entry;
Next, create a file named `extension.c` that will perform the following steps:
* Include PHP headers;
* Declare our new native PHP function `go_print()`;
* Declare the extension metadata.
- Include PHP headers;
- Declare our new native PHP function `go_print()`;
- Declare the extension metadata.
Let's start by including the required headers:
@@ -696,21 +793,33 @@ There's only one thing left to do: implement the `go_upper` function in Go.
Our Go function will take a `*C.zend_string` as a parameter, convert it to a Go string using FrankenPHP's helper function, process it, and return the result as a new `*C.zend_string`. The helper functions handle all the memory management and conversion complexity for us.
```go
import "strings"
package example
// #include <Zend/zend_types.h>
import "C"
import (
"unsafe"
"strings"
"github.com/dunglas/frankenphp"
)
//export go_upper
func go_upper(s *C.zend_string) *C.zend_string {
str := frankenphp.GoString(unsafe.Pointer(s))
upper := strings.ToUpper(str)
return (*C.zend_string)(frankenphp.PHPString(upper, false))
}
```
This approach is much cleaner and safer than manual memory management. FrankenPHP's helper functions handle the conversion between PHP's `zend_string` format and Go strings automatically. The `false` parameter in `PHPString()` indicates that we want to create a new non-persistent string (freed at the end of the request).
This approach is much cleaner and safer than manual memory management.
FrankenPHP's helper functions handle the conversion between PHP's `zend_string` format and Go strings automatically.
The `false` parameter in `PHPString()` indicates that we want to create a new non-persistent string (freed at the end of the request).
> [!TIP]
>
> In this example, we don't perform any error handling, but you should always check that pointers are not `nil` and that the data is valid before using it in your Go functions.
### Integrating the Extension into FrankenPHP

View File

@@ -197,7 +197,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
## Ressources Liées à Docker
- [Définition du fichier Bake](https://docs.docker.com/build/customize/bake/file-definition/)
- [docker buildx build](https://docs.docker.com/engine/reference/commandline/buildx_build/)
- [`docker buildx build`](https://docs.docker.com/engine/reference/commandline/buildx_build/)
## Commande utile

View File

@@ -82,7 +82,7 @@ Certaines fonctionnalités de FrankenPHP nécessitent des dépendances optionnel
Ces fonctionnalités peuvent également être désactivées en passant des tags de compilation au compilateur Go.
| Fonctionnalité | Dépendance | Tag de compilation pour la désactiver |
|---------------------------------------------------------|-----------------------------------------------------------------------|---------------------------------------|
| ------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------- |
| Compression Brotli | [Brotli](https://github.com/google/brotli) | nobrotli |
| Redémarrage des workers en cas de changement de fichier | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |

View File

@@ -1,4 +1,4 @@
# Configuration
# Configuration
FrankenPHP, Caddy ainsi que les modules Mercure et Vulcain peuvent être configurés en utilisant [les formats pris en charge par Caddy](https://caddyserver.com/docs/getting-started#your-first-config).

View File

@@ -7,7 +7,7 @@ Des variantes pour PHP 8.2, 8.3 et 8.4 sont disponibles. [Parcourir les tags](ht
Les tags suivent le pattern suivant: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
- `<frankenphp-version>` et `<php-version>` sont repsectivement les numéros de version de FrankenPHP et PHP, allant de majeur (e.g. `1`), mineur (e.g. `1.2`) à des versions correctives (e.g. `1.2.3`).
- `<os>` est soit `bookworm` (pour Debian Bookworm) ou `alpine` (pour la dernière version stable d'Alpine).
- `<os>` est soit `trixie` (pour Debian Trixie), `bookworm` (pour Debian Bookworm) ou `alpine` (pour la dernière version stable d'Alpine).
[Parcourir les tags](https://hub.docker.com/r/dunglas/frankenphp/tags).

View File

@@ -72,26 +72,27 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
Il y a deux choses importantes à noter ici :
* Une directive `//export_php:function` définit la signature de la fonction en PHP. C'est ainsi que le générateur sait comment générer la fonction PHP avec les bons paramètres et le bon type de retour ;
* La fonction doit retourner un `unsafe.Pointer`. FrankenPHP fournit une API pour vous aider avec le jonglage de types entre C et Go.
- Une directive `//export_php:function` définit la signature de la fonction en PHP. C'est ainsi que le générateur sait comment générer la fonction PHP avec les bons paramètres et le bon type de retour ;
- La fonction doit retourner un `unsafe.Pointer`. FrankenPHP fournit une API pour vous aider avec le jonglage de types entre C et Go.
Alors que le premier point parle de lui-même, le second peut être plus difficile à appréhender. Plongeons plus profondément dans la jonglage de types dans la section suivante.
Alors que le premier point parle de lui-même, le second peut être plus difficile à appréhender. Plongeons plus profondément dans le jonglage de types dans la section suivante.
### Jonglage de Types
Bien que certains types de variables aient la même représentation mémoire entre C/PHP et Go, certains types nécessitent plus de logique pour être directement utilisés. C'est peut-être la partie la plus difficile quand il s'agit d'écrire des extensions car cela nécessite de comprendre les fonctionnements internes du moteur Zend et comment les variables sont stockées dans le moteur de PHP. Ce tableau résume ce que vous devez savoir :
| Type PHP | Type Go | Conversion directe | Assistant C vers Go | Assistant Go vers C | Support des Méthodes de Classe |
|--------------------|---------------------|--------------------|-------------------------|-------------------------|--------------------------------|
| `int` | `int64` | ✅ | - | - | ✅ |
| `?int` | `*int64` | ✅ | - | - | ✅ |
| `float` | `float64` | ✅ | - | - | ✅ |
| `?float` | `*float64` | ✅ | - | - | ✅ |
| `bool` | `bool` | ✅ | - | - | ✅ |
| `?bool` | `*bool` | ✅ | - | - | ✅ |
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
| `object` | `struct` | ❌ | _Pas encore implémenté_ | _Pas encore implémenté_ | ❌ |
| ------------------ | ------------------- | ------------------ | ----------------------- | ----------------------- | ------------------------------ |
| `int` | `int64` | ✅ | - | - | ✅ |
| `?int` | `*int64` | ✅ | - | - | ✅ |
| `float` | `float64` | ✅ | - | - | ✅ |
| `?float` | `*float64` | ✅ | - | - | ✅ |
| `bool` | `bool` | ✅ | - | - | ✅ |
| `?bool` | `*bool` | ✅ | - | - | ✅ |
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
| `object` | `struct` | ❌ | _Pas encore implémenté_ | _Pas encore implémenté_ | ❌ |
> [!NOTE]
> Ce tableau n'est pas encore exhaustif et sera complété au fur et à mesure que l'API de types FrankenPHP deviendra plus complète.
@@ -111,16 +112,16 @@ FrankenPHP fournit un support natif pour les tableaux PHP à travers le type `fr
func process_data(arr *C.zval) unsafe.Pointer {
// Convertir le tableau PHP vers Go
goArray := frankenphp.GoArray(unsafe.Pointer(arr))
result := &frankenphp.Array{}
result.SetInt(0, "first")
result.SetInt(1, "second")
result.Append("third") // Assigne automatiquement la prochaine clé entière
result.SetString("name", "John")
result.SetString("age", int64(30))
for i := uint32(0); i < goArray.Len(); i++ {
key, value := goArray.At(i)
if key.Type == frankenphp.PHPStringKey {
@@ -129,7 +130,7 @@ func process_data(arr *C.zval) unsafe.Pointer {
result.SetInt(key.Int+100, value)
}
}
// Reconvertir vers un tableau PHP
return frankenphp.PHPArray(result)
}
@@ -137,20 +138,20 @@ func process_data(arr *C.zval) unsafe.Pointer {
**Fonctionnalités clés de `frankenphp.Array` :**
* **Paires clé-valeur ordonnées** - Maintient l'ordre d'insertion comme les tableaux PHP
* **Types de clés mixtes** - Supporte les clés entières et chaînes dans le même tableau
* **Sécurité de type** - Le type `PHPKey` assure une gestion appropriée des clés
* **Détection automatique de liste** - Lors de la conversion vers PHP, détecte automatiquement si le tableau doit être une liste compacte ou un hashmap
* **Les objets ne sont pas supportés** - Actuellement, seuls les types scalaires et les tableaux sont supportés. Passer un objet en tant qu'élément du tableau résultera d'une valeur `null` dans le tableau PHP.
- **Paires clé-valeur ordonnées** - Maintient l'ordre d'insertion comme les tableaux PHP
- **Types de clés mixtes** - Supporte les clés entières et chaînes dans le même tableau
- **Sécurité de type** - Le type `PHPKey` assure une gestion appropriée des clés
- **Détection automatique de liste** - Lors de la conversion vers PHP, détecte automatiquement si le tableau doit être une liste compacte ou un hashmap
- **Les objets ne sont pas supportés** - Actuellement, seuls les types scalaires et les tableaux sont supportés. Passer un objet en tant qu'élément du tableau résultera d'une valeur `null` dans le tableau PHP.
**Méthodes disponibles :**
* `SetInt(key int64, value interface{})` - Définir une valeur avec une clé entière
* `SetString(key string, value interface{})` - Définir une valeur avec une clé chaîne
* `Append(value interface{})` - Ajouter une valeur avec la prochaine clé entière disponible
* `Len() uint32` - Obtenir le nombre d'éléments
* `At(index uint32) (PHPKey, interface{})` - Obtenir la paire clé-valeur à l'index
* `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - Convertir vers un tableau PHP
- `SetInt(key int64, value any)` - Définir une valeur avec une clé entière
- `SetString(key string, value any)` - Définir une valeur avec une clé chaîne
- `Append(value any)` - Ajouter une valeur avec la prochaine clé entière disponible
- `Len() uint32` - Obtenir le nombre d'éléments
- `At(index uint32) (PHPKey, any)` - Obtenir la paire clé-valeur à l'index
- `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - Convertir vers un tableau PHP
### Déclarer une Classe PHP Native
@@ -168,11 +169,11 @@ type UserStruct struct {
Les **classes opaques** sont des classes avec lesquelles la structure interne (comprendre : les propriétés) est cachée du code PHP. Cela signifie :
* **Pas d'accès direct aux propriétés** : Vous ne pouvez pas lire ou écrire des propriétés directement depuis PHP (`$user->name` ne fonctionnera pas)
* **Interface uniquement par méthodes** - Toutes les interactions doivent passer par les méthodes que vous définissez
* **Meilleure encapsulation** - La structure de données interne est complètement contrôlée par le code Go
* **Sécurité de type** - Aucun risque que le code PHP corrompe l'état interne avec de mauvais types
* **API plus propre** - Force à concevoir une interface publique appropriée
- **Pas d'accès direct aux propriétés** : Vous ne pouvez pas lire ou écrire des propriétés directement depuis PHP (`$user->name` ne fonctionnera pas)
- **Interface uniquement par méthodes** - Toutes les interactions doivent passer par les méthodes que vous définissez
- **Meilleure encapsulation** - La structure de données interne est complètement contrôlée par le code Go
- **Sécurité de type** - Aucun risque que le code PHP corrompe l'état interne avec de mauvais types
- **API plus propre** - Force à concevoir une interface publique appropriée
Cette approche fournit une meilleure encapsulation et empêche le code PHP de corrompre accidentellement l'état interne de vos objets Go. Toutes les interactions avec l'objet doivent passer par les méthodes que vous définissez explicitement.
@@ -219,12 +220,12 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
if name != nil {
us.Name = frankenphp.GoString(unsafe.Pointer(name))
}
// $age est null?
if age != nil {
us.Age = int(*age)
}
// $active est null?
if active != nil {
us.Active = *active
@@ -234,10 +235,10 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
**Points clés sur les paramètres nullables :**
* **Types primitifs nullables** (`?int`, `?float`, `?bool`) deviennent des pointeurs (`*int64`, `*float64`, `*bool`) en Go
* **Chaînes nullables** (`?string`) restent comme `*C.zend_string` mais peuvent être `nil`
* **Vérifiez `nil`** avant de déréférencer les valeurs de pointeur
* **PHP `null` devient Go `nil`** - quand PHP passe `null`, votre fonction Go reçoit un pointeur `nil`
- **Types primitifs nullables** (`?int`, `?float`, `?bool`) deviennent des pointeurs (`*int64`, `*float64`, `*bool`) en Go
- **Chaînes nullables** (`?string`) restent comme `*C.zend_string` mais peuvent être `nil`
- **Vérifiez `nil`** avant de déréférencer les valeurs de pointeur
- **PHP `null` devient Go `nil`** - quand PHP passe `null`, votre fonction Go reçoit un pointeur `nil`
> [!WARNING]
> Actuellement, les méthodes de classe ont les limitations suivantes. **Les objets ne sont pas supportés** comme types de paramètres ou types de retour. **Les tableaux sont entièrement supportés** pour les paramètres et types de retour. Types supportés : `string`, `int`, `float`, `bool`, `array`, et `void` (pour le type de retour). **Les types de paramètres nullables sont entièrement supportés** pour tous les types scalaires (`?string`, `?int`, `?float`, `?bool`).
@@ -356,7 +357,7 @@ func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(s))
result := strings.Repeat(str, int(count))
if mode == STR_REVERSE {
if mode == STR_REVERSE {
// inverser la chaîne
}
@@ -375,14 +376,14 @@ type StringProcessorStruct struct {
//export_php:method StringProcessor::process(string $input, int $mode): string
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(input))
switch mode {
case MODE_LOWERCASE:
str = strings.ToLower(str)
case MODE_UPPERCASE:
str = strings.ToUpper(str)
}
return frankenphp.PHPString(str, false)
}
```
@@ -437,21 +438,21 @@ echo My\Extension\STATUS_ACTIVE; // 1
#### Notes Importantes
* Seule **une** directive d'espace de noms est autorisée par fichier. Si plusieurs directives d'espace de noms sont trouvées, le générateur retournera une erreur.
* L'espace de noms s'applique à **tous** les symboles exportés dans le fichier : fonctions, classes, méthodes et constantes.
* Les noms d'espaces de noms suivent les conventions des espaces de noms PHP en utilisant les barres obliques inverses (`\`) comme séparateurs.
* Si aucun espace de noms n'est déclaré, les symboles sont exportés vers l'espace de noms global comme d'habitude.
- Seule **une** directive d'espace de noms est autorisée par fichier. Si plusieurs directives d'espace de noms sont trouvées, le générateur retournera une erreur.
- L'espace de noms s'applique à **tous** les symboles exportés dans le fichier : fonctions, classes, méthodes et constantes.
- Les noms d'espaces de noms suivent les conventions des espaces de noms PHP en utilisant les barres obliques inverses (`\`) comme séparateurs.
- Si aucun espace de noms n'est déclaré, les symboles sont exportés vers l'espace de noms global comme d'habitude.
### Générer l'Extension
C'est là que la magie opère, et votre extension peut maintenant être générée. Vous pouvez exécuter le générateur avec la commande suivante :
```console
GEN_STUB_FILE=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
```
> [!NOTE]
> N'oubliez pas de définir la variable d'environnement `GEN_STUB_FILE` sur le chemin du fichier `gen_stub.php` dans les sources PHP que vous avez téléchargées plus tôt. C'est le même script `gen_stub.php` mentionné dans la section d'implémentation manuelle.
> N'oubliez pas de définir la variable d'environnement `GEN_STUB_SCRIPT` sur le chemin du fichier `gen_stub.php` dans les sources PHP que vous avez téléchargées plus tôt. C'est le même script `gen_stub.php` mentionné dans la section d'implémentation manuelle.
Si tout s'est bien passé, un nouveau répertoire nommé `build` devrait avoir été créé. Ce répertoire contient les fichiers générés pour votre extension, incluant le fichier `my_extension.go` avec les stubs de fonction PHP générés.
@@ -567,9 +568,9 @@ extern zend_module_entry ext_module_entry;
Ensuite, créez un fichier nommé `extension.c` qui effectuera les étapes suivantes :
* Inclure les en-têtes PHP ;
* Déclarer notre nouvelle fonction PHP native `go_print()` ;
* Déclarer les métadonnées de l'extension.
- Inclure les en-têtes PHP ;
- Déclarer notre nouvelle fonction PHP native `go_print()` ;
- Déclarer les métadonnées de l'extension.
Commençons par inclure les en-têtes requis :
@@ -701,9 +702,9 @@ import "strings"
//export go_upper
func go_upper(s *C.zend_string) *C.zend_string {
str := frankenphp.GoString(unsafe.Pointer(s))
upper := strings.ToUpper(str)
return (*C.zend_string)(frankenphp.PHPString(upper, false))
}
```

View File

@@ -23,7 +23,7 @@ La fonction [get_browser()](https://www.php.net/manual/fr/function.get-browser.p
## Binaire autonome et images Docker basées sur Alpine
Le binaire autonome et les images docker basées sur Alpine (`dunglas/frankenphp:*-alpine`) utilisent [musl libc](https://musl.libc.org/) au lieu de [glibc et ses amis](https://www.etalabs.net/compare_libcs.html), pour garder une taille de binaire plus petite. Cela peut entraîner des problèmes de compatibilité. En particulier, le drapeau glob `GLOB_BRACE` n'est [pas disponible](https://www.php.net/manual/fr/function.glob.php).
Le binaire autonome et les images Docker basées sur Alpine (`dunglas/frankenphp:*-alpine`) utilisent [musl libc](https://musl.libc.org/) au lieu de [glibc et ses amis](https://www.etalabs.net/compare_libcs.html), pour garder une taille de binaire plus petite. Cela peut entraîner des problèmes de compatibilité. En particulier, le drapeau glob `GLOB_BRACE` n'est [pas disponible](https://www.php.net/manual/fr/function.glob.php).
## Utilisation de `https://127.0.0.1` avec Docker
@@ -130,7 +130,7 @@ pour trouver l'endroit où les certificats CA doivent être installés et stocke
> Les contextes Web et CLI peuvent avoir des paramètres différents.
> Assurez-vous d'exécuter `openssl_get_cert_locations()` dans le bon contexte.
[Les certificats CA extraits de Mozilla peuvent être téléchargés sur le site curl](https://curl.se/docs/caextract.html).
[Les certificats CA extraits de Mozilla peuvent être téléchargés sur le site de cURL](https://curl.se/docs/caextract.html).
Alternativement, de nombreuses distributions, y compris Debian, Ubuntu, et Alpine fournissent des paquets nommés `ca-certificates` qui contiennent ces certificats.

View File

@@ -5,7 +5,7 @@ mais parfois, il est nécessaire d'exécuter du code PHP avant de les envoyer :
contrôle d'accès, statistiques, en-têtes HTTP personnalisés...
Malheureusement, utiliser PHP pour servir de gros fichiers statiques est inefficace comparé à
à l'utilisation directe du serveur web (surcharge mémoire, diminution des performances...).
l'utilisation directe du serveur web (surcharge mémoire, diminution des performances...).
FrankenPHP permet de déléguer l'envoi des fichiers statiques au serveur web
**après** avoir exécuté du code PHP personnalisé.

View File

@@ -7,7 +7,7 @@ every approved pull request or on your own fork once setup.
In the repository settings, under secrets, add the following secrets:
- `REGISTRY_LOGIN_SERVER`: The docker registry to use (e.g. `docker.io`).
- `REGISTRY_LOGIN_SERVER`: The Docker registry to use (e.g. `docker.io`).
- `REGISTRY_USERNAME`: The username to use to log in to the registry (e.g. `dunglas`).
- `REGISTRY_PASSWORD`: The password to use to log in to the registry (e.g. an access key).
- `IMAGE_NAME`: The name of the image (e.g. `dunglas/frankenphp`).

219
docs/ja/CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,219 @@
# コントリビューション
## PHPのコンパイル
### Dockerを使用する場合Linux
開発用Dockerイメージをビルドします
```console
docker build -t frankenphp-dev -f dev.Dockerfile .
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 -p 443:443 -p 443:443/udp -v $PWD:/go/src/app -it frankenphp-dev
```
このイメージには通常の開発ツールGo、GDB、Valgrind、Neovimなどが含まれており、PHP設定ファイルは以下の場所に配置されます。
- php.ini: `/etc/frankenphp/php.ini` 開発用のプリセットが適用されたphp.iniファイルがデフォルトで提供されます。
- 追加の設定ファイル: `/etc/frankenphp/php.d/*.ini`
- PHP拡張モジュール: `/usr/lib/frankenphp/modules/`
お使いのDockerのバージョンが23.0未満の場合、dockerignore[パターンの問題](https://github.com/moby/moby/pull/42676)によりビルドに失敗する可能性があります。以下のように`.dockerignore`にディレクトリを追加してください。
```patch
!testdata/*.php
!testdata/*.txt
+!caddy
+!internal
```
### Dockerを使用しない場合LinuxおよびmacOS
[ソースからのコンパイル手順](https://frankenphp.dev/docs/compile/)に従い、`--debug`設定フラグを渡してください。
## テストスイートの実行
```console
go test -tags watcher -race -v ./...
```
## Caddyモジュール
FrankenPHPのCaddyモジュール付きでCaddyをビルドします
```console
cd caddy/frankenphp/
go build -tags watcher,brotli,nobadger,nomysql,nopgx
cd ../../
```
FrankenPHPのCaddyモジュール付きでCaddyを実行します
```console
cd testdata/
../caddy/frankenphp/frankenphp run
```
サーバーは`127.0.0.1:80`で待ち受けています:
> [!NOTE]
> Dockerを使用している場合は、コンテナのポート80をバインドするか、コンテナ内で実行する必要があります。
```console
curl -vk http://127.0.0.1/phpinfo.php
```
## 最小構成のテストサーバー
最小構成のテストサーバーをビルドします:
```console
cd internal/testserver/
go build
cd ../../
```
テストサーバーを実行します:
```console
cd testdata/
../internal/testserver/testserver
```
サーバーは`127.0.0.1:8080`で待ち受けています:
```console
curl -v http://127.0.0.1:8080/phpinfo.php
```
## Dockerイメージをローカルでビルドする
bakeプランを出力します
```console
docker buildx bake -f docker-bake.hcl --print
```
amd64用のFrankenPHPイメージをローカルでビルドします
```console
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/amd64"
```
arm64用のFrankenPHPイメージをローカルでビルドします
```console
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/arm64"
```
arm64とamd64用のFrankenPHPイメージをスクラッチからビルドしてDocker Hubにプッシュします
```console
docker buildx bake -f docker-bake.hcl --pull --no-cache --push
```
## 静的ビルドでのセグメンテーション違反のデバッグ
1. GitHubからFrankenPHPバイナリのデバッグ版をダウンロードするか、デバッグシンボルを含む独自の静的ビルドを作成します
```console
docker buildx bake \
--load \
--set static-builder.args.DEBUG_SYMBOLS=1 \
--set "static-builder.platform=linux/amd64" \
static-builder
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
```
2. 現在使用している`frankenphp`を、デバッグ版のFrankenPHP実行ファイルに置き換えます
3. 通常通りFrankenPHPを起動しますあるいは、GDBで直接FrankenPHPを開始することもできます`gdb --args frankenphp run`
4. GDBでプロセスにアタッチします
```console
gdb -p `pidof frankenphp`
```
5. 必要に応じて、GDBシェルで`continue`と入力します
6. FrankenPHPをクラッシュさせます
7. GDBシェルで`bt`と入力します
8. 出力結果をコピーします
## GitHub Actionsでのセグメンテーション違反のデバッグ
1. `.github/workflows/tests.yml`を開きます
2. PHPデバッグシンボルを有効にします
```patch
- uses: shivammathur/setup-php@v2
# ...
env:
phpts: ts
+ debug: true
```
3. `tmate`を有効にしてコンテナに接続できるようにします
```patch
- name: Set CGO flags
run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
+ - run: |
+ sudo apt install gdb
+ mkdir -p /home/runner/.config/gdb/
+ printf "set auto-load safe-path /\nhandle SIG34 nostop noprint pass" > /home/runner/.config/gdb/gdbinit
+ - uses: mxschmitt/action-tmate@v3
```
4. コンテナに接続します
5. `frankenphp.go`を開きます
6. `cgosymbolizer`を有効にします
```patch
- //_ "github.com/ianlancetaylor/cgosymbolizer"
+ _ "github.com/ianlancetaylor/cgosymbolizer"
```
7. モジュールを取得します:`go get`
8. コンテナ内で、GDBなどを使用できます
```console
go test -tags watcher -c -ldflags=-w
gdb --args frankenphp.test -test.run ^MyTest$
```
9. バグが修正されたら、これらの変更をすべて元に戻します
## その他の開発リソース
- [uWSGIでのPHP埋め込み](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
- [NGINX UnitでのPHP埋め込み](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
- [Go言語でのPHP埋め込み (go-php)](https://github.com/deuill/go-php)
- [Go言語でのPHP埋め込み (GoEmPHP)](https://github.com/mikespook/goemphp)
- [C++でのPHP埋め込み](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
- [Sara Golemon 著『Extending and Embedding PHP』](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
- [TSRMLS_CCとは何か](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
- [SDL バインディング](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
## Docker関連リソース
- [Bakeファイル定義](https://docs.docker.com/build/customize/bake/file-definition/)
- [`docker buildx build`](https://docs.docker.com/engine/reference/commandline/buildx_build/)
## 便利なコマンド
```console
apk add strace util-linux gdb
strace -e 'trace=!futex,epoll_ctl,epoll_pwait,tgkill,rt_sigreturn' -p 1
```
## ドキュメントの翻訳
新しい言語でドキュメントとサイトを翻訳するには、
以下の手順で行ってください。
1. このリポジトリの`docs/`ディレクトリに、言語の2文字ISOコードを名前にした新しいディレクトリを作成します
2. `docs/`ディレクトリのルートにある全ての`.md`ファイルを新しいディレクトリにコピーします(翻訳のソースとして常に英語版を使用してください。英語版が最新版だからです)
3. ルートディレクトリから`README.md`と`CONTRIBUTING.md`ファイルを新しいディレクトリにコピーします
4. ファイルの内容を翻訳しますが、ファイル名は変更せず、`> [!`で始まる文字列も翻訳しないでくださいこれはGitHub用の特別なマークアップです
5. 翻訳でプルリクエストを作成します
6. [サイトリポジトリ](https://github.com/dunglas/frankenphp-website/tree/main)で、`content/``data/``i18n/`ディレクトリの翻訳ファイルをコピーして翻訳します
7. 作成されたYAMLファイルの値を翻訳します
8. サイトリポジトリでプルリクエストを開きます

110
docs/ja/README.md Normal file
View File

@@ -0,0 +1,110 @@
# FrankenPHP: PHPのためのモダンなアプリケーションサーバー
<h1 align="center"><a href="https://frankenphp.dev"><img src="frankenphp.png" alt="FrankenPHP" width="600"></a></h1>
FrankenPHPは、[Caddy](https://caddyserver.com/) Webサーバーをベースに構築された、PHPのためのモダンなアプリケーションサーバーです。
FrankenPHPは、[_Early Hints_](https://frankenphp.dev/docs/early-hints/)、[ワーカーモード](https://frankenphp.dev/docs/worker/)、[リアルタイム機能](https://frankenphp.dev/docs/mercure/)、自動HTTPS、HTTP/2、HTTP/3などの驚異的な機能により、あなたのPHPアプリに強力な力を与えます。
FrankenPHPはあらゆるPHPアプリと連携し、ワーカーモードの公式統合によってLaravelやSymfonyプロジェクトをこれまで以上に高速化します。
また、FrankenPHPはスタンドアロンのGoライブラリとしても利用可能で、`net/http`を使って任意のアプリにPHPを埋め込むことができます。
[**詳しくは** _frankenphp.dev_](https://frankenphp.dev)と、このスライド資料もご参照ください:
<a href="https://dunglas.dev/2022/10/frankenphp-the-modern-php-app-server-written-in-go/"><img src="https://dunglas.dev/wp-content/uploads/2022/10/frankenphp.png" alt="Slides" width="600"></a>
## はじめに
### スタンドアロンバイナリ
LinuxとmacOS向けに、[PHP 8.4](https://www.php.net/releases/8.4/en.php)と人気のPHP拡張モジュールを含む静的な
FrankenPHPバイナリを提供しています。
Windowsをお使いの場合は、[WSL](https://learn.microsoft.com/windows/wsl/)を使用してFrankenPHPを実行してください。
[FrankenPHPをダウンロード](https://github.com/php/frankenphp/releases)するか、以下のコマンドを
ターミナルにコピーして実行すると、環境に合ったバージョンが自動的にインストールされます:
```console
curl https://frankenphp.dev/install.sh | sh
mv frankenphp /usr/local/bin/
```
現在のディレクトリのコンテンツを配信するには、以下を実行してください:
```console
frankenphp php-server
```
コマンドラインスクリプトも実行できます:
```console
frankenphp php-cli /path/to/your/script.php
```
### Docker
また、[Dockerイメージ](https://frankenphp.dev/docs/docker/)も利用可能です:
```console
docker run -v .:/app/public \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
ブラウザで`https://localhost`にアクセスして、FrankenPHPをお楽しみください
> [!TIP]
>
> `https://127.0.0.1`ではなく、`https://localhost`を使用して、自己署名証明書を受け入れてください。
> 使用するドメインを変更したい場合は、[`SERVER_NAME` 環境変数](docs/config.md#environment-variables)を設定してください。
### Homebrew
FrankenPHPはmacOSおよびLinux向けに[Homebrew](https://brew.sh)パッケージとしても利用可能です。
インストール方法:
```console
brew install dunglas/frankenphp/frankenphp
```
現在のディレクトリのコンテンツを配信するには、以下を実行してください:
```console
frankenphp php-server
```
## ドキュメント
- [クラシックモード](https://frankenphp.dev/docs/classic/)
- [ワーカーモード](https://frankenphp.dev/docs/worker/)
- [Early Hintsサポート103 HTTPステータスコード](https://frankenphp.dev/docs/early-hints/)
- [リアルタイム](https://frankenphp.dev/docs/mercure/)
- [大きな静的ファイルの効率的な提供](https://frankenphp.dev/docs/x-sendfile/)
- [設定](https://frankenphp.dev/docs/config/)
- [Dockerイメージ](https://frankenphp.dev/docs/docker/)
- [本番環境でのデプロイ](https://frankenphp.dev/docs/production/)
- [パフォーマンス最適化](https://frankenphp.dev/docs/performance/)
- [**スタンドアロン**、自己実行可能なPHPアプリの作成](https://frankenphp.dev/docs/embed/)
- [静的バイナリの作成](https://frankenphp.dev/docs/static/)
- [ソースからのコンパイル](https://frankenphp.dev/docs/compile/)
- [FrankenPHPの監視](https://frankenphp.dev/docs/metrics/)
- [Laravel統合](https://frankenphp.dev/docs/laravel/)
- [既知の問題](https://frankenphp.dev/docs/known-issues/)
- [デモアプリSymfonyとベンチマーク](https://github.com/dunglas/frankenphp-demo)
- [Goライブラリドキュメント](https://pkg.go.dev/github.com/dunglas/frankenphp)
- [コントリビューションとデバッグ](https://frankenphp.dev/docs/contributing/)
## 例とスケルトン
- [Symfony](https://github.com/dunglas/symfony-docker)
- [API Platform](https://api-platform.com/docs/symfony)
- [Laravel](https://frankenphp.dev/docs/laravel/)
- [Sulu](https://sulu.io/blog/running-sulu-with-frankenphp)
- [WordPress](https://github.com/StephenMiracle/frankenwp)
- [Drupal](https://github.com/dunglas/frankenphp-drupal)
- [Joomla](https://github.com/alexandreelise/frankenphp-joomla)
- [TYPO3](https://github.com/ochorocho/franken-typo3)
- [Magento2](https://github.com/ekino/frankenphp-magento2)

11
docs/ja/classic.md Normal file
View File

@@ -0,0 +1,11 @@
# クラシックモードの使用
追加の設定を行わなくても、FrankenPHPはクラシックモードで動作します。このモードでは、FrankenPHPは従来のPHPサーバーのように機能し、PHPファイルを直接提供します。これにより、PHP-FPMやmod_phpを使ったApacheの置き換えとしてシームレスに利用できます。
Caddyと同様に、FrankenPHPは無制限の接続を受け付け、[固定数のスレッド](config.md#caddyfile-config)でそれらを処理します。受け入れられキューに入れられる接続の数は、利用可能なシステムリソースによってのみ制限されます。
PHPスレッドプールは、起動時に初期化された固定数のスレッドで動作し、これはPHP-FPMの静的モードに相当します。また、PHP-FPMの動的モードと同様に、[実行時にスレッドを自動的にスケール](performance.md#max_threads)させることも可能です。
キューに入った接続は、PHPスレッドが空くまで無期限に待機します。これを避けるために、FrankenPHP のグローバル設定内の `max_wait_time` [設定](config.md#caddyfile-config)を使って、リクエストが空きスレッドを待てる最大時間を制限し、それを超えるとリクエストが拒否されるようにできます。
加えて、[Caddy側で適切な書き込みタイムアウト](https://caddyserver.com/docs/caddyfile/options#timeouts)を設定することも可能です。
各Caddyインスタンスは、1つのFrankenPHPスレッドプールのみを起動し、すべての`php_server`ブロック間でこのプールを共有します。

127
docs/ja/compile.md Normal file
View File

@@ -0,0 +1,127 @@
# ソースからのコンパイル
このドキュメントでは、PHPを動的ライブラリとしてロードするFrankenPHPバイナリの作成方法を説明します。
これが推奨される方法です。
または、[完全静的およびほぼ静的なビルド](static.md)も作成できます。
## PHPのインストール
FrankenPHPはPHP 8.2以上と互換性があります。
### Homebrewを使用する場合LinuxとMac
FrankenPHPと互換性のあるlibphpのバージョンをインストールする最も簡単な方法は、[Homebrew PHP](https://github.com/shivammathur/homebrew-php)が提供するZTSパッケージを使用することです。
まず、まだインストールしていない場合は[Homebrew](https://brew.sh)をインストールしてください。
次に、PHPのZTSバリアント、Brotliオプション、圧縮サポート用、watcherオプション、ファイル変更検出用をインストールします
```console
brew install shivammathur/php/php-zts brotli watcher
brew link --overwrite --force shivammathur/php/php-zts
```
### PHPをコンパイルする場合
別の方法として、FrankenPHPに必要なオプションを指定してPHPをソースからコンパイルすることもできます。
まず、[PHPのソース](https://www.php.net/downloads.php)を取得して展開します:
```console
tar xf php-*
cd php-*/
```
次に、プラットフォームに応じて必要なオプションを指定して`configure`スクリプトを実行します。
以下の`./configure`フラグは必須ですが、例えば拡張機能モジュールや追加機能をコンパイルするために他のフラグを追加することもできます。
#### Linux
```console
./configure \
--enable-embed \
--enable-zts \
--disable-zend-signals \
--enable-zend-max-execution-timers
```
#### Mac
[Homebrew](https://brew.sh/)パッケージマネージャーを使用して、必須およびオプションの依存関係をインストールします:
```console
brew install libiconv bison brotli re2c pkg-config watcher
echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
```
その後、以下のようにconfigureスクリプトを実行します
```console
./configure \
--enable-embed \
--enable-zts \
--disable-zend-signals \
--with-iconv=/opt/homebrew/opt/libiconv/
```
#### PHPのコンパイル
最後に、PHPをコンパイルしてインストールします
```console
make -j"$(getconf _NPROCESSORS_ONLN)"
sudo make install
```
## オプション依存関係のインストール
FrankenPHPの一部の機能は、システムにインストールされているオプションの依存パッケージに依存しています。
または、Goコンパイラにビルドタグを渡すことで、これらの機能を無効にできます。
| 機能 | 依存関係 | 無効にするためのビルドタグ |
| ------------------------------ | --------------------------------------------------------------------- | -------------------------- |
| Brotli圧縮 | [Brotli](https://github.com/google/brotli) | nobrotli |
| ファイル変更時のワーカー再起動 | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
## Goアプリのコンパイル
いよいよ最終的なバイナリをビルドできるようになりました。
### xcaddyを使う場合
推奨される方法は、[xcaddy](https://github.com/caddyserver/xcaddy)を使用してFrankenPHPをコンパイルする方法です。
`xcaddy`を使うと、[Caddyのカスタムモジュール](https://caddyserver.com/docs/modules/)やFrankenPHP拡張を簡単に追加できます
```console
CGO_ENABLED=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output frankenphp \
--with github.com/dunglas/frankenphp/caddy \
--with github.com/dunglas/mercure/caddy \
--with github.com/dunglas/vulcain/caddy
# 追加のCaddyモジュールとFrankenPHP拡張をここに追加
```
> [!TIP]
>
> musl libcAlpine LinuxのデフォルトとSymfonyを使用している場合、
> デフォルトのスタックサイズを増やす必要がある場合があります。
> そうしないと、`PHP Fatal error: Maximum call stack size of 83360 bytes reached during compilation. Try splitting expression`のようなエラーが発生する可能性があります。
>
> これを行うには、`XCADDY_GO_BUILD_FLAGS`環境変数を
> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'`のようなものに変更してください
> (アプリの要件に応じてスタックサイズの値を変更してください)。
### xcaddyを使用しない場合
代替として、`xcaddy`を使わずに`go`コマンドを直接使ってFrankenPHPをコンパイルすることも可能です
```console
curl -L https://github.com/php/frankenphp/archive/refs/heads/main.tar.gz | tar xz
cd frankenphp-main/caddy/frankenphp
CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build -tags=nobadger,nomysql,nopgx
```

306
docs/ja/config.md Normal file
View File

@@ -0,0 +1,306 @@
# 設定
FrankenPHP、Caddy、そしてMercureやVulcainモジュールは、[Caddyでサポートされる形式](https://caddyserver.com/docs/getting-started#your-first-config)を使用して設定できます。
[Dockerイメージ](docker.md)では、`Caddyfile``/etc/frankenphp/Caddyfile`に配置されています。
静的バイナリは、`frankenphp run`コマンドを実行したディレクトリ内の`Caddyfile`を参照します。
また、`-c`または`--config`オプションでカスタムのパスを指定できます。
PHP自体の設定は[`php.ini` ファイルを使用](https://www.php.net/manual/en/configuration.file.php)して行えます。
インストール方法に応じて、PHPインタープリターは上記いずれかの場所にある設定ファイルを参照します。
## Docker
- `php.ini`: `/usr/local/etc/php/php.ini`(デフォルトでは`php.ini`は含まれていません)
- 追加の設定ファイル: `/usr/local/etc/php/conf.d/*.ini`
- PHP拡張モジュール: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
- PHPプロジェクトが提供する公式テンプレートをコピーすることを推奨します
```dockerfile
FROM dunglas/frankenphp
# 本番環境:
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
# または開発環境:
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
```
## RPMおよびDebianパッケージ
- `php.ini`: `/etc/frankenphp/php.ini`(本番環境向けのプリセットの`php.ini`ファイルがデフォルトで提供されます)
- 追加の設定ファイル: `/etc/frankenphp/php.d/*.ini`
- PHP拡張モジュール: `/usr/lib/frankenphp/modules/`
## 静的バイナリ
- `php.ini`: `frankenphp run`または`frankenphp php-server`を実行したディレクトリ内、なければ`/etc/frankenphp/php.ini`を参照
- 追加の設定ファイル: `/etc/frankenphp/php.d/*.ini`
- PHP拡張モジュール: ロードできません、バイナリ自体にバンドルする必要があります
- [PHPソース](https://github.com/php/php-src/)で提供される`php.ini-production`または`php.ini-development`のいずれかをコピーしてください
## Caddyfileの設定
`php_server`または`php`の[HTTPディレクティブ](https://caddyserver.com/docs/caddyfile/concepts#directives)は、サイトブロック内で使用してPHPアプリを配信できます。
最小構成の例:
```caddyfile
localhost {
# 圧縮を有効化(オプション)
encode zstd br gzip
# 現在のディレクトリ内のPHPファイルを実行し、アセットを配信
php_server
}
```
グローバルオプションを使用してFrankenPHPを明示的に設定することもできます
`frankenphp`の[グローバルオプション](https://caddyserver.com/docs/caddyfile/concepts#global-options)を使用してFrankenPHPを構成できます。
```caddyfile
{
frankenphp {
num_threads <num_threads> # 開始するPHPスレッド数を設定します。デフォルト: 利用可能なCPU数の2倍。
max_threads <num_threads> # 実行時に追加で開始できるPHPスレッドの最大数を制限します。デフォルト: num_threads。 'auto'を設定可能。
max_wait_time <duration> # リクエストがタイムアウトする前にPHPのスレッドが空くのを待つ最大時間を設定します。デフォルト: 無効。
php_ini <key> <value> # php.iniのディレクティブを設定します。複数のディレクティブを設定するために何度でも使用できます。
worker {
file <path> # ワーカースクリプトのパスを設定します。
num <num> # 開始するPHPスレッド数を設定します。デフォルト: 利用可能なCPU数の2倍。
env <key> <value> # 追加の環境変数を指定された値に設定する。複数の環境変数に対して複数回指定することができます。
watch <path> # ファイル変更を監視するパスを設定します。複数のパスに対して複数回指定できます。
name <name> # ワーカーの名前を設定します。ログとメトリクスで使用されます。デフォルト: ワーカーファイルの絶対パス
max_consecutive_failures <num> # workerが不健全とみなされるまでの、連続失敗の最大回数を設定します。 -1 はワーカーを常に再起動することを意味します。デフォルトは 6 です。
}
}
}
# ...
```
代わりに、`worker`オプションのワンライナー形式を使用することもできます:
```caddyfile
{
frankenphp {
worker <file> <num>
}
}
# ...
```
同じサーバーで複数のアプリを提供する場合は、複数のワーカーを定義することもできます:
```caddyfile
app.example.com {
root /path/to/app/public
php_server {
root /path/to/app/public # キャッシュ効率を高める
worker index.php <num>
}
}
other.example.com {
root /path/to/other/public
php_server {
root /path/to/other/public
worker index.php <num>
}
}
# ...
```
通常は`php_server`ディレクティブを使えば十分ですが、
より細かい制御が必要な場合は、より低レベルの`php`ディレクティブを使用できます。
`php`ディレクティブは、対象がPHPファイルかどうかを確認せず、すべての入力をPHPに渡します。
詳しくは[パフォーマンスページ](performance.md#try_files)をお読みください。
`php_server`ディレクティブの使用は、以下の設定と同等です:
```caddyfile
route {
# ディレクトリへのリクエストに末尾スラッシュを追加
@canonicalPath {
file {path}/index.php
not path */
}
redir @canonicalPath {path}/ 308
# 要求されたファイルが存在しない場合は、indexファイルを試行
@indexFiles file {
try_files {path} {path}/index.php index.php
split_path .php
}
rewrite @indexFiles {http.matchers.file.relative}
# FrankenPHP!
@phpFiles path *.php
php @phpFiles
file_server
}
```
`php_server``php`ディレクティブには以下のオプションがあります:
```caddyfile
php_server [<matcher>] {
root <directory> # サイトのルートフォルダを設定します。デフォルト: `root`ディレクティブ。
split_path <delim...> # URIを2つの部分に分割するための部分文字列を設定します。最初にマッチする部分文字列がURIから「パス情報」を分割するために使用されます。最初の部分はマッチする部分文字列で接尾辞が付けられ、実際のリソースCGIスクリプト名とみなされます。2番目の部分はスクリプトが使用する PATH_INFO に設定されます。デフォルト: `.php`
resolve_root_symlink false # シンボリックリンクが存在する場合`root`ディレクトリをシンボリックリンクの評価によって実際の値に解決することを無効にする(デフォルトで有効)。
env <key> <value> # 追加の環境変数を指定された値に設定する。複数の環境変数を指定する場合は、複数回指定することができます。
file_server off # 組み込みのfile_serverディレクティブを無効にします。
worker { # このサーバー固有のワーカーを作成します。複数のワーカーに対して複数回指定できます。
file <path> # ワーカースクリプトへのパスを設定します。php_serverのルートからの相対パスとなります。
num <num> # 起動するPHPスレッド数を設定します。デフォルトは利用可能なスレッド数の 2 倍です。
name <name> # ログとメトリクスで使用されるワーカーの名前を設定します。デフォルト: ワーカーファイルの絶対パス。php_server ブロックで定義されている場合は、常にm#で始まります。
watch <path> # ファイルの変更を監視するパスを設定する。複数のパスに対して複数回指定することができる。
env <key> <value> # 追加の環境変数を指定された値に設定する。複数の環境変数を指定する場合は、複数回指定することができます。このワーカーの環境変数もphp_serverの親から継承されますが、 ここで上書きすることもできます。
match <path> # ワーカーをパスパターンにマッチさせます。try_filesを上書きし、php_serverディレクティブでのみ使用できます。
}
worker <other_file> <num> # グローバルfrankenphpブロックのような短縮形式も使用できます。
}
```
### ファイルの変更監視
ワーカーはアプリケーションを一度だけ起動してメモリに保持するため、
PHPファイルに変更を加えても即座には反映されません。
代わりに、`watch`ディレクティブを使用してファイル変更時にワーカーを再起動させることができます。
これは開発環境において有用です。
```caddyfile
{
frankenphp {
worker {
file /path/to/app/public/worker.php
watch
}
}
}
```
`watch`ディレクトリが指定されていない場合、`./**/*.{php,yaml,yml,twig,env}`にフォールバックします。
これは、FrankenPHPプロセスが開始されたディレクトリおよびそのサブディレクトリ内のすべての`.php``.yaml``.yml``.twig``.env`ファイルすべてを監視します。
代わりに、[シェルのファイル名パターン](https://pkg.go.dev/path/filepath#Match)を使用して
1つ以上のディレクトリを指定することもできます
```caddyfile
{
frankenphp {
worker {
file /path/to/app/public/worker.php
watch /path/to/app # /path/to/app以下すべてのサブディレクトリのファイルを監視
watch /path/to/app/*.php # /path/to/app内の.phpで終わるファイルを監視
watch /path/to/app/**/*.php # /path/to/appおよびサブディレクトリのPHPファイルを監視
watch /path/to/app/**/*.{php,twig} # /path/to/appおよびサブディレクトリ内のPHPとTwigファイルを監視
}
}
}
```
- `**` パターンは再帰的な監視を意味します
- ディレクトリは相対パスFrankenPHPプロセスの開始ディレクトリからにもできます
- 複数のワーカーが定義されている場合、いずれかのファイルが変更されるとすべてのワーカーが再起動されます
- 実行時に生成されるファイル(ログなど)を監視対象に含めると、意図しないワーカーの再起動を引き起こす可能性があるため注意が必要です
ファイルウォッチャーは[e-dant/watcher](https://github.com/e-dant/watcher)に基づいています。
## パスにワーカーをマッチさせる
従来のPHPアプリケーションでは、スクリプトは常にpublicディレクトリに配置されます。
これはワーカースクリプトにも当てはまり、他のPHPスクリプトと同様に扱われます。
ワーカースクリプトをpublicディレクトリの外に配置したい場合は、`match`ディレクティブを使用して実現できます。
`match`ディレクティブは、`try_files`の最適化された代替手段であり、`php_server`および`php`の中でのみ使用できます。
次の例では、public ディレクトリ内にファイルが存在すればそれを配信し、
存在しなければ、パスパターンに一致するワーカーにリクエストを転送します。
```caddyfile
{
frankenphp {
php_server {
worker {
file /path/to/worker.php # ファイルはpublicパス外でも可
match /api/* # /api/で始まるすべてのリクエストはこのワーカーで処理される
}
}
}
}
```
### フルデュプレックスHTTP/1
HTTP/1.xを使用する場合、全体のボディが読み取られる前にレスポンスを書き込めるようにするため、
フルデュプレックスモードを有効にすることが望ましい場合がありますWebSocket、Server-Sent Eventsなど
これは明示的に有効化する必要がある設定で、`Caddyfile`のグローバルオプションに追加する必要があります:
```caddyfile
{
servers {
enable_full_duplex
}
}
```
> [!CAUTION]
>
> このオプションを有効にすると、フルデュプレックスをサポートしない古いHTTP/1.xクライアントでデッドロックが発生する可能性があります。
> これは`CADDY_GLOBAL_OPTIONS`環境設定を使用しても設定できます:
```sh
CADDY_GLOBAL_OPTIONS="servers {
enable_full_duplex
}"
```
この設定の詳細については、[Caddyドキュメント](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex)をご覧ください。
## 環境変数
以下の環境変数を使用することで、`Caddyfile`を直接変更せずにCaddyディレクティブを注入できます
- `SERVER_NAME`: [待ち受けアドレス](https://caddyserver.com/docs/caddyfile/concepts#addresses)を変更し、指定したホスト名はTLS証明書の生成にも使用されます
- `SERVER_ROOT`: サイトのルートディレクトリを変更します。デフォルトは`public/`
- `CADDY_GLOBAL_OPTIONS`: [グローバルオプション](https://caddyserver.com/docs/caddyfile/options)を注入します
- `FRANKENPHP_CONFIG`: `frankenphp`ディレクティブの下に設定を注入します
FPM や CLI SAPI と同様に、環境変数はデフォルトで`$_SERVER`スーパーグローバルで公開されます。
[`variables_order` PHPディレクティブ](https://www.php.net/manual/en/ini.core.php#ini.variables-order)の`S`値は、このディレクティブ内での`E`の位置にかかわらず常に`ES`と同等です。
## PHP設定
[追加のPHP設定ファイル](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan)を読み込むには、
`PHP_INI_SCAN_DIR`環境変数を使用できます。
設定されると、PHPは指定されたディレクトリに存在する`.ini`拡張子を持つすべてのファイルを読み込みます。
また、`Caddyfile``php_ini`ディレクティブを使用してPHP設定を変更することもできます
```caddyfile
{
frankenphp {
php_ini memory_limit 256M
# または
php_ini {
memory_limit 256M
max_execution_time 15
}
}
}
```
## デバッグモードの有効化
Dockerイメージを使用する場合、`CADDY_GLOBAL_OPTIONS`環境変数に`debug`を設定するとデバッグモードが有効になります:
```console
docker run -v $PWD:/app/public \
-e CADDY_GLOBAL_OPTIONS=debug \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```

205
docs/ja/docker.md Normal file
View File

@@ -0,0 +1,205 @@
# カスタムDockerイメージのビルド
[FrankenPHPのDockerイメージ](https://hub.docker.com/r/dunglas/frankenphp)は、[公式PHPイメージ](https://hub.docker.com/_/php/)をベースにしています。主要なアーキテクチャに対してDebianとAlpine Linuxのバリアントを提供しており、Debianバリアントの使用を推奨しています。
PHP 8.2、8.3、8.4向けのバリアントが提供されています。
タグは次のパターンに従います:`dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
- `<frankenphp-version>`および`<php-version>`は、それぞれFrankenPHPおよびPHPのバージョン番号で、メジャー`1`)、マイナー(例:`1.2`)からパッチバージョン(例:`1.2.3`)まであります。
- `<os>``bookworm`Debian Bookworm用または`alpine`Alpine最新安定版用のいずれかです。
[タグを閲覧](https://hub.docker.com/r/dunglas/frankenphp/tags)。
## イメージの使用方法
プロジェクトに`Dockerfile`を作成します:
```dockerfile
FROM dunglas/frankenphp
COPY . /app/public
```
次に、以下のコマンドを実行してDockerイメージをビルドし、実行します
```console
docker build -t my-php-app .
docker run -it --rm --name my-running-app my-php-app
```
## PHP拡張モジュールの追加インストール方法
ベースイメージには[`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer)スクリプトが含まれており、
追加のPHP拡張モジュールを簡単にインストールできます
```dockerfile
FROM dunglas/frankenphp
# 追加の拡張モジュールをここに追加:
RUN install-php-extensions \
pdo_mysql \
gd \
intl \
zip \
opcache
```
## Caddyモジュールの追加インストール方法
FrankenPHPはCaddyをベースに構築されているため、すべての[Caddyモジュール](https://caddyserver.com/docs/modules/)をFrankenPHPでも使用できます。
カスタムCaddyモジュールをインストールする最も簡単な方法は、[xcaddy](https://github.com/caddyserver/xcaddy)を使用することです:
```dockerfile
FROM dunglas/frankenphp:builder AS builder
# builderイメージにxcaddyをコピー
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
# FrankenPHPをビルドするにはCGOを有効にする必要があります
RUN CGO_ENABLED=1 \
XCADDY_SETCAP=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output /usr/local/bin/frankenphp \
--with github.com/dunglas/frankenphp=./ \
--with github.com/dunglas/frankenphp/caddy=./caddy/ \
--with github.com/dunglas/caddy-cbrotli \
# MercureとVulcainは公式ビルドに含まれていますが、お気軽に削除してください
--with github.com/dunglas/mercure/caddy \
--with github.com/dunglas/vulcain/caddy
# ここに追加のCaddyモジュールを指定してください
FROM dunglas/frankenphp AS runner
# 公式バイナリをカスタムモジュールを含むものに置き換え
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
```
FrankenPHPが提供する`builder`イメージには、コンパイル済みの`libphp`が含まれています。
[ビルダーイメージ](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder)は、FrankenPHPおよびPHPのすべてのバージョンに対して、DebianとAlpineの両方が提供されています。
> [!TIP]
>
> Alpine LinuxとSymfonyを使用している場合は、
> [デフォルトのスタックサイズを増やす](compile.md#using-xcaddy) 必要がある場合があります。
## デフォルトでワーカーモードを有効にする
FrankenPHPをワーカースクリプトで起動するには、`FRANKENPHP_CONFIG`環境変数を設定します:
```dockerfile
FROM dunglas/frankenphp
# ...
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
```
## 開発時にボリュームを使う
FrankenPHPでの開発を簡単に行うには、ホスト側のアプリケーションのソースコードを含むディレクトリを、Dockerコンテナ内にボリュームとしてマウントします
```console
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app
```
> [!TIP]
>
> `--tty`オプションを使うと、JSONではなく人間が読みやすいログが表示されます。
Docker Composeを使用する場合
```yaml
# compose.yaml
services:
php:
image: dunglas/frankenphp
# カスタムDockerfileを使用したい場合は以下の行のコメントを外してください
#build: .
# 本番環境で使用する場合は以下の行のコメントを外してください
# restart: always
ports:
- "80:80" # HTTP
- "443:443" # HTTPS
- "443:443/udp" # HTTP/3
volumes:
- ./:/app/public
- caddy_data:/data
- caddy_config:/config
# 開発環境で人間が読みやすいログを出力するため、本番ではこの行をコメントアウトしてください
tty: true
# Caddyの証明書や設定に必要なボリューム
volumes:
caddy_data:
caddy_config:
```
## 非rootユーザーとして実行する
FrankenPHPはDockerで非rootユーザーとして実行できます。
これを行うサンプル`Dockerfile`は以下の通りです:
```dockerfile
FROM dunglas/frankenphp
ARG USER=appuser
RUN \
# Alpine系ディストリビューションでは "adduser -D ${USER}" を使用
useradd ${USER}; \
# ポート 80 や 443 にバインドするための追加ケーパビリティを追加
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
# /data/caddy および /config/caddy への書き込み権限を付与
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
USER ${USER}
```
### ケーパビリティなしでの実行
FrankenPHPをroot以外のユーザーで実行する場合でも、特権ポート80と443でWebサーバーを
バインドするために`CAP_NET_BIND_SERVICE`ケーパビリティが必要です。
FrankenPHPを非特権ポート1024以上で公開する場合は、
ウェブサーバーを非rootユーザーとして実行し、ケーパビリティを必要とせずに実行することが可能です
```dockerfile
FROM dunglas/frankenphp
ARG USER=appuser
RUN \
# Alpine 系ディストリビューションでは "adduser -D ${USER}" を使用
useradd ${USER}; \
# デフォルトのケーパビリティを削除
setcap -r /usr/local/bin/frankenphp; \
# /data/caddy と /config/caddy への書き込み権限を付与
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
USER ${USER}
```
その後、`SERVER_NAME`環境変数を設定して非特権ポートを使用します。
例: `:8000`
## アップデート
Dockerイメージは以下のタイミングでビルドされます
- 新しいリリースがタグ付けされたとき
- 公式PHPイメージに新しいバージョンがある場合、毎日UTC午前4時に自動ビルド
## 開発版
開発版は[`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev)Dockerリポジトリで利用できます。
GitHubリポジトリのmainブランチにコミットがpushされるたびに新しいビルドが実行されます。
`latest*`タグは`main`ブランチのヘッドを指しており、
`sha-<git-commit-hash>` 形式のタグも利用可能です。

21
docs/ja/early-hints.md Normal file
View File

@@ -0,0 +1,21 @@
# Early Hints
FrankenPHPは[103 Early Hints ステータスコード](https://developer.chrome.com/blog/early-hints/)をネイティブサポートしています。
Early Hintsを使用することで、ウェブページの読み込み時間を30%改善できます。
```php
<?php
header('Link: </style.css>; rel=preload; as=style');
headers_send(103);
// 遅いアルゴリズムとSQLクエリ 🤪
echo <<<'HTML'
<!DOCTYPE html>
<title>Hello FrankenPHP</title>
<link rel="stylesheet" href="style.css">
HTML;
```
Early Hintsは通常モードと[ワーカー](worker.md)モードの両方でサポートされています。

143
docs/ja/embed.md Normal file
View File

@@ -0,0 +1,143 @@
# PHPアプリのスタンドアロンバイナリ化
FrankenPHPには、PHPアプリケーションのソースコードやアセットを静的な自己完結型バイナリに埋め込む機能があります。
この機能により、PHPアプリケーション自体に加えて、PHPインタープリターや本番環境対応のWebサーバーCaddyも含んだスタンドアロンバイナリとして配布できます。
この機能について詳しくは、[SymfonyCon 2023でKévinが行ったプレゼンテーション](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/)をご覧ください。
Laravelアプリケーションの埋め込みについては、[こちらの専用ドキュメント](laravel.md#laravel-apps-as-standalone-binaries)をお読みください。
## アプリの準備
自己完結型バイナリを作成する前に、アプリが埋め込みに対応できる状態にあることを確認してください。
例えば、以下のような作業が必要です:
- 本番環境用の依存パッケージをインストールする
- オートローダーをダンプする
- アプリケーションの本番モードを有効にする(ある場合)
- 最終バイナリのサイズを減らすために`.git`やテストなどの不要なファイルを除外する
例えば、Symfonyアプリの場合、以下のコマンドを使用できます
```console
# .git/ などを除去するためにプロジェクトをエクスポート
mkdir $TMPDIR/my-prepared-app
git archive HEAD | tar -x -C $TMPDIR/my-prepared-app
cd $TMPDIR/my-prepared-app
# 適切な環境変数を設定
echo APP_ENV=prod > .env.local
echo APP_DEBUG=0 >> .env.local
# テストやその他不要ファイルを削除して容量削減
# あるいは、 .gitattributes の export-ignore 属性にこれらを追加してもよい
rm -Rf tests/
# 依存パッケージをインストール
composer install --ignore-platform-reqs --no-dev -a
# .env を最適化
composer dump-env prod
```
### 設定のカスタマイズ
[設定](config.md) をカスタマイズするには、埋め込まれるアプリのメインディレクトリ
(前の例では`$TMPDIR/my-prepared-app`)に`Caddyfile``php.ini`ファイルを配置できます。
## Linux用バイナリの作成
Linux用バイナリを作成する最も簡単な方法は、提供されているDockerベースのビルダーを使用することです。
1. アプリのリポジトリに`static-build.Dockerfile`というファイルを作成します:
```dockerfile
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
# アプリをコピー
WORKDIR /go/src/app/dist/app
COPY . .
# 静的バイナリをビルド
WORKDIR /go/src/app/
RUN EMBED=dist/app/ ./build-static.sh
```
> [!CAUTION]
>
> 一部の`.dockerignore`ファイル(例:デフォルトの[Symfony Docker `.dockerignore`](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore)
> は`vendor/`ディレクトリと`.env`ファイルを無視します。ビルド前に`.dockerignore`ファイルを調整または削除してください。
2. ビルドします:
```console
docker build -t static-app -f static-build.Dockerfile .
```
3. バイナリを抽出します:
```console
docker cp $(docker create --name static-app-tmp static-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-app-tmp
```
生成されるバイナリは、現在のディレクトリの`my-app`というファイル名になります。
## 他のOS用のバイナリの作成
Dockerを使用したくない場合や、macOSバイナリを作成したい場合は、提供されているシェルスクリプトを使用してください
```console
git clone https://github.com/php/frankenphp
cd frankenphp
EMBED=/path/to/your/app ./build-static.sh
```
生成されるバイナリは、`dist/`ディレクトリの`frankenphp-<os>-<arch>`という名前のファイルです。
## バイナリの使い方
これで完了です!`my-app`ファイルまたは他のOSでは`dist/frankenphp-<os>-<arch>`)には、自己完結型アプリが含まれています!
Webアプリを起動するには、以下を実行します
```console
./my-app php-server
```
アプリに[ワーカースクリプト](worker.md)が含まれている場合は、以下のようにワーカーを開始します:
```console
./my-app php-server --worker public/index.php
```
HTTPSLet's Encrypt証明書は自動作成、HTTP/2、HTTP/3を有効にするには、使用するドメイン名を指定してください
```console
./my-app php-server --domain localhost
```
バイナリに埋め込まれたPHP CLIスクリプトも実行できます
```console
./my-app php-cli bin/console
```
## PHP拡張モジュール
デフォルトでは、スクリプトはプロジェクトの`composer.json`ファイルで必要な拡張モジュールをビルドします(存在する場合)。
`composer.json`ファイルが存在しない場合、[静的ビルドのドキュメント](static.md)に記載されているデフォルトの拡張モジュールがビルドされます。
拡張モジュールをカスタマイズしたい場合は、`PHP_EXTENSIONS`環境変数を使用してください。
## ビルドのカスタマイズ
バイナリをカスタマイズする方法拡張モジュール、PHPバージョンなどについては、[静的ビルドのドキュメント](static.md)をお読みください。
## バイナリの配布
Linuxでは、作成されたバイナリは[UPX](https://upx.github.io)を使用して圧縮されます。
Macでは、送信前にファイルサイズを減らすために圧縮できます。
`xz`の使用をお勧めします。

639
docs/ja/extensions.md Normal file
View File

@@ -0,0 +1,639 @@
# GoでPHP拡張モジュールを作成する
FrankenPHPでは、**GoでPHP拡張モジュールを作成する**ことができます。これにより、PHPから直接呼び出せる**高パフォーマンスなネイティブ関数**を作成できます。アプリケーションは既存または新しいGoライブラリを活用でき、**PHPコードから直接goroutineの**強力な並行性モデルを使用できます。
PHP拡張モジュールの記述は通常Cで行われますが、少しの追加作業で他の言語でも作成可能です。PHP拡張モジュールは低レベル言語の力を活用してPHPの機能を拡張することができます。例えば、ネイティブ関数を追加したり、特定の操作を最適化したりできます。
Caddyモジュールのおかげで、GoでPHP拡張モジュールを書いてFrankenPHPに簡単に統合できます。
## 2つのアプローチ
FrankenPHPでは、GoでPHP拡張モジュールを作成する2つの方法を提供します
1. **拡張モジュールジェネレーターを使用** - ほとんどのユースケースに必要なボイラープレートを自動生成する推奨アプローチで、Goコードの記述に集中できます
2. **手動実装** - 拡張モジュール構造を細かく制御したい高度なユースケース
最初に始めやすいジェネレーター方式を紹介し、その後で完全な制御が必要な場合の手動実装方式を説明します。
## 拡張モジュールジェネレーターを使用する
FrankenPHPにはGoのみを使用して**PHP拡張モジュールを作成する**ツールが付属しています。**Cコードを書く必要がなく**、CGOを直接使用する必要もありません。FrankenPHPには**パブリック型API**も含まれており、**PHP/CとGo間の型変換**を心配することなくGoでPHP拡張を書くのに役立ちます。
> [!TIP]
> 拡張モジュールをGoで一から書く方法を理解したい場合は、ジェネレーターを使用せずにGoでPHP拡張モジュールを書く方法を紹介する後述の手動実装セクションを参照してください。
注意すべきことは、このツールは**完全な拡張モジュールジェネレーター**ではないことです。GoでシンプルなPHP拡張モジュールを書くのには十分役立ちますが、高度なPHP拡張モジュールの機能には対応していません。より**複雑で最適化された**拡張モジュールを書く必要がある場合は、Cコードを書いたり、CGOを直接使用したりする必要があるかもしれません。
### 前提条件
以下の手動実装セクションでも説明しているように、[PHPのソースを取得](https://www.php.net/downloads.php)し、新しいGoモジュールを作成する必要があります。
#### 新しいモジュールの作成とPHPソースの取得
GoでPHP拡張モジュールを書く最初のステップは、新しいGoモジュールの作成です。以下のコマンドを使用できます
```console
go mod init github.com/my-account/my-module
```
2番目のステップは、次のステップのために[PHPのソースを取得](https://www.php.net/downloads.php)することです。取得したら、Goモジュールのディレクトリ内ではなく、任意のディレクトリに展開します
```console
tar xf php-*
```
### 拡張モジュールの記述
これでGoでネイティブ関数を書く準備が整いました。`stringext.go`という名前の新しいファイルを作成します。最初の関数は文字列を引数として取り、それを指定された回数だけ繰り返し、文字列を逆転するかどうかを示すブール値を受け取り、結果の文字列を返します。これは以下のようになります:
```go
import (
"C"
"github.com/dunglas/frankenphp"
"strings"
)
//export_php:function repeat_this(string $str, int $count, bool $reverse): string
func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(s))
result := strings.Repeat(str, int(count))
if reverse {
runes := []rune(result)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
result = string(runes)
}
return frankenphp.PHPString(result, false)
}
```
ここで重要なポイントが2つあります
- ディレクティブコメント`//export_php:function`はPHPでの関数シグネチャを定義します。これにより、ジェネレーターは適切なパラメータと戻り値の型でPHP関数を生成する方法を知ることができます。
- 関数は`unsafe.Pointer`を返さなければなりません。FrankenPHPはCとGo間の型変換を支援するAPIを提供しています。
前者は理解しやすいですが、後者は少し複雑かもしれません。次のセクションで型変換について詳しく説明します。
### 型変換
C/PHPとGoの間でメモリ表現が同じ変数型もありますが、直接使用するにはより多くのロジックが必要な型もあります。これは拡張モジュールを書く際の最も挑戦的な部分かもしれません。Zendエンジンの内部仕組みや、変数がPHP内でどのように格納されているかを理解する必要があるためです。以下の表は、知っておくべき重要な情報をまとめています
| PHP型 | Go型 | 直接変換 | CからGoヘルパー | GoからCヘルパー | クラスメソッドサポート |
| ------------------ | ---------------- | -------- | --------------------- | ---------------------- | ---------------------- |
| `int` | `int64` | ✅ | - | - | ✅ |
| `?int` | `*int64` | ✅ | - | - | ✅ |
| `float` | `float64` | ✅ | - | - | ✅ |
| `?float` | `*float64` | ✅ | - | - | ✅ |
| `bool` | `bool` | ✅ | - | - | ✅ |
| `?bool` | `*bool` | ✅ | - | - | ✅ |
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
| `array` | `slice`/`map` | ❌ | _未実装_ | _未実装_ | ❌ |
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
| `object` | `struct` | ❌ | _未実装_ | _未実装_ | ❌ |
> [!NOTE]
> この表はまだ完全ではなく、FrankenPHPの型APIがより完全になるにつれて完成されます。
>
> クラスメソッドについては、現在プリミティブ型のみがサポートされています。配列とオブジェクトはまだメソッドパラメータや戻り値の型として使用できません。
前のセクションのコードスニペットを参照すると、最初のパラメータと戻り値の変換にヘルパーが使用されていることがわかります。 `repeat_this()`関数の2番目と3番目の引数は、基礎となる型のメモリ表現がCとGoで同じであるため、変換する必要がありません。
### ネイティブPHPクラスの宣言
ジェネレーターは、PHPオブジェクトを作成するために使用できる**不透明クラスopaque classes**をGo構造体として宣言することをサポートしています。`//export_php:class`ディレクティブコメントを使用してPHPクラスを定義できます。例
```go
//export_php:class User
type UserStruct struct {
Name string
Age int
}
```
#### 不透明クラスとは何ですか?
**不透明クラスopaque classes**は、内部構造プロパティがPHPコードから隠されているクラスです。これは以下を意味します
- **プロパティへの直接アクセス不可** PHPから直接プロパティを読み書きできません`$user->name`は機能しません)
- **メソッド経由のみで操作** - すべてのやりとりはGoで定義したメソッドを通じて行う必要があります
- **より良いカプセル化** - 内部データ構造は完全にGoコードによって制御されます
- **型安全性** - PHP側から誤った型で内部状態が破壊されるリスクがありません
- **よりクリーンなAPI** - 適切な公開インターフェースを設計することを強制します
このアプローチは優れたカプセル化を実現し、PHPコードがGoオブジェクトの内部状態を意図せずに破壊してしまうことを防ぎます。オブジェクトとのすべてのやりとりは、明示的に定義したメソッドを通じて行う必要があります。
#### クラスにメソッドを追加する
プロパティは直接アクセスできないため、不透明クラスとやりとりするには **メソッドを定義する必要があります**`//export_php:method`ディレクティブを使用して動作を定義します:
```go
//export_php:class User
type UserStruct struct {
Name string
Age int
}
//export_php:method User::getName(): string
func (us *UserStruct) GetUserName() unsafe.Pointer {
return frankenphp.PHPString(us.Name, false)
}
//export_php:method User::setAge(int $age): void
func (us *UserStruct) SetUserAge(age int64) {
us.Age = int(age)
}
//export_php:method User::getAge(): int
func (us *UserStruct) GetUserAge() int64 {
return int64(us.Age)
}
//export_php:method User::setNamePrefix(string $prefix = "User"): void
func (us *UserStruct) SetNamePrefix(prefix *C.zend_string) {
us.Name = frankenphp.GoString(unsafe.Pointer(prefix)) + ": " + us.Name
}
```
#### Nullableパラメータ
ジェネレーターは、PHPシグネチャにおける`?`プレフィックスを使用ったnullableパラメータをサポートしています。パラメータがnullableの場合、Go関数内ではポインタとして扱われ、PHP側で値が`null`だったかどうかを確認できます:
```go
//export_php:method User::updateInfo(?string $name, ?int $age, ?bool $active): void
func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool) {
// nameが渡されたnullではないかチェック
if name != nil {
us.Name = frankenphp.GoString(unsafe.Pointer(name))
}
// ageが渡されたnullではないかチェック
if age != nil {
us.Age = int(*age)
}
// activeが渡されたnullではないかチェック
if active != nil {
us.Active = *active
}
}
```
**Nullableパラメータの重要なポイント**
- **プリミティブ型のnullable** (`?int`, `?float`, `?bool`) はGoではそれぞれポインタ (`*int64`, `*float64`, `*bool`) になります
- **nullable文字列** (`?string`) は `*C.zend_string` のままですが、`nil` になることがあります
- ポインタ値を逆参照する前に **`nil`をチェック** してください
- **PHPの`null`はGoの`nil`になります** - PHPが`null`を渡すと、Go関数は`nil`ポインタを受け取ります
> [!WARNING]
> 現在、クラスメソッドには次の制限があります。**配列とオブジェクトはパラメータ型や戻り値の型としてサポートされていません**。サポートされるのは`string`、`int`、`float`、`bool`、`void`(戻り値の型)といったスカラー型のみです。**nullableなスカラー型はすべてサポートされています** `?string`、`?int`、`?float`、`?bool`)。
拡張を生成した後、PHP側でクラスとそのメソッドを使用できるようになります。ただし**プロパティに直接アクセスできない**ことに注意してください:
```php
<?php
$user = new User();
// ✅ これは動作します - メソッドの使用
$user->setAge(25);
echo $user->getName(); // 出力: (empty、デフォルト値)
echo $user->getAge(); // 出力: 25
$user->setNamePrefix("Employee");
// ✅ これも動作します - nullableパラメータ
$user->updateInfo("John", 30, true); // すべて指定
$user->updateInfo("Jane", null, false); // Ageがnull
$user->updateInfo(null, 25, null); // Nameとactiveがnull
// ❌ これは動作しません - プロパティへの直接アクセス
// echo $user->name; // エラー: privateプロパティにアクセスできません
// $user->age = 30; // エラー: privateプロパティにアクセスできません
```
この設計により、Goコードがオブジェクトの状態へのアクセスと変更方法を完全に制御でき、より良いカプセル化と型安全性を提供します。
### 定数の宣言
ジェネレーターは、2つのディレクティブを使用してGo定数をPHPにエクスポートすることをサポートしていますグローバル定数用の`//export_php:const`とクラス定数用の`//export_php:classconstant`です。これにより、GoとPHPコード間で設定値、ステータスコード、その他の定数を共有できます。
#### グローバル定数
`//export_php:const`ディレクティブを使用してグローバルなPHP定数を作成できます
```go
//export_php:const
const MAX_CONNECTIONS = 100
//export_php:const
const API_VERSION = "1.2.3"
//export_php:const
const STATUS_OK = iota
//export_php:const
const STATUS_ERROR = iota
```
#### クラス定数
`//export_php:classconstant ClassName`ディレクティブを使用して、特定のPHPクラスに属する定数を作成できます
```go
//export_php:classconstant User
const STATUS_ACTIVE = 1
//export_php:classconstant User
const STATUS_INACTIVE = 0
//export_php:classconstant User
const ROLE_ADMIN = "admin"
//export_php:classconstant Order
const STATE_PENDING = iota
//export_php:classconstant Order
const STATE_PROCESSING = iota
//export_php:classconstant Order
const STATE_COMPLETED = iota
```
クラス定数は、PHPでクラス名スコープを使用してアクセスできます
```php
<?php
// グローバル定数
echo MAX_CONNECTIONS; // 100
echo API_VERSION; // "1.2.3"
// クラス定数
echo User::STATUS_ACTIVE; // 1
echo User::ROLE_ADMIN; // "admin"
echo Order::STATE_PENDING; // 0
```
ディレクティブは、文字列、整数、ブール値、浮動小数点数、iota定数など、さまざまな値の型をサポートしています。`iota`を使用する場合、ジェネレーターは自動的に連続した値0, 1, 2などを割り当てます。グローバル定数はPHPコードでグローバル定数として利用可能になり、クラス定数はpublicとしてそれぞれのクラスにスコープされます。整数を使用する場合、異なる記法バイナリ、16進数、8進数がサポートされ、PHPのスタブファイルにそのまま出力されます。
Go側のコードでは、いつも通り定数を使用できます。例えば、先ほど作成した`repeat_this()`関数を取り上げ、最後の引数を整数に変更してみましょう:
```go
import (
"C"
"github.com/dunglas/frankenphp"
"strings"
)
//export_php:const
const STR_REVERSE = iota
//export_php:const
const STR_NORMAL = iota
//export_php:classconstant StringProcessor
const MODE_LOWERCASE = 1
//export_php:classconstant StringProcessor
const MODE_UPPERCASE = 2
//export_php:function repeat_this(string $str, int $count, int $mode): string
func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(s))
result := strings.Repeat(str, int(count))
if mode == STR_REVERSE {
// 文字列を逆転
}
if mode == STR_NORMAL {
// 何もしない、定数を示すためのみ記載
}
return frankenphp.PHPString(result, false)
}
//export_php:class StringProcessor
type StringProcessorStruct struct {
// 内部フィールド
}
//export_php:method StringProcessor::process(string $input, int $mode): string
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(input))
switch mode {
case MODE_LOWERCASE:
str = strings.ToLower(str)
case MODE_UPPERCASE:
str = strings.ToUpper(str)
}
return frankenphp.PHPString(str, false)
}
```
### 拡張モジュールの生成
ここでいよいよ、拡張モジュールを生成できるようになります。以下のコマンドでジェネレーターを実行できます:
```console
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
```
> [!NOTE] > `GEN_STUB_SCRIPT`環境変数に、先ほどダウンロードしたPHPソースの`gen_stub.php`ファイルのパスを設定するのを忘れないでください。これは手動実装セクションで言及されているのと同じ`gen_stub.php`スクリプトです。
すべてがうまくいけば、`build`という名前の新しいディレクトリが作成されているはずです。このディレクトリには、生成されたPHP関数スタブを含む`my_extension.go`ファイルなど、拡張用の生成されたファイルが含まれています。
### 生成された拡張モジュールをFrankenPHPへ統合する
拡張モジュールがコンパイルされ、FrankenPHPに統合される準備が整いました。これを行うには、FrankenPHPのコンパイル方法を学ぶために、FrankenPHPの[コンパイルドキュメント](compile.md)を参照してください。`--with`フラグを使用してモジュールを追加し、モジュールのパスを指定します:
```console
CGO_ENABLED=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output frankenphp \
--with github.com/my-account/my-module/build
```
このとき、生成ステップで作成された`/build`サブディレクトリを指していることに注意してください。ただし、これは必須ではなく、生成されたファイルをモジュールのディレクトリにコピーして、直接それを指定することも可能です。
### 生成された拡張モジュールのテスト
作成した関数とクラスをテストするPHPファイルを作成しましょう。例えば、以下の内容で`index.php`ファイルを作成します:
```php
<?php
// グローバル定数を使用
var_dump(repeat_this('Hello World', 5, STR_REVERSE));
// クラス定数を使用
$processor = new StringProcessor();
echo $processor->process('Hello World', StringProcessor::MODE_LOWERCASE); // "hello world"
echo $processor->process('Hello World', StringProcessor::MODE_UPPERCASE); // "HELLO WORLD"
```
前のセクションで示したように拡張モジュールをFrankenPHPに統合し、`./frankenphp php-server`を使用してこのテストファイルを実行することで、拡張モジュールが動作しているのを確認できるはずです。
## 手動実装
拡張モジュールの仕組みを理解したい、または拡張モジュールを完全に制御したい場合は、手動で書くこともできます。このアプローチは完全な制御を実現できますが、より多くのボイラープレートコードが必要になります。
### 基本的な関数
ここでは、新しいネイティブ関数を定義するシンプルなPHP拡張モジュールをGoで手動実装する方法を紹介します。この関数はPHPから呼び出され、その関数がgoroutineを使ってCaddyのログにメッセージ出力するという処理を行います。この関数は引数を取らず、戻り値もありません。
#### Go関数の定義
モジュール内で、PHPから呼び出される新しいネイティブ関数を定義する必要があります。これを行うには、例えば`extension.go`のように任意の名前でファイルを作成し、以下のコードを追加します:
```go
package ext_go
//#include "extension.h"
import "C"
import (
"unsafe"
"github.com/caddyserver/caddy/v2"
"github.com/dunglas/frankenphp"
)
func init() {
frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry))
}
//export go_print_something
func go_print_something() {
go func() {
caddy.Log().Info("Hello from a goroutine!")
}()
}
```
`frankenphp.RegisterExtension()`関数は、内部のPHP登録ロジックを処理することで拡張登録プロセスを簡素化します。`go_print_something`関数は`//export`ディレクティブを使用して、CGOのおかげで、これから書くCコードでアクセスできるようになることを示しています。
この例では、新しい関数がCaddyのログにメッセージ出力するgoroutineをトリガーします。
#### PHP関数の定義
PHPがGo関数を呼び出せるようにするには、対応するPHP関数を定義する必要があります。このために、例えば`extension.stub.php`のようにスタブファイルを作成し、以下のコードを記述します:
```php
<?php
/** @generate-class-entries */
function go_print(): void {}
```
このファイルはPHPから呼び出される`go_print()`関数のシグネチャを定義します。`@generate-class-entries`ディレクティブは、PHPがこの拡張モジュールのために関数エントリを自動生成することを可能にします。
これは手動ではなく、PHPソースで提供されるスクリプトを使用して行いますPHPソースが置かれている場所に基づいて`gen_stub.php`スクリプトのパスを調整してください):
```bash
php ../php-src/build/gen_stub.php extension.stub.php
```
このスクリプトは、PHPがこの関数の定義および呼び出し方法を知るのに必要な情報を含む`extension_arginfo.h`という名前のファイルを生成します。
#### GoとC間のブリッジの作成
今度は、GoとC間をつなぐブリッジを書く必要があります。モジュールディレクトリに`extension.h`という名前のファイルを作成し、以下の内容を書きます:
```c
#ifndef _EXTENSION_H
#define _EXTENSION_H
#include <php.h>
extern zend_module_entry ext_module_entry;
#endif
```
次に、以下のステップを実行する`extension.c`という名前のファイルを作成します:
- PHPヘッダーをインクルードする
- 新しいネイティブPHP関数`go_print()`を宣言する
- 拡張モジュールのメタデータを宣言する
まずは必要なヘッダーのインクルードから始めましょう:
```c
#include <php.h>
#include "extension.h"
#include "extension_arginfo.h"
// Goによってエクスポートされたシンボルを含みます
#include "_cgo_export.h"
```
次に、PHP関数をネイティブ言語関数として定義します
```c
PHP_FUNCTION(go_print)
{
ZEND_PARSE_PARAMETERS_NONE();
go_print_something();
}
zend_module_entry ext_module_entry = {
STANDARD_MODULE_HEADER,
"ext_go",
ext_functions, /* Functions */
NULL, /* MINIT */
NULL, /* MSHUTDOWN */
NULL, /* RINIT */
NULL, /* RSHUTDOWN */
NULL, /* MINFO */
"0.1.1",
STANDARD_MODULE_PROPERTIES
};
```
この場合、関数はパラメータを取らず、何も返しません。単に`//export`ディレクティブを使用してエクスポートした、先ほど定義したGo関数を呼び出します。
最後に、名前、バージョン、プロパティなど、拡張のメタデータを`zend_module_entry`構造体で定義します。この情報はPHPが私たちの拡張モジュールを認識してロードするために必要です。`ext_functions`は定義したPHP関数へのポインタの配列であり、`gen_stub.php`スクリプトによって自動生成された`extension_arginfo.h`ファイル内に定義されています。
拡張モジュールの登録は、Goコード内で呼び出しているFrankenPHPの`RegisterExtension()`関数によって自動的に処理されます。
### 高度な使用方法
基本的なPHP拡張をGoで作成する方法が分かったところで、少し例を複雑にしてみましょう。今度は文字列を引数として受け取り、その大文字版を返すPHP関数を作成します。
#### PHP関数スタブの定義
新しいPHP関数を定義するために、`extension.stub.php`ファイルを修正し、次の関数シグネチャを含めます:
```php
<?php
/** @generate-class-entries */
/**
* Converts a string to uppercase.
*
* @param string $string The string to convert.
* @return string The uppercase version of the string.
*/
function go_upper(string $string): string {}
```
> [!TIP]
> 関数のドキュメントを軽視しないでください!拡張スタブを他の開発者と共有する際、拡張機能の使い方や提供している機能を伝えるための重要な手段になります。
`gen_stub.php`スクリプトでスタブファイルを再生成すると、`extension_arginfo.h`ファイルは以下のようになるはずです:
```c
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_go_upper, 0, 1, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0)
ZEND_END_ARG_INFO()
ZEND_FUNCTION(go_upper);
static const zend_function_entry ext_functions[] = {
ZEND_FE(go_upper, arginfo_go_upper)
ZEND_FE_END
};
```
この出力から、`go_upper`関数が`string`型の引数を1つ受け取り、`string`型の戻り値を返すことが定義されていのがわかります。
#### GoとPHP/C間の型変換Type Juggling
Go関数はPHPの文字列を引数として直接受け取ることはできません。そのためPHPの文字列をGoの文字列へ変換する必要があります。幸いなことに、FrankenPHPは、ジェネレーターアプローチで見たものと同様に、PHP文字列とGo文字列間の変換を処理するヘルパー関数を提供しています。
ヘッダーファイルはシンプルなままです:
```c
#ifndef _EXTENSION_H
#define _EXTENSION_H
#include <php.h>
extern zend_module_entry ext_module_entry;
#endif
```
次に、`extension.c`ファイルにGoとC間のブリッジを書きます。ここではPHPの文字列を直接Go関数に渡します
```c
PHP_FUNCTION(go_upper)
{
zend_string *str;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STR(str)
ZEND_PARSE_PARAMETERS_END();
zend_string *result = go_upper(str);
RETVAL_STR(result);
}
```
`ZEND_PARSE_PARAMETERS_START`や引数のパースについては、[PHP Internals Book](https://www.phpinternalsbook.com/php7/extensions_design/php_functions.html#parsing-parameters-zend-parse-parameters)の該当ページで詳しく学ぶことができます。この例では、関数が`zend_string`として`string`型の必須引数を1つ取ることをPHPに伝えています。その後、この文字列を直接Go関数に渡し、`RETVAL_STR`を使用して結果を返します。
残るはただ一つ、Go側で`go_upper`関数を実装するだけです。
#### Go関数の実装
Go側の関数では`*C.zend_string`を引数として受け取り、FrankenPHPのヘルパー関数を使用してGoの文字列に変換し、処理を行ったうえで、結果を新たな`*C.zend_string`として返します。メモリ管理と変換の複雑さは、ヘルパー関数がすべて対応してくれます。
```go
import "strings"
//export go_upper
func go_upper(s *C.zend_string) *C.zend_string {
str := frankenphp.GoString(unsafe.Pointer(s))
upper := strings.ToUpper(str)
return (*C.zend_string)(frankenphp.PHPString(upper, false))
}
```
このアプローチは、手動メモリ管理よりもはるかにクリーンで安全です。FrankenPHPのヘルパー関数は、PHPの`zend_string`形式とGoの文字列間の変換を自動的に処理してくれます。`PHPString()``false`引数を指定していることで、新しい非永続文字列(リクエストの終了時に解放される)を作成したいことを示しています。
> [!TIP]
> この例ではエラーハンドリングを省略していますが、Go関数内でポインタが`nil`ではないこと、渡されたデータが有効であることを常に確認するべきです。
### 拡張モジュールのFrankenPHPへの統合
拡張モジュールがコンパイルされ、FrankenPHPに統合される準備が整いました。手順についてはFrankenPHPのコンパイル方法を学ぶために、FrankenPHPの[コンパイルドキュメント](compile.md)を参照してください。`--with`フラグを使用してモジュールを追加し、モジュールのパスを指定します:
```console
CGO_ENABLED=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output frankenphp \
--with github.com/my-account/my-module
```
これで完了です拡張モジュールがFrankenPHPに統合され、PHPコードで利用できるようになりました。
### 拡張モジュールのテスト
拡張モジュールをFrankenPHPに統合したら、実装した関数を試すための`index.php`ファイルを作成します:
```php
<?php
// 基本関数のテスト
go_print();
// 高度な関数のテスト
echo go_upper("hello world") . "\n";
```
このファイルを使用して`./frankenphp php-server`でFrankenPHPを実行でき、拡張モジュールが動作しているのを確認できるはずです。

31
docs/ja/github-actions.md Normal file
View File

@@ -0,0 +1,31 @@
# GitHub Actionsの使用
このリポジトリでは、承認されたプルリクエストごと、またはセットアップ後のあなた自身のフォークで、
Dockerイメージをビルドして[Docker Hub](https://hub.docker.com/r/dunglas/frankenphp)にデプロイします。
## GitHub Actionsのセットアップ
リポジトリ設定のシークレットで、以下のシークレットを追加してください:
- `REGISTRY_LOGIN_SERVER`: 使用するDockerレジストリ`docker.io`
- `REGISTRY_USERNAME`: レジストリログイン用のユーザー名(例:`dunglas`
- `REGISTRY_PASSWORD`: レジストリログイン用のパスワード(例:アクセスキー)
- `IMAGE_NAME`: イメージの名前(例:`dunglas/frankenphp`
## イメージのビルドとプッシュ
1. プルリクエストを作成するか、フォークにプッシュします
2. GitHub Actionsがイメージをビルドし、テストを実行します
3. ビルドが成功した場合、イメージは`pr-x``x`はPR番号をタグとしてレジストリにプッシュされます
## イメージのデプロイ
1. プルリクエストがマージされると、GitHub Actionsが再度テストを実行し、新しいイメージをビルドします
2. ビルドが成功した場合、Dockerレジストリの`main`タグが更新されます
## リリース
1. リポジトリで新しいタグを作成します
2. GitHub Actionsがイメージをビルドし、テストを実行します
3. ビルドが成功した場合、イメージはタグ名をタグとしてレジストリにプッシュされます(例:`v1.2.3``v1.2`が作成されます)
4. `latest`タグも更新されます

143
docs/ja/known-issues.md Normal file
View File

@@ -0,0 +1,143 @@
# 既知の問題
## 未対応のPHP拡張モジュール
以下の拡張モジュールはFrankenPHPと互換性がないことが確認されています
| 名前 | 理由 | 代替手段 |
| ----------------------------------------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------- |
| [imap](https://www.php.net/manual/en/imap.installation.php) | スレッドセーフでない | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | スレッドセーフでない | - |
## バグのあるPHP拡張モジュール
以下の拡張モジュールはFrankenPHPとの組み合わせで既知のバグや予期しない動作が確認されています
| 名前 | 問題 |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | FrankenPHPの静的ビルドmusl libcでビルドを使用した場合、高負荷時にOpenSSL拡張がクラッシュすることがあります。回避策として動的リンクのビルドDockerイメージで使用されているものを使用してください。このバグは[PHP側で追跡中](https://github.com/php/php-src/issues/13648)です。 |
## get_browser
[get_browser()](https://www.php.net/manual/en/function.get-browser.php)関数は継続使用するとパフォーマンスが悪化することが確認されています。回避策として、User Agentごとの結果をキャッシュ[APCu](https://www.php.net/manual/en/book.apcu.php)を利用してください。User Agentごとの結果は静的なためです。
## スタンドアロンバイナリおよびAlpineベースのDockerイメージ
スタンドアロンバイナリおよびAlpineベースのDockerイメージ`dunglas/frankenphp:*-alpine`)は、バイナリサイズを小さく保つために[glibc and friends](https://www.etalabs.net/compare_libcs.html)ではなく[musl libc](https://musl.libc.org/)を使用しています。これによりいくつかの互換性問題が発生する可能性があります。特に、globフラグ`GLOB_BRACE`は [サポートされていません](https://www.php.net/manual/en/function.glob.php) 。
## Dockerで`https://127.0.0.1`を使用する
デフォルトでは、FrankenPHPは`localhost`用のTLS証明書を生成します。
これはローカル開発における最も簡単かつ推奨される方法です。
どうしても`127.0.0.1`をホストとして使用したい場合は、サーバー名を`127.0.0.1`に設定してその証明書を生成させることが可能です。
ただし、[Dockerのネットワークシステム](https://docs.docker.com/network/)の仕組みにより、Dockerを使用する場合はこれだけでは不十分です。
この場合、`curl: (35) LibreSSL/3.3.6: error:1404B438:SSL routines:ST_CONNECT:tlsv1 alert internal error`のようなTLSエラーが発生します。
Linuxを使用している場合、[ホストネットワークドライバー](https://docs.docker.com/network/network-tutorial-host/)を使用することで、この問題を解決できます:
```console
docker run \
-e SERVER_NAME="127.0.0.1" \
-v $PWD:/app/public \
--network host \
dunglas/frankenphp
```
ホストネットワークドライバーはMacとWindowsではサポートされていません。これらのプラットフォームでは、コンテナのIPアドレスを推測してサーバー名に含める必要があります。
`docker network inspect bridge`を実行し、`Containers`キーを確認して`IPv4Address`にある現在割り当てられている最後のIPアドレスを特定し、それに1を加えます。コンテナがまだ実行されていない場合、最初に割り当てられるIPアドレスは通常`172.17.0.2`です。
そして、これを`SERVER_NAME`環境変数に含めます:
```console
docker run \
-e SERVER_NAME="127.0.0.1, 172.17.0.3" \
-v $PWD:/app/public \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
> [!CAUTION]
>
> `172.17.0.3`の部分は、実際にコンテナに割り当てられるIPに置き換えてください。
これでホストマシンから`https://127.0.0.1`へアクセスできるはずです。
うまくいかない場合は、FrankenPHPをデバッグモードで起動して問題を特定してみてください
```console
docker run \
-e CADDY_GLOBAL_OPTIONS="debug" \
-e SERVER_NAME="127.0.0.1" \
-v $PWD:/app/public \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
## `@php` を参照するComposerスクリプト
[Composerスクリプト](https://getcomposer.org/doc/articles/scripts.md)では、いくつかのタスクでPHPバイナリを実行したい場合があります。例えば、[Laravelプロジェクト](laravel.md)で`@php artisan package:discover --ansi`を実行する場合です。しかし現在これは以下の2つの理由で[失敗します](https://github.com/dunglas/frankenphp/issues/483#issuecomment-1899890915)
- ComposerはFrankenPHPバイナリを呼び出す方法を知りません
- Composerはコマンドで`-d`フラグを使用してPHP設定を追加する場合があり、FrankenPHPはまだサポートしていません
回避策として、未サポートのパラメータを削除してFrankenPHPを呼び出すシェルスクリプトを`/usr/local/bin/php`に作成できます:
```bash
#!/usr/bin/env bash
args=("$@")
index=0
for i in "$@"
do
if [ "$i" == "-d" ]; then
unset 'args[$index]'
unset 'args[$index+1]'
fi
index=$((index+1))
done
/usr/local/bin/frankenphp php-cli ${args[@]}
```
次に、環境変数`PHP_BINARY`にこの`php`スクリプトのパスを設定してComposerを実行します
```console
export PHP_BINARY=/usr/local/bin/php
composer install
```
## 静的バイナリでのTLS/SSL問題のトラブルシューティング
静的バイナリを使用する場合、例えばSTARTTLSを使用してメールを送信する際に以下のTLS関連エラーが発生する可能性があります
```text
Unable to connect with STARTTLS: stream_socket_enable_crypto(): SSL operation failed with code 5. OpenSSL Error messages:
error:80000002:system library::No such file or directory
error:80000002:system library::No such file or directory
error:80000002:system library::No such file or directory
error:0A000086:SSL routines::certificate verify failed
```
静的バイナリにはTLS証明書がバンドルされていないため、OpenSSLにローカルのCA証明書の位置を明示する必要があります。
[`openssl_get_cert_locations()`](https://www.php.net/manual/en/function.openssl-get-cert-locations.php)の出力を調べて、
CA証明書をどこにインストールすべきか確認し、その場所に保存してください。
> [!WARNING]
>
> WebとCLIコンテキストでは設定が異なる場合があります。
> 適切なコンテキストで`openssl_get_cert_locations()`を実行してください。
[Mozillaから抽出されたCA証明書はcurlのサイトでダウンロードできます](https://curl.se/docs/caextract.html)。
または、Debian、Ubuntu、Alpineなどのディストリビューションでも、これらの証明書を含む`ca-certificates`というパッケージを提供しています。
`SSL_CERT_FILE`および`SSL_CERT_DIR`を使用してOpenSSLにCA証明書を探す場所をヒントとして与えることも可能です
```console
# TLS 証明書の環境変数を設定
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
export SSL_CERT_DIR=/etc/ssl/certs
```

183
docs/ja/laravel.md Normal file
View File

@@ -0,0 +1,183 @@
# Laravel
## Docker
FrankenPHPを使用して[Laravel](https://laravel.com)のWebアプリケーションを配信するのは簡単で、公式Dockerイメージの`/app`ディレクトリにプロジェクトをマウントするだけです。
Laravelアプリのメインディレクトリからこのコマンドを実行してください
```console
docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
```
お楽しみください!
## ローカルインストール
または、ローカルマシンでFrankenPHPを使用してLaravelプロジェクトを実行することもできます
1. [使用しているシステムに対応するバイナリをダウンロードします](../#standalone-binary)
2. Laravelプロジェクトのルートディレクトリに`Caddyfile`という名前のファイルを作成し、以下の設定を追加します:
```caddyfile
{
frankenphp
}
# サーバーのドメイン名
localhost {
# webroot を public/ ディレクトリに設定
root public/
# 圧縮を有効にする(任意)
encode zstd br gzip
# public/ ディレクトリ内の PHP ファイルを実行し、アセットを提供
php_server {
try_files {path} index.php
}
}
```
3. LaravelプロジェクトのルートディレクトリからFrankenPHPを起動します `frankenphp run`
## Laravel Octane
OctaneはComposerパッケージマネージャーを使用してインストールできます
```console
composer require laravel/octane
```
Octaneをインストールした後、`octane:install` Artisanコマンドを実行すると、Octaneの設定ファイルがアプリケーションにインストールされます
```console
php artisan octane:install --server=frankenphp
```
Octaneサーバーは`octane:frankenphp` Artisanコマンドで開始できます。
```console
php artisan octane:frankenphp
```
`octane:frankenphp`コマンドは以下のオプションが利用可能です:
- `--host`: サーバーがバインドするIPアドレスデフォルト`127.0.0.1`
- `--port`: サーバーが使用するポート(デフォルト: `8000`
- `--admin-port`: 管理サーバーが使用するポート(デフォルト: `2019`
- `--workers`: リクエスト処理に使うワーカー数(デフォルト: `auto`
- `--max-requests`: サーバーを再起動するまでに処理するリクエスト数(デフォルト: `500`
- `--caddyfile`: FrankenPHPの`Caddyfile`ファイルのパス(デフォルト: [Laravel OctaneのスタブCaddyfile](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile)
- `--https`: HTTPS、HTTP/2、HTTP/3を有効にし、証明書を自動的に生成・更新する
- `--http-redirect`: HTTPからHTTPSへのリダイレクトを有効にする--httpsオプション指定時のみ有効
- `--watch`: アプリケーションが変更されたときに自動的にサーバーをリロードする
- `--poll`: ネットワーク越しのファイル監視のためにファイルシステムポーリングを使用する
- `--log-level`: ネイティブCaddyロガーを使用して、指定されたログレベル以上でログメッセージを記録する
> [!TIP]
> 構造化されたJSONログログ分析ソリューションを使用する際に便利を取得するには、明示的に`--log-level`オプションを指定してください。
詳しくは[Laravel Octaneの公式ドキュメント](https://laravel.com/docs/octane)をご覧ください。
## Laravelアプリのスタンドアロンバイナリ化
[FrankenPHPのアプリケーション埋め込み機能](embed.md)を使用して、Laravelアプリをスタンドアロンバイナリとして
配布することが可能です。
LaravelアプリをLinux用のスタンドアロンバイナリとしてパッケージ化するには、以下の手順に従ってください
1. アプリのリポジトリに`static-build.Dockerfile`という名前のファイルを作成します:
```dockerfile
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
# アプリをコピー
WORKDIR /go/src/app/dist/app
COPY . .
# スペースを節約するためにテストやその他の不要なファイルを削除
# 代わりに .dockerignore に記述して除外することも可能
RUN rm -Rf tests/
# .envファイルをコピー
RUN cp .env.example .env
# APP_ENV と APP_DEBUG を本番用に変更
RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env
# 必要に応じて .env ファイルにさらに変更を加える
# 依存関係をインストール
RUN composer install --ignore-platform-reqs --no-dev -a
# 静的バイナリをビルド
WORKDIR /go/src/app/
RUN EMBED=dist/app/ ./build-static.sh
```
> [!CAUTION]
>
> 一部の`.dockerignore`ファイルは
> `vendor/`ディレクトリや`.env`ファイルを無視します。ビルド前に`.dockerignore`ファイルを調整または削除してください。
2. ビルドします:
```console
docker build -t static-laravel-app -f static-build.Dockerfile .
```
3. バイナリを取り出します:
```console
docker cp $(docker create --name static-laravel-app-tmp static-laravel-app):/go/src/app/dist/frankenphp-linux-x86_64 frankenphp ; docker rm static-laravel-app-tmp
```
4. キャッシュを構築します:
```console
frankenphp php-cli artisan optimize
```
5. データベースマイグレーションを実行します(ある場合):
```console
frankenphp php-cli artisan migrate
```
6. アプリの秘密鍵を生成します:
```console
frankenphp php-cli artisan key:generate
```
7. サーバーを起動します:
```console
frankenphp php-server
```
これで、アプリの準備は完了です!
利用可能なオプションや他のOSでバイナリをビルドする方法については、[アプリケーション埋め込み](embed.md)ドキュメントをご覧ください。
### ストレージパスの変更
Laravelはアップロードされたファイルやキャッシュ、ログなどをデフォルトでアプリケーションの`storage/`ディレクトリに保存します。
しかし、これは埋め込みアプリケーションには適していません。なぜなら、アプリの新しいバージョンごとに異なる一時ディレクトリに展開されるためです。
この問題を回避するには、`LARAVEL_STORAGE_PATH`環境変数を設定(例:`.env`ファイル内)するか、 `Illuminate\Foundation\Application::useStoragePath()`メソッドを呼び出して、一時ディレクトリの外にある任意のディレクトリを使用してください。
### スタンドアロンバイナリでOctaneを実行する
Laravel Octaneアプリもスタンドアロンバイナリとしてパッケージ化することが可能です
そのためには、[Octaneを正しくインストール](#laravel-octane)し、[前のセクション](#laravelアプリのスタンドアロンバイナリ化)で説明した手順に従ってください。
次に、Octaneを通じてワーカーモードでFrankenPHPを起動するには、以下を実行してください
```console
PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp
```
> [!CAUTION]
>
> コマンドを動作させるためには、スタンドアロンバイナリのファイル名が**必ず**`frankenphp`でなければなりません。
> Octaneは`frankenphp`という名前の実行ファイルがパス上に存在することを前提としています。

15
docs/ja/mercure.md Normal file
View File

@@ -0,0 +1,15 @@
# リアルタイム
FrankenPHPには組み込みの[Mercure](https://mercure.rocks)ハブが付属しています!
Mercureを使用すると、接続されているすべてのデバイスにリアルタイムイベントをプッシュでき、各デバイスは即座にJavaScriptイベントを受信します。
JSライブラリやSDKは必要ありません
![Mercure](mercure-hub.png)
Mercureハブを有効にするには、[Mercureのサイト](https://mercure.rocks/docs/hub/config)で説明されているように`Caddyfile`を更新してください。
Mercureハブのパスは`/.well-known/mercure`です。
FrankenPHPをDocker内で実行している場合、完全な送信URLは`http://php/.well-known/mercure`のようになります。ここでの`php`はFrankenPHPを実行するコンテナの名前です。
コードからMercureの更新をプッシュするには、[Symfony Mercure Component](https://symfony.com/components/Mercure)をお勧めします。なお、Symfonyのフルスタックフレームワークは必要ありません。

17
docs/ja/metrics.md Normal file
View File

@@ -0,0 +1,17 @@
# メトリクス
[Caddyのメトリクス](https://caddyserver.com/docs/metrics)が有効になっていると、FrankenPHPは以下のメトリクスを公開します
- `frankenphp_total_threads`: PHPスレッドの総数
- `frankenphp_busy_threads`: 現在リクエストを処理中のPHPスレッド数。なお、実行中のワーカーは常にスレッドを消費します
- `frankenphp_queue_depth`: 通常のキューに入っているリクエストの数
- `frankenphp_total_workers{worker="[worker_name]"}`: ワーカーの総数
- `frankenphp_busy_workers{worker="[worker_name]"}`: 現在リクエストを処理中のワーカーの数
- `frankenphp_worker_request_time{worker="[worker_name]"}`: すべてのワーカーがリクエスト処理に費やした時間
- `frankenphp_worker_request_count{worker="[worker_name]"}`: すべてのワーカーが処理したリクエスト数
- `frankenphp_ready_workers{worker="[worker_name]"}`: 少なくとも一度は `frankenphp_handle_request` を呼び出したワーカーの数
- `frankenphp_worker_crashes{worker="[worker_name]"}`: ワーカーが予期せず終了した回数
- `frankenphp_worker_restarts{worker="[worker_name]"}`: ワーカーが意図的に再起動された回数
- `frankenphp_worker_queue_depth{worker="[worker_name]"}`: キューに入っているリクエストの数
ワーカーメトリクスの`[worker_name]`プレースホルダーは、Caddyfileに指定されたワーカー名に置き換えられます。ワーカー名が指定されていない場合は、ワーカーファイルの絶対パスが使用されます。

157
docs/ja/performance.md Normal file
View File

@@ -0,0 +1,157 @@
# パフォーマンス
デフォルトでは、FrankenPHPはパフォーマンスと使いやすさのバランスが取れた構成を提供するよう設計されています。
ただし、適切な設定により、パフォーマンスを大幅に向上させることが可能です。
## スレッド数とワーカー数
デフォルトでは、FrankenPHPは利用可能なCPU数の2倍のスレッドとワーカーワーカーモードでを開始します。
適切な値は、アプリケーションの書き方、機能、ハードウェアに大きく依存します。
これらの値を調整することを強く推奨します。最適なシステムの安定性のためには、`num_threads` x `memory_limit` < `available_memory`とすることをお勧めします。
適切な値を見つけるには、実際のトラフィックをシミュレートした負荷テストを実行するのが最も効果的です。
そのためのツールとして、[k6](https://k6.io)や[Gatling](https://gatling.io)が有用です。
スレッド数を設定するには、`php_server``php`ディレクティブ内の`num_threads`オプションを使用してください。
ワーカー数を変更するには、`frankenphp`ディレクティブ内の`worker`セクションにある`num`オプションを使用してください。
### `max_threads`
実際のトラフィックがどのようなものになるかを正確に把握できれば理想ですが、現実のアプリケーションでは
予測困難な挙動が多いものです。`max_threads`[設定](config.md#caddyfile-config) により、FrankenPHPは指定された制限まで実行時に追加スレッドを自動的に生成できます。
`max_threads`はトラフィックを処理するために必要なスレッド数を把握するのに役立ち、レイテンシのスパイクに対してサーバーをより回復力のあるものにできます。
`auto`に設定すると、制限は`php.ini``memory_limit`に基づいて推定されます。推定できない場合、
`auto`は代わりに`num_threads`の2倍がデフォルトになります。`auto`は必要なスレッド数を大幅に過小評価する可能性があることに留意してください。
`max_threads`はPHP FPMの[pm.max_children](https://www.php.net/manual/en/install.fpm.configuration.php#pm.max-children)に似ています。主な違いは、FrankenPHPがプロセスではなくスレッドを使用し、
必要に応じて異なるワーカースクリプトと「クラシックモード」間で自動的に委譲することです。
## ワーカーモード
[ワーカーモード](worker.md)を有効にするとパフォーマンスが劇的に向上しますが、
アプリがこのモードと互換性があるように適応する必要があります:
ワーカースクリプトを作成し、アプリがメモリリークしていないことを確認する必要があります。
## muslを使用しない
公式Dockerイメージと私たちが提供するデフォルトバイナリのAlpine Linuxバリアントは、[musl libc](https://musl.libc.org)を使用しています。
PHPは、従来のGNUライブラリの代わりにこの代替Cライブラリを使用すると[遅くなる](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381)ことが知られており、
特にFrankenPHPに必要なZTSモードスレッドセーフでコンパイルされた場合です。高度にスレッド化された環境では、差が大きくなる可能性があります。
また、[一部のバグはmuslを使用した場合にのみ発生します](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl)。
本番環境では、glibcにリンクされたFrankenPHPを使用することをお勧めします。
これは、Debian Dockerイメージデフォルトを使用するか、[リリースページ](https://github.com/php/frankenphp/releases)から -gnu サフィックス付きバイナリをダウンロードするか、あるいは[FrankenPHPをソースからコンパイル](compile.md)することで実現できます。
または、[mimalloc allocator](https://github.com/microsoft/mimalloc)でコンパイルされた静的muslバイナリも提供しており、これによりスレッド環境での問題を軽減できます。
## Go Runtime設定
FrankenPHPはGoで書かれています。
一般的に、Go runtimeは特別な設定を必要としませんが、特定の状況では、
特定の設定でパフォーマンスが向上する場合があります。
おそらく`GODEBUG`環境変数を`cgocheck=0`に設定したいでしょうFrankenPHP Dockerイメージのデフォルト
FrankenPHPをコンテナDocker、Kubernetes、LXC...)で実行しており、コンテナで利用可能なメモリを制限している場合は、
`GOMEMLIMIT`環境変数に利用可能なメモリ量を設定してください。
詳細については、Go ランタイムを最大限に活用するために、[この主題に特化したGoドキュメントページ](https://pkg.go.dev/runtime#hdr-Environment_Variables)を読むことを強く推奨します。
## `file_server`
デフォルトでは、`php_server`ディレクティブは自動的にファイルサーバーを設定して
ルートディレクトリに保存された静的ファイル(アセット)を配信します。
この機能は便利ですが、コストがかかります。
無効にするには、以下の設定を使用してください:
```caddyfile
php_server {
file_server off
}
```
## `try_files`
`php_server`は、静的ファイルとPHPファイルに加えて、アプリケーションのインデックスファイル
およびディレクトリインデックスファイル(`/path/` -> `/path/index.php`)も試行します。ディレクトリインデックスが不要な場合、
次のように`try_files`を明示的に定義して無効にできます:
```caddyfile
php_server {
try_files {path} index.php
root /root/to/your/app # ここで root を明示的に追加すると、キャッシュの効率が向上します
}
```
これにより、不要なファイルの操作の回数を大幅に削減できます。
ファイルシステムへの不要な操作を完全にゼロにする代替アプローチとして、`php`ディレクティブを使用し、
パスによってPHPファイルとそれ以外を分ける方法があります。アプリケーション全体が1つのエントリーファイルで提供される場合、この方法は有効です。
たとえば`/assets`フォルダの背後で静的ファイルを提供する[設定](config.md#caddyfile-config)は次のようになります:
```caddyfile
route {
@assets {
path /assets/*
}
# /assets 以下のリクエストはファイルサーバーが処理する
file_server @assets {
root /root/to/your/app
}
# /assets 以外のすべてのリクエストは index または worker の PHP ファイルで処理する
rewrite index.php
php {
root /root/to/your/app # ここで root を明示的に追加すると、キャッシュの効率が向上します
}
}
```
## プレースホルダー
`root`および`env`ディレクティブ内では、[プレースホルダー](https://caddyserver.com/docs/conventions#placeholders)を使用できます。
ただし、これによりこれらの値をキャッシュすることができなくなり、大幅なパフォーマンスコストが発生します。
可能であれば、これらのディレクティブではプレースホルダーの使用を避けてください。
## `resolve_root_symlink`
デフォルトでは、ドキュメントルートがシンボリックリンクである場合、FrankenPHP はそれを自動的に解決します(これは PHP が正しく動作するために必要です)。
ドキュメントルートがシンボリックリンクでない場合、この機能を無効にできます。
```caddyfile
php_server {
resolve_root_symlink false
}
```
この設定は、`root`ディレクティブに[プレースホルダー](https://caddyserver.com/docs/conventions#placeholders)が含まれている場合にパフォーマンスを向上させます。
それ以外の場合の効果はごくわずかです。
## ログ
ログ出力は当然ながら非常に有用ですが、その性質上、
I/O操作およびメモリ確保が必要となり、パフォーマンスを大幅に低下させます。
[ログレベルを正しく設定](https://caddyserver.com/docs/caddyfile/options#log)し、
必要なもののみをログに記録するようにしてください。
## PHPパフォーマンス
FrankenPHPは公式のPHPインタープリターを使用しています。
通常のPHPに関するパフォーマンス最適化はすべてFrankenPHPでも有効です。
特に以下の点を確認してください:
- [OPcache](https://www.php.net/manual/en/book.opcache.php)がインストールされ、有効化され、適切に設定されていること
- [Composer autoloader optimizations](https://getcomposer.org/doc/articles/autoloader-optimization.md)を有効にすること
- `realpath`キャッシュがアプリケーションのニーズに合わせて十分な大きさであること
- [preloading](https://www.php.net/manual/en/opcache.preloading.php)を使用すること
詳細については、[Symfonyの専用ドキュメントエントリ](https://symfony.com/doc/current/performance.html)をお読みください
Symfonyを使用していなくても、多くのヒントが役立ちます

144
docs/ja/production.md Normal file
View File

@@ -0,0 +1,144 @@
# 本番環境でのデプロイ
このチュートリアルでは、Docker Composeを使用して単一サーバーにPHPアプリケーションをデプロイする方法を学びます。
Symfonyを使用している場合は、Symfony Dockerプロジェクトの「[本番環境へのデプロイ](https://github.com/dunglas/symfony-docker/blob/main/docs/production.md)」ドキュメントを参照してください。
API Platformを使用している場合は、[フレームワークのデプロイドキュメント](https://api-platform.com/docs/deployment/)を参照してください。
## アプリの準備
まず、PHPプロジェクトのルートディレクトリに`Dockerfile`を作成します:
```dockerfile
FROM dunglas/frankenphp
# "your-domain-name.example.com" を実際のドメイン名に置き換えてください
ENV SERVER_NAME=your-domain-name.example.com
# HTTPSを無効にしたい場合は、次の値を代わりに使用してください
#ENV SERVER_NAME=:80
# プロジェクトで "public" ディレクトリをWebルートとして使用していない場合、ここで設定できます:
# ENV SERVER_ROOT=web/
# PHPの本番設定を有効化
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# プロジェクトのPHPファイルをpublicディレクトリにコピー
COPY . /app/public
# Symfony や Laravel を使用している場合は、代わりにプロジェクト全体をコピーする必要があります:
#COPY . /app
```
より詳細な情報やカスタマイズ方法、PHP拡張モジュールやCaddyモジュールのインストール方法については、
「[カスタムDockerイメージのビルド](docker.md)」を参照してください。
プロジェクトでComposerを使用している場合は、
DockerイメージにComposerを含め、依存関係をインストールしてください。
次に、 `compose.yaml` ファイルを追加します:
```yaml
services:
php:
image: dunglas/frankenphp
restart: always
ports:
- "80:80" # HTTP
- "443:443" # HTTPS
- "443:443/udp" # HTTP/3
volumes:
- caddy_data:/data
- caddy_config:/config
# Caddyの証明書と設定に必要なボリューム
volumes:
caddy_data:
caddy_config:
```
> [!NOTE]
>
> 上記の例は本番環境向けです。
> 開発環境では、ボリューム、異なるPHP設定、`SERVER_NAME`環境変数の異なる値を使用したい場合があります。
>
> [Symfony Docker](https://github.com/dunglas/symfony-docker)プロジェクトFrankenPHPを使用では、
> マルチステージイメージ、Composer、追加のPHP拡張モジュールなどを活用した、
> より高度な例を見ることができます。
最後に、Gitを使用している場合は、これらのファイルをコミットしてプッシュします。
## サーバーの準備
本番環境にアプリケーションをデプロイするには、サーバーが必要です。
このチュートリアルではDigitalOceanの仮想マシンを使用しますが、他のLinuxサーバーでも同様に動作します。
DockerがインストールされたLinuxサーバーが既にある場合は、[次のセクション](#ドメイン名の設定)に進んでください。
まだサーバーがない場合は、[このアフィリエイトリンク](https://m.do.co/c/5d8aabe3ab80)を使用して$200の無料クレジットを取得し、アカウントを作成してください。その後、「Create a Droplet」をクリックします。
次に、「Choose an image」セクションの下の「Marketplace」タブをクリックし、「Docker」という名前のアプリを検索します。
これにより、DockerとDocker Composeの最新バージョンが既にインストールされたUbuntuサーバーがプロビジョニングされます
テスト目的であれば、最安のプランで十分です。
実際の本番使用では、おそらくニーズに合わせて「general purpose」セクションのプランを選びたいでしょう。
![FrankenPHPをDockerでDigitalOceanにデプロイ](digitalocean-droplet.png)
他の設定はデフォルトのままにするか、必要に応じて調整も可能です。
SSHキーを追加するかパスワードを作成することを忘れずに行い、「Finalize and create」ボタンをクリックしてください。
次に、Dropletがプロビジョニングされるまで数秒待ちます。
Dropletの準備ができたら、SSHを使用して接続します
```console
ssh root@<droplet-ip>
```
## ドメイン名の設定
ほとんどの場合、サイトにドメイン名を関連付けたいでしょう。
まだドメイン名を所有していない場合は、レジストラーを通じて購入する必要があります。
次に、サーバーのIPアドレスを指すドメイン名のタイプ`A`のDNSレコードを作成します
```dns
your-domain-name.example.com. IN A 207.154.233.113
```
DigitalOceanのドメインサービス「Networking」 > 「Domains」での例
![DigitalOceanでのDNS設定](digitalocean-dns.png)
> [!NOTE]
>
> FrankenPHPがデフォルトで使用しているTLS証明書の自動生成サービスLet's Encryptは、IPアドレスの直接使用をサポートしていません。Let's Encryptを使用するにはドメイン名の使用が必須です。
## デプロイ
`git clone``scp`など、目的に合ったツールを使用してプロジェクトをサーバーにコピーします。
GitHubを使用している場合は、[deploy key](https://docs.github.com/en/free-pro-team@latest/developers/overview/managing-deploy-keys#deploy-keys)の使用を検討してください。
deploy keyは[GitLabでもサポートされています](https://docs.gitlab.com/ee/user/project/deploy_keys/)。
Gitでの例
```console
git clone git@github.com:<username>/<project-name>.git
```
プロジェクトディレクトリ(`<project-name>`)に移動し、本番モードでアプリを開始します:
```console
docker compose up --wait
```
サーバーが起動し、HTTPS証明書が自動的に生成されます。
`https://your-domain-name.example.com`にアクセスしてお楽しみください!
> [!CAUTION]
>
> Dockerはキャッシュレイヤーを持つ可能性があるため、各デプロイメントで正しいビルドを持っているか確認するか、キャッシュの問題を避けるために`--no-cache`オプションでプロジェクトを再ビルドしてください。
## 複数ノードへのデプロイ
複数のマシンクラスターにアプリをデプロイしたい場合は、提供されるComposeファイルと互換性のある[Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/)を
使用できます。
Kubernetesでデプロイするには、FrankenPHPを使用する[API Platformで提供されるHelmチャート](https://api-platform.com/docs/deployment/kubernetes/)をご覧ください。

161
docs/ja/static.md Normal file
View File

@@ -0,0 +1,161 @@
# 静的ビルドの作成
PHPライブラリのローカルインストールを使用する代わりに、
[static-php-cli プロジェクト](https://github.com/crazywhalecc/static-php-cli)を利用して、FrankenPHPの静的またはほぼ静的なビルドを作成することが可能ですプロジェクト名に「CLI」とありますが、CLIだけでなく全てのSAPIをサポートしています
この方法を使えば、PHPインタープリター、Caddy Webサーバー、FrankenPHPをすべて含んだ単一でポータブルなバイナリを作成できます
完全に静的なネイティブ実行ファイルは依存関係を全く必要とせず、[`scratch` Dockerイメージ](https://docs.docker.com/build/building/base-images/#create-a-minimal-base-image-using-scratch)上でも実行可能です。
ただし、動的PHP拡張モジュールXdebugなどをロードできず、musl libcを使用しているため、いくつかの制限があります。
ほぼ静的なバイナリは`glibc`のみを必要とし、動的拡張モジュールをロードできます。
可能であれば、glibcベースのほぼ静的ビルドの使用をお勧めします。
また、FrankenPHPは[静的バイナリへのPHPアプリの埋め込み](embed.md)もサポートしています。
## Linux
静的なLinuxバイナリをビルドするためのDockerイメージを提供しています
### muslベースの完全静的ビルド
依存関係なしにあらゆるLinuxディストリビューションで動作する完全静的バイナリただし拡張モジュールの動的ロードはサポートしないを作成するには、以下を実行します
```console
docker buildx bake --load static-builder-musl
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-musl
```
高い並行性が求められるシナリオでは、より良いパフォーマンスのため、[mimalloc](https://github.com/microsoft/mimalloc)アロケーターの使用を検討してください。
```console
docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl
```
### glibcベースのほぼ静的なビルド動的拡張モジュールのサポートあり
選択した拡張モジュールを静的にコンパイルしながら、さらにPHP拡張モジュールを動的にロードできるバイナリを作成するには、以下を実行します
```console
docker buildx bake --load static-builder-gnu
docker cp $(docker create --name static-builder-gnu dunglas/frankenphp:static-builder-gnu):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-gnu
```
このバイナリは、glibcバージョン2.17以上をすべてサポートしますが、muslベースシステムAlpine Linuxなどでは動作しません。
生成されたほぼ静的(`glibc`を除く)バイナリは`frankenphp`という名前で、カレントディレクトリに出力されます。
Dockerを使わずに静的バイナリをビルドしたい場合は、macOS向けの手順を参照してください。これらの手順はLinuxでも使用できます。
### カスタム拡張モジュール
デフォルトでは、よく使われるPHP拡張モジュールがコンパイルされます。
バイナリのサイズを削減したり、攻撃対象領域(アタックサーフェス)を減らすために、`PHP_EXTENSIONS`というDocker引数を使用してビルドする拡張モジュールを明示的に指定できます。
例えば、`opcache``pdo_sqlite`拡張モジュールのみをビルドするには、以下のように実行します:
```console
docker buildx bake --load --set static-builder-musl.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder-musl
# ...
```
有効にした拡張に必要なライブラリを追加するには、`PHP_EXTENSION_LIBS`というDocker引数を渡すことができます
```console
docker buildx bake \
--load \
--set static-builder-musl.args.PHP_EXTENSIONS=gd \
--set static-builder-musl.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \
static-builder-musl
```
### 追加のCaddyモジュール
Caddyの拡張モジュールを追加したい場合は、`XCADDY_ARGS`というDocker引数を使用して、[xcaddy](https://github.com/caddyserver/xcaddy)に渡す引数を以下のように指定できます:
```console
docker buildx bake \
--load \
--set static-builder-musl.args.XCADDY_ARGS="--with github.com/darkweak/souin/plugins/caddy --with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" \
static-builder-musl
```
この例では、Caddy用の[Souin](https://souin.io)HTTPキャッシュモジュールと[cbrotli](https://github.com/dunglas/caddy-cbrotli)、[Mercure](https://mercure.rocks)、[Vulcain](https://vulcain.rocks)モジュールを追加しています。
> [!TIP]
>
> cbrotli、Mercure、Vulcainモジュールは、`XCADDY_ARGS`が空または設定されていない場合はデフォルトで含まれます。
> `XCADDY_ARGS`の値をカスタマイズする場合、デフォルトのモジュールは含まれなくなるため、必要なものは明示的に記述してください。
[ビルドのカスタマイズ](#ビルドのカスタマイズ)も参照してください
### GitHubトークン
GitHub API レート制限に達した場合は、`GITHUB_TOKEN`という名前の環境変数にGitHub Personal Access Tokenを設定してください
```console
GITHUB_TOKEN="xxx" docker --load buildx bake static-builder-musl
# ...
```
## macOS
macOS用の静的バイナリを作成するには以下のスクリプトを実行してください[Homebrew](https://brew.sh/)がインストールされている必要があります):
```console
git clone https://github.com/php/frankenphp
cd frankenphp
./build-static.sh
```
なお、このスクリプトはLinuxおそらく他のUnix系OSでも動作し、私たちが提供するDockerイメージ内部でも使用されています。
## ビルドのカスタマイズ
以下の環境変数を`docker build``build-static.sh`
スクリプトに渡すことで、静的ビルドをカスタマイズできます:
- `FRANKENPHP_VERSION`: 使用するFrankenPHPのバージョン
- `PHP_VERSION`: 使用するPHPのバージョン
- `PHP_EXTENSIONS`: ビルドするPHP拡張[サポートされる拡張のリスト](https://static-php.dev/en/guide/extensions.html)
- `PHP_EXTENSION_LIBS`: 拡張モジュールに追加機能を持たせるためにビルドする追加ライブラリ
- `XCADDY_ARGS`: 追加のCaddyモジュールを導入するなど[xcaddy](https://github.com/caddyserver/xcaddy)に渡す引数
- `EMBED`: バイナリに埋め込むPHPアプリケーションのパス
- `CLEAN`: 指定するとlibphpおよびそのすべての依存関係がスクラッチからビルドされますキャッシュなし
- `NO_COMPRESS`: UPXを使用して結果のバイナリを圧縮しない
- `DEBUG_SYMBOLS`: 指定すると、デバッグシンボルが除去されず、バイナリに含まれます
- `MIMALLOC`: 実験的、Linuxのみパフォーマンス向上のためにmuslのmallocngを[mimalloc](https://github.com/microsoft/mimalloc)に置き換えます。muslをターゲットとするビルドにのみこれを使用することをお勧めします。glibcの場合は、このオプションを無効にして、代わりにバイナリを実行する際に[`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html)を使用することをお勧めします。
- `RELEASE`: メンテナー用指定すると、生成されたバイナリがGitHubにアップロードされます
## 拡張モジュール
glibcまたはmacOSベースのバイナリでは、PHP拡張モジュールを動的にロードできます。ただし、これらの拡張はZTSサポートでコンパイルされている必要があります。
ほとんどのパッケージマネージャーは現在、拡張のZTSバージョンを提供していないため、自分でコンパイルする必要があります。
このために、`static-builder-gnu`Dockerコンテナをビルドして実行し、リモートでアクセスし、`./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config`で拡張をコンパイルできます。
[Xdebug拡張モジュール](https://xdebug.org)の場合:
```console
docker build -t gnu-ext -f static-builder-gnu.Dockerfile --build-arg FRANKENPHP_VERSION=1.0 .
docker create --name static-builder-gnu -it gnu-ext /bin/sh
docker start static-builder-gnu
docker exec -it static-builder-gnu /bin/sh
cd /go/src/app/dist/static-php-cli/buildroot/bin
git clone https://github.com/xdebug/xdebug.git && cd xdebug
source scl_source enable devtoolset-10
../phpize
./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config
make
exit
docker cp static-builder-gnu:/go/src/app/dist/static-php-cli/buildroot/bin/xdebug/modules/xdebug.so xdebug-zts.so
docker cp static-builder-gnu:/go/src/app/dist/frankenphp-linux-$(uname -m) ./frankenphp
docker stop static-builder-gnu
docker rm static-builder-gnu
docker rmi gnu-ext
```
これにより、現在のディレクトリに`frankenphp``xdebug-zts.so`が作成されます。
`xdebug-zts.so`を拡張ディレクトリに移動し、php.iniに`zend_extension=xdebug-zts.so`を追加してFrankenPHPを実行すると、Xdebugがロードされます。

181
docs/ja/worker.md Normal file
View File

@@ -0,0 +1,181 @@
# FrankenPHPワーカーの使用
アプリケーションを一度起動してメモリに保持します。
FrankenPHPは数ミリ秒で受信リクエストを処理します。
## ワーカースクリプトの開始
### Docker
`FRANKENPHP_CONFIG`環境変数の値を`worker /path/to/your/worker/script.php`に設定します:
```console
docker run \
-e FRANKENPHP_CONFIG="worker /app/path/to/your/worker/script.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
### スタンドアロンバイナリ
`php-server`コマンドの`--worker`オプションを使って、現在のディレクトリのコンテンツをワーカーを通じて提供できます:
```console
frankenphp php-server --worker /path/to/your/worker/script.php
```
PHPアプリが[バイナリに埋め込まれている](embed.md)場合は、アプリのルートディレクトリにカスタムの`Caddyfile`を追加することができます。
これが自動的に使用されます。
また、`--watch`オプションを使えば、[ファイルの変更に応じてワーカーを再起動](config.md#watching-for-file-changes)することも可能です。
以下のコマンドは、`/path/to/your/app/`ディレクトリおよびそのサブディレクトリ内の`.php`で終わるファイルが変更された場合に再起動をトリガーします:
```console
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
```
## Symfonyランタイム
FrankenPHPのワーカーモードは[Symfony Runtime Component](https://symfony.com/doc/current/components/runtime.html)によってサポートされています。
ワーカーでSymfonyアプリケーションを開始するには、FrankenPHP用の[PHP Runtime](https://github.com/php-runtime/runtime)パッケージをインストールします:
```console
composer require runtime/frankenphp-symfony
```
アプリケーションサーバーを起動するには、FrankenPHP Symfony Runtimeを使用するように`APP_RUNTIME`環境変数を定義します:
```console
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
## Laravel Octane
[専用ドキュメント](laravel.md#laravel-octane)を参照してください。
## カスタムアプリ
以下の例は、サードパーティライブラリに依存せずに独自のワーカースクリプトを作成する方法を示しています:
```php
<?php
// public/index.php
// クライアント接続が中断されたときのワーカースクリプト終了を防ぐ
ignore_user_abort(true);
// アプリを起動
require __DIR__.'/vendor/autoload.php';
$myApp = new \App\Kernel();
$myApp->boot();
// ループの外側にハンドラーを配置してパフォーマンスを向上(処理量を減らす)
$handler = static function () use ($myApp) {
// リクエストを受信した際に呼び出され、
// スーパーグローバルや php://input などがリセットされます。
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
};
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
$keepRunning = \frankenphp_handle_request($handler);
// HTTPレスポンスの送信後に何か処理を行います
$myApp->terminate();
// ページ生成の途中でガベージコレクタが起動する可能性を減らすために、ここでガベージコレクタを明示的に呼び出す。
gc_collect_cycles();
if (!$keepRunning) break;
}
// クリーンアップ
$myApp->shutdown();
```
次に、アプリを開始し、`FRANKENPHP_CONFIG`環境変数を使用してワーカーを設定します:
```console
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
デフォルトでは、CPU当たり2つのワーカーが開始されます。
開始するワーカー数を設定することもできます:
```console
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php 42" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
### 一定数のリクエスト処理後にワーカーを再起動する
PHPはもともと長時間実行されるプロセス向けに設計されていなかったため、メモリリークを引き起こすライブラリやレガシーコードがいまだに多く存在します。
こうしたコードをワーカーモードで利用するための回避策として、一定数のリクエストを処理した後にワーカースクリプトを再起動する方法があります:
前述のワーカー用スニペットでは、`MAX_REQUESTS`という名前の環境変数を設定することで、処理する最大リクエスト数を設定できます。
### ワーカーの手動再起動
[ファイルの変更を監視](config.md#watching-for-file-changes)してワーカーを再起動することも可能ですが、
[Caddy admin API](https://caddyserver.com/docs/api)を使用してすべてのワーカーをグレースフルに安全に再起動することも可能です。adminが
[Caddyfile](config.md#caddyfile-config)で有効になっている場合、次のような単純なPOSTリクエストで再起動エンドポイントにpingできます
```console
curl -X POST http://localhost:2019/frankenphp/workers/restart
```
### ワーカーの失敗
ワーカースクリプトがゼロ以外の終了コードでクラッシュした場合、FrankenPHP は指数的バックオフ戦略を用いて再起動を行います。
ワーカースクリプトが最後のバックオフ時間 × 2 より長く稼働し続けた場合、
それ以降の再起動ではペナルティを科しません。
しかし、スクリプトにタイプミスがあるなど短時間で何度もゼロ以外の終了コードで失敗し続ける場合、
FrankenPHP は`too many consecutive failures`というエラーとともにクラッシュします。
連続失敗の回数上限は、[Caddyfile](config.md#caddyfile-config)の`max_consecutive_failures`オプションで設定できます:
```caddyfile
frankenphp {
worker {
# ...
max_consecutive_failures 10
}
}
```
## スーパーグローバルの動作
[PHPのスーパーグローバル](https://www.php.net/manual/en/language.variables.superglobals.php)`$_SERVER``$_ENV``$_GET`など)
は以下のように動作します:
- `frankenphp_handle_request()`が最初に呼び出される前は、スーパーグローバルにはワーカースクリプト自体にバインドされた値が格納されています
- `frankenphp_handle_request()`の呼び出し中および呼び出し後は、スーパーグローバルには処理されたHTTPリクエストから生成された値が格納され、`frankenphp_handle_request()`を呼び出すたびにスーパーグローバルの値が変更されます
コールバック内でワーカースクリプトのスーパーグローバルにアクセスするには、それらをコピーしてコールバックのスコープにコピーをインポートする必要があります:
```php
<?php
// frankenphp_handle_request()を最初に呼び出す前に、ワーカーの $_SERVER スーパーグローバルをコピー
$workerServer = $_SERVER;
$handler = static function () use ($workerServer) {
var_dump($_SERVER); // リクエストにバインドされた $_SERVER
var_dump($workerServer); // ワーカースクリプトの $_SERVER
};
// ...
```

71
docs/ja/x-sendfile.md Normal file
View File

@@ -0,0 +1,71 @@
# 大きな静的ファイルを効率的に配信する `X-Sendfile`/`X-Accel-Redirect`
通常、静的ファイルはウェブサーバーによって直接配信されますが、
時にはファイルを送信する前にPHPコードを実行する必要があります。
例えば、アクセス制御、統計、カスタムHTTPヘッダーなど
残念ながら、PHPを使用して大きな静的ファイルを配信することは、
ウェブサーバーを直接使うより非効率的です(メモリ過負荷、パフォーマンス低下など)。
FrankenPHPでは、カスタマイズされたPHPコードを実行した**後**に、
静的ファイルの送信をウェブサーバーに委譲できます。
この機能を使うには、PHPアプリケーションは提供するファイルのパスを含む
カスタムHTTPヘッダーを定義するだけです。残りの処理はFrankenPHPが行います。
この機能は、Apacheでは **`X-Sendfile`** 、NGINXでは **`X-Accel-Redirect`** として知られています。
以下の例では、プロジェクトのドキュメントルートが`public/`ディレクトリであり、
`public/`ディレクトリの外部に保存されたファイルを
`private-files/`ディレクトリからPHPで提供したいと仮定します。
## 設定方法
まず、この機能を有効にするために以下の設定を`Caddyfile`に追加します:
```patch
root public/
# ...
+ # Symfony や Laravel など、Symfony HttpFoundation コンポーネントを使用するプロジェクトに必要
+ request_header X-Sendfile-Type x-accel-redirect
+ request_header X-Accel-Mapping ../private-files=/private-files
+
+ intercept {
+ @accel header X-Accel-Redirect *
+ handle_response @accel {
+ root private-files/
+ rewrite * {resp.header.X-Accel-Redirect}
+ method * GET
+
+ # セキュリティ強化のため、 PHP によって設定された X-Accel-Redirect ヘッダーを削除
+ header -X-Accel-Redirect
+
+ file_server
+ }
+ }
php_server
```
## プレーンなPHPの場合
`private-files/`からの相対パスを`X-Accel-Redirect`ヘッダーの値として設定します:
```php
header('X-Accel-Redirect: file.txt');
```
## Symfony HttpFoundationコンポーネントを使用するプロジェクトの場合Symfony、Laravel、Drupalなど
SymfonyのHttpFoundationは[この機能をネイティブサポート](https://symfony.com/doc/current/components/http_foundation.html#serving-files)しており、
`X-Accel-Redirect`ヘッダーの正しい値を自動的に決定してレスポンスに追加します。
```php
use Symfony\Component\HttpFoundation\BinaryFileResponse;
BinaryFileResponse::trustXSendfileTypeHeader();
$response = new BinaryFileResponse(__DIR__.'/../private-files/file.txt');
// ...
```

View File

@@ -13,9 +13,9 @@ The following extensions are known not to be compatible with FrankenPHP:
The following extensions have known bugs and unexpected behaviors when used with FrankenPHP:
| Name | Problem |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | When using a static build of FrankenPHP (built with the musl libc), the OpenSSL extension may crash under heavy loads. A workaround is to use a dynamically linked build (like the one used in Docker images). This bug is [being tracked by PHP](https://github.com/php/php-src/issues/13648). |
| Name | Problem |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | When using musl libc, the OpenSSL extension may crash under heavy loads. The problem doesn't occur when using the more popular GNU libc. This bug is [being tracked by PHP](https://github.com/php/php-src/issues/13648). |
## get_browser
@@ -23,7 +23,11 @@ The [get_browser()](https://www.php.net/manual/en/function.get-browser.php) func
## Standalone Binary and Alpine-based Docker Images
The standalone binary and Alpine-based docker images (`dunglas/frankenphp:*-alpine`) use [musl libc](https://musl.libc.org/) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), to keep a smaller binary size. This may lead to some compatibility issues. In particular, the glob flag `GLOB_BRACE` is [not available](https://www.php.net/manual/en/function.glob.php)
The fully binary and Alpine-based Docker images (`dunglas/frankenphp:*-alpine`) use [musl libc](https://musl.libc.org/) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), to keep a smaller binary size.
This may lead to some compatibility issues.
In particular, the glob flag `GLOB_BRACE` is [not available](https://www.php.net/manual/en/function.glob.php)
Prefer using the GNU variant of the static binary and Debian-based Docker images if you encounter issues.
## Using `https://127.0.0.1` with Docker
@@ -130,7 +134,7 @@ to find where CA certificates must be installed and store them at this location.
> Web and CLI contexts may have different settings.
> Be sure to run `openssl_get_cert_locations()` in the proper context.
[CA certificates extracted from Mozilla can be downloaded on the curl site](https://curl.se/docs/caextract.html).
[CA certificates extracted from Mozilla can be downloaded on the cURL site](https://curl.se/docs/caextract.html).
Alternatively, many distributions, including Debian, Ubuntu, and Alpine provide packages named `ca-certificates` that contain these certificates.

View File

@@ -76,6 +76,8 @@ The `octane:frankenphp` command can take the following options:
> [!TIP]
> To get structured JSON logs (useful when using log analytics solutions), explicitly the pass `--log-level` option.
See also [how to use Mercure with Octane](#mercure-support).
Learn more about [Laravel Octane in its official documentation](https://laravel.com/docs/octane).
## Laravel Apps As Standalone Binaries
@@ -166,6 +168,34 @@ This is not suitable for embedded applications, as each new version will be extr
Set the `LARAVEL_STORAGE_PATH` environment variable (for example, in your `.env` file) or call the `Illuminate\Foundation\Application::useStoragePath()` method to use a directory outside the temporary directory.
### Mercure Support
[Mercure](https://mercure.rocks) is a great way to add real-time capabilities to your Laravel apps.
FrankenPHP includes [Mercure support out of the box](mercure.md).
If you are not using [Octane](#laravel-octane), see [the Mercure documentation entry](mercure.md).
If you are using Octane, you can use enable Mercure support by adding the following lines to your `config/octane.php` file:
```php
// ...
return [
// ...
'mercure' => [
'anonymous' => true,
'publisher_jwt' => '!ChangeThisMercureHubJWTSecretKey!',
'subscriber_jwt' => '!ChangeThisMercureHubJWTSecretKey!',
],
];
```
You can use [all directives supported by Mercure](https://mercure.rocks/docs/hub/config#directives) in this array.
To publish and subscribe to updates, we recommend using the [Laravel Mercure Broadcaster](https://github.com/mvanduijker/laravel-mercure-broadcaster) library.
Alternatively, see [the Mercure documentation](mercure.md) to do it in pure PHP and JavaScript.
### Running Octane With Standalone Binaries
It's even possible to package Laravel Octane apps as standalone binaries!

View File

@@ -3,13 +3,124 @@
FrankenPHP comes with a built-in [Mercure](https://mercure.rocks) hub!
Mercure allows you to push real-time events to all the connected devices: they will receive a JavaScript event instantly.
No JS library or SDK is required!
It's a convenient alternative to WebSockets that is simple to use and is natively supported by all modern web browsers!
![Mercure](mercure-hub.png)
To enable the Mercure hub, update the `Caddyfile` as described [on Mercure's site](https://mercure.rocks/docs/hub/config).
## Enabling Mercure
The path of the Mercure hub is `/.well-known/mercure`.
When running FrankenPHP inside Docker, the full send URL would look like `http://php/.well-known/mercure` (with `php` being the container's name running FrankenPHP).
Mercure support is disabled by default.
Here is a minimal example of a `Caddyfile` enabling both FrankenPHP and the Mercure hub:
To push Mercure updates from your code, we recommend the [Symfony Mercure Component](https://symfony.com/components/Mercure) (you don't need the Symfony full-stack framework to use it).
```caddyfile
# The hostname to respond to
localhost
mercure {
# The secret key used to sign the JWT tokens for publishers
publisher_jwt !ChangeThisMercureHubJWTSecretKey!
# Allows anonymous subscribers (without JWT)
anonymous
}
root public/
php_server
```
> [!TIP]
>
> The [sample `Caddyfile`](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile)
> provided by [the Docker images](docker.md) already includes a commented Mercure configuration
> with convenient environment variables to configure it.
>
> Uncomment the Mercure section in `/etc/frankenphp/Caddyfile` to enable it.
## Subscribing to Updates
By default, the Mercure hub is available on the `/.well-known/mercure` path of your FrankenPHP server.
To subscribe to updates, use the native [`EventSource`](https://developer.mozilla.org/docs/Web/API/EventSource) JavaScript class:
```html
<!-- public/index.html -->
<!doctype html>
<title>Mercure Example</title>
<script>
const eventSource = new EventSource("/.well-known/mercure?topic=my-topic");
eventSource.onmessage = function (event) {
console.log("New message:", event.data);
};
</script>
```
## Publishing Updates
### Using `file_put_contents()`
To dispatch an update to connected subscribers, send an authenticated POST request to the Mercure hub with the `topic` and `data` parameters:
```php
<?php
// public/publish.php
const JWT = 'eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.PXwpfIGng6KObfZlcOXvcnWCJOWTFLtswGI5DZuWSK4';
$updateID = file_put_contents('https://localhost/.well-known/mercure', context: stream_context_create(['http' => [
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer " . JWT,
'content' => http_build_query([
'topic' => 'my-topic',
'data' => json_encode(['key' => 'value']),
]),
]]));
// Write to FrankenPHP's logs
error_log("update $updateID published", 4);
```
The key passed as parameter of the `mercure.publisher_jwt` option in the `Caddyfile` must used to sign the JWT token used in the `Authorization` header.
The JWT must include a `mercure` claim with a `publish` permission for the topics you want to publish to.
See [the Mercure documentation](https://mercure.rocks/spec#publishers) about authorization.
To generate your own tokens, you can use [this jwt.io link](https://www.jwt.io/#token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.PXwpfIGng6KObfZlcOXvcnWCJOWTFLtswGI5DZuWSK4),
but for production apps, it's recommended to use short-lived tokens generated aerodynamically using with a trusted [JWT library](https://www.jwt.io/libraries?programming_language=php).
### Using Symfony Mercure
Alternatively, you can use the [Symfony Mercure Component](https://symfony.com/components/Mercure), a standalone PHP library.
This library handled the JWT generation, update publishing as well as cookie-based authorization for subscribers.
First, install the library using Composer:
```console
composer require symfony/mercure lcobucci/jwt
```
Then, you can use it like this:
```php
<?php
// public/publish.php
require __DIR__ . '/../vendor/autoload.php';
const JWT_SECRET = '!ChangeThisMercureHubJWTSecretKey!'; // Must be the same as mercure.publisher_jwt in Caddyfile
// Set up the JWT token provider
$jwFactory = new \Symfony\Component\Mercure\Jwt\LcobucciFactory(JWT_SECRET);
$provider = new \Symfony\Component\Mercure\Jwt\FactoryTokenProvider($jwFactory, publish: ['*']);
$hub = new \Symfony\Component\Mercure\Hub('https://localhost/.well-known/mercure', $provider);
// Serialize the update, and dispatch it to the hub, that will broadcast it to the clients
$updateID = $hub->publish(new \Symfony\Component\Mercure\Update('my-topic', json_encode(['key' => 'value'])));
// Write to FrankenPHP's logs
error_log("update $updateID published", 4);
```
Mercure is also natively supported by:
- [Laravel](laravel.md#mercure-support)
- [Symfony](https://symfony.com/doc/current/mercure.html)
- [API Platform](https://api-platform.com/docs/core/mercure/)

View File

@@ -155,3 +155,36 @@ In particular:
For more details, read [the dedicated Symfony documentation entry](https://symfony.com/doc/current/performance.html)
(most tips are useful even if you don't use Symfony).
## Splitting The Thread Pool
It is common for applications to interact with slow external services, like an
API that tends to be unreliable under high load or consistently takes 10+ seconds to respond.
In such cases, it can be beneficial to split the thread pool to have dedicated "slow" pools.
This prevents the slow endpoints from consuming all server resources/threads and
limits the concurrency of requests going towards the slow endpoint, similar to a
connection pool.
```caddyfile
{
frankenphp {
max_threads 100 # max 100 threads shared by all workers
}
}
example.com {
php_server {
root /app/public # the root of your application
worker index.php {
match /slow-endpoint/* # all requests with path /slow-endpoint/* are handled by this thread pool
num 10 # minimum 10 threads for requests matching /slow-endpoint/*
}
worker index.php {
match * # all other requests are handled separately
num 20 # minimum 20 threads for other requests, even if the slow endppoints start hanging
}
}
}
```
Generally it's also advisable to handle very slow endpoints asynchronously, by using relevant mechanisms such as message queues.

238
docs/pt-br/CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,238 @@
# Contribuindo
## Compilando o PHP
### Com Docker (Linux)
Crie a imagem Docker de desenvolvimento:
```console
docker build -t frankenphp-dev -f dev.Dockerfile .
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 -p 443:443 -p 443:443/udp -v $PWD:/go/src/app -it frankenphp-dev
```
A imagem contém as ferramentas de desenvolvimento usuais (Go, GDB, Valgrind,
Neovim...) e usa os seguintes locais de configuração do PHP:
- php.ini: `/etc/frankenphp/php.ini`.
Um arquivo `php.ini` com configurações de desenvolvimento é fornecido por
padrão.
- Arquivos de configuração adicionais: `/etc/frankenphp/php.d/*.ini`.
- Extensões PHP: `/usr/lib/frankenphp/modules/`.
Se a sua versão do Docker for anterior à 23.0, a compilação falhará devido ao
[problema de padrão do `.dockerignore`](https://github.com/moby/moby/pull/42676).
Adicione diretórios ao `.dockerignore`.
```patch
!testdata/*.php
!testdata/*.txt
+!caddy
+!internal
```
### Sem Docker (Linux e macOS)
[Siga as instruções para compilar a partir do código-fonte](compile.md) e passe
a flag de configuração `--debug`.
## Executando a suite de testes
```console
go test -tags watcher -race -v ./...
```
## Módulo Caddy
Construa o Caddy com o módulo Caddy FrankenPHP:
```console
cd caddy/frankenphp/
go build -tags watcher,brotli,nobadger,nomysql,nopgx
cd ../../
```
Execute o Caddy com o módulo Caddy FrankenPHP:
```console
cd testdata/
../caddy/frankenphp/frankenphp run
```
O servidor está escutando em `127.0.0.1:80`:
> [!NOTE]
> Se você estiver usando o Docker, terá que vincular a porta 80 do contêiner ou
> executar de dentro do contêiner.
```console
curl -vk http://127.0.0.1/phpinfo.php
```
## Servidor de teste mínimo
Construa o servidor de teste mínimo:
```console
cd internal/testserver/
go build
cd ../../
```
Execute o servidor de teste:
```console
cd testdata/
../internal/testserver/testserver
```
O servidor está escutando em `127.0.0.1:8080`:
```console
curl -v http://127.0.0.1:8080/phpinfo.php
```
## Construindo imagens Docker localmente
Imprima o plano do bake:
```console
docker buildx bake -f docker-bake.hcl --print
```
Construa imagens FrankenPHP para amd64 localmente:
```console
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/amd64"
```
Construa imagens FrankenPHP para arm64 localmente:
```console
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/arm64"
```
Construa imagens FrankenPHP do zero para arm64 e amd64 e envie para o Docker
Hub:
```console
docker buildx bake -f docker-bake.hcl --pull --no-cache --push
```
## Depurando falhas de segmentação com compilações estáticas
1. Baixe a versão de depuração do binário do FrankenPHP do GitHub ou crie sua
própria compilação estática personalizada, incluindo símbolos de depuração:
```console
docker buildx bake \
--load \
--set static-builder.args.DEBUG_SYMBOLS=1 \
--set "static-builder.platform=linux/amd64" \
static-builder
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
```
2. Substitua sua versão atual do `frankenphp` pelo executável de depuração do
FrankenPHP.
3. Inicie o FrankenPHP normalmente (alternativamente, você pode iniciar o
FrankenPHP diretamente com o GDB: `gdb --args frankenphp run`).
4. Anexe ao processo com o GDB:
```console
gdb -p `pidof frankenphp`
```
5. Se necessário, digite `continue` no shell do GDB.
6. Faça o FrankenPHP travar.
7. Digite `bt` no shell do GDB.
8. Copie a saída.
## Depurando falhas de segmentação no GitHub Actions
1. Abra o arquivo `.github/workflows/tests.yml`.
2. Habilite os símbolos de depuração do PHP:
```patch
- uses: shivammathur/setup-php@v2
# ...
env:
phpts: ts
+ debug: true
```
3. Habilite o `tmate` para se conectar ao contêiner:
```patch
- name: Set CGO flags
run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
+ - run: |
+ sudo apt install gdb
+ mkdir -p /home/runner/.config/gdb/
+ printf "set auto-load safe-path /\nhandle SIG34 nostop noprint pass" > /home/runner/.config/gdb/gdbinit
+ - uses: mxschmitt/action-tmate@v3
```
4. Conecte-se ao contêiner.
5. Abra o `frankenphp.go`.
6. Habilite o `cgosymbolizer`:
```patch
- //_ "github.com/ianlancetaylor/cgosymbolizer"
+ _ "github.com/ianlancetaylor/cgosymbolizer"
```
7. Baixe o módulo: `go get`.
8. No contêiner, você pode usar o GDB e similares:
```console
go test -tags watcher -c -ldflags=-w
gdb --args frankenphp.test -test.run ^MyTest$
```
9. Quando a falha for corrigida, reverta todas essas alterações.
## Recursos diversos de desenvolvimento
- [PHP embedding in uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
- [PHP embedding in NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
- [PHP embedding in Go (go-php)](https://github.com/deuill/go-php)
- [PHP embedding in Go (GoEmPHP)](https://github.com/mikespook/goemphp)
- [PHP embedding in C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
- [Extending and Embedding PHP, por Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
- [What the heck is TSRMLS_CC, anyway?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
- [SDL bindings](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
## Recursos relacionados ao Docker
- [Definição do arquivo Bake](https://docs.docker.com/build/customize/bake/file-definition/)
- [`docker buildx build`](https://docs.docker.com/engine/reference/commandline/buildx_build/)
## Comando útil
```console
apk add strace util-linux gdb
strace -e 'trace=!futex,epoll_ctl,epoll_pwait,tgkill,rt_sigreturn' -p 1
```
## Traduzindo a documentação
Para traduzir a documentação e o site para um novo idioma, siga estes passos:
1. Crie um novo diretório com o código ISO de 2 caracteres do idioma no
diretório `docs/` deste repositório.
2. Copie todos os arquivos `.md` da raiz do diretório `docs/` para o novo
diretório (sempre use a versão em inglês como fonte para tradução, pois está
sempre atualizada).
3. Copie os arquivos `README.md` e `CONTRIBUTING.md` do diretório raiz para o
novo diretório.
4. Traduza o conteúdo dos arquivos, mas não altere os nomes dos arquivos, nem
traduza strings que comecem com `> [!` (é uma marcação especial para o
GitHub).
5. Crie um pull request com as traduções.
6. No
[repositório do site](https://github.com/dunglas/frankenphp-website/tree/main),
copie e traduza os arquivos de tradução nos diretórios `content/`, `data/` e
`i18n/`.
7. Traduza os valores no arquivo YAML criado.
8. Abra um pull request no repositório do site.

126
docs/pt-br/README.md Normal file
View File

@@ -0,0 +1,126 @@
# FrankenPHP: um moderno servidor de aplicações para PHP
<h1 align="center"><a href="https://frankenphp.dev/pt-br"><img src="frankenphp.png" alt="FrankenPHP" width="600"></a></h1>
O FrankenPHP é um moderno servidor de aplicações para PHP, construído sobre o
servidor web [Caddy](https://caddyserver.com/).
O FrankenPHP dá superpoderes às suas aplicações PHP graças aos seus recursos
impressionantes: [_Early Hints_](early-hints.md), [modo worker](worker.md),
[recursos em tempo real](mercure.md), suporte automático a HTTPS, HTTP/2 e
HTTP/3...
O FrankenPHP funciona com qualquer aplicação PHP e torna seus projetos Laravel e
Symfony mais rápidos do que nunca, graças às suas integrações oficiais com o
modo worker.
O FrankenPHP também pode ser usado como uma biblioteca Go independente para
incorporar PHP em qualquer aplicação usando `net/http`.
[**Saiba mais** em _frankenphp.dev_](https://frankenphp.dev/pt-br) e neste
conjunto de slides:
<a href="https://dunglas.dev/2022/10/frankenphp-the-modern-php-app-server-written-in-go/"><img src="https://dunglas.dev/wp-content/uploads/2022/10/frankenphp.png" alt="Slides" width="600"></a>
## Começando
### Binário independente
Fornecemos binários estáticos do FrankenPHP para Linux e macOS contendo o
[PHP 8.4](https://www.php.net/releases/8.4/pt_BR.php) e as extensões PHP mais
populares.
No Windows, use [WSL](https://learn.microsoft.com/pt-br/windows/wsl/) para
executar o FrankenPHP.
[Baixe o FrankenPHP](https://github.com/php/frankenphp/releases) ou copie esta
linha no seu terminal para instalar automaticamente a versão apropriada para sua
plataforma:
```console
curl https://frankenphp.dev/install.sh | sh
mv frankenphp /usr/local/bin/
```
Para servir o conteúdo do diretório atual, execute:
```console
frankenphp php-server
```
Você também pode executar scripts de linha de comando com:
```console
frankenphp php-cli /caminho/para/seu/script.php
```
### Docker
Alternativamente, [imagens Docker](docker.md) estão disponíveis:
```console
docker run -v .:/app/public \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
Acesse `https://localhost` e divirta-se!
> [!TIP]
>
> Não tente usar `https://127.0.0.1`.
> Use `https://localhost` e aceite o certificado autoassinado.
> Use a
> [variável de ambiente `SERVER_NAME`](config.md#variaveis-de-ambiente)
> para alterar o domínio a ser usado.
### Homebrew
O FrankenPHP também está disponível como um pacote [Homebrew](https://brew.sh)
para macOS e Linux.
Para instalá-lo:
```console
brew install dunglas/frankenphp/frankenphp
```
Para servir o conteúdo do diretório atual, execute:
```console
frankenphp php-server
```
## Documentação
- [Modo clássico](classic.md)
- [Modo worker](worker.md)
- [Suporte a Early Hints (código de status HTTP 103)](early-hints.md)
- [Tempo real](mercure.md)
- [Servindo grandes arquivos estáticos com eficiência](x-sendfile.md)
- [Configuração](config.md)
- [Escrevendo extensões PHP em Go](extensions.md)
- [Imagens Docker](docker.md)
- [Implantação em produção](production.md)
- [Otimização de desempenho](performance.md)
- [Crie aplicações PHP **independentes** e autoexecutáveis](embed.md)
- [Crie binários estáticos](static.md)
- [Compile a partir do código-fonte](compile.md)
- [Monitorando o FrankenPHP](metrics.md)
- [Integração com Laravel](laravel.md)
- [Problemas conhecidos](known-issues.md)
- [Aplicação de demonstração (Symfony) e benchmarks](https://github.com/dunglas/frankenphp-demo)
- [Documentação da biblioteca Go](https://pkg.go.dev/github.com/php/frankenphp)
- [Contribuindo e depurando](CONTRIBUTING.md)
## Exemplos e esqueletos
- [Symfony](https://github.com/dunglas/symfony-docker)
- [API Platform](https://api-platform.com/docs/symfony)
- [Laravel](laravel.md)
- [Sulu](https://sulu.io/blog/running-sulu-with-frankenphp)
- [WordPress](https://github.com/StephenMiracle/frankenwp)
- [Drupal](https://github.com/dunglas/frankenphp-drupal)
- [Joomla](https://github.com/alexandreelise/frankenphp-joomla)
- [TYPO3](https://github.com/ochorocho/franken-typo3)
- [Magento2](https://github.com/ekino/frankenphp-magento2)

28
docs/pt-br/classic.md Normal file
View File

@@ -0,0 +1,28 @@
# Usando o modo clássico
Sem nenhuma configuração adicional, o FrankenPHP opera no modo clássico.
Neste modo, o FrankenPHP funciona como um servidor PHP tradicional, servindo
diretamente arquivos PHP.
Isso o torna um substituto perfeito para PHP-FPM ou Apache com mod_php.
Semelhante ao Caddy, o FrankenPHP aceita um número ilimitado de conexões e usa
um [número fixo de threads](config.md#configuracao-do-caddyfile) para servi-las.
O número de conexões aceitas e enfileiradas é limitado apenas pelos recursos
disponíveis no sistema.
O pool de threads do PHP opera com um número fixo de threads inicializadas na
inicialização, comparável ao modo estático do PHP-FPM.
Também é possível permitir que as threads
[escalem automaticamente em tempo de execução](performance.md#max_threads),
semelhante ao modo dinâmico do PHP-FPM.
As conexões enfileiradas aguardarão indefinidamente até que uma thread PHP
esteja disponível para servi-las.
Para evitar isso, você pode usar a
[configuração](config.md#configuracao-do-caddyfile) `max_wait_time` na
configuração global do FrankenPHP para limitar o tempo que uma requisição pode
esperar por uma thread PHP livre antes de ser rejeitada.
Além disso, você pode definir um
[tempo limite de escrita razoável no Caddy](https://caddyserver.com/docs/caddyfile/options#timeouts).
Cada instância do Caddy ativará apenas um pool de threads do FrankenPHP, que
será compartilhado entre todos os blocos `php_server`.

147
docs/pt-br/compile.md Normal file
View File

@@ -0,0 +1,147 @@
# Compilar a partir do código-fonte
Este documento explica como criar um binário FrankenPHP que carregará o PHP como
uma biblioteca dinâmica.
Este é o método recomendado.
Como alternativa, [compilações totalmente e principalmente estáticas](static.md)
também podem ser criadas.
## Instalar o PHP
O FrankenPHP é compatível com PHP 8.2 e versões superiores.
### Com o Homebrew (Linux e Mac)
A maneira mais fácil de instalar uma versão da `libphp` compatível com o
FrankenPHP é usar os pacotes ZTS fornecidos pelo
[Homebrew PHP](https://github.com/shivammathur/homebrew-php).
Primeiro, se ainda não o fez, instale o [Homebrew](https://brew.sh).
Em seguida, instale a variante ZTS do PHP, o Brotli (opcional, para suporte à
compressão) e o watcher (opcional, para detecção de alterações em arquivos):
```console
brew install shivammathur/php/php-zts brotli watcher
brew link --overwrite --force shivammathur/php/php-zts
```
### Compilando o PHP
Alternativamente, você pode compilar o PHP a partir do código-fonte com as
opções necessárias para o FrankenPHP seguindo estes passos.
Primeiro, [obtenha o código-fonte do PHP](https://www.php.net/downloads.php) e
extraia-os:
```console
tar xf php-*
cd php-*/
```
Em seguida, execute o script `configure` com as opções necessárias para sua
plataforma.
As seguintes flags `./configure` são obrigatórias, mas você pode adicionar
outras, por exemplo, para compilar extensões ou recursos adicionais.
#### Linux
```console
./configure \
--enable-embed \
--enable-zts \
--disable-zend-signals \
--enable-zend-max-execution-timers
```
#### Mac
Use o gerenciador de pacotes [Homebrew](https://brew.sh/) para instalar as
dependências necessárias e opcionais:
```console
brew install libiconv bison brotli re2c pkg-config watcher
echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
```
Em seguida, execute o script `configure`:
```console
./configure \
--enable-embed \
--enable-zts \
--disable-zend-signals \
--with-iconv=/opt/homebrew/opt/libiconv/
```
#### Compilar o PHP
Finalmente, compile e instale o PHP:
```console
make -j"$(getconf _NPROCESSORS_ONLN)"
sudo make install
```
## Instalar dependências opcionais
Alguns recursos do FrankenPHP dependem de dependências opcionais do sistema que
devem ser instaladas.
Alternativamente, esses recursos podem ser desabilitados passando as tags de
compilação para o compilador Go.
| Recurso | Dependência | Tag de compilação para desabilitá-lo |
| ------------------------------------- | --------------------------------------------------------------------- | ------------------------------------ |
| Compressão Brotli | [Brotli](https://github.com/google/brotli) | `nobrotli` |
| Reiniciar workers ao alterar arquivos | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | `nowatcher` |
## Compilando a aplicação Go
Agora você pode compilar o binário final.
### Usando o `xcaddy`
A maneira recomendada é usar o [`xcaddy`](https://github.com/caddyserver/xcaddy)
para compilar o FrankenPHP.
O `xcaddy` também permite adicionar facilmente
[módulos Caddy personalizados](https://caddyserver.com/docs/modules/) e
extensões FrankenPHP:
```console
CGO_ENABLED=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output frankenphp \
--with github.com/php/frankenphp/caddy \
--with github.com/dunglas/mercure/caddy \
--with github.com/dunglas/vulcain/caddy
# Adicione módulos Caddy e extensões FrankenPHP extras aqui
```
> [!TIP]
>
> Se você estiver usando a `libc` `musl` (o padrão no Alpine Linux) e Symfony,
> pode ser necessário aumentar o tamanho da pilha padrão.
> Caso contrário, você poderá receber erros como `PHP Fatal error: Maximum call
stack size of 83360 bytes reached during compilation.
Try splitting expression`.
>
> Para fazer isso, altere a variável de ambiente `XCADDY_GO_BUILD_FLAGS` para
> algo como
> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'`
> (altere o valor do tamanho da pilha de acordo com as necessidades da sua
> aplicação).
### Sem o `xcaddy`
Alternativamente, é possível compilar o FrankenPHP sem o `xcaddy` usando o
comando `go` diretamente:
```console
curl -L https://github.com/php/frankenphp/archive/refs/heads/main.tar.gz | tar xz
cd frankenphp-main/caddy/frankenphp
CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build -tags=nobadger,nomysql,nopgx
```

352
docs/pt-br/config.md Normal file
View File

@@ -0,0 +1,352 @@
# Configuração
FrankenPHP, Caddy, bem como os módulos Mercure e Vulcain, podem ser configurados
usando
[os formatos suportados pelo Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
Nas [imagens Docker](docker.md), o `Caddyfile` está localizado em
`/etc/frankenphp/Caddyfile`.
O binário estático também procurará pelo `Caddyfile` no diretório onde o comando
`frankenphp run` é executado.
Você pode especificar um caminho personalizado com a opção `-c` ou `--config`.
O próprio PHP pode ser configurado
[usando um arquivo `php.ini`](https://www.php.net/manual/pt_BR/configuration.file.php).
Dependendo do seu método de instalação, o interpretador PHP procurará por
arquivos de configuração nos locais descritos acima.
## Docker
- `php.ini`: `/usr/local/etc/php/php.ini` (nenhum `php.ini` é fornecido por
padrão);
- Arquivos de configuração adicionais: `/usr/local/etc/php/conf.d/*.ini`;
- Extensões PHP: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`;
- Você deve copiar um template oficial fornecido pelo projeto PHP:
```dockerfile
FROM dunglas/frankenphp
# Produção:
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
# Ou desenvolvimento:
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
```
## Pacotes RPM e Debian
- `php.ini`: `/etc/frankenphp/php.ini` (um arquivo `php.ini` com configurações
de produção é fornecido por padrão);
- Arquivos de configuração adicionais: `/etc/frankenphp/php.d/*.ini`;
- Extensões PHP: `/usr/lib/frankenphp/modules/`.
## Binário estático
- `php.ini`: O diretório no qual `frankenphp run` ou `frankenphp php-server` é
executado e, em seguida, `/etc/frankenphp/php.ini`;
- Arquivos de configuração adicionais: `/etc/frankenphp/php.d/*.ini`;
- Extensões PHP: não podem ser carregadas, empacote-as no próprio binário;
- Copie um dos arquivos `php.ini-production` ou `php.ini-development` fornecidos
[no código-fonte do PHP](https://github.com/php/php-src/).
## Configuração do Caddyfile
As [diretivas HTTP](https://caddyserver.com/docs/caddyfile/concepts#directives)
`php_server` ou `php` podem ser usadas dentro dos blocos de site para servir sua
aplicação PHP.
Exemplo mínimo:
```caddyfile
localhost {
# Habilita compressão (opcional)
encode zstd br gzip
# Executa arquivos PHP no diretório atual e serve assets
php_server
}
```
Você também pode configurar explicitamente o FrankenPHP usando a opção global:
A [opção global](https://caddyserver.com/docs/caddyfile/concepts#global-options)
`frankenphp` pode ser usada para configurar o FrankenPHP.
```caddyfile
{
frankenphp {
num_threads <num_threads> # Define o número de threads PHP a serem iniciadas. Padrão: 2x o número de CPUs disponíveis.
max_threads <num_threads> # Limita o número de threads PHP adicionais que podem ser iniciadas em tempo de execução. Padrão: num_threads. Pode ser definido como 'auto'.
max_wait_time <duracao> # Define o tempo máximo que uma requisição pode esperar por uma thread PHP livre antes de atingir o tempo limite. Padrão: disabled.
php_ini <chave> <valor> # Define uma diretiva php.ini. Pode ser usada várias vezes para definir múltiplas diretivas.
worker {
file <caminho> # Define o caminho para o worker script.
num <num> # Define o número de threads PHP a serem iniciadas, o padrão é 2x o número de CPUs disponíveis.
env <chave> <valor> # Define uma variável de ambiente extra para o valor fornecido. Pode ser especificada mais de uma vez para múltiplas variáveis de ambiente.
watch <caminho> # Define o caminho para monitorar alterações em arquivos. Pode ser especificada mais de uma vez para múltiplos caminhos.
name <nome> # Define o nome do worker, usado em logs e métricas. Padrão: caminho absoluto do arquivo do worker.
max_consecutive_failures <num> # Define o número máximo de falhas consecutivas antes do worker ser considerado inoperante. -1 significa que o worker sempre reiniciará. Padrão: 6.
}
}
}
# ...
```
Alternativamente, você pode usar a forma abreviada de uma linha da opção
`worker`:
```caddyfile
{
frankenphp {
worker <arquivo> <num>
}
}
# ...
```
Você também pode definir vários workers se servir várias aplicações no mesmo
servidor:
```caddyfile
app.example.com {
root /caminho/para/aplicacao/public
php_server {
root /caminho/para/aplicacao/public # permite melhor armazenamento em cache
worker index.php <num>
}
}
outra.example.com {
root /caminho/para/outra/aplicacao/public
php_server {
root /caminho/para/outra/aplicacao/public
worker index.php <num>
}
}
# ...
```
Usar a diretiva `php_server` geralmente é o que você precisa, mas se precisar de
controle total, você pode usar a diretiva `php` de mais baixo nível.
A diretiva `php` passa toda a entrada para o PHP, em vez de primeiro verificar
se é um arquivo PHP ou não.
Leia mais sobre isso na [página de desempenho](performance.md#try_files).
Usar a diretiva `php_server` é equivalente a esta configuração:
```caddyfile
route {
# Adiciona barra final para requisições de diretório
@canonicalPath {
file {path}/index.php
not path */
}
redir @canonicalPath {path}/ 308
# Se o arquivo requisitado não existir, tenta os arquivos index
@indexFiles file {
try_files {path} {path}/index.php index.php
split_path .php
}
rewrite @indexFiles {http.matchers.file.relative}
# FrankenPHP!
@phpFiles path *.php
php @phpFiles
file_server
}
```
As diretivas `php_server` e `php` têm as seguintes opções:
```caddyfile
php_server [<matcher>] {
root <directory> # Define a pasta raiz para o site. Padrão: diretiva `root`.
split_path <delim...> # Define as substrings para dividir o URI em duas partes. A primeira substring correspondente será usada para separar as "informações de caminho" do caminho. A primeira parte é sufixada com a substring correspondente e será assumida como o nome real do recurso (script CGI). A segunda parte será definida como PATH_INFO para o script usar. Padrão: `.php`
resolve_root_symlink false # Desabilita a resolução do diretório `root` para seu valor real avaliando um link simbólico, se houver (habilitado por padrão).
env <chave> <valor> # Define uma variável de ambiente extra para o valor fornecido. Pode ser especificada mais de uma vez para múltiplas variáveis de ambiente.
file_server off # Desabilita a diretiva interna file_server.
worker { # Cria um worker específico para este servidor. Pode ser especificada mais de uma vez para múltiplos workers.
file <caminho> # Define o caminho para o worker script, pode ser relativo à raiz do php_server.
num <num> # Define o número de threads PHP a serem iniciadas, o padrão é 2x o número de threads disponíveis.
name <nome> # Define o nome do worker, usado em logs e métricas. Padrão: caminho absoluto do arquivo do worker. Sempre começa com m# quando definido em um bloco php_server.
watch <caminho> # Define o caminho para monitorar alterações em arquivos. Pode ser especificada mais de uma vez para múltiplos caminhos.
env <chave> <valor> # Define uma variável de ambiente extra para o valor fornecido. Pode ser especificada mais de uma vez para múltiplas variáveis de ambiente. As variáveis de ambiente para este worker também são herdadas do pai do php_server, mas podem ser sobrescritas aqui.
match <caminho> # Corresponde o worker a um padrão de caminho. Substitui try_files e só pode ser usada na diretiva php_server.
}
worker <outro_arquivo> <num> # Também pode usar a forma abreviada, como no bloco global frankenphp.
}
```
### Monitorando alterações em arquivos
Como os workers inicializam sua aplicação apenas uma vez e a mantêm na memória,
quaisquer alterações nos seus arquivos PHP não serão refletidas imediatamente.
Os workers podem ser reiniciados em caso de alterações em arquivos por meio da
diretiva `watch`.
Isso é útil para ambientes de desenvolvimento.
```caddyfile
{
frankenphp {
worker {
file /caminho/para/aplicacao/public/worker.php
watch
}
}
}
```
Se o diretório `watch` não for especificado, ele usará o valor padrão
`./**/*.{php,yaml,yml,twig,env}`,
que monitora todos os arquivos `.php`, `.yaml`, `.yml`, `.twig` e `.env` no
diretório e subdiretórios onde o processo FrankenPHP foi iniciado.
Você também pode especificar um ou mais diretórios por meio de um
[padrão de nome de arquivo shell](https://pkg.go.dev/path/filepath#Match):
```caddyfile
{
frankenphp {
worker {
file /caminho/para/aplicacao/public/worker.php
watch /caminho/para/aplicacao # monitora todos os arquivos em todos os subdiretórios de /caminho/para/aplicacao
watch /caminho/para/aplicacao/*.php # monitora arquivos terminados em .php em /caminho/para/aplicacao
watch /caminho/para/aplicacao/**/*.php # monitora arquivos PHP em /caminho/para/aplicacao e subdiretórios
watch /caminho/para/aplicacao/**/*.{php,twig} # monitora arquivos PHP e Twig em /caminho/para/aplicacao e subdiretórios
}
}
}
```
- O padrão `**` significa monitoramento recursivo
- Diretórios também podem ser relativos (ao local de início do processo
FrankenPHP)
- Se você tiver vários workers definidos, todos eles serão reiniciados quando um
arquivo for alterado
- Tenha cuidado ao monitorar arquivos criados em tempo de execução (como logs),
pois eles podem causar reinicializações indesejadas de workers.
O monitor de arquivos é baseado no
[e-dant/watcher](https://github.com/e-dant/watcher).
## Correspondendo o worker a um caminho
Em aplicações PHP tradicionais, os scripts são sempre colocados no diretório
público.
Isso também se aplica aos worker scripts, que são tratados como qualquer outro
script PHP.
Se você quiser colocar o worker script fora do diretório público, pode fazê-lo
por meio da diretiva `match`.
A diretiva `match` é uma alternativa otimizada ao `try_files`, disponível apenas
dentro do `php_server` e do `php`.
O exemplo a seguir sempre servirá um arquivo no diretório público, se presente,
e, caso contrário, encaminhará a requisição para o worker que corresponde ao
padrão de caminho.
```caddyfile
{
frankenphp {
php_server {
worker {
file /caminho/para/worker.php # arquivo pode estar fora do caminho público
match /api/* # todas as requisições que começam com /api/ serão tratadas por este worker
}
}
}
}
```
### Full Duplex (HTTP/1)
Ao usar HTTP/1.x, pode ser desejável habilitar o modo full-duplex para permitir
a gravação de uma resposta antes que todo o corpo tenha sido lido.
(por exemplo: WebSocket, Server-Sent Events, etc.)
Esta é uma configuração opcional que precisa ser adicionada às opções globais no
`Caddyfile`:
```caddyfile
{
servers {
enable_full_duplex
}
}
```
> [!CAUTION]
>
> Habilitar esta opção pode causar deadlock em clientes HTTP/1.x antigos que não
> suportam full-duplex.
> Isso também pode ser configurado usando a configuração de ambiente
> `CADDY_GLOBAL_OPTIONS`:
```sh
CADDY_GLOBAL_OPTIONS="servers {
enable_full_duplex
}"
```
Você pode encontrar mais informações sobre esta configuração na
[documentação do Caddy](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
## Variáveis de ambiente
As seguintes variáveis de ambiente podem ser usadas para injetar diretivas Caddy
no `Caddyfile` sem modificá-lo:
- `SERVER_NAME`: altera
[os endereços nos quais escutar](https://caddyserver.com/docs/caddyfile/concepts#addresses),
os nomes de host fornecidos também serão usados para o certificado TLS gerado;
- `SERVER_ROOT`: altera o diretório raiz do site, o padrão é `public/`;
- `CADDY_GLOBAL_OPTIONS`: injeta
[opções globais](https://caddyserver.com/docs/caddyfile/options);
- `FRANKENPHP_CONFIG`: injeta a configuração sob a diretiva `frankenphp`.
Quanto às SAPIs FPM e CLI, as variáveis de ambiente são expostas por padrão na
superglobal `$_SERVER`.
O valor `S` da
[diretiva `variables_order` do PHP](https://www.php.net/manual/pt_BR/ini.core.php#ini.variables-order)
é sempre equivalente a `ES`, independentemente da colocação de `E` em outra
parte desta diretiva.
## Configuração do PHP
Para carregar
[arquivos de configuração adicionais do PHP](https://www.php.net/manual/pt_BR/configuration.file.php#configuration.file.scan),
a variável de ambiente `PHP_INI_SCAN_DIR` pode ser usada.
Quando definida, o PHP carregará todos os arquivos com a extensão `.ini`
presentes nos diretórios fornecidos.
Você também pode alterar a configuração do PHP usando a diretiva `php_ini` no
`Caddyfile`:
```caddyfile
{
frankenphp {
php_ini memory_limit 256M
# ou
php_ini {
memory_limit 256M
max_execution_time 15
}
}
}
```
## Habilitar o modo de depuração
Ao usar a imagem Docker, defina a variável de ambiente `CADDY_GLOBAL_OPTIONS`
como `debug` para habilitar o modo de depuração:
```console
docker run -v $PWD:/app/public \
-e CADDY_GLOBAL_OPTIONS=debug \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```

232
docs/pt-br/docker.md Normal file
View File

@@ -0,0 +1,232 @@
# Construindo uma imagem Docker personalizada
[As imagens Docker do FrankenPHP](https://hub.docker.com/r/dunglas/frankenphp)
são baseadas em [imagens oficiais do PHP](https://hub.docker.com/_/php/).
Variantes do Debian e do Alpine Linux são fornecidas para arquiteturas
populares.
Variantes do Debian são recomendadas.
Variantes para PHP 8.2, 8.3 e 8.4 são fornecidas.
As tags seguem este padrão:
`dunglas/frankenphp:<versao-do-frankenphp>-php<versao-do-php>-<so>`.
- `<versao-do-frankenphp>` e `<versao-do-php>` são números de versão do
FrankenPHP e do PHP, respectivamente, variando de maior (ex.: `1`), menor
(ex.: `1.2`) a versões de patch (ex.: `1.2.3`).
- `<so>` é `bookworm` (para Debian Bookworm) ou `alpine` (para a versão estável
mais recente do Alpine).
[Navegue pelas tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
## Como usar as imagens
Crie um `Dockerfile` no seu projeto:
```dockerfile
FROM dunglas/frankenphp
COPY . /app/public
```
Em seguida, execute estes comandos para construir e executar a imagem Docker:
```console
docker build -t minha-app-php .
docker run -it --rm --name minha-app-rodando minha-app-php
```
## Como instalar mais extensões PHP
O script
[`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer)
é fornecido na imagem base.
Adicionar extensões PHP adicionais é simples:
```dockerfile
FROM dunglas/frankenphp
# adicione extensões adicionais aqui:
RUN install-php-extensions \
pdo_mysql \
gd \
intl \
zip \
opcache
```
## Como instalar mais módulos Caddy
O FrankenPHP é construído sobre o Caddy, e todos os
[módulos Caddy](https://caddyserver.com/docs/modules/) podem ser usados com o
FrankenPHP.
A maneira mais fácil de instalar módulos Caddy personalizados é usar o
[xcaddy](https://github.com/caddyserver/xcaddy):
```dockerfile
FROM dunglas/frankenphp:builder AS builder
# Copia o xcaddy para a imagem do builder
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
# O CGO precisa estar habilitado para compilar o FrankenPHP
RUN CGO_ENABLED=1 \
XCADDY_SETCAP=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output /usr/local/bin/frankenphp \
--with github.com/php/frankenphp=./ \
--with github.com/php/frankenphp/caddy=./caddy/ \
--with github.com/dunglas/caddy-cbrotli \
# Mercure e Vulcain estão incluídos na compilação oficial, mas sinta-se
# à vontade para removê-los
--with github.com/dunglas/mercure/caddy \
--with github.com/dunglas/vulcain/caddy
# Adicione módulos Caddy extras aqui
FROM dunglas/frankenphp AS runner
# Substitui o binário oficial pelo que contém seus módulos personalizados
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
```
A imagem `builder` fornecida pelo FrankenPHP contém uma versão compilada da
`libphp`.
[Imagens de builder](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder)
são fornecidas para todas as versões do FrankenPHP e do PHP, tanto para Debian
quanto para Alpine.
> [!TIP]
>
> Se você estiver usando Alpine Linux e Symfony, pode ser necessário
> [aumentar o tamanho padrão da pilha](compile.md#using-xcaddy).
## Habilitando o modo worker por padrão
Defina a variável de ambiente `FRANKENPHP_CONFIG` para iniciar o FrankenPHP com
um worker script:
```dockerfile
FROM dunglas/frankenphp
# ...
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
```
## Usando um volume em desenvolvimento
Para desenvolver facilmente com o FrankenPHP, monte o diretório do seu host que
contém o código-fonte da aplicação como um volume no contêiner Docker:
```console
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty minha-app-php
```
> [!TIP]
>
> A opção `--tty` permite ter logs legíveis por humanos em vez de logs JSON.
Com o Docker Compose:
```yaml
# compose.yaml
services:
php:
image: dunglas/frankenphp
# Descomente a linha a seguir se quiser usar um Dockerfile personalizado
#build: .
# Descomente a linha a seguir se quiser executar isso em um ambiente de
# produção
# restart: always
ports:
- "80:80" # HTTP
- "443:443" # HTTPS
- "443:443/udp" # HTTP/3
volumes:
- ./:/app/public
- caddy_data:/data
- caddy_config:/config
# Comente a linha a seguir em produção, isso permite ter logs legíveis em
# desenvolvimento
tty: true
# Volumes necessários para certificados e configuração do Caddy
volumes:
caddy_data:
caddy_config:
```
## Executando como um usuário não root
O FrankenPHP pode ser executado como um usuário não root no Docker.
Aqui está um exemplo de `Dockerfile` fazendo isso:
```dockerfile
FROM dunglas/frankenphp
ARG USER=appuser
RUN \
# Use "adduser -D ${USER}" para distribuições baseadas em Alpine
useradd ${USER}; \
# Adiciona capacidade adicional para vincular às portas 80 e 443
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
# Concede acesso de escrita a /config/caddy e /data/caddy
chown -R ${USER}:${USER} /config/caddy /data/caddy
USER ${USER}
```
### Executando sem capacidades adicionais
Mesmo executando sem root, o FrankenPHP precisa do recurso
`CAP_NET_BIND_SERVICE` para vincular o servidor web em portas privilegiadas (80
e 443).
Se você expor o FrankenPHP em uma porta não privilegiada (1024 e acima), é
possível executar o servidor web como um usuário não root e sem a necessidade de
nenhuma capacidade adicional:
```dockerfile
FROM dunglas/frankenphp
ARG USER=appuser
RUN \
# Use "adduser -D ${USER}" para distribuições baseadas em Alpine
useradd ${USER}; \
# Remove a capacidade padrão
setcap -r /usr/local/bin/frankenphp; \
# Concede acesso de escrita a /config/caddy e /data/caddy
chown -R ${USER}:${USER} /config/caddy /data/caddy
USER ${USER}
```
Em seguida, defina a variável de ambiente `SERVER_NAME` para usar uma porta sem
privilégios.
Exemplo: `:8000`
## Atualizações
As imagens Docker são construídas:
- Quando uma tag de uma nova versão é criada;
- Diariamente às 4h UTC, se novas versões das imagens oficiais do PHP estiverem
disponíveis.
## Versões de desenvolvimento
As versões de desenvolvimento estão disponíveis no repositório Docker
[`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev).
Uma nova construção é acionada sempre que um commit é enviado para o branch
principal do repositório do GitHub.
As tags `latest*` apontam para o HEAD do branch `main`.
Tags no formato `sha-<git-commit-hash>` também estão disponíveis.

24
docs/pt-br/early-hints.md Normal file
View File

@@ -0,0 +1,24 @@
# Early Hints
O FrankenPHP suporta nativamente o
[código de status 103 Early Hints](https://developer.chrome.com/blog/early-hints/).
Usar Early Hints pode melhorar o tempo de carregamento das suas páginas web em
30%.
```php
<?php
header('Link: </style.css>; rel=preload; as=style');
headers_send(103);
// seus algoritmos e consultas SQL lentos 🤪
echo <<<'HTML'
<!DOCTYPE html>
<title>Olá FrankenPHP</title>
<link rel="stylesheet" href="style.css">
HTML;
```
As Early Hints são suportadas tanto pelo modo normal quanto pelo modo
[worker](worker.md).

166
docs/pt-br/embed.md Normal file
View File

@@ -0,0 +1,166 @@
# Aplicações PHP como binários independentes
O FrankenPHP tem a capacidade de incorporar o código-fonte e os assets de
aplicações PHP em um binário estático e independente.
Graças a esse recurso, aplicações PHP podem ser distribuídas como binários
independentes que incluem a própria aplicação, o interpretador PHP e o Caddy, um
servidor web de nível de produção.
Saiba mais sobre esse recurso
[na apresentação feita por Kévin na SymfonyCon 2023](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/).
Para incorporar aplicações Laravel,
[leia esta entrada específica na documentação](laravel.md#aplicacoes-laravel-como-binarios-independentes).
## Preparando sua aplicação
Antes de criar o binário independente, certifique-se de que sua aplicação esteja
pronta para ser incorporada.
Por exemplo, você provavelmente deseja:
- Instalar as dependências de produção da aplicação;
- Fazer o dump do carregador automático;
- Habilitar o modo de produção da sua aplicação (se houver);
- Remover arquivos desnecessários, como `.git` ou testes, para reduzir o tamanho
do seu binário final.
Por exemplo, para uma aplicação Symfony, você pode usar os seguintes comandos:
```console
# Exporta o projeto para se livrar de .git/, etc.
mkdir $TMPDIR/minha-aplicacao-preparada
git archive HEAD | tar -x -C $TMPDIR/minha-aplicacao-preparada
cd $TMPDIR/minha-aplicacao-preparada
# Define as variáveis de ambiente adequadas
echo APP_ENV=prod > .env.local
echo APP_DEBUG=0 >> .env.local
# Remove os testes e outros arquivos desnecessários para economizar espaço.
# Como alternativa, adicione esses arquivos com o atributo export-ignore no seu
# arquivo .gitattributes.
rm -Rf tests/
# Instala as dependências
composer install --ignore-platform-reqs --no-dev -a
# Otimiza o arquivo .env
composer dump-env prod
```
### Personalizando a configuração
Para personalizar
[a configuração](config.md), você pode colocar um arquivo `Caddyfile` e um
arquivo `php.ini` no diretório principal da aplicação a ser incorporada
(`$TMPDIR/minha-aplicacao-preparada` no exemplo anterior).
## Criando um binário do Linux
A maneira mais fácil de criar um binário do Linux é usar o builder baseado em
Docker que fornecemos.
1. Crie um arquivo chamado `static-build.Dockerfile` no repositório da sua
aplicação:
```dockerfile
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
# Copia sua aplicação
WORKDIR /go/src/app/dist/app
COPY . .
# Compila o binário estático
WORKDIR /go/src/app/
RUN EMBED=dist/app/ ./build-static.sh
```
> [!CAUTION]
>
> Alguns arquivos `.dockerignore` (por exemplo, o
> [`.dockerignore` padrão do Docker do Symfony](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))
> ignorarão o diretório `vendor/` e os arquivos `.env`.
> Certifique-se de ajustar ou remover o arquivo `.dockerignore` antes da
> construção.
2. Construa:
```console
docker build -t aplicacao-estatica -f static-build.Dockerfile .
```
3. Extraia o binário:
```console
docker cp $(docker create --name aplicacao-estatica-tmp aplicacao-estatica):/go/src/app/dist/frankenphp-linux-x86_64 minha-aplicacao ; docker rm aplicacao-estatica-tmp
```
O binário resultante é o arquivo `minha-aplicacao` no diretório atual.
## Criando um binário para outros sistemas operacionais
Se você não quiser usar o Docker ou quiser compilar um binário para macOS, use o
script de shell que fornecemos:
```console
git clone https://github.com/php/frankenphp
cd frankenphp
EMBED=/caminho/para/sua/aplicacao ./build-static.sh
```
O binário resultante é o arquivo `frankenphp-<os>-<arch>` no diretório `dist/`.
## Usando o binário
É isso! O arquivo `minha-aplicacao` (ou `dist/frankenphp-<os>-<arch>` em outros
sistemas operacionais) contém sua aplicação independente!
Para iniciar a aplicação web, execute:
```console
./minha-aplicacao php-server
```
Se a sua aplicação contiver um [worker script](worker.md), inicie o worker com
algo como:
```console
./minha-aplicacao php-server --worker public/index.php
```
Para habilitar HTTPS (um certificado Let's Encrypt é criado automaticamente),
HTTP/2 e HTTP/3, especifique o nome de domínio a ser usado:
```console
./minha-aplicacao php-server --domain localhost
```
Você também pode executar os scripts PHP CLI incorporados ao seu binário:
```console
./minha-aplicacao php-cli bin/console
```
## Extensões PHP
Por padrão, o script compilará as extensões requeridas pelo arquivo
`composer.json` do seu projeto, se houver.
Se o arquivo `composer.json` não existir, as extensões padrão serão compiladas,
conforme documentado na [entrada de compilações estáticas](static.md).
Para personalizar as extensões, use a variável de ambiente `PHP_EXTENSIONS`.
## Personalizando a compilação
[Leia a documentação da compilação estática](static.md) para ver como
personalizar o binário (extensões, versão do PHP...).
## Distribuindo o binário
No Linux, o binário criado é compactado usando [UPX](https://upx.github.io).
No Mac, para reduzir o tamanho do arquivo antes de enviá-lo, você pode
compactá-lo.
Recomendamos usar `xz`.

1002
docs/pt-br/extensions.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
# Usando GitHub Actions
Este repositório constrói e implanta a imagem Docker no
[Docker Hub](https://hub.docker.com/r/dunglas/frankenphp) a cada pull request
aprovado ou em seu próprio fork após a configuração.
## Configurando GitHub Actions
Nas configurações do repositório, em "Secrets", adicione os seguintes segredos:
- `REGISTRY_LOGIN_SERVER`: O registro do Docker a ser usado (por exemplo,
`docker.io`).
- `REGISTRY_USERNAME`: O nome de usuário a ser usado para fazer login no
registro (por exemplo, `dunglas`).
- `REGISTRY_PASSWORD`: A senha a ser usada para fazer login no registro (por
exemplo, uma chave de acesso).
- `IMAGE_NAME`: O nome da imagem (por exemplo, `dunglas/frankenphp`).
## Construindo e enviando a imagem
1. Crie um pull request ou faça o push para o seu fork.
2. O GitHub Actions construirá a imagem e executará os testes.
3. Se a construção for bem-sucedida, a imagem será enviada para o registro
usando a tag `pr-x`, onde `x` é o número do PR.
## Implantando a imagem
1. Após o merge do pull request, o GitHub Actions executará os testes novamente
e criará uma nova imagem.
2. Se a construção for bem-sucedida, a tag `main` será atualizada no registro do
Docker.
## Versões
1. Crie uma nova tag no repositório.
2. O GitHub Actions construirá a imagem e executará os testes.
3. Se a construção for bem-sucedida, a imagem será enviada para o registro
usando o nome da tag como tag (por exemplo, `v1.2.3` e `v1.2` serão criadas).
4. A tag `latest` também será atualizada.

185
docs/pt-br/known-issues.md Normal file
View File

@@ -0,0 +1,185 @@
# Problemas conhecidos
## Extensões PHP não suportadas
As seguintes extensões são conhecidas por não serem compatíveis com o
FrankenPHP:
| Nome | Motivo | Alternativas |
| ----------------------------------------------------------------------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------- |
| [imap](https://www.php.net/manual/pt_BR/imap.installation.php) | Não é thread-safe | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | Não é thread-safe | - |
## Extensões PHP com falhas
As seguintes extensões apresentam falhas conhecidas e comportamentos inesperados
quando usadas com o FrankenPHP:
| Nome | Problema |
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [ext-openssl](https://www.php.net/manual/pt_BR/book.openssl.php) | Ao usar uma versão estática do FrankenPHP (compilada com a `libc` `musl`), a extensão OpenSSL pode quebrar sob cargas pesadas. Uma solução alternativa é usar uma versão vinculada dinamicamente (como a usada em imagens Docker). Esta falha está [sendo monitorada pelo PHP](https://github.com/php/php-src/issues/13648) |
## `get_browser`
A função
[`get_browser()`](https://www.php.net/manual/pt_BR/function.get-browser.php)
parece apresentar mau desempenho após algum tempo.
Uma solução alternativa é armazenar em cache (por exemplo, com
[APCu](https://www.php.net/manual/pt_BR/book.apcu.php)) os resultados por Agente
de Usuário, pois são estáticos.
## Imagens Docker binárias independentes e baseadas em Alpine
As imagens Docker binárias independentes e baseadas em Alpine
(`dunglas/frankenphp:*-alpine`) usam a [`libc` `musl`](https://musl.libc.org/)
em vez de [`glibc` e similares](https://www.etalabs.net/compare_libcs.html) para
manter um tamanho binário menor.
Isso pode levar a alguns problemas de compatibilidade.
Em particular, o sinalizador glob `GLOB_BRACE`
[não está disponível](https://www.php.net/manual/pt_BR/function.glob.php)
## Usando `https://127.0.0.1` com o Docker
Por padrão, o FrankenPHP gera um certificado TLS para `localhost`.
É a opção mais fácil e recomendada para desenvolvimento local.
Se você realmente deseja usar `127.0.0.1` como host, é possível configurá-lo
para gerar um certificado definindo o nome do servidor como `127.0.0.1`.
Infelizmente, isso não é suficiente ao usar o Docker devido ao
[seu sistema de rede](https://docs.docker.com/network/).
Você receberá um erro TLS semelhante a
`curl: (35) LibreSSL/3.3.6: erro:1404B438:SSL routines:ST_CONNECT:tlsv1 alert internal error`.
Se você estiver usando Linux, uma solução é usar
[o driver de rede do host](https://docs.docker.com/network/network-tutorial-host/):
```console
docker run \
-e SERVER_NAME="127.0.0.1" \
-v $PWD:/app/public \
--network host \
dunglas/frankenphp
```
O driver de rede do host não é compatível com Mac e Windows.
Nessas plataformas, você terá que descobrir o endereço IP do contêiner e
incluí-lo nos nomes dos servidores.
Execute o comando `docker network inspect bridge` e verifique a chave
`Containers` para identificar o último endereço IP atribuído atualmente sob a
chave `IPv4Address` e incremente-o em um.
Se nenhum contêiner estiver em execução, o primeiro endereço IP atribuído
geralmente é `172.17.0.2`.
Em seguida, inclua isso na variável de ambiente `SERVER_NAME`:
```console
docker run \
-e SERVER_NAME="127.0.0.1, 172.17.0.3" \
-v $PWD:/app/public \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
> [!CAUTION]
>
> Certifique-se de substituir `172.17.0.3` pelo IP que será atribuído ao seu
> contêiner.
Agora você deve conseguir acessar `https://127.0.0.1` a partir da máquina host.
Se este não for o caso, inicie o FrankenPHP em modo de depuração para tentar
descobrir o problema:
```console
docker run \
-e CADDY_GLOBAL_OPTIONS="debug" \
-e SERVER_NAME="127.0.0.1" \
-v $PWD:/app/public \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
## Scripts do Composer que referenciam `@php`
[Scripts do Composer](https://getcomposer.org/doc/articles/scripts.md) podem
querer executar um binário PHP para algumas tarefas, por exemplo, em
[um projeto Laravel](laravel.md) para executar
`@php artisan package:discover --ansi`.
Isso
[atualmente falha](https://github.com/php/frankenphp/issues/483#issuecomment-1899890915)
por dois motivos:
- O Composer não sabe como chamar o binário do FrankenPHP;
- O Composer pode adicionar configurações do PHP usando a flag `-d` no comando,
que o FrankenPHP ainda não suporta.
Como solução alternativa, podemos criar um script de shell em
`/usr/local/bin/php` que remove os parâmetros não suportados e, em seguida,
chama o FrankenPHP:
```bash
#!/usr/bin/env bash
args=("$@")
index=0
for i in "$@"
do
if [ "$i" == "-d" ]; then
unset 'args[$index]'
unset 'args[$index+1]'
fi
index=$((index+1))
done
/usr/local/bin/frankenphp php-cli ${args[@]}
```
Em seguida, defina a variável de ambiente `PHP_BINARY` para o caminho do nosso
script `php` e execute o Composer:
```console
export PHP_BINARY=/usr/local/bin/php
composer install
```
## Solução de problemas de TLS/SSL com binários estáticos
Ao usar binários estáticos, você pode encontrar os seguintes erros relacionados
a TLS, por exemplo, ao enviar emails usando STARTTLS:
```text
Unable to connect with STARTTLS: stream_socket_enable_crypto(): SSL operation failed with code 5. OpenSSL Error messages:
error:80000002:system library::No such file or directory
error:80000002:system library::No such file or directory
error:80000002:system library::No such file or directory
error:0A000086:SSL routines::certificate verify failed
```
Como o binário estático não empacota certificados TLS, você precisa apontar o
OpenSSL para a instalação local de certificados de CA.
Inspecione a saída de
[`openssl_get_cert_locations()`](https://www.php.net/manual/pt_BR/function.openssl-get-cert-locations.php),
para descobrir onde os certificados de CA devem ser instalados e armazene-os
neste local.
> [!WARNING]
>
> Contextos web e CLI podem ter configurações diferentes.
> Certifique-se de executar `openssl_get_cert_locations()` no contexto
> apropriado.
[Certificados CA extraídos do Mozilla podem ser baixados no site do cURL](https://curl.se/docs/caextract.html).
Como alternativa, muitas distribuições, incluindo Debian, Ubuntu e Alpine,
fornecem pacotes chamados `ca-certificates` que contêm esses certificados.
Também é possível usar `SSL_CERT_FILE` e `SSL_CERT_DIR` para indicar à OpenSSL
onde procurar certificados CA:
```console
# Define variáveis de ambiente para certificados TLS
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
export SSL_CERT_DIR=/etc/ssl/certs
```

216
docs/pt-br/laravel.md Normal file
View File

@@ -0,0 +1,216 @@
# Laravel
## Docker
Servir uma aplicação web [Laravel](https://laravel.com) com FrankenPHP é tão
fácil quanto montar o projeto no diretório `/app` da imagem Docker oficial.
Execute este comando a partir do diretório principal da sua aplicação Laravel:
```console
docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
```
E divirta-se!
## Instalação local
Alternativamente, você pode executar seus projetos Laravel com FrankenPHP a
partir da sua máquina local:
1. [Baixe o binário correspondente ao seu sistema](../#standalone-binary).
2. Adicione a seguinte configuração a um arquivo chamado `Caddyfile` no
diretório raiz do seu projeto Laravel:
```caddyfile
{
frankenphp
}
# O nome de domínio do seu servidor
localhost {
# Define o diretório raiz como public/
root public/
# Habilita a compressão (opcional)
encode zstd br gzip
# Executa os arquivos PHP a partir do diretório public/ e serve os assets
php_server {
try_files {path} index.php
}
}
```
3. Inicie o FrankenPHP a partir do diretório raiz do seu projeto Laravel:
`frankenphp run`.
## Laravel Octane
O Octane pode ser instalado através do gerenciador de pacotes Composer:
```console
composer require laravel/octane
```
Após instalar o Octane, você pode executar o comando `octane:install` do
Artisan, que instalará o arquivo de configuração do Octane em sua aplicação:
```console
php artisan octane:install --server=frankenphp
```
O servidor Octane pode ser iniciado por meio do comando `octane:frankenphp` do
Artisan.
```console
php artisan octane:frankenphp
```
O comando `octane:frankenphp` pode receber as seguintes opções:
- `--host`: O endereço IP ao qual o servidor deve se vincular (padrão:
`127.0.0.1`);
- `--port`: A porta na qual o servidor deve estar disponível (padrão: `8000`);
- `--admin-port`: A porta na qual o servidor de administração deve estar
disponível (padrão: `2019`);
- `--workers`: O número de workers que devem estar disponíveis para processar
requisições (padrão: `auto`);
- `--max-requests`: O número de requisições a serem processadas antes de
recarregar o servidor (padrão: `500`);
- `--caddyfile`: O caminho para o arquivo `Caddyfile` do FrankenPHP (padrão:
[stub do `Caddyfile` no Laravel Octane](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile));
- `--https`: Habilita HTTPS, HTTP/2 e HTTP/3 e gera e renova certificados
automaticamente;
- `--http-redirect`: Habilita o redirecionamento de HTTP para HTTPS (somente
- habilitado se `--https` for passada);
- `--watch`: Recarrega o servidor automaticamente quando a aplicação é
modificada;
- `--poll`: Usa o polling do sistema de arquivos durante a verificação para
monitorar arquivos em uma rede;
- `--log-level`: Registra mensagens de log no nível de log especificado ou acima
dele, usando o logger nativo do Caddy.
> [!TIP]
> Para obter logs JSON estruturados (útil ao usar soluções de análise de logs),
> passe explicitamente a opção `--log-level`.
Saiba mais sobre o
[Laravel Octane em sua documentação oficial](https://laravel.com/docs/octane).
## Aplicações Laravel como binários independentes
Usando o [recurso de incorporação de aplicações do FrankenPHP](embed.md), é
possível distribuir aplicações Laravel como binários independentes.
Siga estes passos para empacotar sua aplicação Laravel como um binário
independente para Linux:
1. Crie um arquivo chamado `static-build.Dockerfile` no repositório da sua
aplicação:
```dockerfile
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
# Copia sua aplicação
WORKDIR /go/src/app/dist/app
COPY . .
# Remove os testes e outros arquivos desnecessários para economizar espaço
# Como alternativa, adicione esses arquivos a um arquivo .dockerignore
RUN rm -Rf tests/
# Copia o arquivo .env
RUN cp .env.example .env
# Altera APP_ENV e APP_DEBUG para que estejam prontas para produção
RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env
# Faça outras alterações no seu arquivo .env, se necessário
# Instala as dependências
RUN composer install --ignore-platform-reqs --no-dev -a
# Compila o binário estático
WORKDIR /go/src/app/
RUN EMBED=dist/app/ ./build-static.sh
```
> [!CAUTION]
>
> Alguns arquivos `.dockerignore` ignorarão o diretório `vendor/` e os
> arquivos `.env`.
> Certifique-se de ajustar ou remover o arquivo `.dockerignore` antes da
> compilação.
2. Construa:
```console
docker build -t static-laravel-app -f static-build.Dockerfile .
```
3. Extraia o binário:
```console
docker cp $(docker create --name static-laravel-app-tmp static-laravel-app):/go/src/app/dist/frankenphp-linux-x86_64 frankenphp ; docker rm static-laravel-app-tmp
```
4. Popule os caches:
```console
frankenphp php-cli artisan optimize
```
5. Execute as migrações de banco de dados (se houver):
```console
frankenphp php-cli artisan migrate
```
6. Gere a chave secreta da aplicação:
```console
frankenphp php-cli artisan key:generate
```
7. Inicie o servidor:
```console
frankenphp php-server
```
Agora sua aplicação está pronta!
Saiba mais sobre as opções disponíveis e como compilar binários para outros
sistemas operacionais na documentação de
[incorporação de aplicações](embed.md).
### Alterando o caminho do armazenamento
Por padrão, o Laravel armazena arquivos enviados, caches, logs, etc., no
diretório `storage/` da aplicação.
Isso não é adequado para aplicações embarcadas, pois cada nova versão será
extraída para um diretório temporário diferente.
Defina a variável de ambiente `LARAVEL_STORAGE_PATH` (por exemplo, no seu
arquivo `.env`) ou chame o método
`Illuminate\Foundation\Application::useStoragePath()` para usar um diretório
fora do diretório temporário.
### Executando o Octane com binários independentes
É possível até empacotar aplicações Octane do Laravel como binários
independentes!
Para fazer isso, [instale o Octane corretamente](#laravel-octane) e siga os
passos descritos na
[seção anterior](#aplicações-laravel-como-binários-independentes).
Em seguida, para iniciar o FrankenPHP no modo worker através do Octane, execute:
```console
PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp
```
> [!CAUTION]
>
> Para que o comando funcione, o binário independente **deve** ser nomeado
> `frankenphp` porque o Octane precisa de um programa chamado `frankenphp`
> disponível no caminho.

21
docs/pt-br/mercure.md Normal file
View File

@@ -0,0 +1,21 @@
# Tempo real
O FrankenPHP vem com um hub [Mercure](https://mercure.rocks) integrado!
O Mercure permite que você envie eventos em tempo real para todos os
dispositivos conectados: eles receberão um evento JavaScript instantaneamente.
Não é necessária nenhuma biblioteca JS ou SDK!
![Mercure](mercure-hub.png)
Para habilitar o hub Mercure, atualize o `Caddyfile` conforme descrito
[no site do Mercure](https://mercure.rocks/docs/hub/config).
O caminho do hub Mercure é `/.well-known/mercure`.
Ao executar o FrankenPHP dentro do Docker, a URL de envio completa seria
`http://php/.well-known/mercure` (com `php` sendo o nome do contêiner que
executa o FrankenPHP).
Para enviar atualizações do Mercure a partir do seu código, recomendamos o
[Componente Symfony Mercure](https://symfony.com/components/Mercure) (você não
precisa do framework full-stack do Symfony para usá-lo).

29
docs/pt-br/metrics.md Normal file
View File

@@ -0,0 +1,29 @@
# Métricas
Quando as [métricas do Caddy](https://caddyserver.com/docs/metrics) estão
habilitadas, o FrankenPHP expõe as seguintes métricas:
- `frankenphp_total_threads`: O número total de threads PHP.
- `frankenphp_busy_threads`: O número de threads PHP processando uma requisição
no momento (workers em execução sempre consomem uma thread).
- `frankenphp_queue_depth`: O número de requisições regulares na fila.
- `frankenphp_total_workers{worker="[nome_do_worker]"}`: O número total de
workers.
- `frankenphp_busy_workers{worker="[nome_do_worker]"}`: O número de workers
processando uma requisição no momento.
- `frankenphp_worker_request_time{worker="[nome_do_worker]"}`: O tempo gasto no
processamento de requisições por todos os workers.
- `frankenphp_worker_request_count{worker="[nome_do_worker]"}`: O número de
requisições processadas por todos os workers.
- `frankenphp_ready_workers{worker="[nome_do_worker]"}`: O número de workers que
chamaram `frankenphp_handle_request` pelo menos uma vez.
- `frankenphp_worker_crashes{worker="[nome_do_worker]"}`: O número de vezes que
um worker foi encerrado inesperadamente.
- `frankenphp_worker_restarts{worker="[nome_do_worker]"}`: O número de vezes que
um worker foi reiniciado deliberadamente.
- `frankenphp_worker_queue_depth{worker="[nome_do_worker]"}`: O número de
requisições na fila.
Para métricas de worker, o placeholder `[nome_do_worker]` é substituído pelo
nome do worker no Caddyfile; caso contrário, o caminho absoluto do arquivo do
worker será usado.

215
docs/pt-br/performance.md Normal file
View File

@@ -0,0 +1,215 @@
# Desempenho
Por padrão, o FrankenPHP tenta oferecer um bom equilíbrio entre desempenho e
facilidade de uso.
No entanto, é possível melhorar substancialmente o desempenho usando uma
configuração apropriada.
## Número de threads e workers
Por padrão, o FrankenPHP inicia 2 vezes mais threads e workers (no modo worker)
do que a quantidade de CPU disponível.
Os valores apropriados dependem muito de como sua aplicação foi escrita, do que
ela faz e do seu hardware.
Recomendamos fortemente alterar esses valores.
Para melhor estabilidade do sistema, recomenda-se ter `num_threads` x
`memory_limit` < `available_memory`.
Para encontrar os valores corretos, é melhor executar testes de carga simulando
tráfego real.
[k6](https://k6.io) e [Gatling](https://gatling.io) são boas ferramentas para
isso.
Para configurar o número de threads, use a opção `num_threads` das diretivas
`php_server` e `php`.
Para alterar o número de workers, use a opção `num` da seção `worker` da
diretiva `frankenphp`.
### `max_threads`
Embora seja sempre melhor saber exatamente como será o seu tráfego, aplicações
reais tendem a ser mais imprevisíveis.
A [configuração](config.md#configuracao-do-caddyfile) `max_threads` permite que
o FrankenPHP crie threads adicionais automaticamente em tempo de execução até o
limite especificado.
`max_threads` pode ajudar você a descobrir quantas threads são necessárias para
lidar com seu tráfego e pode tornar o servidor mais resiliente a picos de
latência.
Se definido como `auto`, o limite será estimado com base no `memory_limit` em
seu `php.ini`.
Caso contrário, `auto` assumirá como padrão o valor 2x `num_threads`.
Lembre-se de que `auto` pode subestimar bastante o número de threads
necessárias.
`max_threads` é semelhante ao
[pm.max_children](https://www.php.net/manual/pt_BR/install.fpm.configuration.php#pm.max-children)
do PHP FPM.
A principal diferença é que o FrankenPHP usa threads em vez de processos e as
delega automaticamente entre diferentes worker scripts e o modo clássico,
conforme necessário.
## Modo worker
Habilitar [o modo worker](worker.md) melhora drasticamente o desempenho, mas sua
aplicação precisa ser adaptada para ser compatível com este modo: você precisa
criar um worker script e garantir que a aplicação não esteja com vazamento de
memória.
## Não use `musl`
A variante Alpine Linux das imagens oficiais do Docker e os binários padrão que
fornecemos usam [a biblioteca C `musl`](https://musl.libc.org).
O PHP é conhecido por ser
[mais lento](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381)
ao usar esta biblioteca C alternativa em vez da biblioteca GNU tradicional,
especialmente quando compilado no modo ZTS (thread-safe), necessário para o
FrankenPHP.
A diferença pode ser significativa em um ambiente com muitas threads.
Além disso,
[alguns bugs só acontecem ao usar `musl`](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl).
Em ambientes de produção, recomendamos o uso do FrankenPHP vinculado à `glibc`.
Isso pode ser feito usando as imagens Docker do Debian (o padrão), baixando o
binário com sufixo -gnu de nossos
[Lançamentos](https://github.com/php/frankenphp/releases) ou
[compilando o FrankenPHP a partir do código-fonte](compile.md).
Como alternativa, fornecemos binários `musl` estáticos compilados com
[o alocador `mimalloc`](https://github.com/microsoft/mimalloc), o que alivia os
problemas em cenários com threads.
## Configuração do runtime do Go
O FrankenPHP é escrito em Go.
Em geral, o runtime do Go não requer nenhuma configuração especial, mas em
certas circunstâncias, configurações específicas melhoram o desempenho.
Você provavelmente deseja definir a variável de ambiente `GODEBUG` como
`cgocheck=0` (o padrão nas imagens Docker do FrankenPHP).
Se você executa o FrankenPHP em contêineres (Docker, Kubernetes, LXC...) e
limita a memória disponível para os contêineres, defina a variável de ambiente
`GOMEMLIMIT` para a quantidade de memória disponível.
Para mais detalhes,
[a página da documentação do Go dedicada a este assunto](https://pkg.go.dev/runtime#hdr-Environment_Variables)
é uma leitura obrigatória para aproveitar ao máximo o runtime.
## `file_server`
Por padrão, a diretiva `php_server` configura automaticamente um servidor de
arquivos para servir arquivos estáticos (assets) armazenados no diretório raiz.
Este recurso é conveniente, mas tem um custo.
Para desativá-lo, use a seguinte configuração:
```caddyfile
php_server {
file_server off
}
```
## `try_files`
Além de arquivos estáticos e arquivos PHP, `php_server` também tentará servir o
arquivo index da sua aplicação e os arquivos index de diretório (`/path/` ->
`/path/index.php`).
Se você não precisa de arquivos index de diretório, pode desativá-los definindo
explicitamente `try_files` assim:
```caddyfile
php_server {
try_files {path} index.php
root /raiz/da/sua/aplicacao # adicionar explicitamente a raiz aqui permite um melhor armazenamento em cache
}
```
Isso pode reduzir significativamente o número de operações desnecessárias com
arquivos.
Uma abordagem alternativa com 0 operações desnecessárias no sistema de arquivos
seria usar a diretiva `php` e separar os arquivos estáticos do PHP usando
caminhos.
Essa abordagem funciona bem se toda a sua aplicação for servida por um arquivo
de entrada.
Um exemplo de [configuração](config.md#configuracao-do-caddyfile) que serve
arquivos estáticos a partir de uma pasta `/assets` poderia ser assim:
```caddyfile
route {
@assets {
path /assets/*
}
# tudo a partir de /assets é gerenciado pelo servidor de arquivos
file_server @assets {
root /raiz/da/sua/aplicacao
}
# tudo o que não está em /assets é gerenciado pelo seu arquivo index ou worker PHP
rewrite index.php
php {
root /raiz/da/sua/aplicacao # adicionar explicitamente a raiz aqui permite um melhor armazenamento em cache
}
}
```
## Placeholders
Você pode usar
[placeholders](https://caddyserver.com/docs/conventions#placeholders) nas
diretivas `root` e `env`.
No entanto, isso impede o armazenamento em cache desses valores e acarreta um
custo significativo de desempenho.
Se possível, evite placeholders nessas diretivas.
## `resolve_root_symlink`
Por padrão, se o diretório raiz for um link simbólico, ele será resolvido
automaticamente pelo FrankenPHP (isso é necessário para o funcionamento correto
do PHP).
Se o diretório raiz não for um link simbólico, você pode desativar esse recurso.
```caddyfile
php_server {
resolve_root_symlink false
}
```
Isso melhorará o desempenho se a diretiva `root` contiver
[placeholders](https://caddyserver.com/docs/conventions#placeholders).
O ganho será insignificante em outros casos.
## Logs
O logging é obviamente muito útil, mas, por definição, requer operações de E/S e
alocações de memória, o que reduz consideravelmente o desempenho.
Certifique-se de
[definir o nível de logging](https://caddyserver.com/docs/caddyfile/options#log)
corretamente e registrar em log apenas o necessário.
## Desempenho do PHP
O FrankenPHP usa o interpretador PHP oficial.
Todas as otimizações de desempenho usuais relacionadas ao PHP se aplicam ao
FrankenPHP.
Em particular:
- Verifique se o [OPcache](https://www.php.net/manual/pt_BR/book.opcache.php)
está instalado, habilitado e configurado corretamente;
- Habilite as
[otimizações do carregador automático do Composer](https://getcomposer.org/doc/articles/autoloader-optimization.md);
- Certifique-se de que o cache do `realpath` seja grande o suficiente para as
necessidades da sua aplicação;
- Use
[pré-carregamento](https://www.php.net/manual/pt_BR/opcache.preloading.php).
Para mais detalhes, leia
[a entrada dedicada na documentação do Symfony](https://symfony.com/doc/current/performance.html)
(a maioria das dicas é útil mesmo se você não usa o Symfony).

174
docs/pt-br/production.md Normal file
View File

@@ -0,0 +1,174 @@
# Implantando em produção
Neste tutorial, aprenderemos como implantar uma aplicação PHP em um único
servidor usando o Docker Compose.
Se você estiver usando o Symfony, leia a documentação
[Implantar em produção](https://github.com/dunglas/symfony-docker/blob/main/docs/production.md)
do projeto Docker do Symfony (que usa FrankenPHP).
Se você estiver usando a API Platform (que também usa FrankenPHP), consulte
[a documentação de implantação do framework](https://api-platform.com/docs/deployment/).
## Preparando sua aplicação
Primeiro, crie um `Dockerfile` no diretório raiz do seu projeto PHP:
```dockerfile
FROM dunglas/frankenphp
# Certifique-se de substituir "seu-nome-de-dominio.example.com" pelo seu nome de
# domínio
ENV SERVER_NAME=seu-nome-de-dominio.example.com
# Se quiser desabilitar o HTTPS, use este valor:
#ENV SERVER_NAME=:80
# Se o seu projeto não estiver usando o diretório "public" como diretório raiz,
# você pode defini-lo aqui:
# ENV SERVER_ROOT=web/
# Habilita as configurações de produção do PHP
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# Copia os arquivos PHP do seu projeto para o diretório public
COPY . /app/public
# Se você usa Symfony ou Laravel, precisa copiar o projeto inteiro:
#COPY . /app
```
Consulte [Construindo uma imagem Docker personalizada](docker.md) para mais
detalhes e opções, e para aprender como personalizar a configuração, instalar
extensões PHP e módulos Caddy.
Se o seu projeto usa o Composer, certifique-se de incluí-lo na imagem Docker e
instalar suas dependências.
Em seguida, adicione um arquivo `compose.yaml`:
```yaml
services:
php:
image: dunglas/frankenphp
restart: always
ports:
- "80:80" # HTTP
- "443:443" # HTTPS
- "443:443/udp" # HTTP/3
volumes:
- caddy_data:/data
- caddy_config:/config
# Volumes necessários para certificados e configuração do Caddy
volumes:
caddy_data:
caddy_config:
```
> [!NOTE]
>
> Os exemplos anteriores são destinados ao uso em produção.
> Em desenvolvimento, você pode querer usar um volume, uma configuração PHP
> diferente e um valor diferente para a variável de ambiente `SERVER_NAME`.
>
> Consulte o projeto [Symfony Docker](https://github.com/dunglas/symfony-docker)
> (que usa FrankenPHP) para um exemplo mais avançado usando imagens
> multiestágio, Composer, extensões PHP extras, etc.
Finalmente, se você usa Git, faça o commit e o push desses arquivos.
## Preparando um servidor
Para implantar sua aplicação em produção, você precisa de um servidor.
Neste tutorial, usaremos uma máquina virtual fornecida pela DigitalOcean, mas
qualquer servidor Linux pode ser usado.
Se você já possui um servidor Linux com o Docker instalado, pode pular direto
para [a próxima seção](#configurando-um-nome-de-domínio).
Caso contrário, use [este link de afiliado](https://m.do.co/c/5d8aabe3ab80) para
obter US$ 200 em créditos gratuitos, crie uma conta e clique em "Create a
Droplet".
Em seguida, clique na aba "Marketplace" na seção "Choose an image" e procure a
aplicação "Docker".
Isso provisionará um servidor Ubuntu com as versões mais recentes do Docker e do
Docker Compose já instaladas!
Para fins de teste, os planos mais baratos serão suficientes.
Para uso real em produção, você provavelmente escolherá um plano na seção
"General Purpose" que atenda às suas necessidades.
![Implantando o FrankenPHP na DigitalOcean com Docker](digitalocean-droplet.png)
Você pode manter os padrões para outras configurações ou ajustá-los de acordo
com suas necessidades.
Não se esqueça de adicionar sua chave SSH ou criar uma senha e, em seguida,
clicar no botão "Finalize and create".
Em seguida, aguarde alguns segundos enquanto seu Droplet é provisionado.
Quando seu Droplet estiver pronto, use SSH para se conectar:
```console
ssh root@<droplet-ip>
```
## Configurando um nome de domínio
Na maioria dos casos, você precisará associar um nome de domínio ao seu site.
Se você ainda não possui um nome de domínio, precisará comprar um por meio de um
registrar.
Em seguida, crie um registro DNS do tipo `A` para o seu nome de domínio,
apontando para o endereço IP do seu servidor:
```dns
seu-nome-de-dominio.example.com. IN A <ip-do-seu-servidor>
```
Exemplo com o serviço DigitalOcean Domains ("Networking" > "Domains"):
![Configurando DNS na DigitalOcean](digitalocean-dns.png)
> [!NOTE]
>
> O Let's Encrypt, o serviço usado por padrão pelo FrankenPHP para gerar
> automaticamente um certificado TLS, não suporta o uso de endereços IP.
> O uso de um nome de domínio é obrigatório para usar o Let's Encrypt.
## Implantando
Copie seu projeto para o servidor usando `git clone`, `scp` ou qualquer outra
ferramenta que atenda às suas necessidades.
Se você usa o GitHub, pode ser útil usar
[uma chave de implantação](https://docs.github.com/en/free-pro-team@latest/developers/overview/managing-deploy-keys#deploy-keys).
Chaves de implantação também são [suportadas pelo GitLab](https://docs.gitlab.com/ee/user/project/deploy_keys/).
Exemplo com Git:
```console
git clone git@github.com:<usuario>/<nome-do-projeto>.git
```
Acesse o diretório que contém seu projeto (`<nome-do-projeto>`) e inicie a
aplicação em modo de produção:
```console
docker compose up --wait
```
Seu servidor está funcionando e um certificado HTTPS foi gerado automaticamente
para você.
Acesse `https://seu-nome-de-dominio.example.com` e divirta-se!
> [!CAUTION]
>
> O Docker pode ter uma camada de cache; certifique-se de ter a construção
> correta para cada implantação ou reconstrua seu projeto com a opção
> `--no-cache` para evitar problemas de cache.
## Implantando em múltiplos nós
Se você deseja implantar sua aplicação em um cluster de máquinas, pode usar o
[Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/), que é
compatível com os arquivos Compose fornecidos.
Para implantar no Kubernetes, consulte o
[chart do Helm fornecido com a API Platform](https://api-platform.com/docs/deployment/kubernetes/),
que usa FrankenPHP.

208
docs/pt-br/static.md Normal file
View File

@@ -0,0 +1,208 @@
# Criar uma compilação estática
Em vez de usar uma instalação local da biblioteca PHP, é possível criar uma
compilação estática ou principalmente estática do FrankenPHP graças ao excelente
[projeto static-php-cli](https://github.com/crazywhalecc/static-php-cli) (apesar
do nome, este projeto suporta todas as SAPIs, não apenas CLI).
Com este método, um único binário portátil conterá o interpretador PHP, o
servidor web Caddy e o FrankenPHP!
Executáveis nativos totalmente estáticos não requerem dependências e podem até
ser executados na
[imagem Docker `scratch`](https://docs.docker.com/build/building/base-images/#create-a-minimal-base-image-using-scratch).
No entanto, eles não podem carregar extensões PHP dinâmicas (como o Xdebug) e
têm algumas limitações por usarem a `libc` `musl`.
A maioria dos binários estáticos requer apenas `glibc` e pode carregar extensões
dinâmicas.
Sempre que possível, recomendamos o uso de compilações principalmente estáticas
baseadas na `glibc`.
O FrankenPHP também suporta
[a incorporação da aplicação PHP no binário estático](embed.md).
## Linux
Fornecemos imagens Docker para compilar binários estáticos para Linux:
### Compilação totalmente estática baseada na `musl`
Para um binário totalmente estático que roda em qualquer distribuição Linux sem
dependências, mas não suporta carregamento dinâmico de extensões:
```console
docker buildx bake --load static-builder-musl
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-musl
```
Para melhor desempenho em cenários com alta concorrência, considere usar o
alocador [`mimalloc`](https://github.com/microsoft/mimalloc).
```console
docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl
```
### Compilação principalmente estática baseada na `glibc` (com suporte a extensões dinâmicas)
Para um binário que suporta o carregamento dinâmico de extensões PHP, mantendo
as extensões selecionadas compiladas estaticamente:
```console
docker buildx bake --load static-builder-gnu
docker cp $(docker create --name static-builder-gnu dunglas/frankenphp:static-builder-gnu):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-gnu
```
Este binário suporta todas as versões 2.17 e superiores da `glibc`, mas não roda
em sistemas baseados em `musl` (como o Alpine Linux).
O binário principalmente estático (exceto a `glibc`) resultante é chamado
`frankenphp` e está disponível no diretório atual.
Se você quiser compilar o binário estático sem o Docker, consulte as instruções
para macOS, que também funcionam para Linux.
### Extensões personalizadas
Por padrão, as extensões PHP mais populares são compiladas.
Para reduzir o tamanho do binário e a superfície de ataque, você pode escolher a
lista de extensões a serem compiladas usando o `ARG` `PHP_EXTENSIONS` do Docker.
Por exemplo, execute o seguinte comando para compilar apenas a extensão
`opcache`:
```console
docker buildx bake --load --set static-builder-musl.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder-musl
# ...
```
Para adicionar bibliotecas que habilitem funcionalidades adicionais às extensões
habilitadas, você pode passar o `ARG` `PHP_EXTENSION_LIBS` do Docker:
```console
docker buildx bake \
--load \
--set static-builder-musl.args.PHP_EXTENSIONS=gd \
--set static-builder-musl.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \
static-builder-musl
```
### Módulos Caddy extras
Para adicionar módulos Caddy extras ou passar outros argumentos para o
[`xcaddy`](https://github.com/caddyserver/xcaddy), use o `ARG` `XCADDY_ARGS` do
Docker:
```console
docker buildx bake \
--load \
--set static-builder-musl.args.XCADDY_ARGS="--with github.com/darkweak/souin/plugins/caddy --with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" \
static-builder-musl
```
Neste exemplo, adicionamos o módulo de cache HTTP [Souin](https://souin.io) para
o Caddy, bem como os módulos
[cbrotli](https://github.com/dunglas/caddy-cbrotli),
[Mercure](https://mercure.rocks) e [Vulcain](https://vulcain.rocks).
> [!TIP]
>
> Os módulos cbrotli, Mercure e Vulcain são incluídos por padrão se
> `XCADDY_ARGS` estiver vazio ou não definido.
> Se você personalizar o valor de `XCADDY_ARGS`, deverá incluí-los
> explicitamente se desejar que sejam incluídos.
Veja também como [personalizar a compilação](#personalizando-a-compilação).
### Token do GitHub
Se você atingir o limite de taxa da API do GitHub, defina um Token de Acesso
Pessoal do GitHub em uma variável de ambiente chamada `GITHUB_TOKEN`:
```console
GITHUB_TOKEN="xxx" docker --load buildx bake static-builder-musl
# ...
```
## macOS
Execute o seguinte script para criar um binário estático para macOS (você
precisa ter o [Homebrew](https://brew.sh/) instalado):
```console
git clone https://github.com/php/frankenphp
cd frankenphp
./build-static.sh
```
Observação: este script também funciona no Linux (e provavelmente em outros
sistemas Unix) e é usado internamente pelas imagens Docker que fornecemos.
## Personalizando a compilação
As seguintes variáveis de ambiente podem ser passadas para `docker build` e para
o script `build-static.sh` para personalizar a compilação estática:
- `FRANKENPHP_VERSION`: a versão do FrankenPHP a ser usada;
- `PHP_VERSION`: a versão do PHP a ser usada;
- `PHP_EXTENSIONS`: as extensões PHP a serem compiladas
([lista de extensões suportadas](https://static-php.dev/en/guide/extensions.html));
- `PHP_EXTENSION_LIBS`: bibliotecas extras a serem compiladas que adicionam
recursos às extensões;
- `XCADDY_ARGS`: argumentos a passar para o
[`xcaddy`](https://github.com/caddyserver/xcaddy), por exemplo, para adicionar
módulos Caddy extras;
- `EMBED`: caminho da aplicação PHP a ser incorporada no binário;
- `CLEAN`: quando definida, a `libphp` e todas as suas dependências são
compiladas do zero (sem cache);
- `NO_COMPRESS`: não compacta o binário resultante usando UPX;
- `DEBUG_SYMBOLS`: quando definida, os símbolos de depuração não serão removidos
e serão adicionados ao binário;
- `MIMALLOC`: (experimental, somente Linux) substitui `mallocng` da `musl` por
[`mimalloc`](https://github.com/microsoft/mimalloc) para melhor desempenho.
Recomendamos usar isso apenas para compilações direcionadas à `musl`; para
`glibc`, prefira desabilitar essa opção e usar
[`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html) ao
executar seu binário;
- `RELEASE`: (somente pessoas mantenedoras) quando definida, o binário
resultante será enviado para o GitHub.
## Extensões
Com os binários baseados na `glibc` ou no macOS, você pode carregar extensões
PHP dinamicamente.
No entanto, essas extensões precisarão ser compiladas com suporte a ZTS.
Como a maioria dos gerenciadores de pacotes não oferece atualmente versões ZTS
de suas extensões, você terá que compilá-las você mesmo.
Para isso, você pode compilar e executar o contêiner Docker
`static-builder-gnu`, acessá-lo remotamente e compilar as extensões com
`./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config`.
Passos de exemplo para [a extensão Xdebug](https://xdebug.org):
```console
docker build -t gnu-ext -f static-builder-gnu.Dockerfile --build-arg FRANKENPHP_VERSION=1.0 .
docker create --name static-builder-gnu -it gnu-ext /bin/sh
docker start static-builder-gnu
docker exec -it static-builder-gnu /bin/sh
cd /go/src/app/dist/static-php-cli/buildroot/bin
git clone https://github.com/xdebug/xdebug.git && cd xdebug
source scl_source enable devtoolset-10
../phpize
./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config
make
exit
docker cp static-builder-gnu:/go/src/app/dist/static-php-cli/buildroot/bin/xdebug/modules/xdebug.so xdebug-zts.so
docker cp static-builder-gnu:/go/src/app/dist/frankenphp-linux-$(uname -m) ./frankenphp
docker stop static-builder-gnu
docker rm static-builder-gnu
docker rmi gnu-ext
```
Isso criará `frankenphp` e `xdebug-zts.so` no diretório atual.
Se você mover `xdebug-zts.so` para o diretório de extensões, adicione
`zend_extension=xdebug-zts.so` ao seu `php.ini` e execute o FrankenPHP, ele
carregará o Xdebug.

214
docs/pt-br/worker.md Normal file
View File

@@ -0,0 +1,214 @@
# Usando workers do FrankenPHP
Inicialize sua aplicação uma vez e mantenha-a na memória.
O FrankenPHP processará as requisições recebidas em poucos milissegundos.
## Iniciando worker scripts
### Docker
Defina o valor da variável de ambiente `FRANKENPHP_CONFIG` como
`worker /caminho/para/seu/worker/script.php`:
```console
docker run \
-e FRANKENPHP_CONFIG="worker /app/caminho/para/seu/worker/script.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
### Binário independente
Use a opção `--worker` do comando `php-server` para servir o conteúdo do
diretório atual usando um worker:
```console
frankenphp php-server --worker /caminho/para/seu/worker/script.php
```
Se a sua aplicação PHP estiver [embutida no binário](embed.md), você pode
adicionar um `Caddyfile` personalizado no diretório raiz da aplicação.
Ele será usado automaticamente.
Também é possível
[reiniciar o worker em caso de alterações em arquivos](config.md#monitorando-alteracoes-em-arquivos)
com a opção `--watch`.
O comando a seguir acionará uma reinicialização se qualquer arquivo terminado em
`.php` no diretório `/caminho/para/sua/aplicacao/` ou subdiretórios for
modificado:
```console
frankenphp php-server --worker /caminho/para/seu/worker/script.php --watch="/caminho/para/sua/aplicacao/**/*.php"
```
## Symfony Runtime
O modo worker do FrankenPHP é suportado pelo
[Componente Symfony Runtime](https://symfony.com/doc/current/components/runtime.html).
Para iniciar qualquer aplicação Symfony em um worker, instale o pacote
FrankenPHP do [PHP Runtime](https://github.com/php-runtime/runtime):
```console
composer require runtime/frankenphp-symfony
```
Inicie seu servidor de aplicações definindo a variável de ambiente `APP_RUNTIME`
para usar o Symfony Runtime do FrankenPHP:
```console
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
## Laravel Octane
Consulte [a documentação dedicada](laravel.md#laravel-octane).
## Aplicações personalizadas
O exemplo a seguir mostra como criar seu próprio worker script sem depender de
uma biblioteca de terceiros:
```php
<?php
// public/index.php
// Impede o encerramento do worker script quando uma conexão do cliente for
// interrompida
ignore_user_abort(true);
// Inicializa a aplicação
require __DIR__.'/vendor/autoload.php';
$myApp = new \App\Kernel();
$myApp->boot();
// Manipulador fora do loop para melhor desempenho (fazendo menos trabalho)
$handler = static function () use ($myApp) {
// Chamado quando uma requisição é recebida,
// superglobals, php://input e similares são redefinidos
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
};
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
$keepRunning = \frankenphp_handle_request($handler);
// Faz algo depois de enviar a resposta HTTP
$myApp->terminate();
// Chama o coletor de lixo para reduzir as chances de ele ser acionado no
// meio da geração de uma página
gc_collect_cycles();
if (!$keepRunning) break;
}
// Limpeza
$myApp->shutdown();
```
Em seguida, inicie sua aplicação e use a variável de ambiente
`FRANKENPHP_CONFIG` para configurar seu worker:
```console
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
Por padrão, são iniciados 2 workers por CPU.
Você também pode configurar o número de workers a serem iniciados:
```console
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php 42" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
### Reiniciar o worker após um certo número de requisições
Como o PHP não foi originalmente projetado para processos de longa duração,
ainda existem muitas bibliotecas e códigos legados que vazam memória.
Uma solução alternativa para usar esse tipo de código no modo worker é reiniciar
o worker script após processar um certo número de requisições:
O trecho de código de worker anterior permite configurar um número máximo de
requisições a serem processadas, definindo uma variável de ambiente chamada
`MAX_REQUESTS`.
### Reiniciar os workers manualmente
Embora seja possível reiniciar os workers
[em alterações de arquivo](config.md#monitorando-alteracoes-em-arquivos), também
é possível reiniciar todos os workers graciosamente por meio da
[API de administração do Caddy](https://caddyserver.com/docs/api).
Se o administrador estiver habilitado no seu
[Caddyfile](config.md#configuracao-do-caddyfile), você pode executar ping no
endpoint de reinicialização com uma simples requisição POST como esta:
```console
curl -X POST http://localhost:2019/frankenphp/workers/restart
```
### Falhas de worker
Se um worker script travar com um código de saída diferente de zero, o
FrankenPHP o reiniciará com uma estratégia de backoff exponencial.
Se o worker script permanecer ativo por mais tempo do que o último backoff \* 2,
ele não irá penalizar o worker script e reiniciá-lo novamente.
No entanto, se o worker script continuar a falhar com um código de saída
diferente de zero em um curto período de tempo (por exemplo, com um erro de
digitação em um script), o FrankenPHP travará com o erro:
`too many consecutive failures` (muitas falhas consecutivas).
O número de falhas consecutivas pode ser configurado no seu
[Caddyfile](config.md#caddyfile-config) com a opção `max_consecutive_failures`:
```caddyfile
frankenphp {
worker {
# ...
max_consecutive_failures 10
}
}
```
## Comportamento das superglobais
As
[superglobais do PHP](https://www.php.net/manual/pt_BR/language.variables.superglobals.php)
(`$_SERVER`, `$_ENV`, `$_GET`...) se comportam da seguinte maneira:
- antes da primeira chamada para `frankenphp_handle_request()`, as superglobais
contêm valores vinculados ao próprio worker script.
- durante e após a chamada para `frankenphp_handle_request()`, as superglobais
contêm valores gerados a partir da requisição HTTP processada.
Cada chamada para `frankenphp_handle_request()` altera os valores das
superglobais.
Para acessar as superglobais do worker script dentro do retorno de chamada, você
deve copiá-las e importar a cópia para o escopo do retorno de chamada:
```php
<?php
// Copia a superglobal $_SERVER do worker antes da primeira chamada para
// frankenphp_handle_request()
$workerServer = $_SERVER;
$handler = static function () use ($workerServer) {
var_dump($_SERVER); // $_SERVER vinculada à requisição
var_dump($workerServer); // $_SERVER do worker script
};
// ...
```

80
docs/pt-br/x-sendfile.md Normal file
View File

@@ -0,0 +1,80 @@
# Servindo arquivos estáticos grandes com eficiência (`X-Sendfile`/`X-Accel-Redirect`)
Normalmente, arquivos estáticos podem ser servidos diretamente pelo servidor
web, mas às vezes é necessário executar algum código PHP antes de enviá-los:
controle de acesso, estatísticas, cabeçalhos HTTP personalizados...
Infelizmente, usar PHP para servir arquivos estáticos grandes é ineficiente em
comparação com o uso direto do servidor web (sobrecarga de memória, desempenho
reduzido...).
O FrankenPHP permite delegar o envio de arquivos estáticos ao servidor web
**após** a execução do código PHP personalizado.
Para fazer isso, sua aplicação PHP só precisa definir um cabeçalho HTTP
personalizado contendo o caminho do arquivo a ser servido.
O FrankenPHP cuida do resto.
Esse recurso é conhecido como **`X-Sendfile`** para Apache e
**`X-Accel-Redirect`** para NGINX.
Nos exemplos a seguir, assumimos que o diretório raiz do projeto é o diretório
`public/` e que queremos usar PHP para servir arquivos armazenados fora do
diretório `public/`, de um diretório chamado `arquivos-privados/`.
## Configuração
Primeiro, adicione a seguinte configuração ao seu `Caddyfile` para habilitar
este recurso:
```patch
root public/
# ...
+ # Necessário para Symfony, Laravel e outros projetos que usam o componente
+ # Symfony HttpFoundation
+ request_header X-Sendfile-Type x-accel-redirect
+ request_header X-Accel-Mapping ../arquivos-privados=/arquivos-privados
+
+ intercept {
+ @accel header X-Accel-Redirect *
+ handle_response @accel {
+ root arquivos-privados/
+ rewrite * {resp.header.X-Accel-Redirect}
+ method * GET
+
+ # Remove o cabeçalho X-Accel-Redirect definido pelo PHP para maior
+ # segurança
+ header -X-Accel-Redirect
+
+ file_server
+ }
+ }
php_server
```
## PHP simples
Defina o caminho relativo do arquivo (de `arquivos-privados/`) como o valor do
cabeçalho `X-Accel-Redirect`:
```php
header('X-Accel-Redirect: arquivo.txt');
```
## Projetos que utilizam o componente Symfony HttpFoundation (Symfony, Laravel, Drupal...)
Symfony HttpFoundation
[suporta nativamente este recurso](https://symfony.com/doc/current/components/http_foundation.html#serving-files).
Ele determinará automaticamente o valor correto para o cabeçalho
`X-Accel-Redirect` e o adicionará à resposta.
```php
use Symfony\Component\HttpFoundation\BinaryFileResponse;
BinaryFileResponse::trustXSendfileTypeHeader();
$response = new BinaryFileResponse(__DIR__.'/../arquivos-privados/arquivo.txt');
// ...
```

View File

@@ -7,7 +7,7 @@
Теги следуют следующему шаблону: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`.
- `<frankenphp-version>` и `<php-version>` — версии FrankenPHP и PHP соответственно: от основных (например, `1`) до минорных (например, `1.2`) и патч-версий (например, `1.2.3`).
- `<os>` может быть `bookworm` (для Debian Bookworm) или `alpine` (для последней стабильной версии Alpine).
- `<os>` может быть `trixie` (для Debian Trixie), `bookworm` (для Debian Bookworm) или `alpine` (для последней стабильной версии Alpine).
[Просмотреть доступные теги](https://hub.docker.com/r/dunglas/frankenphp/tags).

View File

@@ -127,7 +127,7 @@ error:0A000086:SSL routines::certificate verify failed
> Веб и CLI контексты могут иметь разные настройки.
> Запустите `openssl_get_cert_locations()` в нужном контексте.
[Сертификаты CA, извлечённые из Mozilla, можно скачать с сайта curl](https://curl.se/docs/caextract.html).
[Сертификаты CA, извлечённые из Mozilla, можно скачать с сайта cURL](https://curl.se/docs/caextract.html).
Кроме того, многие дистрибутивы, такие как Debian, Ubuntu и Alpine, предоставляют пакеты `ca-certificates`, содержащие эти сертификаты.

View File

@@ -196,7 +196,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
## Docker ile İlgili Kaynaklar
- [Pişirme (bake) dosya tanımı](https://docs.docker.com/build/customize/bake/file-definition/)
- [docker buildx build](https://docs.docker.com/engine/reference/commandline/buildx_build/)
- [`docker buildx build`](https://docs.docker.com/engine/reference/commandline/buildx_build/)
## Faydalı Komut

Some files were not shown because too many files have changed in this diff Show More