PHP Fundamentals

PHP Configuration & Best Practices

13 min Lesson 29 of 45

PHP Configuration & Best Practices

Proper PHP configuration and following best practices are crucial for building secure, performant, and maintainable applications. This lesson covers essential configuration settings and coding standards.

Understanding php.ini

The php.ini file contains configuration directives that control PHP behavior:

; Location of php.ini: ; - Linux: /etc/php/8.x/php.ini ; - Windows: C:\php\php.ini ; - macOS: /usr/local/etc/php/8.x/php.ini ; Find your php.ini location: php --ini ; View all PHP settings: php -i | grep "Configuration File"

Essential PHP.ini Settings

; === Error Handling === ; Development environment display_errors = On display_startup_errors = On error_reporting = E_ALL ; Production environment display_errors = Off display_startup_errors = Off error_reporting = E_ALL log_errors = On error_log = /var/log/php/error.log ; === Security Settings === expose_php = Off ; Hide PHP version allow_url_fopen = Off ; Disable remote file access allow_url_include = Off ; Disable remote file inclusion disable_functions = exec,passthru,shell_exec,system,proc_open,popen open_basedir = /var/www:/tmp ; Restrict file access ; === Performance Settings === max_execution_time = 30 ; Maximum execution time (seconds) max_input_time = 60 ; Maximum input parsing time memory_limit = 128M ; Maximum memory per script post_max_size = 8M ; Maximum POST data size upload_max_filesize = 2M ; Maximum upload file size ; === Session Settings === session.cookie_httponly = 1 ; Prevent JavaScript access to cookies session.cookie_secure = 1 ; Only send cookies over HTTPS session.use_strict_mode = 1 ; Reject uninitialized session IDs session.gc_maxlifetime = 1440 ; Session garbage collection (24 min) ; === OPcache (Production) === opcache.enable = 1 opcache.memory_consumption = 128 opcache.interned_strings_buffer = 8 opcache.max_accelerated_files = 10000 opcache.validate_timestamps = 0 ; Disable for production
Warning: Never set display_errors = On in production! This can expose sensitive information to attackers.

Runtime Configuration with ini_set()

You can change some settings at runtime using ini_set():

<?php // Change settings at runtime ini_set('display_errors', 1); ini_set('memory_limit', '256M'); ini_set('max_execution_time', 300); ini_set('error_log', '/custom/path/errors.log'); // Get current setting value echo ini_get('memory_limit'); // 256M echo ini_get('max_execution_time'); // 300 // Check if a directive can be changed $directives = [ 'display_errors', 'memory_limit', 'max_execution_time' ]; foreach ($directives as $directive) { $mode = ini_get_all($directive)[$directive]['access']; echo "$directive can be changed: "; echo ($mode & INI_USER) ? 'Yes' : 'No'; echo "\n"; } // Environment-based configuration if ($_SERVER['SERVER_NAME'] === 'localhost') { // Development ini_set('display_errors', 1); error_reporting(E_ALL); } else { // Production ini_set('display_errors', 0); ini_set('log_errors', 1); } ?>
Note: Not all php.ini directives can be changed with ini_set(). Some require changes in php.ini or .htaccess.

Security Best Practices

<?php // 1. Input Validation function validateEmail($email) { return filter_var($email, FILTER_VALIDATE_EMAIL) !== false; } function validateAge($age) { return filter_var($age, FILTER_VALIDATE_INT, [ 'options' => ['min_range' => 0, 'max_range' => 150] ]) !== false; } // 2. Output Escaping $userInput = "<script>alert('XSS')</script>"; echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8'); // 3. SQL Injection Prevention // ❌ Never do this: $sql = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'"; // ✅ Always use prepared statements: $stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?"); $stmt->execute([$_POST['email']]); // 4. Password Security $password = $_POST['password']; // Hash password $hash = password_hash($password, PASSWORD_DEFAULT); // Verify password if (password_verify($password, $hash)) { echo "Password correct"; } // 5. CSRF Protection session_start(); // Generate token if (!isset($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } // In form: // <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>"> // Verify token if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) { die('CSRF token validation failed'); } // 6. Secure Session Management ini_set('session.cookie_httponly', 1); ini_set('session.cookie_secure', 1); // Only for HTTPS ini_set('session.use_strict_mode', 1); session_start(); // Regenerate session ID after login session_regenerate_id(true); ?>

Code Organization Best Practices

<?php // 1. Use meaningful variable names // ❌ Bad $d = 86400; $arr = [1, 2, 3]; // ✅ Good $secondsPerDay = 86400; $userIds = [1, 2, 3]; // 2. Use constants for fixed values define('TAX_RATE', 0.15); define('MAX_LOGIN_ATTEMPTS', 5); // Or use const (better for classes) const DATABASE_HOST = 'localhost'; const DATABASE_NAME = 'myapp'; // 3. One class per file // File: User.php class User { private $id; private $email; public function __construct($id, $email) { $this->id = $id; $this->email = $email; } } // 4. Proper namespacing namespace App\Models; class Product { // Class implementation } // 5. Use type declarations (PHP 7+) function addNumbers(int $a, int $b): int { return $a + $b; } function getUser(int $id): ?User { // Returns User or null return $user ?? null; } // 6. DRY Principle (Don't Repeat Yourself) // ❌ Bad - Repeated code if ($user['role'] === 'admin') { // Admin code } if ($user['role'] === 'admin') { // Same check again } // ✅ Good - Single responsibility function isAdmin($user): bool { return $user['role'] === 'admin'; } if (isAdmin($user)) { // Admin code } ?>

