@@ -15,6 +15,7 @@ import (
1515 "github.com/cloudfoundry/libbuildpack"
1616 "github.com/cloudfoundry/php-buildpack/src/php/config"
1717 "github.com/cloudfoundry/php-buildpack/src/php/extensions"
18+ "github.com/cloudfoundry/php-buildpack/src/php/util"
1819)
1920
2021// ComposerExtension downloads, installs and runs Composer
@@ -125,7 +126,7 @@ func (e *ComposerExtension) Configure(ctx *extensions.Context) error {
125126 }
126127
127128 // Update context with unique extensions
128- ctx .Set ("PHP_EXTENSIONS" , uniqueStrings (exts ))
129+ ctx .Set ("PHP_EXTENSIONS" , util . UniqueStrings (exts ))
129130 ctx .Set ("PHP_VM" , "php" )
130131
131132 return nil
@@ -422,16 +423,16 @@ func (e *ComposerExtension) versionMatchesConstraint(version, constraint string)
422423
423424 if strings .HasPrefix (constraint , ">=" ) {
424425 minVersion := strings .TrimSpace (constraint [2 :])
425- return e . compareVersions (version , minVersion ) >= 0
426+ return util . CompareVersions (version , minVersion ) >= 0
426427 } else if strings .HasPrefix (constraint , ">" ) {
427428 minVersion := strings .TrimSpace (constraint [1 :])
428- return e . compareVersions (version , minVersion ) > 0
429+ return util . CompareVersions (version , minVersion ) > 0
429430 } else if strings .HasPrefix (constraint , "<=" ) {
430431 maxVersion := strings .TrimSpace (constraint [2 :])
431- return e . compareVersions (version , maxVersion ) <= 0
432+ return util . CompareVersions (version , maxVersion ) <= 0
432433 } else if strings .HasPrefix (constraint , "<" ) {
433434 maxVersion := strings .TrimSpace (constraint [1 :])
434- return e . compareVersions (version , maxVersion ) < 0
435+ return util . CompareVersions (version , maxVersion ) < 0
435436 } else if strings .HasPrefix (constraint , "^" ) {
436437 baseVersion := strings .TrimSpace (constraint [1 :])
437438 return e .isCompatible (version , baseVersion )
@@ -449,8 +450,8 @@ func (e *ComposerExtension) versionMatchesConstraint(version, constraint string)
449450func (e * ComposerExtension ) findHighestVersionGTE (minVersion string , versions []string ) string {
450451 var best string
451452 for _ , v := range versions {
452- if e . compareVersions (v , minVersion ) >= 0 {
453- if best == "" || e . compareVersions (v , best ) > 0 {
453+ if util . CompareVersions (v , minVersion ) >= 0 {
454+ if best == "" || util . CompareVersions (v , best ) > 0 {
454455 best = v
455456 }
456457 }
@@ -462,8 +463,8 @@ func (e *ComposerExtension) findHighestVersionGTE(minVersion string, versions []
462463func (e * ComposerExtension ) findHighestVersionGT (minVersion string , versions []string ) string {
463464 var best string
464465 for _ , v := range versions {
465- if e . compareVersions (v , minVersion ) > 0 {
466- if best == "" || e . compareVersions (v , best ) > 0 {
466+ if util . CompareVersions (v , minVersion ) > 0 {
467+ if best == "" || util . CompareVersions (v , best ) > 0 {
467468 best = v
468469 }
469470 }
@@ -475,8 +476,8 @@ func (e *ComposerExtension) findHighestVersionGT(minVersion string, versions []s
475476func (e * ComposerExtension ) findHighestVersionLTE (maxVersion string , versions []string ) string {
476477 var best string
477478 for _ , v := range versions {
478- if e . compareVersions (v , maxVersion ) <= 0 {
479- if best == "" || e . compareVersions (v , best ) > 0 {
479+ if util . CompareVersions (v , maxVersion ) <= 0 {
480+ if best == "" || util . CompareVersions (v , best ) > 0 {
480481 best = v
481482 }
482483 }
@@ -488,8 +489,8 @@ func (e *ComposerExtension) findHighestVersionLTE(maxVersion string, versions []
488489func (e * ComposerExtension ) findHighestVersionLT (maxVersion string , versions []string ) string {
489490 var best string
490491 for _ , v := range versions {
491- if e . compareVersions (v , maxVersion ) < 0 {
492- if best == "" || e . compareVersions (v , best ) > 0 {
492+ if util . CompareVersions (v , maxVersion ) < 0 {
493+ if best == "" || util . CompareVersions (v , best ) > 0 {
493494 best = v
494495 }
495496 }
@@ -504,7 +505,7 @@ func (e *ComposerExtension) findHighestVersion(versions []string) string {
504505 }
505506 best := versions [0 ]
506507 for _ , v := range versions [1 :] {
507- if e . compareVersions (v , best ) > 0 {
508+ if util . CompareVersions (v , best ) > 0 {
508509 best = v
509510 }
510511 }
@@ -516,7 +517,7 @@ func (e *ComposerExtension) findCompatibleVersion(baseVersion string, versions [
516517 var best string
517518 for _ , v := range versions {
518519 if e .isCompatible (v , baseVersion ) {
519- if best == "" || e . compareVersions (v , best ) > 0 {
520+ if best == "" || util . CompareVersions (v , best ) > 0 {
520521 best = v
521522 }
522523 }
@@ -529,7 +530,7 @@ func (e *ComposerExtension) findApproximateVersion(baseVersion string, versions
529530 var best string
530531 for _ , v := range versions {
531532 if e .isApproximatelyEquivalent (v , baseVersion ) {
532- if best == "" || e . compareVersions (v , best ) > 0 {
533+ if best == "" || util . CompareVersions (v , best ) > 0 {
533534 best = v
534535 }
535536 }
@@ -546,7 +547,7 @@ func (e *ComposerExtension) findWildcardMatch(pattern string, versions []string)
546547 var best string
547548 for _ , v := range versions {
548549 if strings .HasPrefix (v , prefix ) {
549- if best == "" || e . compareVersions (v , best ) > 0 {
550+ if best == "" || util . CompareVersions (v , best ) > 0 {
550551 best = v
551552 }
552553 }
@@ -570,7 +571,7 @@ func (e *ComposerExtension) isCompatible(version, base string) bool {
570571 }
571572
572573 // Must be >= base version
573- return e . compareVersions (version , base ) >= 0
574+ return util . CompareVersions (version , base ) >= 0
574575}
575576
576577// isApproximatelyEquivalent checks if version is approximately equivalent to base (~ constraint)
@@ -589,39 +590,11 @@ func (e *ComposerExtension) isApproximatelyEquivalent(version, base string) bool
589590 }
590591
591592 // Must be >= base version
592- return e . compareVersions (version , base ) >= 0
593+ return util . CompareVersions (version , base ) >= 0
593594}
594595
595596// compareVersions compares two semantic versions
596597// Returns: -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2
597- func (e * ComposerExtension ) compareVersions (v1 , v2 string ) int {
598- parts1 := strings .Split (v1 , "." )
599- parts2 := strings .Split (v2 , "." )
600-
601- maxLen := len (parts1 )
602- if len (parts2 ) > maxLen {
603- maxLen = len (parts2 )
604- }
605-
606- for i := 0 ; i < maxLen ; i ++ {
607- var n1 , n2 int
608-
609- if i < len (parts1 ) {
610- fmt .Sscanf (parts1 [i ], "%d" , & n1 )
611- }
612- if i < len (parts2 ) {
613- fmt .Sscanf (parts2 [i ], "%d" , & n2 )
614- }
615-
616- if n1 < n2 {
617- return - 1
618- } else if n1 > n2 {
619- return 1
620- }
621- }
622-
623- return 0
624- }
625598
626599// Compile downloads and runs Composer
627600func (e * ComposerExtension ) Compile (ctx * extensions.Context , installer * extensions.Installer ) error {
@@ -946,37 +919,6 @@ func (e *ComposerExtension) setupPHPConfig(ctx *extensions.Context) error {
946919 return nil
947920}
948921
949- // getCompiledModules returns a list of built-in PHP modules by running `php -m`
950- func getCompiledModules (phpBinPath , phpLibPath string ) (map [string ]bool , error ) {
951- cmd := exec .Command (phpBinPath , "-m" )
952- // Set LD_LIBRARY_PATH so php binary can find its shared libraries
953- env := os .Environ ()
954- env = append (env , fmt .Sprintf ("LD_LIBRARY_PATH=%s" , phpLibPath ))
955- cmd .Env = env
956-
957- output , err := cmd .Output ()
958- if err != nil {
959- return nil , fmt .Errorf ("failed to run php -m: %w" , err )
960- }
961-
962- // Parse output - skip header lines and empty lines
963- compiledModules := make (map [string ]bool )
964- skipLines := map [string ]bool {
965- "[PHP Modules]" : true ,
966- "[Zend Modules]" : true ,
967- }
968-
969- for _ , line := range strings .Split (string (output ), "\n " ) {
970- line = strings .TrimSpace (line )
971- if line != "" && ! skipLines [line ] {
972- // Store lowercase version for case-insensitive comparison
973- compiledModules [strings .ToLower (line )] = true
974- }
975- }
976-
977- return compiledModules , nil
978- }
979-
980922// processPhpIni processes php.ini to replace extension placeholders with actual extension directives
981923func (e * ComposerExtension ) processPhpIni (ctx * extensions.Context , phpIniPath string ) error {
982924 // Read the php.ini file
@@ -1013,7 +955,7 @@ func (e *ComposerExtension) processPhpIni(ctx *extensions.Context, phpIniPath st
1013955 // Get list of built-in PHP modules (extensions compiled into PHP core)
1014956 phpBinary := filepath .Join (e .buildDir , "php" , "bin" , "php" )
1015957 phpLib := filepath .Join (e .buildDir , "php" , "lib" )
1016- compiledModules , err := getCompiledModules (phpBinary , phpLib )
958+ compiledModules , err := util . GetCompiledModules (phpBinary , phpLib )
1017959 if err != nil {
1018960 fmt .Printf (" WARNING: Failed to get compiled PHP modules: %v\n " , err )
1019961 compiledModules = make (map [string ]bool ) // Continue without built-in module list
@@ -1250,17 +1192,82 @@ func (e *ComposerExtension) copyFile(src, dst string) error {
12501192 return err
12511193}
12521194
1253- // uniqueStrings returns a slice with duplicate strings removed
1254- func uniqueStrings (input []string ) []string {
1255- seen := make (map [string ]bool )
1256- result := []string {}
1195+ func (e * ComposerExtension ) loadUserExtensions (ctx * extensions.Context ) error {
1196+ userPhpIniDir := filepath .Join (e .buildDir , ".bp-config" , "php" , "php.ini.d" )
1197+ if _ , err := os .Stat (userPhpIniDir ); os .IsNotExist (err ) {
1198+ return nil
1199+ }
1200+
1201+ fmt .Println (" Loading user-requested extensions from .bp-config/php/php.ini.d" )
1202+
1203+ currentExtensions := ctx .GetStringSlice ("PHP_EXTENSIONS" )
1204+ currentZendExtensions := ctx .GetStringSlice ("ZEND_EXTENSIONS" )
12571205
1258- for _ , item := range input {
1259- if ! seen [item ] {
1260- seen [item ] = true
1261- result = append (result , item )
1206+ extensionsToAdd := make (map [string ]bool )
1207+ zendExtensionsToAdd := make (map [string ]bool )
1208+
1209+ err := filepath .Walk (userPhpIniDir , func (path string , info os.FileInfo , err error ) error {
1210+ if err != nil {
1211+ return err
1212+ }
1213+
1214+ if info .IsDir () || ! strings .HasSuffix (strings .ToLower (path ), ".ini" ) {
1215+ return nil
1216+ }
1217+
1218+ content , err := os .ReadFile (path )
1219+ if err != nil {
1220+ fmt .Printf (" WARNING: Failed to read %s: %v\n " , path , err )
1221+ return nil
12621222 }
1223+
1224+ for _ , line := range strings .Split (string (content ), "\n " ) {
1225+ line = strings .TrimSpace (line )
1226+
1227+ if strings .HasPrefix (line , ";" ) || strings .HasPrefix (line , "#" ) || line == "" {
1228+ continue
1229+ }
1230+
1231+ if strings .HasPrefix (line , "extension=" ) || strings .HasPrefix (line , "extension =" ) {
1232+ extLine := strings .TrimSpace (strings .TrimPrefix (line , "extension=" ))
1233+ extLine = strings .TrimSpace (strings .TrimPrefix (extLine , "extension =" ))
1234+ extName := strings .TrimSuffix (extLine , ".so" )
1235+ extName = strings .Trim (extName , "\" ' " )
1236+ if extName != "" {
1237+ extensionsToAdd [extName ] = true
1238+ }
1239+ } else if strings .HasPrefix (line , "zend_extension=" ) || strings .HasPrefix (line , "zend_extension =" ) {
1240+ extLine := strings .TrimSpace (strings .TrimPrefix (line , "zend_extension=" ))
1241+ extLine = strings .TrimSpace (strings .TrimPrefix (extLine , "zend_extension =" ))
1242+ extName := strings .TrimSuffix (extLine , ".so" )
1243+ extName = strings .Trim (extName , "\" ' " )
1244+ if extName != "" {
1245+ zendExtensionsToAdd [extName ] = true
1246+ }
1247+ }
1248+ }
1249+
1250+ return nil
1251+ })
1252+
1253+ if err != nil {
1254+ return fmt .Errorf ("failed to scan user ini files: %w" , err )
12631255 }
12641256
1265- return result
1257+ for ext := range extensionsToAdd {
1258+ currentExtensions = append (currentExtensions , ext )
1259+ }
1260+ for ext := range zendExtensionsToAdd {
1261+ currentZendExtensions = append (currentZendExtensions , ext )
1262+ }
1263+
1264+ ctx .Set ("PHP_EXTENSIONS" , util .UniqueStrings (currentExtensions ))
1265+ ctx .Set ("ZEND_EXTENSIONS" , util .UniqueStrings (currentZendExtensions ))
1266+
1267+ if len (extensionsToAdd ) > 0 || len (zendExtensionsToAdd ) > 0 {
1268+ fmt .Printf (" Found %d extension(s) and %d zend extension(s) in user config\n " ,
1269+ len (extensionsToAdd ), len (zendExtensionsToAdd ))
1270+ }
1271+
1272+ return nil
12661273}
0 commit comments