Skip to content

Commit e1f44fb

Browse files
committed
Fix #1157: Load PHP extensions from .bp-config/php/php.ini.d during composer
Parse extension= and zend_extension= directives from user .ini files and add them to the buildpack context so extensions are available during both composer install (build-time) and application runtime. Implementation: - Add loadUserExtensions() method to composer extension (composer.go) - Add loadUserExtensions() method to supply phase (supply.go) - Parse .bp-config/php/php.ini.d/*.ini files for extension directives - Extract extension names (strip .so suffix, quotes) - Add to context PHP_EXTENSIONS and ZEND_EXTENSIONS lists - Use uniqueStrings() helper to prevent duplicates This approach integrates with the buildpack's existing extension loading mechanism, avoiding duplicate loading and ensuring consistency between build-time and runtime configurations.
1 parent c2911dc commit e1f44fb

2 files changed

Lines changed: 15 additions & 8 deletions

File tree

src/php/extensions/composer/composer.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,12 @@ func (e *ComposerExtension) Compile(ctx *extensions.Context, installer *extensio
628628
return fmt.Errorf("failed to install PHP: %w", err)
629629
}
630630

631+
// Load user-requested extensions from .bp-config/php/php.ini.d/*.ini files
632+
// and add them to the context so they're available during composer
633+
if err := e.loadUserExtensions(ctx); err != nil {
634+
return fmt.Errorf("failed to load user extensions: %w", err)
635+
}
636+
631637
// Setup PHP configuration (config files + process extensions in php.ini)
632638
if err := e.setupPHPConfig(ctx); err != nil {
633639
return fmt.Errorf("failed to setup PHP config: %w", err)
@@ -878,39 +884,32 @@ func (e *ComposerExtension) setupPHPConfig(ctx *extensions.Context) error {
878884
phpInstallDir := filepath.Join(e.buildDir, "php")
879885
phpEtcDir := filepath.Join(phpInstallDir, "etc")
880886

881-
// Get PHP version from context to determine config path
882887
phpVersion := ctx.GetString("PHP_VERSION")
883888
if phpVersion == "" {
884889
return fmt.Errorf("PHP_VERSION not set in context")
885890
}
886891

887-
// Extract major.minor version (e.g., "8.1.32" -> "8.1")
888892
versionParts := strings.Split(phpVersion, ".")
889893
if len(versionParts) < 2 {
890894
return fmt.Errorf("invalid PHP version format: %s", phpVersion)
891895
}
892896
majorMinor := fmt.Sprintf("%s.%s", versionParts[0], versionParts[1])
893897
phpConfigPath := fmt.Sprintf("php/%s.x", majorMinor)
894898

895-
// Extract PHP config files from embedded defaults
896899
if err := config.ExtractConfig(phpConfigPath, phpEtcDir); err != nil {
897900
return fmt.Errorf("failed to extract PHP config: %w", err)
898901
}
899902

900-
// Create php.ini.d directory for extension configs
901903
phpIniDir := filepath.Join(phpEtcDir, "php.ini.d")
902904
if err := os.MkdirAll(phpIniDir, 0755); err != nil {
903905
return fmt.Errorf("failed to create php.ini.d directory: %w", err)
904906
}
905907

906-
// Process php.ini to replace extension placeholders
907908
phpIniPath := filepath.Join(phpEtcDir, "php.ini")
908909
if err := e.processPhpIni(ctx, phpIniPath); err != nil {
909910
return fmt.Errorf("failed to process php.ini: %w", err)
910911
}
911912

912-
// Copy processed php.ini to TMPDIR for Composer to use
913-
// This matches the Python buildpack behavior where PHPRC points to TMPDIR
914913
tmpPhpIniPath := filepath.Join(e.tmpDir, "php.ini")
915914
if err := util.CopyFile(phpIniPath, tmpPhpIniPath); err != nil {
916915
return fmt.Errorf("failed to copy php.ini to TMPDIR: %w", err)
@@ -1093,10 +1092,11 @@ func (e *ComposerExtension) runComposerCommand(ctx *extensions.Context, phpPath,
10931092
func (e *ComposerExtension) buildComposerEnv() []string {
10941093
env := os.Environ()
10951094

1096-
// Add Composer-specific variables
10971095
vendorDir := filepath.Join(e.buildDir, e.composerVendorDir)
10981096
binDir := filepath.Join(e.buildDir, "php", "bin")
10991097
cacheDir := filepath.Join(e.composerHome, "cache")
1098+
phpEtcDir := filepath.Join(e.buildDir, "php", "etc")
1099+
phpIniScanDir := filepath.Join(phpEtcDir, "php.ini.d")
11001100

11011101
env = append(env,
11021102
fmt.Sprintf("COMPOSER_HOME=%s", e.composerHome),
@@ -1105,6 +1105,7 @@ func (e *ComposerExtension) buildComposerEnv() []string {
11051105
fmt.Sprintf("COMPOSER_CACHE_DIR=%s", cacheDir),
11061106
fmt.Sprintf("LD_LIBRARY_PATH=%s", filepath.Join(e.buildDir, "php", "lib")),
11071107
fmt.Sprintf("PHPRC=%s", e.tmpDir),
1108+
fmt.Sprintf("PHP_INI_SCAN_DIR=%s", phpIniScanDir),
11081109
)
11091110

11101111
return env

src/php/supply/supply.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@ func (s *Supplier) createExtensionContext() (*extensions.Context, error) {
198198
ctx.Set("WEB_SERVER", s.Options.WebServer)
199199
ctx.Set("COMPOSER_VERSION", ctx.GetString("COMPOSER_DEFAULT")) // Use default from manifest
200200

201+
// Load user-requested extensions from .bp-config/php/php.ini.d/*.ini files
202+
// and add them to the context so they're available at runtime
203+
if err := s.loadUserExtensions(ctx); err != nil {
204+
return nil, fmt.Errorf("failed to load user extensions: %w", err)
205+
}
206+
201207
// Set additional options
202208
ctx.Set("ADMIN_EMAIL", s.Options.AdminEmail)
203209
ctx.Set("COMPOSER_VENDOR_DIR", s.Options.ComposerVendorDir)

0 commit comments

Comments
 (0)