diff --git a/docs/php.md b/docs/php.md index e6368f36..070cda4c 100644 --- a/docs/php.md +++ b/docs/php.md @@ -50,9 +50,9 @@ Flock's PHP includes the extensions most Laravel apps need, all of them on, with ```text apcu bcmath bz2 calendar ctype curl dom exif fileinfo filter ftp gd -gmp iconv igbinary imagick intl ldap mbstring memcached mongodb mysqli -opcache openssl pcntl pdo_mysql pdo_pgsql pdo_sqlite pgsql phar posix -readline redis session shmop simplexml soap sockets sodium sqlite3 +gmp iconv igbinary imagick imap intl ldap mbstring memcached mongodb +mysqli opcache openssl pcntl pdo_mysql pdo_pgsql pdo_sqlite pgsql phar +posix readline redis session shmop simplexml soap sockets sodium sqlite3 tokenizer xml xmlreader xmlwriter xsl zip zlib ``` @@ -60,6 +60,7 @@ tokenizer xml xmlreader xmlwriter xsl zip zlib - **pcov** is on for the command line only (`php`, `composer`, `php artisan test`), so your tests can measure code coverage while web requests stay fast. - **Xdebug** loads only for requests that ask for it. See [Xdebug](xdebug.md). - On Windows, `memcached`, `pcntl` and `posix` aren't available, and `mongodb` needs PHP 8.1 or newer. +- **imap** left PHP's core in 8.4; Flock's PHP has it for every version. It isn't thread-safe, so sites on [FrankenPHP](#frankenphp) don't have it. It doesn't support Kerberos (GSSAPI) logins. ### Which extensions a project needs diff --git a/internal/frankenphp/frankenphp.go b/internal/frankenphp/frankenphp.go index 15fa9eba..c23c638f 100644 --- a/internal/frankenphp/frankenphp.go +++ b/internal/frankenphp/frankenphp.go @@ -213,13 +213,14 @@ func confDir(root, minor string) string { return filepath.Join(Dir(root, minor), // EnsureIni (re)writes Flock's ini files for a build: the extensions in its ext/ load as for the // regular PHP (on Windows the official build's plus Flock's extension pack; on macOS the larger -// ones the build doesn't compile in), and Flock's defaults apply as for it (see php.DefaultsIni). +// ones the build doesn't compile in) except php.ThreadUnsafeExtensions, and Flock's defaults +// apply as for it (see php.DefaultsIni). func EnsureIni(root, minor string) error { dir := confDir(root, minor) if err := os.MkdirAll(dir, 0o755); err != nil { return err } - body := php.ExtensionsIni(filepath.Join(Dir(root, minor), "ext")) + php.DefaultsIni(root, minor) + body := php.ExtensionsIni(filepath.Join(Dir(root, minor), "ext"), php.ThreadUnsafeExtensions...) + php.DefaultsIni(root, minor) return os.WriteFile(filepath.Join(dir, "00-flock.ini"), []byte(body), 0o644) } diff --git a/internal/php/php.go b/internal/php/php.go index 6e94f30d..45e3118f 100644 --- a/internal/php/php.go +++ b/internal/php/php.go @@ -29,6 +29,7 @@ import ( "path/filepath" "regexp" "runtime" + "slices" "sort" "strconv" "strings" @@ -317,11 +318,15 @@ func ExtensionPath(root, minor, name string) string { // compile most of these in and ship only the larger ones (imagick, mongodb, memcached) shared. // Xdebug and pcov aren't here: Xdebug loads only for debugging, pcov only for the CLI. var extensions = []string{ - "bz2", "curl", "exif", "fileinfo", "ftp", "gd", "gettext", "gmp", "intl", "ldap", "mbstring", "mysqli", + "bz2", "curl", "exif", "fileinfo", "ftp", "gd", "gettext", "gmp", "imap", "intl", "ldap", "mbstring", "mysqli", "openssl", "pdo_mysql", "pdo_pgsql", "pdo_sqlite", "pgsql", "shmop", "soap", "sockets", "sodium", "sqlite3", "xsl", "zip", "apcu", "igbinary", "redis", "imagick", "mongodb", "memcached", } +// ThreadUnsafeExtensions stay out of FrankenPHP, which runs PHP in many threads of one process. +// Its macOS and Linux builds don't ship them; on Windows it shares the official build's DLLs. +var ThreadUnsafeExtensions = []string{"imap"} + // ExtensionFile is a shared extension's file in extDir: imagick.so, or php_imagick.dll on Windows. func ExtensionFile(extDir, name string) string { if runtime.GOOS == "windows" { @@ -387,8 +392,8 @@ func IniValue(ini, directive string) (value string, ok bool) { } // ExtensionsIni is the ini that points a build at its ext/ directory and loads the extensions -// present there (FrankenPHP's builds use the same). -func ExtensionsIni(extDir string) string { +// present there, except skip (FrankenPHP's builds use the same, skipping ThreadUnsafeExtensions). +func ExtensionsIni(extDir string, skip ...string) string { var b strings.Builder fmt.Fprintf(&b, "; Managed by Flock — regenerated on install, edit php.ini instead.\nextension_dir = \"%s\"\n", extDir) // opcache is compiled in from PHP 8.5 on, and into the macOS builds from PHP 8.0 on. @@ -396,7 +401,7 @@ func ExtensionsIni(extDir string) string { b.WriteString("zend_extension = opcache\n") } for _, name := range extensions { - if fileExists(ExtensionFile(extDir, name)) { + if !slices.Contains(skip, name) && fileExists(ExtensionFile(extDir, name)) { fmt.Fprintf(&b, "extension = %s\n", name) } } diff --git a/internal/php/php_test.go b/internal/php/php_test.go index 9242ccf5..5b1d72dd 100644 --- a/internal/php/php_test.go +++ b/internal/php/php_test.go @@ -96,6 +96,22 @@ func TestDebugCGIPortsStayClearOfTheWorkers(t *testing.T) { } } +func TestExtensionsIniSkipsThreadUnsafeForFrankenPHP(t *testing.T) { + extDir := t.TempDir() + for _, name := range []string{"imap", "intl"} { + if err := os.WriteFile(ExtensionFile(extDir, name), nil, 0o644); err != nil { + t.Fatal(err) + } + } + + if ini := ExtensionsIni(extDir); !strings.Contains(ini, "extension = imap\n") || !strings.Contains(ini, "extension = intl\n") { + t.Errorf("PHP doesn't load imap and intl:\n%s", ini) + } + if ini := ExtensionsIni(extDir, ThreadUnsafeExtensions...); strings.Contains(ini, "imap") || !strings.Contains(ini, "extension = intl\n") { + t.Errorf("FrankenPHP loads imap or misses intl:\n%s", ini) + } +} + func TestDefaultsYieldToPhpIni(t *testing.T) { root := t.TempDir() flockIni := func() string {