Error Handling Best Practices

<?php // 1. Use exceptions for error handling class ValidationException extends Exception {} function registerUser($email, $password) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new ValidationException("Invalid email address"); } if (strlen($password) < 8) { throw new ValidationException("Password must be at least 8 characters"); } // Registration logic } try { registerUser($_POST['email'], $_POST['password']); } catch (ValidationException $e) { echo "Validation error: " . $e->getMessage(); } catch (Exception $e) { error_log($e->getMessage()); echo "An error occurred. Please try again."; } // 2. Fail fast function processPayment($amount) { if ($amount <= 0) { throw new InvalidArgumentException("Amount must be positive"); } if (!isUserAuthenticated()) { throw new Exception("User must be authenticated"); } // Process payment } // 3. Log errors properly function logError($message, $context = []) { $timestamp = date('Y-m-d H:i:s'); $contextJson = json_encode($context); error_log("[$timestamp] $message | Context: $contextJson"); } try { $result = riskyOperation(); } catch (Exception $e) { logError("Operation failed", [ 'exception' => get_class($e), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine() ]); } ?>

Performance Best Practices

<?php // 1. Use OPcache in production // Enable in php.ini: // opcache.enable=1 // opcache.memory_consumption=128 // 2. Avoid repeated function calls // ❌ Bad for ($i = 0; $i < count($items); $i++) { echo $items[$i]; } // ✅ Good $count = count($items); for ($i = 0; $i < $count; $i++) { echo $items[$i]; } // 3. Use built-in functions // ❌ Bad - Custom implementation function arraySum($arr) { $sum = 0; foreach ($arr as $val) { $sum += $val; } return $sum; } // ✅ Good - Built-in function $sum = array_sum($arr); // 4. Database query optimization // ❌ Bad - N+1 query problem $users = $db->query("SELECT * FROM users"); foreach ($users as $user) { $orders = $db->query("SELECT * FROM orders WHERE user_id = " . $user['id']); } // ✅ Good - Single query with JOIN $users = $db->query(" SELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id = o.user_id "); // 5. Use lazy loading class User { private $orders = null; public function getOrders() { if ($this->orders === null) { $this->orders = $this->loadOrders(); } return $this->orders; } } // 6. Cache expensive operations function getPopularProducts() { $cacheKey = 'popular_products'; $cached = cache_get($cacheKey); if ($cached !== false) { return $cached; } $products = expensiveDatabaseQuery(); cache_set($cacheKey, $products, 3600); // Cache for 1 hour return $products; } ?>
Tip: Enable OPcache in production to cache compiled PHP code and significantly improve performance.

Dependency Management with Composer

# Install Composer curl -sS https://getcomposer.org/installer | php # Create composer.json { "require": { "monolog/monolog": "^2.0", "guzzlehttp/guzzle": "^7.0" }, "autoload": { "psr-4": { "App\\": "src/" } } } # Install dependencies composer install # Update dependencies composer update # Autoload classes require 'vendor/autoload.php';
<?php // Using Composer autoloading require __DIR__ . '/vendor/autoload.php'; // Use installed packages use Monolog\Logger; use Monolog\Handler\StreamHandler; $log = new Logger('app'); $log->pushHandler(new StreamHandler('app.log', Logger::WARNING)); $log->warning('This is a warning'); // Autoload your own classes use App\Models\User; use App\Controllers\UserController; $user = new User(); $controller = new UserController(); ?>

Environment Configuration

<?php // .env file (never commit to git!) // DB_HOST=localhost // DB_NAME=myapp // DB_USER=root // DB_PASS=secret // APP_ENV=production // APP_DEBUG=false // Load environment variables function loadEnv($path) { if (!file_exists($path)) { throw new Exception(".env file not found"); } $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { if (strpos(trim($line), '#') === 0) { continue; // Skip comments } list($name, $value) = explode('=', $line, 2); $_ENV[trim($name)] = trim($value); putenv(trim($name) . '=' . trim($value)); } } loadEnv(__DIR__ . '/.env'); // Access environment variables $dbHost = $_ENV['DB_HOST'] ?? 'localhost'; $dbName = getenv('DB_NAME'); $isDebug = $_ENV['APP_DEBUG'] === 'true'; // Configuration based on environment $config = [ 'database' => [ 'host' => $_ENV['DB_HOST'], 'name' => $_ENV['DB_NAME'], 'user' => $_ENV['DB_USER'], 'pass' => $_ENV['DB_PASS'] ], 'app' => [ 'env' => $_ENV['APP_ENV'], 'debug' => $_ENV['APP_DEBUG'] === 'true' ] ]; ?>

