This document compares the PHP buildpack v5.x architecture with other Cloud Foundry buildpacks to demonstrate alignment with CF buildpack best practices.
- Overview
- Environment Variable Handling
- Configuration Patterns
- Service Binding Patterns
- Profile.d Script Usage
- Key Findings
The PHP buildpack v5.x has been refactored from Python (v4.x) to Go, aligning it with the architecture patterns used across all other Cloud Foundry buildpacks.
| Buildpack | Language | VCAP_SERVICES Support | Config Pattern |
|---|---|---|---|
| PHP v5.x | Go | ✅ Staging-time | Build-time placeholders |
| PHP v4.x | Python | ✅ Staging + Runtime | Runtime rewrite |
| Go | Go | ✅ Staging-time | No placeholders |
| Java | Go | ✅ Staging-time | No placeholders |
| Ruby | Go | ✅ Staging-time | No placeholders |
| Python | Go | ✅ Staging-time | No placeholders |
| .NET Core | Go | ✅ Staging-time | No placeholders |
| Node.js | Go | ✅ Staging-time | No placeholders |
Key Observation: All modern CF buildpacks (except PHP v4.x) use staging-time configuration with no runtime rewriting.
All buildpacks follow this pattern:
STAGING PHASE:
1. Read environment variables (VCAP_SERVICES, VCAP_APPLICATION, etc.)
2. Parse and extract needed values
3. Write configuration files
4. Create profile.d scripts for runtime env vars
RUNTIME:
- Pre-configured files already in droplet
- profile.d scripts export additional env vars
- No config rewriting needed
Extension Context (supply phase):
// src/php/extensions/extension.go
func NewContext() (*Context, error) {
// Read VCAP_SERVICES during staging
if vcapServicesJSON := os.Getenv("VCAP_SERVICES"); vcapServicesJSON != "" {
json.Unmarshal([]byte(vcapServicesJSON), &ctx.VcapServices)
}
// Read VCAP_APPLICATION during staging
if vcapAppJSON := os.Getenv("VCAP_APPLICATION"); vcapAppJSON != "" {
json.Unmarshal([]byte(vcapAppJSON), &ctx.VcapApplication)
}
return ctx, nil
}AppDynamics Hook (supply phase):
// go-buildpack/src/go/hooks/appdynamics.go
func (h AppdynamicsHook) BeforeCompile(stager *libbuildpack.Stager) error {
// Read VCAP_SERVICES during staging
vcapServices := os.Getenv("VCAP_SERVICES")
services := make(map[string][]Plan)
json.Unmarshal([]byte(vcapServices), &services)
if val, ok := services["appdynamics"]; ok {
// Extract credentials
appdEnv := map[string]string{
"APPD_CONTROLLER_HOST": val[0].Credentials.ControllerHost,
"APPD_ACCOUNT_KEY": val[0].Credentials.AccountAccessKey,
}
// Write profile.d script
stager.WriteProfileD("appdynamics.sh", scriptContents)
}
}VCAP Services Helper (supply phase):
// java-buildpack/src/java/common/context.go
type VCAPServices map[string][]VCAPService
func GetVCAPServices() (VCAPServices, error) {
vcapServicesStr := os.Getenv("VCAP_SERVICES")
if vcapServicesStr == "" {
return VCAPServices{}, nil
}
var services VCAPServices
err := json.Unmarshal([]byte(vcapServicesStr), &services)
return services, err
}Used in frameworks:
// java-buildpack/src/java/frameworks/sealights_agent.go
vcapServices, err := GetVCAPServices()
if sealightsService, found := findService(vcapServices, "sealights"); found {
// Configure agent with service credentials
}| Pattern | PHP v5.x | Go | Java | Ruby | Python |
|---|---|---|---|---|---|
| Read env vars during staging | ✅ | ✅ | ✅ | ✅ | ✅ |
| Parse VCAP_SERVICES | ✅ | ✅ | ✅ | ✅ | ✅ |
| Write config files at build time | ✅ | ✅ | ✅ | ✅ | ✅ |
| Create profile.d scripts | ✅ | ✅ | ✅ | ✅ | ✅ |
| No runtime config rewriting | ✅ | ✅ | ✅ | ✅ | ✅ |
| Feature | PHP v4.x | All Others (including PHP v5.x) |
|---|---|---|
| Runtime config file rewriting | ✅ Unique | ❌ Never had |
| @{ARBITRARY_VAR} placeholders | ✅ Unique | ❌ Never had |
| Rewrite on every container start | ✅ Unique | ❌ Never had |
All buildpacks configure NewRelic the same way:
// src/php/extensions/newrelic/newrelic.go
const newrelicEnvScript = `if [[ -z "${NEWRELIC_LICENSE:-}" ]]; then
export NEWRELIC_LICENSE=$(echo $VCAP_SERVICES | jq -r '.newrelic[0].credentials.licenseKey')
fi`
// Install() writes this script to profile.d during staging// go-buildpack integrates NewRelic via multi-buildpack
// Same pattern: reads VCAP_SERVICES during staging// java-buildpack reads VCAP_SERVICES during staging
// Configures NewRelic agent with extracted credentialsNo buildpack writes database credentials to config files. All use application code:
<?php
$vcap = json_decode(getenv('VCAP_SERVICES'), true);
$db = $vcap['mysql'][0]['credentials'];
$pdo = new PDO(
"mysql:host={$db['host']};dbname={$db['name']}",
$db['username'],
$db['password']
);
?>require 'json'
vcap_services = JSON.parse(ENV['VCAP_SERVICES'])
mysql = vcap_services['mysql'].first['credentials']
ActiveRecord::Base.establish_connection(
adapter: 'mysql2',
host: mysql['host'],
username: mysql['username'],
password: mysql['password'],
database: mysql['name']
)import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
String vcapServices = System.getenv("VCAP_SERVICES");
JsonNode services = new ObjectMapper().readTree(vcapServices);
JsonNode mysql = services.get("mysql").get(0).get("credentials");
String url = "jdbc:mysql://" + mysql.get("host").asText() + "/" + mysql.get("name").asText();
String username = mysql.get("username").asText();
String password = mysql.get("password").asText();Pattern: All buildpacks expect applications to parse VCAP_SERVICES in code, not config files.
Purpose: Set environment variables at runtime based on staging-time analysis.
Location: deps/{idx}/profile.d/*.sh (sourced by Cloud Foundry at container startup)
# Written by CreatePHPEnvironmentScript()
#!/usr/bin/env bash
: ${DEPS_DIR:=/home/vcap/deps}
export DEPS_DIR
export PATH="$DEPS_DIR/0/php/bin:$DEPS_DIR/0/php/sbin:$PATH"# Written by NewRelic extension during staging
#!/usr/bin/env bash
if [[ -z "${NEWRELIC_LICENSE:-}" ]]; then
export NEWRELIC_LICENSE=$(echo $VCAP_SERVICES | jq -r '.newrelic[0].credentials.licenseKey')
fi# Generated from extension.json preprocess_commands
#!/usr/bin/env bash
# Extension environment variables
export MY_VAR='value'
export ANOTHER_VAR='value2'# go-buildpack/src/go/hooks/appdynamics.go generates:
#!/usr/bin/env bash
export APPD_APP_NAME=my-app
export APPD_TIER_NAME=web-tier
export APPD_CONTROLLER_HOST=controller.example.com
export APPD_ACCOUNT_KEY=secret-key# ruby-buildpack generates during staging:
#!/usr/bin/env bash
export SECRET_KEY_BASE=${SECRET_KEY_BASE:-generated-secret-from-rake}
export RAILS_ENV=${RAILS_ENV:-production}- All buildpacks use profile.d for runtime environment setup
- Scripts can parse VCAP_SERVICES at runtime if needed
- Values extracted during staging can be baked into scripts
- No buildpack rewrites config files at runtime
The refactored PHP buildpack follows the exact same patterns as all other Cloud Foundry buildpacks:
| Capability | Status |
|---|---|
| Read VCAP_SERVICES during staging | ✅ Same as all buildpacks |
| Parse and use service credentials | ✅ Same as all buildpacks |
| Write profile.d scripts | ✅ Same as all buildpacks |
| No runtime config rewriting | ✅ Same as all buildpacks |
| Build-time configuration | ✅ Same as all buildpacks |
The bin/rewrite script and @{ARBITRARY_VAR} placeholder support was:
- ❌ Not used by any other buildpack
- ❌ Not a CF buildpack standard
- ❌ Had performance/security trade-offs
- ✅ Removed for good reasons
Every v4.x pattern has a v5.x equivalent that matches other buildpacks:
| v4.x Pattern | v5.x Equivalent | Used By |
|---|---|---|
| Extension reads VCAP_SERVICES | Extension reads VCAP_SERVICES | All buildpacks |
| profile.d scripts | profile.d scripts | All buildpacks |
| App code parses VCAP_SERVICES | App code parses VCAP_SERVICES | All buildpacks |
| ❌ Never standard | PHP v4.x only |
When compared to other buildpacks (not v4.x), PHP v5.x has:
- ✅ Same capabilities
- ✅ Same patterns
- ✅ Same limitations
- ✅ Same extension model
The only "lost" feature is one that no other buildpack ever had.
The migration from Python (v4.x) to Go (v5.x) successfully:
- ✅ Aligns with Cloud Foundry buildpack best practices
- ✅ Follows patterns used by Go, Java, Ruby, Python, .NET buildpacks
- ✅ Maintains all standard CF functionality
- ✅ Improves performance (no runtime rewriting)
- ✅ Enhances security (reduced runtime code execution)
- ✅ Increases maintainability (Go vs Python)
While the removal of runtime config rewriting is a breaking change for PHP users:
- It was never a CF standard (PHP-only feature)
- It had performance and security costs
- All use cases have standard CF equivalents
- The change brings alignment with the broader ecosystem
The PHP buildpack v5.x should be considered fully compliant with Cloud Foundry buildpack architecture standards and best practices.
- VCAP_SERVICES_USAGE.md - Detailed VCAP_SERVICES usage guide
- REWRITE_MIGRATION.md - v4.x to v5.x migration guide
- libbuildpack Documentation - Shared library used by all Go buildpacks