From 2f6aa0429b2649009996316eb7198a872e3feec9 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Sat, 29 Aug 2026 21:22:56 +0200 Subject: [PATCH] Read php.ini from the dependency directory in the sessions extension The sessions extension resolves php.ini through PHPConfigHelper, which builds its paths from ctx.BuildDir. That field is populated in NewContext from a BUILD_DIR environment variable that the lifecycle never exports, so it is empty and the helper opens the relative path php/etc/php.ini. Any app with a bound Redis or Memcached session service therefore fails staging with: Extension compilation failed: extension sessions compile failed: failed to load PHP config: failed to load php.ini: failed to read config file php/etc/php.ini The supply phase records the real location under the DEPS_DIR key, and that is also where PHP is installed: php.ini lives in //php/etc, never under the build directory. The unit test passed only because it assigned ctx.BuildDir directly, bypassing the code path production uses, and laid out php/etc under the build directory. It now models the dependency directory instead. --- src/php/extensions/extension.go | 9 ++++++-- src/php/extensions/sessions/sessions_test.go | 22 ++++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/php/extensions/extension.go b/src/php/extensions/extension.go index fff3da3ab..a3c138585 100644 --- a/src/php/extensions/extension.go +++ b/src/php/extensions/extension.go @@ -447,10 +447,15 @@ type PHPConfigHelper struct { // NewPHPConfigHelper creates a new PHP config helper func NewPHPConfigHelper(ctx *Context) *PHPConfigHelper { + // PHP is installed into the dependency directory, and ctx.BuildDir is empty + // during staging because BUILD_DIR is not exported to the buildpack. The + // supply phase records the real path under the DEPS_DIR key instead. + phpEtcDir := filepath.Join(ctx.GetString("DEPS_DIR"), "php", "etc") + return &PHPConfigHelper{ ctx: ctx, - phpIniPath: filepath.Join(ctx.BuildDir, "php", "etc", "php.ini"), - phpFpmPath: filepath.Join(ctx.BuildDir, "php", "etc", "php-fpm.conf"), + phpIniPath: filepath.Join(phpEtcDir, "php.ini"), + phpFpmPath: filepath.Join(phpEtcDir, "php-fpm.conf"), } } diff --git a/src/php/extensions/sessions/sessions_test.go b/src/php/extensions/sessions/sessions_test.go index 4fd321ab2..cb84f2309 100644 --- a/src/php/extensions/sessions/sessions_test.go +++ b/src/php/extensions/sessions/sessions_test.go @@ -12,10 +12,10 @@ import ( var _ = Describe("SessionsExtension", func() { var ( - ext *sessions.SessionsExtension - ctx *extensions.Context - err error - buildDir string + ext *sessions.SessionsExtension + ctx *extensions.Context + err error + depsDir string ) BeforeEach(func() { @@ -23,18 +23,18 @@ var _ = Describe("SessionsExtension", func() { ctx, err = extensions.NewContext() Expect(err).NotTo(HaveOccurred()) - // Create temp build directory for file operations - buildDir, err = os.MkdirTemp("", "sessions-test") + // PHP is installed into the dependency directory, which is where the + // supply phase records DEPS_DIR and writes php.ini. + depsDir, err = os.MkdirTemp("", "sessions-test") Expect(err).NotTo(HaveOccurred()) - // Set BuildDir directly on the struct field (not via Set() which uses Data map) - ctx.BuildDir = buildDir + ctx.Set("DEPS_DIR", depsDir) ctx.Set("BP_DIR", "/tmp/bp") }) AfterEach(func() { - if buildDir != "" { - os.RemoveAll(buildDir) + if depsDir != "" { + os.RemoveAll(depsDir) } }) @@ -213,7 +213,7 @@ var _ = Describe("SessionsExtension", func() { var phpDir string BeforeEach(func() { - phpDir = filepath.Join(buildDir, "php") + phpDir = filepath.Join(depsDir, "php") err := os.MkdirAll(filepath.Join(phpDir, "etc"), 0755) Expect(err).NotTo(HaveOccurred())