Code Documentation

<?php /** * Calculate the total price of items in cart * * This function applies discounts and tax to calculate * the final total price for all items in the shopping cart. * * @param array $items Array of cart items with price and quantity * @param float $taxRate Tax rate as decimal (e.g., 0.15 for 15%) * @param float $discount Discount amount to subtract from total * @return float The final total price including tax and discount * @throws InvalidArgumentException If tax rate is negative */ function calculateTotal(array $items, float $taxRate = 0.15, float $discount = 0): float { if ($taxRate < 0) { throw new InvalidArgumentException("Tax rate cannot be negative"); } $subtotal = 0; foreach ($items as $item) { $subtotal += $item['price'] * $item['quantity']; } $total = $subtotal - $discount; $total += $total * $taxRate; return round($total, 2); } /** * User model representing a registered user * * @property int $id User ID * @property string $email User email address * @property string $name User full name */ class User { /** @var int User ID */ private $id; /** @var string User email */ private $email; /** * Create a new user instance * * @param int $id User ID * @param string $email User email */ public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } } ?>

Testing Best Practices

<?php // Install PHPUnit // composer require --dev phpunit/phpunit // Test file: tests/CalculatorTest.php use PHPUnit\Framework\TestCase; class CalculatorTest extends TestCase { public function testAddition() { $calculator = new Calculator(); $result = $calculator->add(2, 3); $this->assertEquals(5, $result); } public function testDivisionByZero() { $this->expectException(DivisionByZeroError::class); $calculator = new Calculator(); $calculator->divide(10, 0); } public function testMultiplication() { $calculator = new Calculator(); $this->assertEquals(6, $calculator->multiply(2, 3)); $this->assertEquals(0, $calculator->multiply(5, 0)); $this->assertEquals(-10, $calculator->multiply(5, -2)); } } // Run tests // vendor/bin/phpunit tests ?>

Coding Standards (PSR-12)

<?php // PSR-12 Coding Standard // 1. Opening PHP tag and namespace <?php namespace App\Controllers; // 2. Use statements use App\Models\User; use App\Services\EmailService; // 3. Class declaration class UserController { // 4. Properties private $emailService; protected $users = []; // 5. Constructor public function __construct(EmailService $emailService) { $this->emailService = $emailService; } // 6. Methods public function createUser(string $email, string $name): User { // 7. Control structures if (empty($email)) { throw new InvalidArgumentException('Email is required'); } // 8. Indentation: 4 spaces $user = new User(); $user->setEmail($email); $user->setName($name); // 9. Method chaining $user->setEmail($email) ->setName($name) ->setStatus('active'); return $user; } // 10. Array formatting private function getConfig(): array { return [ 'smtp_host' => 'localhost', 'smtp_port' => 587, 'from_email' => 'noreply@example.com', ]; } } ?>
Exercise:
  1. Create a configuration class that loads settings from a .env file
  2. Implement a secure login system with password hashing and CSRF protection
  3. Write a reusable validator class with methods for common validations
  4. Create a simple logger class that writes to different files based on log level
  5. Build a database wrapper class using PDO with prepared statements

Production Deployment Checklist

Before deploying to production:
  • ✓ Set display_errors = Off in php.ini
  • ✓ Enable error logging: log_errors = On
  • ✓ Set appropriate file permissions (644 for files, 755 for directories)
  • ✓ Remove development tools and debug code
  • ✓ Enable OPcache for better performance
  • ✓ Use environment variables for sensitive data
  • ✓ Implement CSRF and XSS protection
  • ✓ Use HTTPS (SSL/TLS certificates)
  • ✓ Set secure session cookie parameters
  • ✓ Keep PHP and dependencies updated
  • ✓ Implement rate limiting for APIs
  • ✓ Set up automated backups

Common Mistakes to Avoid

<?php // ❌ 1. Displaying errors in production ini_set('display_errors', 1); // ❌ 2. Not using prepared statements $sql = "SELECT * FROM users WHERE id = " . $_GET['id']; // ❌ 3. Storing passwords in plain text $password = $_POST['password']; $sql = "INSERT INTO users (password) VALUES ('$password')"; // ❌ 4. Not validating user input $email = $_POST['email']; sendEmail($email); // ❌ 5. Using deprecated functions mysql_connect(); // Removed in PHP 7 ereg(); // Removed in PHP 7 // ✅ Correct approaches ini_set('display_errors', 0); ini_set('log_errors', 1); $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_GET['id']]); $hashedPassword = password_hash($_POST['password'], PASSWORD_DEFAULT); if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) { sendEmail($_POST['email']); } mysqli_connect(); // Use mysqli or PDO preg_match(); // Use PCRE functions ?>
Remember: Security and performance should be considered from the beginning of your project, not added as an afterthought. Follow these best practices consistently to build robust PHP applications.