PhpContainerFactory.java
package com.cloudforgeci.api.compute;
import com.cloudforge.core.interfaces.CmsSpec;
import com.cloudforge.core.interfaces.PhpRuntimeConfig;
import java.util.HashMap;
import java.util.Map;
/**
* Factory for creating PHP container configurations for CMS platforms.
*
* <p>Generates container environment variables, PHP-FPM configuration,
* and NGINX configuration for PHP-based CMS deployments on Fargate.</p>
*
* <h2>Usage Example:</h2>
* <pre>{@code
* CmsSpec wordpress = new WordPressApplicationSpec();
* PhpRuntimeConfig config = PhpRuntimeConfig.forWordPress();
*
* Map<String, String> env = PhpContainerFactory.createEnvironment(wordpress, config);
* String nginxConf = PhpContainerFactory.generateNginxConfig(wordpress);
* }</pre>
*
* @since 3.1.0
*/
public final class PhpContainerFactory {
private PhpContainerFactory() {
// Utility class
}
/**
* Generate PHP container environment variables.
*
* <p>Creates environment variables for:</p>
* <ul>
* <li>PHP memory and execution limits</li>
* <li>OPcache settings</li>
* <li>Upload limits</li>
* <li>Timezone</li>
* </ul>
*
* @param spec the CMS specification
* @param config the PHP runtime configuration
* @return map of environment variables
*/
public static Map<String, String> createEnvironment(CmsSpec spec, PhpRuntimeConfig config) {
Map<String, String> env = new HashMap<>();
// PHP settings
env.put("PHP_MEMORY_LIMIT", config.memoryLimit() + "M");
env.put("PHP_MAX_EXECUTION_TIME", String.valueOf(config.maxExecutionTime()));
env.put("PHP_UPLOAD_MAX_FILESIZE", config.uploadMaxFilesize() + "M");
env.put("PHP_POST_MAX_SIZE", config.postMaxSize() + "M");
env.put("PHP_MAX_INPUT_VARS", "10000");
env.put("PHP_MAX_INPUT_TIME", "600");
// OPcache settings as environment variables
for (Map.Entry<String, String> entry : config.opcacheConfig().entrySet()) {
String key = entry.getKey().toUpperCase().replace(".", "_");
env.put("PHP_" + key, entry.getValue());
}
// PHP-FPM settings
for (Map.Entry<String, String> entry : config.fpmConfig().entrySet()) {
String key = entry.getKey().toUpperCase().replace(".", "_");
env.put("PHP_FPM_" + key, entry.getValue());
}
// Timezone
env.put("TZ", "UTC");
// Application identity for logging/tracing
env.put("CMS_APPLICATION_ID", spec.applicationId());
return env;
}
/**
* Generate PHP-FPM configuration file content.
*
* @param spec the CMS specification
* @param config the PHP runtime configuration
* @return PHP-FPM pool configuration content
*/
public static String generateFpmConfig(CmsSpec spec, PhpRuntimeConfig config) {
StringBuilder fpm = new StringBuilder();
fpm.append("; CloudForge generated PHP-FPM configuration for ").append(spec.displayName()).append("\n\n");
fpm.append("[www]\n");
fpm.append("user = www-data\n");
fpm.append("group = www-data\n");
fpm.append("listen = 127.0.0.1:9000\n");
fpm.append("listen.owner = www-data\n");
fpm.append("listen.group = www-data\n\n");
for (Map.Entry<String, String> entry : config.fpmConfig().entrySet()) {
fpm.append(entry.getKey()).append(" = ").append(entry.getValue()).append("\n");
}
fpm.append("\n; Slow log for debugging\n");
fpm.append("slowlog = /var/log/php-fpm/slow.log\n");
fpm.append("request_slowlog_timeout = 10s\n");
fpm.append("\n; Status page for monitoring\n");
fpm.append("pm.status_path = /fpm-status\n");
fpm.append("ping.path = /fpm-ping\n");
return fpm.toString();
}
/**
* Get the base container image for the specified PHP version.
*
* @param phpVersion PHP version (e.g., "8.2", "8.1")
* @return Docker image tag
*/
public static String getBaseImage(String phpVersion) {
return String.format("php:%s-fpm-alpine", phpVersion);
}
/**
* Get the official CMS container image if available.
*
* @param spec the CMS specification
* @return container image reference
*/
public static String getCmsImage(CmsSpec spec) {
// Check annotation for default image
var annotation = spec.getClass().getAnnotation(
com.cloudforge.core.annotation.CmsPlugin.class);
if (annotation != null && !annotation.defaultImage().isEmpty()) {
return annotation.defaultImage();
}
// Fall back to default container image from spec
return spec.defaultContainerImage();
}
/**
* Generate NGINX configuration for PHP-FPM reverse proxy.
*
* @param spec the CMS specification
* @return NGINX configuration content
*/
public static String generateNginxConfig(CmsSpec spec) {
String documentRoot = spec.documentRoot();
int port = spec.applicationPort();
StringBuilder nginx = new StringBuilder();
nginx.append("# CloudForge generated NGINX configuration for ").append(spec.displayName()).append("\n\n");
nginx.append("server {\n");
nginx.append(" listen ").append(port).append(";\n");
nginx.append(" listen [::]:").append(port).append(";\n");
nginx.append(" server_name _;\n\n");
nginx.append(" root ").append(documentRoot).append(";\n");
nginx.append(" index index.php index.html index.htm;\n\n");
// Client body size for uploads
nginx.append(" client_max_body_size 128M;\n\n");
// Logging
nginx.append(" access_log /var/log/nginx/access.log;\n");
nginx.append(" error_log /var/log/nginx/error.log;\n\n");
// Security headers
nginx.append(" # Security headers\n");
nginx.append(" add_header X-Frame-Options \"SAMEORIGIN\" always;\n");
nginx.append(" add_header X-Content-Type-Options \"nosniff\" always;\n");
nginx.append(" add_header X-XSS-Protection \"1; mode=block\" always;\n\n");
// Main location block
nginx.append(" location / {\n");
nginx.append(" try_files $uri $uri/ /index.php?$args;\n");
nginx.append(" }\n\n");
// PHP processing
nginx.append(" location ~ \\.php$ {\n");
nginx.append(" try_files $uri =404;\n");
nginx.append(" fastcgi_split_path_info ^(.+\\.php)(/.+)$;\n");
nginx.append(" fastcgi_pass 127.0.0.1:9000;\n");
nginx.append(" fastcgi_index index.php;\n");
nginx.append(" include fastcgi_params;\n");
nginx.append(" fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n");
nginx.append(" fastcgi_param PATH_INFO $fastcgi_path_info;\n");
nginx.append(" fastcgi_read_timeout 300;\n");
nginx.append(" fastcgi_buffer_size 128k;\n");
nginx.append(" fastcgi_buffers 4 256k;\n");
nginx.append(" fastcgi_busy_buffers_size 256k;\n");
nginx.append(" }\n\n");
// Static file caching
nginx.append(" # Static file caching\n");
nginx.append(" location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {\n");
nginx.append(" expires 30d;\n");
nginx.append(" add_header Cache-Control \"public, immutable\";\n");
nginx.append(" access_log off;\n");
nginx.append(" }\n\n");
// Deny access to sensitive files
nginx.append(" # Deny access to sensitive files\n");
nginx.append(" location ~ /\\. {\n");
nginx.append(" deny all;\n");
nginx.append(" }\n\n");
nginx.append(" location ~ \\.(sql|log|htaccess)$ {\n");
nginx.append(" deny all;\n");
nginx.append(" }\n\n");
// Health check endpoint
nginx.append(" # Health check for ALB\n");
nginx.append(" location = /health {\n");
nginx.append(" access_log off;\n");
nginx.append(" return 200 'OK';\n");
nginx.append(" add_header Content-Type text/plain;\n");
nginx.append(" }\n\n");
// PHP-FPM status (internal only)
nginx.append(" # PHP-FPM status (internal)\n");
nginx.append(" location = /fpm-status {\n");
nginx.append(" access_log off;\n");
nginx.append(" allow 127.0.0.1;\n");
nginx.append(" deny all;\n");
nginx.append(" include fastcgi_params;\n");
nginx.append(" fastcgi_pass 127.0.0.1:9000;\n");
nginx.append(" }\n\n");
nginx.append(" location = /fpm-ping {\n");
nginx.append(" access_log off;\n");
nginx.append(" allow 127.0.0.1;\n");
nginx.append(" deny all;\n");
nginx.append(" include fastcgi_params;\n");
nginx.append(" fastcgi_pass 127.0.0.1:9000;\n");
nginx.append(" }\n");
nginx.append("}\n");
return nginx.toString();
}
/**
* Generate WordPress-specific NGINX configuration.
*
* @param documentRoot document root path
* @param port listen port
* @param isMultisite whether multisite is enabled
* @return WordPress NGINX configuration
*/
public static String generateWordPressNginxConfig(String documentRoot, int port, boolean isMultisite) {
StringBuilder nginx = new StringBuilder();
nginx.append("# CloudForge generated NGINX configuration for WordPress\n\n");
nginx.append("server {\n");
nginx.append(" listen ").append(port).append(";\n");
nginx.append(" listen [::]:").append(port).append(";\n");
nginx.append(" server_name _;\n\n");
nginx.append(" root ").append(documentRoot).append(";\n");
nginx.append(" index index.php index.html;\n\n");
nginx.append(" client_max_body_size 128M;\n\n");
// WordPress-specific security
nginx.append(" # WordPress security\n");
nginx.append(" location = /xmlrpc.php {\n");
nginx.append(" deny all;\n");
nginx.append(" }\n\n");
nginx.append(" location ~* /wp-includes/.*\\.php$ {\n");
nginx.append(" deny all;\n");
nginx.append(" }\n\n");
nginx.append(" location ~* /wp-content/uploads/.*\\.php$ {\n");
nginx.append(" deny all;\n");
nginx.append(" }\n\n");
// WordPress permalinks
nginx.append(" location / {\n");
nginx.append(" try_files $uri $uri/ /index.php?$args;\n");
nginx.append(" }\n\n");
// Multisite rules
if (isMultisite) {
nginx.append(" # WordPress Multisite\n");
nginx.append(" if (!-e $request_filename) {\n");
nginx.append(" rewrite /wp-admin$ $scheme://$host$uri/ permanent;\n");
nginx.append(" rewrite ^(/[^/]+)?(/wp-.*) $2 last;\n");
nginx.append(" rewrite ^(/[^/]+)?(/.*\\.php) $2 last;\n");
nginx.append(" }\n\n");
}
// PHP processing
nginx.append(" location ~ \\.php$ {\n");
nginx.append(" try_files $uri =404;\n");
nginx.append(" fastcgi_split_path_info ^(.+\\.php)(/.+)$;\n");
nginx.append(" fastcgi_pass 127.0.0.1:9000;\n");
nginx.append(" fastcgi_index index.php;\n");
nginx.append(" include fastcgi_params;\n");
nginx.append(" fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n");
nginx.append(" fastcgi_read_timeout 300;\n");
nginx.append(" }\n\n");
// Static file caching
nginx.append(" location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {\n");
nginx.append(" expires 30d;\n");
nginx.append(" add_header Cache-Control \"public, immutable\";\n");
nginx.append(" }\n\n");
// wp-content uploads
nginx.append(" location /wp-content/uploads/ {\n");
nginx.append(" location ~ \\.php$ { deny all; }\n");
nginx.append(" }\n\n");
// Health check
nginx.append(" location = /health {\n");
nginx.append(" access_log off;\n");
nginx.append(" return 200 'OK';\n");
nginx.append(" }\n");
nginx.append("}\n");
return nginx.toString();
}
/**
* Generate Magento-specific NGINX configuration.
*
* @param documentRoot document root path (typically /var/www/html)
* @param port listen port
* @return Magento NGINX configuration
*/
public static String generateMagentoNginxConfig(String documentRoot, int port) {
StringBuilder nginx = new StringBuilder();
nginx.append("# CloudForge generated NGINX configuration for Magento 2\n\n");
nginx.append("upstream fastcgi_backend {\n");
nginx.append(" server 127.0.0.1:9000;\n");
nginx.append("}\n\n");
nginx.append("server {\n");
nginx.append(" listen ").append(port).append(";\n");
nginx.append(" server_name _;\n\n");
nginx.append(" set $MAGE_ROOT ").append(documentRoot).append(";\n");
nginx.append(" set $MAGE_MODE production;\n\n");
nginx.append(" root $MAGE_ROOT/pub;\n");
nginx.append(" index index.php;\n\n");
nginx.append(" client_max_body_size 256M;\n\n");
// Include Magento nginx config
nginx.append(" include ").append(documentRoot).append("/nginx.conf.sample;\n\n");
// Health check
nginx.append(" location = /health {\n");
nginx.append(" access_log off;\n");
nginx.append(" return 200 'OK';\n");
nginx.append(" }\n");
nginx.append("}\n");
return nginx.toString();
}
/**
* Generate environment variables for database connection.
*
* @param spec the CMS specification
* @param dbHost database hostname
* @param dbPort database port
* @param dbName database name
* @param dbUser database username
* @return map of database environment variables
*/
public static Map<String, String> createDatabaseEnvironment(
CmsSpec spec,
String dbHost,
int dbPort,
String dbName,
String dbUser) {
// Delegate to the spec — each CmsSpec declares its own DB env var names via databaseEnvVars().
// Passwords are injected by ECS task definition secrets, not passed here as plaintext.
return spec.databaseEnvVars(dbHost, dbPort, dbName, dbUser);
}
/**
* Generate environment variables for Redis object cache.
*
* @param spec the CMS specification
* @param redisHost Redis hostname
* @param redisPort Redis port
* @return map of Redis environment variables
*/
public static Map<String, String> createRedisEnvironment(
CmsSpec spec,
String redisHost,
int redisPort) {
// Delegate to the spec — no hardcoded CMS IDs needed here.
return spec.redisEnvVars(redisHost, redisPort);
}
}