# Testing Methodologies Standards for PHP
This document outlines the standards for testing methodologies in PHP projects. Adhering to these standards ensures maintainable, reliable, and secure code. This document focuses on unit, integration, and end-to-end testing strategies specifically within the PHP ecosystem and leverages modern approaches based on the latest PHP versions.
## 1. General Testing Principles
### 1.1 Write Tests From the Start (Test-Driven Development - TDD)
* **Do This:** Adopt a TDD approach where possible. Write tests *before* implementing the code the tests will verify.
* **Don't Do This:** Write tests as an afterthought, or skip them altogether.
* **Why:** TDD leads to better design, reduces bugs, and results in more testable code. It forces you to think about the desired interface and behavior before implementation.
### 1.2 Test Early, Test Often
* **Do This:** Integrate testing into your development workflow. Run tests frequently, ideally with every commit or push.
* **Don't Do This:** Wait until the end of a sprint or release cycle to start testing.
* **Why:** Frequent testing helps you catch errors early on, when they are easier and cheaper to fix. It also provides continuous feedback on the health of your codebase.
### 1.3 Write Independent Tests
* **Do This:** Ensure each test is isolated and independent. Avoid shared state or dependencies between tests. Use mocking and stubbing appropriately.
* **Don't Do This:** Create tests that rely on the outcome of other tests.
* **Why:** Independent tests are easier to understand, maintain, and execute. They also provide more reliable results. If one test fails, it shouldn't cascade failures to others.
### 1.4 Use Clear and Descriptive Test Names
* **Do This:** Name your tests descriptively, clearly indicating what the test is verifying. Use a consistent naming convention.
* **Don't Do This:** Use vague or ambiguous test names.
* **Why:** Clear test names make it easier to understand what the test is doing and why it failed if it does.
### 1.5 Aim for High Test Coverage
* **Do This:** Strive for a high level of test coverage, aiming for at least 80% line coverage. Focus on covering critical functionality and edge cases.
* **Don't Do This:** Exclude complex or difficult-to-test code from your test suite.
* **Why:** High test coverage provides greater confidence in the correctness of your code. It also makes it easier to refactor and maintain your codebase. Note, however, that coverage alone is not enough; the tests *must* be meaningful.
### 1.6 Use Assertions Appropriately
* **Do This:** Use the appropriate assertion methods for the specific condition you are testing. Check the assertions offered by your testing framework (e.g., PHPUnit).
* **Don't Do This:** Use generic assertions like "assertTrue()" or "assertFalse()" without providing specific context.
* **Why:** Using the correct assertion methods improves the readability and clarity of your tests.
### 1.7 Keep Tests Concise
* **Do This:** Keep tests focused and concise. Each test should verify a single aspect of the code's behavior.
* **Don't Do This:** Write overly complex or lengthy tests that cover multiple scenarios.
* **Why:** Concise tests are easier to understand, maintain, and debug.
## 2. Unit Testing
### 2.1 Purpose of Unit Testing
Unit tests verify the behavior of individual units of code, such as classes, functions, or methods, in isolation.
### 2.2 PHPUnit
PHPUnit is the de facto standard testing framework for PHP. This standard assumes use of PHPUnit, but the principles apply regardless of framework.
### 2.3 Mocking and Stubbing
* **Do This:** Use mocking and stubbing to isolate the unit under test from its dependencies. Use a mocking framework like PHPUnit's built-in mocking capabilities or Mockery.
* **Don't Do This:** Test units of code with real dependencies, as this introduces external factors and makes tests less reliable and more difficult to maintain.
* **Why:** Mocking and stubbing allow you to control the behavior of dependencies, making it easier to test the unit in isolation and cover all possible scenarios.
"""php
emailService = $emailService;
}
public function register(string $email, string $password): void
{
// Hash the password, save to database, etc.
// Send a welcome email
$this->emailService->sendWelcomeEmail($email);
}
}
class EmailService
{
public function sendWelcomeEmail(string $email): void
{
// Actually send the email (implementation omitted for brevity)
}
}
class UserTest extends TestCase
{
public function testRegisterSendsWelcomeEmail(): void
{
// Create a mock of the EmailService
$emailServiceMock = $this->createMock(EmailService::class);
// Set up the expectation that the sendWelcomeEmail method will be called once
// with the expected email address
$emailServiceMock->expects($this->once())
->method('sendWelcomeEmail')
->with('test@example.com');
// Create the User object with the mock EmailService
$user = new User($emailServiceMock);
// Call the register method
$user->register('test@example.com', 'password');
// The assertion in the mock setup will verify that the sendWelcomeEmail method was called as expected.
}
}
"""
### 2.4 Data Providers
* **Do This:** Use data providers to run the same test with different input values.
* **Don't Do This:** Duplicate the same test code for different input values.
* **Why:** Data providers reduce code duplication and make it easier to test different scenarios.
"""php
assertEquals($expectedLength, StringUtils::length($input));
}
public static function stringLengthProvider(): array
{
return [
'empty string' => ['', 0],
'single character' => ['a', 1],
'multiple characters' => ['abc', 3],
'unicode characters' => ['你好世界', 6], // UTF-8 encoding: 3 bytes per character * 2 words
];
}
}
"""
### 2.5 Test Doubles
* **Do This:** Use test doubles (mocks, stubs, spies, dummies, fakes) appropriately based on your use case.
* **Don't Do This:** Overuse mocks when a simple stub would suffice, or vice-versa. Understand the difference between the different types of test doubles.
* **Why:** Using the correct type of test double makes your tests more focused and easier to maintain.
### 2.6 Assertion Messages
* **Do This:** Include informative messages with your assertions to help diagnose failures.
* **Don't Do This:** Rely on the default assertion messages which can be vague.
* **Why:** Helps identify the cause of failure faster.
"""php
add(2, 3);
$this->assertEquals(5, $result, "The sum of 2 and 3 should be 5.");
}
}
"""
## 3. Integration Testing
### 3.1 Purpose of Integration Testing
Integration tests verify the interaction between different parts of the system, such as classes, modules, or external services.
### 3.2 Database Interactions
* **Do This:** Use a separate test database for integration tests. Use database transactions to ensure that tests do not affect the state of the database.
* **Don't Do This:** Run integration tests against a production database, or without using transactions.
* **Why:** Using a separate test database prevents integration tests from interfering with the production environment. Transactions ensure that each test runs in isolation and that the database is left in a consistent state. Libraries like Doctrine ORM and Laravel's database testing tools can help.
"""php
pdo = new PDO('sqlite::memory:'); // In-memory SQLite database for testing
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Create a users table (if it doesn't exist)
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL
)
");
// Begin a transaction
$this->pdo->beginTransaction(); // Start transaction before each test
}
protected function tearDown(): void
{
// Rollback the transaction after each test
$this->pdo->rollBack(); // Rollback transaction after each test to clear data
}
public function testUserCanRegisterSuccessfully(): void
{
$email = 'test@example.com';
$password = 'password123';
// Simulate user registration (replace with actual registration logic)
$stmt = $this->pdo->prepare("INSERT INTO users (email, password) VALUES (?, ?)");
$stmt->execute([$email, password_hash($password, PASSWORD_DEFAULT)]);
// Verify the user was inserted (replace with actual verification logic)
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
$this->assertIsArray($user, 'User should be registered in the database');
$this->assertEquals($email, $user['email'], 'User email should match');
$this->assertTrue(password_verify($password, $user['password']), 'Password should be correctly hashed');
}
}
"""
### 3.3 API Interactions
* **Do This:** Use a mocking library to mock external API calls during integration tests OR create a dedicated testing API endpoint that returns controlled, deterministic responses. Utilize tools like Guzzle to make HTTP requests.
* **Don't Do This:** Rely on live external APIs during integration tests, as this makes tests unreliable and slow.
* **Why:** Mocking API calls allows you to control the behavior of external services and isolate your system from external failures. Using a test API ensures predictable behavior and avoids unintended side effects on real systems.
### 3.4 Message Queues
* **Do This:** If your application uses message queues, write integration tests that verify the sending and receiving of messages.
* **Don't Do This:** Skip testing message queue interactions, as this can lead to integration issues.
* **Why:** Integration tests verify that messages are being sent and received correctly, and that the system responds appropriately to different message types.
### 3.5 Container Initialization
* **Do This:** Carefully examine interactions that involve dependency injection containers. Ensure services are correctly configured and resolved.
* **Don't Do This:** Ignore the testing of container wiring and configuration, as misconfiguration can have subtle and impactful consequences.
* **Why:** DI container misconfigurations can lead to runtime errors that are hard to trace. Automated tests are required.
### 3.6 Real Implementations vs Stubs/Mocks
* **Do This:** Use real implementations of infrastructure components (databases, message queues) when possible in integration tests, relying on the principle that *integration* tests exercise *integration*. Carefully consider the trade-offs of using mocks vs setting up infrastructure for test purposes.
* **Don't Do This:** Replace too many components with mocks, essentially turning the integration test into a unit test.
* **Why:** True integration tests provide more confidence that the system will function correctly in a production-like environment.
## 4. End-to-End (E2E) Testing
### 4.1 Purpose of End-to-End Testing
End-to-end tests verify the entire system flow from beginning to end, including UI, backend, and external services. They simulate real user interactions.
### 4.2 Tools and Technologies
* **Do This:** Use a browser automation tool like Selenium, Cypress, or Codeception to automate end-to-end tests. Consider using a headless browser for faster execution.
* **Don't Do This:** Manually test the entire system flow for every release.
* **Why:** Browser automation allows you to create repeatable and reliable end-to-end tests that cover critical user flows.
### 4.3 Test Data Management
* **Do This:** Use realistic test data, but avoid using real user data. Seed the database with test data before running end-to-end tests.
* **Don't Do This:** Use personally identifiable information (PII) in your test data.
* **Why:** Using realistic test data ensures that the tests accurately reflect real-world scenarios. Using test data in the database ensures tests are repeatable and reliable.
### 4.4 Test Environment
* **Do This:** Run end-to-end tests against a staging environment that is as close as possible to the production environment.
* **Don't Do This:** Run end-to-end tests against a development environment, as this may not accurately reflect the production environment.
* **Why:** Running tests in a staging environment provides greater confidence that the system will work correctly in production.
### 4.5 Asynchronous Operations
* **Do This:** Implement appropriate waiting mechanisms to handle asynchronous operations, such as AJAX requests or background jobs.
* **Don't Do This:** Rely on fixed timeouts, as this can lead to flaky tests.
* **Why:** Asynchronous operations can introduce timing issues in end-to-end tests. Waiting mechanisms ensure that the tests wait for the operations to complete before proceeding. This is critical with modern Javascript frameworks.
"""php
request('GET', '/');
$this->assertSelectorTextContains('h1', 'Welcome to My Website');
}
public function testFormSubmissionWorks(): void
{
$client = static::createPantherClient();
$client->request('GET', '/contact');
$client->submitForm('Submit', [
'name' => 'John Doe',
'email' => 'john.doe@example.com',
'message' => 'This is a test message',
]);
// Wait for the success message to appear (using a selector)
$client->waitFor('.alert-success'); // Wait using a CSS selector
$this->assertSelectorTextContains('.alert-success', 'Message sent successfully!');
}
}
"""
### 4.6 Visual Regression Testing
* **Do This:** Consider integrating visual regression testing to detect unintended UI changes.
* **Don't Do This:** Rely solely on functional tests, as they may not catch visual regressions.
* **Why:** Visual regression testing helps to ensure that the UI remains consistent across different releases.
## 5. Code Coverage and Analysis
### 5.1 Code Coverage Tools
* **Do This:** Utilize tools like Xdebug to generate code coverage reports. Integrate these reports into your CI/CD pipeline.
* **Don't Do This:** Ignore code coverage metrics. Treat code coverage as the *goal*, rather than a *metric*.
* **Why:** Code coverage reports provide insights into the areas of your code that are not covered by tests. High coverage is *only* a proxy.
### 5.2 Static Analysis
* **Do This:** Use static analysis tools like PHPStan or Psalm to identify potential errors and code quality issues.
* **Don't Do This:** Skip static analysis, as this can lead to runtime errors and maintainability issues.
* **Why:** Static analysis can detect errors and code quality issues early on, before they make it into production.
### 5.3 Mutation Testing
* **Do This:** Consider using mutation testing tools like Infection to assess the quality of your tests.
* **Don't Do This:** Assume that high code coverage automatically means that your tests are effective.
* **Why:** Mutation testing can help you identify tests that are not actually verifying the behavior of the code.
## 6. Continuous Integration/Continuous Deployment (CI/CD)
### 6.1 Automated Testing
* **Do This:** Integrate all types of tests (unit, integration, end-to-end) into your CI/CD pipeline. Run the tests automatically with every commit or pull request.
* **Don't Do This:** Run tests manually, as this is time-consuming and error-prone.
* **Why:** Automated testing ensures that tests are run consistently and that errors are caught early on.
### 6.2 Build Status
* **Do This:** Display the build status prominently in your repository, e.g., using a badge.
* **Don't Do This:** Ignore failing builds.
* **Why:** The build status provides immediate feedback on the health of the codebase. Failing builds should be addressed promptly.
### 6.3 Deployment Pipelines
* **Do This:** Automate the deployment process using a CI/CD tool like Jenkins, GitLab CI, GitHub Actions, or CircleCI.
* **Don't Do This:** Deploy code manually, as this is error-prone.
* **Why:** Automated deployment ensures that code is deployed consistently and reliably.
## 7. Security Testing
### 7.1 Static Analysis for Security
* **Do This:** Utilize static analysis tools specifically designed to identify security vulnerabilities in PHP code (e.g., Psalm with security plugins).
* **Don't Do This:** Rely solely on general-purpose static analysis tools.
* **Why:** Dedicated security analysis tools can detect vulnerabilities such as SQL injection, cross-site scripting (XSS), and other common web application security issues.
### 7.2 Vulnerability Scanning
* **Do This:** Integrate vulnerability scanning tools into your CI/CD pipeline to detect known vulnerabilities in your dependencies.
* **Don't Do This:** Neglect dependency security, as this can lead to security breaches.
* **Why:** Vulnerability scanning can identify and flag dependencies with known security vulnerabilities, allowing you to update or replace them.
### 7.3 Penetration Testing
* **Do This:** Conduct regular penetration testing to identify security vulnerabilities that may not be detected by automated tools.
* **Don't Do This:** Assume that automated testing is sufficient to ensure security.
* **Why:** Penetration testing can uncover vulnerabilities that may be missed by automated tools, such as business logic flaws or complex attack vectors.
### 7.4 Input Validation and Output Encoding
* **Do This:** Write tests specifically to verify the input validation and output encoding mechanisms of your application.
* **Don't Do This:** Assume that input validation and output encoding are correctly implemented without explicit verification.
* **Why:** These security measures are critical to prevent XSS and injection attacks.
### 7.5 Authentication and Authorization
* **Do This:** Thoroughly test the authentication and authorization mechanisms of your application, including user registration, login, password reset, and access control.
* **Don't Do This:** Overlook security tests in favor of solely functional ones.
* **Why:** Flaws in authentication and authorization can grant unauthorized access to sensitive data and functionality.
## 8. Performance Testing
### 8.1 Load Testing
* **Do This:** Conduct load testing to simulate realistic user traffic and identify performance bottlenecks. Tools: Apache JMeter, Locust.
* **Don't Do This:** Assume your application can handle peak loads without empirical testing.
* **Why:** Load testing helps to ensure that your application can handle the expected user traffic.
### 8.2 Performance Profiling
* **Do This:** Use performance profiling tools like Xdebug and Blackfire.io to identify slow code paths and optimize performance.
* **Don't Do This:** Rely on guesswork to identify performance bottlenecks.
* **Why:** Profiling helps you pinpoint performance issues and focus your optimization efforts on the most critical areas.
### 8.3 Database Optimization
* **Do This:** Analyze database queries and optimize them for performance. Use database profiling tools to identify slow queries. Check index usage. Consider caching strategies.
* **Don't Do This:** Ignore database performance, as this can be a major bottleneck.
* **Why:** Database performance is critical to the overall performance of your application.
### 8.4 Caching
* **Do This:** Implement caching strategies to reduce database load and improve response times. Use caching technologies like Redis or Memcached. Implement HTTP caching.
* **Don't Do This:** Over-cache or under-cache. Cache invalidation can be tricky.
* **Why:** Caching can significantly improve the performance of your application.
## 9. Maintaining the Test Suite
### 9.1 Refactor Tests
* **Do This:** Refactor your tests regularly to keep them clean, maintainable, and relevant.
* **Don't Do This:** Let your tests become outdated or difficult to understand.
* **Why:** A well-maintained test suite is easier to understand, modify, and extend.
### 9.2 Remove Duplication
* **Do This:** Remove duplicate test code by using helper functions, test traits, or custom assertion methods.
* **Don't Do This:** Duplicate test code, as this makes the test suite more difficult to maintain.
* **Why:** Reducing duplication makes the test suite more concise and easier to maintain.
### 9.3 Keep Tests Up-to-Date
* **Do This:** Update your tests whenever you change the code they are verifying.
* **Don't Do This:** Let your tests become out of sync with the code.
* **Why:** Up-to-date tests provide accurate feedback on the correctness of your code. Stale tests give a false sense of security.
### 9.4 Track Test Execution Time
* **Do This:** Regularly monitor the execution time of your test suite. Optimize slow tests.
* **Don't Do This:** Ignore slow test suites, as this can slow down the development process.
* **Why:** Slow tests can make the development process less efficient. Optimize slow tests or parallelize them.
By adhering to these comprehensive testing standards, PHP developers can significantly improve the quality, reliability, and security of their applications. This also greatly improves the efficiency of code generated by AI coding assistants.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Deployment and DevOps Standards for PHP This document outlines the necessary standards for effective deployment and DevOps practices within PHP projects. It focuses on establishing reliable, automated, and scalable processes that align with modern PHP development, emphasizing continuous integration, continuous delivery, automated testing, and production environment configuration. ## 1. Build Processes & CI/CD ### 1.1. Continuous Integration/Continuous Delivery (CI/CD) * **Do This:** Implement a CI/CD pipeline for every PHP project, regardless of size. * **Don't Do This:** Manually deploy code changes to production servers without automated testing or rollback strategies. **Explanation:** CI/CD automates testing and deployment, reducing human error and accelerating release cycles. Automated pipelines offer faster feedback loops, enabling developers to efficiently address production issues. **Automated Pipeline Configuration (GitHub Actions Example):** """yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.2' # Updated to PHP 8.2 extensions: mbstring, intl, dom, pdo, pdo_mysql, zip - name: Install Dependencies run: composer install --no-dev --optimize-autoloader --no-interaction - name: Run Tests run: ./vendor/bin/phpunit - name: Deploy to Server uses: appleboy/ssh-action@master with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USERNAME }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: 22 script: | cd /var/www/your-project git pull origin main composer install --no-dev --optimize-autoloader --no-interaction php artisan migrate --force # Laravel specific, run DB migrations php artisan cache:clear # Laravel specific, clear cache """ **Explanation:** * The YAML file details the CI/CD workflow triggered upon pushing to the "main" branch. * It sets up PHP 8.2 (latest stable version at the time of writing) with necessary extensions. * Install Composer dependencies and run PHPUnit tests to ensure code integrity. * Uses SSH to securely deploy the code to the production server, pulls the latest changes, installs dependencies and runs database migrations. **Anti-Pattern:** Configuring CI/CD pipelines directly on production servers. Pipelines should exist as code within your repository. ### 1.2. Build Artifacts * **Do This:** Create build artifacts (e.g., tarballs, Docker images) for deployments. * **Don't Do This:** Deploy directly from the repository without creating intermediate artifacts. **Explanation:** Build artifacts ensure consistency across different environments and are especially useful during rollbacks. They encapsulate a specific, tested version of the application. **Creating a Build Artifact (Tarball):** """bash #!/bin/bash VERSION=$(git describe --tags --abbrev=0) TIMESTAMP=$(date +%Y%m%d%H%M%S) ARTIFACT_NAME="your-project-${VERSION}-${TIMESTAMP}.tar.gz" # Exclude development-related directories tar -czvf "$ARTIFACT_NAME" \ --exclude='./.git' \ --exclude='./node_modules' \ --exclude='./tests' \ --exclude='./.github' \ . echo "Created artifact: $ARTIFACT_NAME" # Optionally, upload to an artifact storage (e.g., AWS S3, Artifactory) # aws s3 cp "$ARTIFACT_NAME" s3://your-bucket/artifacts/ """ **Explanation:** This script creates a compressed tarball of your project directory, excluding development-related files (like ".git", "node_modules", and test directories). The artifact name includes the version and timestamp for easy identification. **Using Docker for Artifacts:** """dockerfile # Dockerfile FROM php:8.2-fpm-alpine # Use a specific PHP 8.2 image WORKDIR /var/www/html COPY . /var/www/html RUN apk add --no-cache $PHPIZE_DEPS \ && pecl install xdebug \ && docker-php-ext-enable xdebug \ && apk del $PHPIZE_DEPS \ && composer install --no-dev --optimize-autoloader --no-interaction EXPOSE 9000 CMD ["php-fpm"] """ **Explanation:** * Uses a PHP 8.2 FPM Alpine image as a base. * Copies the application code, installs dependencies, and configures the environment. * The resulting Docker image serves as a complete, reproducible build artifact. **Anti-Pattern:** Including sensitive information (API keys, database passwords) directly inside a Docker image. Use environment variables or secrets management. ### 1.3. Version Control * **Do This:** Use a branching strategy (e.g., Gitflow) for managing releases, hotfixes, and feature development. * **Don't Do This:** Directly commit to the "main" branch, especially in collaborative or production-bound environments. **Explanation:** Branching allows parallel development efforts without stability issues and ensures a clear separation between stable and experimental code. **Example Gitflow Workflow:** 1. "develop" branch: Main development branch for feature integration. 2. "feature/*" branches: Created for new features based on the "develop" branch. Merge back into "develop". 3. "release/*" branches: Created for preparing a release from "develop". Merge into "main" and "develop". 4. "hotfix/*" branches: Created from "main" for critical bug fixes. Merge into "main" and "develop". 5. "main" branch: Represents production-ready code. Tagged with release versions. ## 2. Production Considerations ### 2.1. Environment Variables * **Do This:** Store all configurations (database credentials, API keys, external service endpoints) as environment variables. * **Don't Do This:** Hardcode sensitive data in code or configuration files directly. **Explanation:** Environment variables provide a standardized and secure way to configure applications without modifying the codebase. They are runtime-specific, allowing different configurations for development, staging, and production environments. **Using Environment Variables in PHP (with ".env" file and "vlucas/phpdotenv"):** """php <?php require_once __DIR__ . '/vendor/autoload.php'; use Dotenv\Dotenv; $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->load(); $db_host = $_ENV['DB_HOST']; $db_user = $_ENV['DB_USER']; $db_pass = $_ENV['DB_PASS']; $db_name = $_ENV['DB_NAME']; echo "Connecting to database: $db_name on $db_host\n"; try { $pdo = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_pass); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); echo "Successfully connected to the database.\n"; // Your database operations here... } catch (PDOException $e) { echo "Connection failed: " . $e->getMessage() . "\n"; } """ ".env" file (example): """ DB_HOST=localhost DB_USER=your_username DB_PASS=your_password DB_NAME=your_database API_KEY=your_secret_api_key """ **Explanation:** * Uses the "vlucas/phpdotenv" library to load environment variables from a ".env" file into the "$_ENV" superglobal. * Retrieves database credentials from environment variables. * This approach keeps credentials separate from the codebase and allows for different configurations in each environment. On real servers, environment variables should be set directly in the system environment, NOT relied upon from .env files. **Anti-Pattern:** Checking ".env" files into version control. This exposes secrets to anyone with access to the repository. ### 2.2. Logging and Monitoring * **Do This:** Implement comprehensive logging and monitoring solutions to proactively identify and address issues. * **Don't Do This:** Rely solely on server logs; use aggregated logging services and monitoring tools. **Explanation:** Logging provides insights into application behavior, while monitoring tracks performance metrics. They are crucial for identifying bottlenecks, errors, and security issues. **Example utilizing Monolog for Logging:** """php <?php require_once __DIR__ . '/vendor/autoload.php'; use Monolog\Logger; use Monolog\Handler\StreamHandler; // Create a log channel $log = new Logger('app'); $log->pushHandler(new StreamHandler(__DIR__.'/logs/app.log', Logger::WARNING)); // Log some events $log->warning('Foo'); $log->error('Bar'); """ **Explanation:** * Install Monolog: "composer require monolog/monolog" * Creates a Logger instance and configures it to write to a file ("app.log") with a minimum log level of "WARNING". * Logs a warning and an error message. Monolog offers fine-grained control over log format, handlers (file, database, syslog, etc..) and processors (adding extra context to log messages). **Monitoring with Prometheus and Grafana:** 1. **Expose Metrics (Example using a custom collector):** You'd need to install a Prometheus client library for PHP, such as "promphp/prometheus_client_php". This example uses a static variable to avoid having to build a proper, and long, class. """php <?php use Prometheus\CollectorRegistry; use Prometheus\Counter; class MyAppMetrics { private static ?Counter $httpRequestsTotalCounter = null; public static function getHttpRequestCounter(CollectorRegistry $registry): Counter { if (self::$httpRequestsTotalCounter === null) { self::$httpRequestsTotalCounter = $registry->registerCounter( 'http_requests_total', 'Total number of HTTP requests', ['method', 'path'] ); } return self::$httpRequestsTotalCounter; } } //Usage (In your request handling logic for example): require 'vendor/autoload.php'; use Prometheus\CollectorRegistry; use Prometheus\Storage\InMemory; $adapter = new InMemory(); $registry = new CollectorRegistry($adapter, false); $requestMethod = $_SERVER['REQUEST_METHOD']; $requestPath = $_SERVER['REQUEST_URI']; MyAppMetrics::getHttpRequestCounter($registry)->inc(['method' => $requestMethod, 'path' => $requestPath]); header('Content-type: text/plain'); $result = $registry->getMetricFamilySamples(); $serializer = new \Prometheus\RenderTextFormat(); $metricOutput = $serializer->render($result); echo $metricOutput; """ 2. **Configure Prometheus:** Configure Prometheus to scrape the exposed endpoint. 3. **Create Grafana Dashboard:** Visualize the data in Grafana. **Anti-Pattern:** Writing logs to a single file without rotation. This can lead to disk space exhaustion and performance degradation. Implement log rotation using tools like "logrotate". ### 2.3. Caching * **Do This:** Implement caching strategies at multiple levels (HTTP cache, opcode cache, data cache) to improve performance. * **Don't Do This:** Over-cache data or neglect cache invalidation strategies. **Explanation:** Caching reduces the load on your servers and databases by storing frequently accessed data in memory or on disk. Proper cache invalidation is crucial to avoid serving stale data. **Opcode Caching (Zend OPcache):** Ensure Zend OPcache is enabled and properly configured in "php.ini": """ini opcache.enable=1 opcache.enable_cli=1 opcache.memory_consumption=128 ; Adjust as needed opcache.interned_strings_buffer=8 ; Adjust as needed opcache.max_accelerated_files=10000 ; Adjust as needed opcache.validate_timestamps=1 ; Set to 0 in production (after thorough testing) """ **Explanation:** OPcache improves the performance of PHP by storing precompiled script bytecode in shared memory, thereby removing the need for PHP to load and parse scripts on each request. **HTTP Caching (Proper Headers):** """php <?php // Example: Cache for 1 hour $cache_expire = 60*60; header("Cache-Control: public, max-age=$cache_expire"); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $cache_expire) . ' GMT'); ?> """ **Data Caching (Redis):** """php <?php require 'vendor/autoload.php'; use Predis\Client; $redis = new Client([ 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => 6379, ]); $key = 'my_data'; $cachedData = $redis->get($key); if ($cachedData) { echo "Data from cache: " . $cachedData . "\n"; } else { $data = 'Some expensive data to compute'; // Simulate retrieving data from a database or other source $redis->set($key, $data); $redis->expire($key, 3600); // Cache for 1 hour echo "Data from source: " . $data . "\n"; } """ **Explanation:** * Installs redis via composer. * Connects to Redis. * Retrieves data from cache (if it exists). * If the data is not in the cache, it is retrieved from the source, stored in Redis, and a TTL (Time-To-Live) is set. This code shows the basic principle. More sophisticated caching solutions will track dependencies and cache keys for more accurate invalidation. **Anti-Pattern:** Using file-based caching in a distributed environment. This creates inconsistencies between servers. Use a centralized caching solution like Redis or Memcached. ### 2.4. Database Migrations * **Do This:** Use a database migration tool (e.g., Flyway, Doctrine Migrations, Laravel Migrations) to manage database schema changes. * **Don't Do This:** Apply database changes manually on production, or directly without version control. **Explanation:** Database migrations provide a version-controlled and automated way to evolve your database schema. This ensures consistency and allows you to easily roll back changes if necessary. **Laravel Migration Example:** """php <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } } """ **Explanation:** * Defines a migration class that creates a "users" table. * The "up()" method defines the schema changes to be applied. * The "down()" method defines the rollback operation (dropping the table). **Anti-Pattern:** Granting direct access to the production database to developers. Use migration tools and enforce code review processes for schema changes. ### 2.5. Security * **Do This:** Implement security best practices for PHP, including input validation, output encoding, protection against common web vulnerabilities (SQL injection, XSS, CSRF), and regular security audits. * **Don't Do This:** Trust user input directly, or expose detailed error messages in production. **Explanation:** Securing PHP applications requires a multi-layered approach, from code-level hardening to server-side protection. **Input Validation and Sanitization:** """php <?php $email = $_POST['email'] ?? ''; // Validate email format if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { echo "Invalid email format"; exit; } // Sanitize email to prevent injection $email = filter_var($email, FILTER_SANITIZE_EMAIL); """ **Output Encoding (preventing XSS):** """php <?php $username = $_POST['username'] ?? ''; // Escape output using htmlspecialchars echo htmlspecialchars($username, ENT_QUOTES, 'UTF-8'); ?> """ **Using Prepared Statements (preventing SQL injection):** """php <?php $stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?"); $stmt->execute([$email]); $user = $stmt->fetch(); ?> """ **Best practices checklist** * Keep PHP and all dependencies up to date with security patches. * Use a Content Security Policy (CSP) to mitigate XSS. * Enforce HTTPS and use HSTS. * Regularly scan your application for vulnerabilities using tools like Snyk or OWASP ZAP. ## 3. Modern Approaches and Patterns ### 3.1. Infrastructure as Code (IaC) * **Do This:** Define and manage infrastructure using code (e.g., Terraform, Ansible, CloudFormation). * **Don't Do This:** Manually configure servers and infrastructure components. **Explanation:** Infrastructure as Code allows you to provision and manage your infrastructure in a predictable, repeatable, and version-controlled manner. **Example Terraform Configuration (provisioning a web server):** """terraform resource "aws_instance" "web_server" { ami = "ami-0c55b243c1244368a" # Example AMI ID instance_type = "t2.micro" key_name = "your_key_pair" tags = { Name = "Web Server" } user_data = <<-EOF #!/bin/bash sudo apt update sudo apt install -y apache2 php libapache2-mod-php echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/index.php sudo systemctl restart apache2 EOF } output "public_ip" { value = aws_instance.web_server.public_ip } """ **Explanation:** * This Terraform configuration defines a resource for an AWS EC2 instance, specifying the AMI, instance type, key pair, and tags. * The "user_data" script installs Apache, PHP, and a basic "phpinfo()" page on the instance. * Run "terraform init", "terraform plan", and "terraform apply" to provision the infrastructure. **Anti-pattern:** Storing Terraform state files locally. Use remote state storage (e.g., AWS S3, HashiCorp Consul) for collaboration and version control. ### 3.2. Containerization (Docker) and Orchestration (Kubernetes) * **Do This:** Package PHP applications into Docker containers for portability and scalability. Use Kubernetes or similar orchestration platforms for deployment and management. * **Don't Do This:** Deploy directly to virtual machines without containerization. **Explanation:** Containerization provides a consistent and isolated environment for running PHP applications. Kubernetes automates the deployment, scaling, and management of containerized applications. **Example Kubernetes Deployment:** """yaml apiVersion: apps/v1 kind: Deployment metadata: name: your-app-deployment spec: replicas: 3 selector: matchLabels: app: your-app template: metadata: labels: app: your-app spec: containers: - name: your-app-container image: your-docker-registry/your-app:latest ports: - containerPort: 80 env: - name: DB_HOST valueFrom: secretKeyRef: name: db-credentials key: host - name: DB_USER valueFrom: secretKeyRef: name: db-credentials key: user """ **Explanation:** * Defines a Kubernetes Deployment that manages a specified number of replicas of your application. * Specifies the Docker image to use, the container port, and environment variables. **Anti-Pattern:** Running a single container instance on a single server. Utilize Kubernetes or similar to benefit from auto-scaling, self-healing, and rolling deployments. ### 3.3. Microservices * **Do This:** Consider breaking down monolithic PHP applications into smaller, independent microservices. * **Don't Do This:** Unnecessarily create microservices for simple applications. **Explanation:** Microservices improve scalability, maintainability, and fault isolation. **Technology considerations when using microservices:** * Use an API gateway to route requests to the appropriate microservice. * Implement service discovery to allow microservices to locate each other. * Use asynchronous communication (e.g., message queues) for decoupling services. * Implement distributed tracing to monitor requests that span multiple microservices. --- By following these standards, PHP development teams can ensure their deployment and DevOps processes are reliable, automated, secure, and scalable. This will lead to faster release cycles, fewer production issues, and improved overall application quality.
# Core Architecture Standards for PHP This document outlines the core architectural standards for PHP development, focusing on fundamental patterns, project structure, and organization. Following these standards will improve maintainability, performance, security, and collaboration within development teams. This guide is tailored for modern PHP (version 8.1 and above), emphasizing current best practices. ## 1. Architectural Patterns ### 1.1. Layered Architecture **Standard:** Implement a layered architecture to separate concerns and improve maintainability. * **Do This:** Divide your application into distinct layers: Presentation (UI), Application (Business Logic), Domain (Core Concepts), and Infrastructure (Data Access, external services). * **Don't Do This:** Mix presentation logic directly with data access or business logic. **Why:** Layered architecture promotes separation of concerns, making code easier to understand, test, and modify. **Code Example:** """php // Infrastructure Layer (Data Access) namespace App\Infrastructure\Persistence\Doctrine; use App\Domain\Model\User; use App\Domain\Repository\UserRepositoryInterface; use Doctrine\ORM\EntityManagerInterface; class DoctrineUserRepository implements UserRepositoryInterface { private EntityManagerInterface $entityManager; public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; } public function findById(int $id): ?User { return $this->entityManager->find(User::class, $id); } public function save(User $user): void { $this->entityManager->persist($user); $this->entityManager->flush(); } } // Domain Layer (Business Logic) namespace App\Domain\Model; class User { private int $id; private string $email; private string $password; private bool $isActive; public function __construct(string $email, string $password) { $this->email = $email; $this->password = password_hash($password, PASSWORD_DEFAULT); $this->isActive = true; } // Getters and setters (omitted for brevity) } // Application Layer (Use Cases) namespace App\Application\User; use App\Domain\Model\User; use App\Domain\Repository\UserRepositoryInterface; class RegisterUser { private UserRepositoryInterface $userRepository; public function __construct(UserRepositoryInterface $userRepository) { $this->userRepository = $userRepository; } public function execute(string $email, string $password): User { $user = new User($email, $password); $this->userRepository->save($user); return $user; } } // Presentation Layer (Controller) namespace App\Presentation\Http\Controller; use App\Application\User\RegisterUser; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class UserController { #[Route('/register', methods: ['POST'])] public function register(Request $request, RegisterUser $registerUser): Response { $email = $request->request->get('email'); $password = $request->request->get('password'); $user = $registerUser->execute($email, $password); return new Response('User registered successfully with ID: ' . $user->getId(), Response::HTTP_CREATED); } } """ **Anti-Pattern:** A monolithic class that handles everything from request processing to database interaction. ### 1.2. Domain-Driven Design (DDD) **Standard:** Consider DDD principles for complex business domains. * **Do This:** Model your domain using Entities, Value Objects, Aggregates, and Services. Clearly define a Ubiquitous Language. * **Don't Do This:** Treat your domain model as simple data transfer objects (DTOs). Avoid anemic domain models. **Why:** DDD helps create a robust and maintainable system that accurately reflects the business domain. **Code Example:** """php // Value Object namespace App\Domain\ValueObject; use InvalidArgumentException; class Email { private string $email; public function __construct(string $email) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email format'); } $this->email = $email; } public function __toString(): string { return $this->email; } public function equals(Email $other): bool { return $this->email === $other->email; } } // Entity namespace App\Domain\Model; use App\Domain\ValueObject\Email; class Customer { private int $id; private Email $email; private string $name; public function __construct(Email $email, string $name) { $this->email = $email; $this->name = $name; } public function getEmail(): Email { return $this->email; } // Other methods } // Aggregate Root namespace App\Domain\Model; class Order { private int $id; private Customer $customer; private array $lineItems = []; public function __construct(Customer $customer) { $this->customer = $customer; } public function addLineItem(LineItem $lineItem): void { $this->lineItems[] = $lineItem; } public function getTotalAmount(): float { return array_sum(array_map(fn(LineItem $item) => $item->getPrice() * $item->getQuantity(), $this->lineItems)); } //Other methods } // Domain Service namespace App\Domain\Service; use App\Domain\Model\Order; class DiscountService { public function applyCustomerDiscount(Order $order): float { //Complex discount logic based on customer history. return $order->getTotalAmount() * 0.9; // 10% discount. } } """ **Anti-Pattern:** Building a system focused on database tables rather than business concepts. Ignoring the ubiquitous language. ### 1.3. Microservices Architecture **Standard:** Adopt microservices when appropriate for large, complex applications requiring independent deployment and scalability. * **Do This:** Design services around specific business capabilities. Use lightweight communication protocols like HTTP/REST or messaging queues. * **Don't Do This:** Create tightly coupled services that depend on each other's internal implementation details. Build a distributed monolith. **Why:** Microservices allow teams to work independently, scale specific parts of the application as needed, and use different technologies where appropriate. **Code Example (simplified, illustrating HTTP communication):** """php // Service A (Product Service) namespace App\ProductService; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Annotation\Route; class ProductController { #[Route('/products/{id}', methods: ['GET'])] public function getProduct(int $id): JsonResponse { // Retrieve product information $product = ['id' => $id, 'name' => 'Example Product']; return new JsonResponse($product); } } // Service B (Order Service) namespace App\OrderService; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Contracts\HttpClient\HttpClientInterface; class OrderController { private HttpClientInterface $httpClient; public function __construct(HttpClientInterface $httpClient) { $this->httpClient = $httpClient; } #[Route('/orders', methods: ['POST'])] public function createOrder(Request $request): Response { $productId = $request->request->get('productId'); // Call Product Service to get product details $response = $this->httpClient->request( 'GET', 'http://product-service/products/' . $productId ); $product = $response->toArray(); // Process order creation using the product details // ... return new Response('Order created successfully for product: ' . $product['name']); } } """ **Anti-Pattern:** Breaking down a small, simple application into microservices prematurely (over-engineering). Shared database schema across multiple microservices. ## 2. Project Structure and Organization ### 2.1. Namespace Usage **Standard:** Utilize namespaces consistently to organize code and prevent naming collisions. * **Do This:** Follow the PSR-4 autoloading standard. Use meaningful namespaces that reflect the project's structure and domain. * **Don't Do This:** Use global namespace for your application code. **Why:** Namespaces improve code organization, prevent naming conflicts, and make it easier to autoload classes. **Code Example:** """php // File: src/MyProject/Domain/Model/User.php namespace MyProject\Domain\Model; class User { // ... } // File: src/MyProject/Infrastructure/Persistence/UserRepository.php namespace MyProject\Infrastructure\Persistence; use MyProject\Domain\Model\User; class UserRepository { public function find(int $id): ?User { // ... } } """ **Anti-Pattern:** Inconsistent namespace usage across the project. Very long and unwieldy namespace names. ### 2.2. Directory Structure **Standard:** Employ a consistent and logical directory structure. * **Do This:** Separate code by layers or modules (e.g., "src/Domain", "src/Application", "src/Infrastructure", "src/Presentation"). Consider a "config" directory for configuration files, and "tests" for tests. * **Don't Do This:** Dump all PHP files into a single directory. **Why:** A clear directory structure makes it easier to navigate the codebase and locate specific files. **Code Example (example structure):** """ my-project/ ├── config/ # Configuration files ├── src/ # Source code │ ├── Domain/ # Domain layer (Entities, Value Objects, Interfaces) │ ├── Application/ # Application layer (Use Cases, Services) │ ├── Infrastructure/ # Infrastructure layer (Data Access, External Integrations) │ ├── Presentation/ # Presentation layer (Controllers, Views) ├── tests/ # Unit and Integration tests ├── composer.json ├── composer.lock """ **Anti-Pattern:** Randomly placing files without a clear organizational scheme. Mixing configuration files with source code. ### 2.3. Dependency Injection (DI) and Inversion of Control (IoC) **Standard:** Utilize Dependency Injection and Inversion of Control to decouple components. * **Do This:** Use constructor injection to provide dependencies. Consider using a DI container (e.g., Symfony's DI container, PHP-DI, or similar) for managing dependencies. Define interfaces for dependencies. * **Don't Do This:** Create tight coupling by directly instantiating dependencies within classes. Use static factories excessively. **Why:** DI promotes loose coupling, making code more testable, maintainable, and reusable. IoC allows for dynamic configuration and swapping of implementations. **Code Example:** """php // Interface namespace App\Domain\Repository; interface LoggerInterface { public function log(string $message): void; } // Implementation namespace App\Infrastructure\Logger; class FileLogger implements LoggerInterface { private string $logFile; public function __construct(string $logFile) { $this->logFile = $logFile; } public function log(string $message): void { file_put_contents($this->logFile, $message . "\n", FILE_APPEND); } } // Class utilizing Dependency Injection namespace App\Application\Service; use App\Domain\Repository\LoggerInterface; class UserService { private LoggerInterface $logger; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } public function createUser(string $username, string $email): void { // ... create user logic ... $this->logger->log("User created: " . $username); } } // Configuration (using Symfony's DI container as example) use App\Application\Service\UserService; use App\Infrastructure\Logger\FileLogger; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; $containerBuilder = new ContainerBuilder(); $containerBuilder->register('logger', FileLogger::class) ->addArgument('%kernel.logs_dir%/app.log'); $containerBuilder->register('user_service', UserService::class) ->addArgument(new Reference('logger')); $containerBuilder->compile(); /** @var UserService $userService */ $userService = $containerBuilder->get('user_service'); $userService->createUser("testuser", "test@example.com"); """ **Anti-Pattern:** Creating service locators instead of using constructor injection. Hardcoding dependencies. ### 2.4. Configuration Management **Standard:** Centralize configuration using environment variables, configuration files, or dedicated configuration management tools. * **Do This:** Use environment variables for sensitive information (API keys, database passwords). Store application settings in configuration files (e.g., YAML, JSON, INI). Utilize libraries like "Symfony\Component\Config" for loading and managing configuration. Consider using tools like Dotenv to manage environment variables in development. * **Don't Do This:** Hardcode configuration values directly in the code. Commit sensitive information to the repository. **Why:** Proper configuration management makes it easier to deploy the application to different environments and avoids exposing sensitive data. **Code Example:** """php // .env (example using Dotenv) DATABASE_URL=mysql://user:password@host:port/database API_KEY=YOUR_API_KEY // Using environment variables $databaseUrl = $_ENV['DATABASE_URL']; $apiKey = $_ENV['API_KEY']; //Using symfony config component for YAML configuration (config/services.yaml): #Example content for config/services.yaml inside Symfony project services: App\Service\MyService: arguments: $apiKey: '%env(API_KEY)%' // In the MyService class: namespace App\Service; class MyService { private string $apiKey; public function __construct(string $apiKey) { $this->apiKey = $apiKey; } public function doSomething(): void { // Use the API key } } """ **Anti-Pattern:** Scattering configuration values throughout the code. Storing sensitive data in plain text in configuration files. ## 3. Implementation Details & Best Practices ### 3.1. Error Handling and Logging **Standard:** Implement robust error handling and logging. * **Do This:** Use exceptions for exceptional situations. Implement a global exception handler. Log errors, warnings, and important events using a logging library (e.g., Monolog). Use structured logging formats (e.g., JSON) for easier analysis. Include context information in log messages (e.g., user ID, request ID). * **Don't Do This:** Suppress errors without logging them. "die()" or "exit()" in production code. Expose sensitive information in error messages. **Why:** Proper error handling prevents application crashes and makes it easier to diagnose issues. Logging provides valuable insights into the application's behavior. **Code Example:** """php use Monolog\Logger; use Monolog\Handler\StreamHandler; use Exception; // Create a log channel $log = new Logger('my_app'); $log->pushHandler(new StreamHandler(__DIR__.'/app.log', Logger::WARNING)); try { // Some code that might throw an exception $result = 10 / 0; // Division by zero } catch (Exception $e) { // Log the error $log->error('An error occurred: ' . $e->getMessage(), ['exception' => $e]); // Display a user-friendly error message (don't expose sensitive details) echo "An unexpected error occurred. Please try again later."; } //Global exception handling in Symfony (example) // config/services.yaml services: App\EventListener\ExceptionListener: class: App\EventListener\ExceptionListener arguments: ['%kernel.debug%'] # Pass kernel.debug to check if in dev environment. tags: - { name: kernel.event_listener, event: kernel.exception, method: onKernelException } // src/EventListener/ExceptionListener.php namespace App\EventListener; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Psr\Log\LoggerInterface; class ExceptionListener { private bool $debug; private LoggerInterface $logger; public function __construct(bool $debug, LoggerInterface $logger) { $this->debug = $debug; $this->logger = $logger; } public function onKernelException(ExceptionEvent $event): void { $exception = $event->getThrowable(); $this->logger->error($exception->getMessage(), ['exception' => $exception]); $response = new JsonResponse(); if ($this->debug) { $response->setData([ 'message' => $exception->getMessage(), 'trace' => $exception->getTrace(), ]); } else { $statusCode = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500; $response->setStatusCode($statusCode); $response->setData(['message' => 'An error occurred. Please try again later.']); } $event->setResponse($response); } } """ **Anti-Pattern:** Using "@" to silence errors without proper handling. Catching general "Exception" without specific handling. Not using a logging library. ### 3.2. Code Style and Formatting **Standard:** Adhere to a consistent code style. * **Do This:** Follow the PSR-12 coding style guide ([https://www.php-fig.org/psr/psr-12/](https://www.php-fig.org/psr/psr-12/)). Use a code formatter (e.g., PHP-CS-Fixer) and a linter (e.g., PHPStan, Psalm) to automatically enforce the coding style. * **Don't Do This:** Use inconsistent indentation, spacing, or naming conventions. **Why:** Consistent code style improves readability and maintainability. **Code Example (demonstrating PSR-12):** """php <?php namespace MyProject\Example; use DateTimeInterface; class Example { public function myFunction(int $param1, string $param2): void { if ($param1 > 0) { echo "Param1 is positive"; } else { echo "Param1 is not positive"; } } public function anotherFunction(DateTimeInterface $date): string { return $date->format('Y-m-d H:i:s'); } } """ **Anti-Pattern:** Inconsistent indentation. Mixing tabs and spaces. Overly long lines of code. Ignoring code style guidelines. ### 3.3. Performance Optimization **Standard:** Write efficient code and optimize for performance. * **Do This:** Use appropriate data structures and algorithms. Minimize database queries. Use caching (e.g., Redis, Memcached) for frequently accessed data. Profile your code to identify performance bottlenecks. Use opcache for bytecode caching. Lazy load data and resources when appropriate. Avoid N+1 query problems. * **Don't Do This:** Perform unnecessary computations. Load large datasets into memory unnecessarily. Ignore performance testing. **Why:** Performance optimization ensures that the application responds quickly and efficiently, providing a good user experience and reducing server costs. **Code Example:** """php // Caching using Redis $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $cacheKey = 'user_data_' . $userId; $userData = $redis->get($cacheKey); if ($userData === false) { // Data not in cache, fetch from database $userData = $this->fetchUserDataFromDatabase($userId); $redis->set($cacheKey, serialize($userData), 3600); // Cache for 1 hour } else { $userData = unserialize($userData); } // Avoiding N+1 query problem using Doctrine (eager loading) // In your repository: /** * @return Collection<int, Product> */ public function findAll(): array { return $this->createQueryBuilder('p') ->leftJoin('p.category', 'c') //Eager loading of category ->addSelect('c') ->orderBy('p.id', 'ASC') ->getQuery() ->getResult() ; } """ **Anti-Pattern:** Ignoring database indexes. Retrieving all columns from a table when only a few are needed. Not using profiling tools. ### 3.4. Security Best Practices **Standard:** Implement security best practices to protect against common vulnerabilities. * **Do This:** Sanitize and validate all user inputs. Use prepared statements to prevent SQL injection. Escape output to prevent XSS attacks. Implement proper authentication and authorization. Use strong passwords and hashing algorithms. Protect against CSRF attacks. Keep your dependencies up to date. Utilize static analysis tools to identify potential security flaws. * **Don't Do This:** Trust user input without validation. Store passwords in plain text. Disable security features. Expose sensitive information in error messages. **Why:** Security best practices protect the application and its users from malicious attacks. **Code Example:** """php // Prepared statement to prevent SQL injection $statement = $pdo->prepare("SELECT * FROM users WHERE email = :email"); $statement->bindParam(':email', $email, PDO::PARAM_STR); $statement->execute(); // Output escaping to prevent XSS $escapedName = htmlspecialchars($userName, ENT_QUOTES, 'UTF-8'); echo "Hello, " . $escapedName; //Password Hashing: $password = 'P@$$wOrd'; $hashedPassword = password_hash($password, PASSWORD_DEFAULT); // Verification if (password_verify($password, $hashedPassword)) { echo 'Password is valid!'; } else { echo 'Invalid password.'; } """ **Anti-Pattern:** Using "eval()". Storing API keys in client-side code. Not validating file uploads. ### 3.5. Testing **Standard:** Write automated tests for all critical parts of the application. * **Do This:** Write unit tests, integration tests, and end-to-end tests. Use a testing framework (e.g., PHPUnit, Pest). Aim for high test coverage. Use mocks and stubs to isolate units of code. Follow the Arrange-Act-Assert pattern in your tests. * **Don't Do This:** Skip testing. Write tests that are brittle and difficult to maintain. Test implementation details instead of behavior. **Why:** Testing ensures that the application works as expected and prevents regressions. **Code Example:** """php // Unit test using PHPUnit use PHPUnit\Framework\TestCase; use App\Domain\Model\User; class UserTest extends TestCase { public function testCreateUser(): void { $user = new User('test@example.com', 'password'); $this->assertInstanceOf(User::class, $user); // $this->assertEquals('test@example.com', $user->getEmail()); //Assuming you have a getEmail() method. } } """ **Anti-Pattern:** Writing integration tests that rely on external services without mocking. Testing private methods directly. Not running tests regularly. ### 3.6 API Design **Standard:** Design APIs that are well-documented, secure and follow RESTful principles where appropriate. * **Do This:** Use standard HTTP methods (GET, POST, PUT, DELETE). Use meaningful endpoint names ("/users", "/products/{id}"). Return appropriate HTTP status codes. Use a standard data format (e.g., JSON). Use API versioning. Document your API using tools like OpenAPI (Swagger). Implement rate limiting to prevent abuse. Authenticate and authorize API requests. * **Don't Do This:** Violate RESTful principles without a good reason. Expose internal implementation details in the API. Neglect API documentation. **Why:** Well-designed APIs are easy to use, maintain, and extend. **Code Example:** """php // A simple RESTful API endpoint (example using Symfony) namespace App\Controller; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class ApiProductController { #[Route('/api/products/{id}', methods: ['GET'])] public function getProduct(int $id): JsonResponse { $product = ['id' => $id, 'name' => 'Example Product', 'price' => 19.99]; if (!$product) { return new JsonResponse(['message' => 'Product not found'], Response::HTTP_NOT_FOUND); } return new JsonResponse($product); } } // OpenAPI (Swagger) documentation (example snippet) /** * @OA\Get( * path="/api/products/{id}", * summary="Get a product by ID", * @OA\Parameter( * name="id", * in="path", * description="Product ID", * required=true, * @OA\Schema(type="integer") * ), * @OA\Response( * response="200", * description="Successful operation", * @OA\JsonContent( * type="object", * @OA\Property(property="id", type="integer"), * @OA\Property(property="name", type="string"), * @OA\Property(property="price", type="number", format="float") * ) * ), * @OA\Response( * response="404", * description="Product not found" * ) * ) */ """ **Anti-Pattern:** Designing APIs that are difficult to understand and use. Not providing proper error handling. Exposing sensitive data through the API.
# State Management Standards for PHP This document outlines the standards for managing application state in PHP, encompassing data flow, reactivity, and persistence. These standards aim to promote maintainable, performant, and secure applications. PHP has traditionally been stateless, but modern frameworks and design patterns enable effective state management. ## 1. Architecture and General Principles ### 1.1. Stateless vs. Stateful * **Do This:** Embrace stateless principles where possible, especially for API endpoints. Each request should contain all the necessary information for processing. * **Don't Do This:** Rely on server-side session state for everything. Over-reliance on sessions can lead to scalability issues. * **Why:** Statelessness simplifies scaling, reduces server resource consumption, and enhances predictability. For stateful interactions, consider well-defined, minimized state management mechanisms. ### 1.2. Explicit State Management * **Do This:** Clearly define where state is managed (client-side, server-side, database), its lifespan, and how it’s accessed and modified. * **Don't Do This:** Implicitly rely on global variables or hidden state that make the code hard to reason about. * **Why:** Explicit state management improves code readability, debuggability, and maintainability. It also allows for easier auditing and security analysis. ### 1.3. Separation of Concerns * **Do This:** Separate state management logic from business logic. Use dedicated classes or services to handle state operations. * **Don't Do This:** Mix state manipulation directly into controllers or other components responsible for handling user requests. * **Why:** Separation of concerns keeps the codebase organized, testable, and reduces the risk of unintended side effects. ### 1.4. Immutable State * **Do This:** When appropriate, leverage immutable data structures. For example, when dealing with configuration or read-only data, use immutable classes. * **Don't Do This:** Mutate state directly without considering the consequences. Mutation can lead to unexpected bugs, especially in concurrent environments. * **Why:** Immutability simplifies reasoning about state and helps prevent accidental modifications. In PHP 8.1 and later, read-only properties provide a strong mechanism for enforcing immutability. ## 2. State Management Techniques ### 2.1. Sessions * **Do This:** Use PHP's built-in session management features judiciously for managing user authentication and authorization. Utilize "session_start()", "$_SESSION", "session_regenerate_id()", and "session_destroy()" appropriately. Configure session options such as "session.cookie_secure", "session.cookie_httponly", and "session.gc_maxlifetime" in "php.ini" or using "ini_set()" for security. * **Don't Do This:** Store sensitive data like passwords directly in sessions. Avoid storing large objects in sessions as this can impact performance. * **Why:** Sessions provide a convenient way to maintain user-specific state across multiple requests. Securely configuring session options is critical. """php <?php // Secure session configuration (put in a central configuration file) ini_set('session.cookie_secure', true); // Only send cookies over HTTPS ini_set('session.cookie_httponly', true); // Prevent JavaScript access to the cookie ini_set('session.gc_maxlifetime', 3600); // Session expires after 1 hour of inactivity session_start(); // Store user ID in the session after authentication $_SESSION['user_id'] = $user->getId(); // Regenerate session ID after successful login to prevent session fixation session_regenerate_id(true); // Accessing data if (isset($_SESSION['user_id'])) { $userId = $_SESSION['user_id']; // ... } // Destroy session on logout session_destroy(); // Clears the $_SESSION array and unsets session data session_start(); // Start a new session for the logout process so that we can display some message $_SESSION['message'] = "Logged out successfully"; header("Location: /login"); // redirects the user to the login page exit; // Ensure that no further code is executed ?> """ ### 2.2. Cookies * **Do This:** Use cookies sparingly for non-sensitive data, such as user preferences or tracking identifiers. Set appropriate expiration times and use the "SameSite" attribute for enhanced security. Always encode and sanitize cookie data. * **Don't Do This:** Store sensitive information in cookies, like authentication tokens or personal details. Trust cookie data without validation. * **Why:** Cookies are stored client-side and are susceptible to tampering. Proper handling is essential to prevent vulnerabilities. """php <?php // Setting a cookie with security attributes setcookie( 'user_preference', 'dark_mode', [ 'expires' => time() + (30 * 24 * 60 * 60), // Expires in 30 days 'path' => '/', 'domain' => $_SERVER['HTTP_HOST'], // restrict cookie to your domain 'secure' => true, // Only send over HTTPS 'httponly' => true, // Prevent JavaScript access 'samesite' => 'Lax', // Mitigate CSRF attacks ] ); // Accessing a cookie if (isset($_COOKIE['user_preference'])) { $preference = htmlspecialchars($_COOKIE['user_preference'], ENT_QUOTES, 'UTF-8'); // ...Use the preference safely after HTML encoding } // Deleting a cookie setcookie('user_preference', '', ['expires' => time() - 3600, 'path' => '/']); // set expiry to past time ?> """ ### 2.3. Database * **Do This:** Persist application state in a database when it needs to be persistent across sessions. Use parameterized queries or prepared statements to prevent SQL injection. Normalise your database schema. Consider using an ORM (Doctrine, Eloquent). * **Don't Do This:** Store unstructured data directly in the database (e.g., serializing large objects) unless absolutely necessary. Write raw SQL queries without proper escaping. * **Why:** The database is the primary persistent store for most applications. Secure and efficient database interactions are paramount. """php <?php use Doctrine\ORM\EntityManagerInterface; use App\Entity\User; class UserService { private EntityManagerInterface $entityManager; public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; } public function updateUser(int $userId, array $data): User { $user = $this->entityManager->getRepository(User::class)->find($userId); if (!$user) { throw new \Exception("User not found"); } $user->setName($data['name'] ?? $user->getName()); $user->setEmail($data['email'] ?? $user->getEmail()); // Other potential updates based on $data array $this->entityManager->flush(); // persists the changes to the database return $user; } } // Example Usage $userService = new UserService($entityManager); $updatedUser = $userService->updateUser(123, ['name' => 'John Doe', 'email' => 'john.doe@example.com']); echo "Updated user: " . $updatedUser->getName(); ?> """ ### 2.4. Caching * **Do This:** Utilize caching mechanisms (Redis, Memcached, APCu) to store frequently accessed data or computed results to improve performance. Set appropriate cache expiration policies (TTL). Use cache tags/invalidation strategies. * **Don't Do This:** Cache sensitive information without encryption. Cache data indefinitely without any invalidation strategy. * **Why:** Caching can significantly reduce database load and improve response times. """php <?php use Symfony\Component\Cache\Adapter\RedisAdapter; // Connect to Redis $redis = new RedisAdapter( RedisAdapter::createConnection('redis://localhost') ); $cacheKey = 'user_profile_' . $userId; $userProfile = $redis->get($cacheKey, function () use ($userId, $db) { // Fetch user profile from the database (expensive operation) $userProfile = $db->fetchUserProfile($userId); return $userProfile; }); // Use the retrieved user profile echo $userProfile['name']; // Invalidate cache $redis->delete($cacheKey); """ ### 2.5. Client-Side State * **Do This:** If using JavaScript frameworks (React, Vue, Angular), manage UI state on the client-side whenever possible. Use appropriate state management libraries (Redux, Vuex, Pinia) for complex applications. Employ local storage or session storage for persistent client-side data. * **Don't Do This:** Expose sensitive server-side data to the client that isn't needed for the UI. Rely *solely* on client-side state for critical application functionality. * **Why:** Client-side state management can improve UI responsiveness and offload processing from the server. ### 2.6. Request Attributes * **Do This:** In frameworks like Symfony or Laravel, use request attributes to pass data between middleware and controllers within a single request. Utilize value objects where appropriate for type-hinting and validation. * **Don't Do This:** Rely on global variables or session state for data that is only needed within a single request. * **Why:** Request attributes provide a clean and scoped way to manage data flow within a request lifecycle. """php <?php namespace App\Middleware; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Message\ResponseInterface; class UserMiddleware { public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $userId = $request->getHeaderLine('X-User-ID'); if ($userId) { // In a real application, validate the ID and fetch the user $user = ['id' => $userId, 'name' => 'Example User']; // Set the user data as a request attribute $request = $request->withAttribute('user', $user); } return $handler->handle($request); } } namespace App\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; class MyController { public function index(Request $request): Response { $user = $request->attributes->get('user'); if ($user) { return new Response('Hello, ' . $user['name']); } else { return new Response('Hello, Guest'); } } } ?> """ ## 3. Modern Approaches and Patterns ### 3.1. CQRS (Command Query Responsibility Segregation) * **Do This:** Consider applying CQRS for applications with complex data models and high read/write loads. Separate read and write operations into distinct models. * **Don't Do This:** Overcomplicate simple applications with CQRS. It's best suited for scenarios where the benefits outweigh the added complexity. * **Why:** CQRS allows you to optimize read and write models independently, improving performance and scalability. ### 3.2. Event Sourcing * **Do This:** Explore Event Sourcing for applications where you need a complete audit trail of all state changes. Store the sequence of events that led to the current state. * **Don't Do This:** Use Event Sourcing as a replacement for traditional CRUD operations without understanding its implications. It requires a different mindset and infrastructure. * **Why:** Event Sourcing provides a reliable and auditable history of state changes, enabling features like time-travel debugging and replayability. ### 3.3. Reactive Programming * **Do This:** Consider integrating reactive programming principles (e.g., using libraries like RxPHP) for handling asynchronous data streams and UI updates. * **Don't Do This:** Introduce reactive programming without thoroughly understanding its concepts (Observables, Subjects, Schedulers). It can add complexity if not used correctly. * **Why:** Reactive programming enables building responsive and event-driven applications that can handle large volumes of data efficiently. ### 3.4. State Machines * **Do This:** Model complex application workflows as state machines using libraries like "Finite" or "Symfony Workflow". Clearly define states, transitions, and associated actions. * **Don't Do This:** Implement complex state logic using nested "if" statements or procedural code. This leads to unmaintainable code. * **Why:** State machines provide a structured and visual way to manage application state transitions, reducing complexity and improving maintainability. ## 4. Security Considerations ### 4.1. Session Security * **Do This:** Use HTTPS to encrypt session cookies. Set "session.cookie_secure" and "session.cookie_httponly" directives. Regenerate session IDs after login and logout. Implement session timeouts. Store sensitive data securely (hashed passwords, etc.). Use "session_regenerate_id(true)" to delete the old session file. * **Don't Do This:** Store sensitive information directly in the session without encryption or proper sanitization. Use default session settings. Rely solely on session cookies for authentication without additional security mechanisms. * **Why:** Sessions are a prime target for attackers. Proper configuration and security measures are essential to prevent session hijacking and other vulnerabilities. ### 4.2. Cookie Security * **Do This:** Use the "secure" attribute for cookies containing sensitive data so they are only transmitted over HTTPS. Set the "httponly" attribute to prevent JavaScript access. Use the "SameSite" attribute to mitigate CSRF attacks. Validate cookie data to prevent tampering. * **Don't Do This:** Store sensitive data in cookies without encryption or proper encoding. * **Why:** Cookies are stored client-side and can be easily tampered with. Secure cookie configuration is critical to prevent vulnerabilities. ### 4.3. Database Security * **Do This:** Use parameterized queries or prepared statements to prevent SQL injection. Enforce the principle of least privilege for database users. Regularly update database software. Store sensitive data encrypted in the database. * **Don't Do This:** Construct SQL queries using string concatenation with user-supplied data. Grant excessive database privileges. * **Why:** SQL injection is a common and serious vulnerability. Proper database security practices are crucial to protect data integrity and confidentiality. ### 4.4. Caching Security * **Do This:** Avoid caching sensitive information without encryption. Implement proper access control to cache stores. Invalidate cache data when underlying data changes. Use a cache backend that supports authentication and authorization. * **Don't Do This:** Cache encrypted or sensitive data without encryption. * **Why:** Caches can be a source of information leakage if not properly secured. ## 5. Performance Optimization ### 5.1. Session Optimization * **Do This:** Minimize the amount of data stored in sessions. Use session handlers to store session data in a database or other persistent store instead of the default file-based storage, especially in clustered environments. Consider lazy-loading session data. * **Don't Do This:** Store large objects in sessions. Enable "session.auto_start" in production environments (it can lead to unnecessary overhead). * **Why:** Excessive session data can significantly impact performance and scalability. ### 5.2. Caching Optimization * **Do This:** Use appropriate cache expiration policies (TTL) based on the volatility of the data. Use cache invalidation strategies to ensure that cached data is up-to-date. Use hierarchical caching (e.g., combining APCu and Redis). Utilize compression to reduce the size of cached data. * **Don't Do This:** Cache data indefinitely without any invalidation strategy. Over-cache data, which can lead to stale content and inconsistent application behavior. * **Why:** Effective caching can dramatically reduce database load and improve response times. Improper caching can lead to various problems. ### 5.3. Database Optimization * **Do This:** Use database indexes to optimize query performance. Profile slow queries and optimize them. Use connection pooling to reduce database connection overhead. Use caching for frequently accessed data. * **Don't Do This:** Neglect database optimization. Write inefficient queries. * **Why:** Database performance is critical for application responsiveness. ## 6. Emerging Technology and Language Features ### 6.1 Readonly Classes and Properties * **Do This:** Utilize readonly properties and classes (PHP 8.1+) to define immutable state, ensuring data integrity and predictability. * **Don't Do This:** Modify readonly properties after object instantiation unless specifically allowed within the constructor. * **Why:** Readonly properties enforce immutability, which simplifies reasoning about state management and reduces the potential for accidental modification, enhancing code reliability. """php class Configuration { public readonly string $apiKey; public readonly int $timeout; public function __construct(string $apiKey, int $timeout) { $this->apiKey = $apiKey; $this->timeout = $timeout; } } $config = new Configuration('your_api_key', 30); // $config->timeout = 60; // This will cause Error: Cannot modify readonly property echo $config->apiKey; // Outputs 'your_api_key' """ ### 6.2 Fibre-Based Concurrency * **Do This:** Explore Fibres (PHP 8.1+) to manage concurrent state within a single request, useful for non-blocking I/O operations and asynchronous tasks. * **Don't Do This:** Assume Fibres replace multi-threading entirely; they're best suited for I/O-bound, not CPU-bound, operations. * **Why:** Fibres enable efficient context switching for asynchronous tasks, improving responsiveness and resource utilization, especially when dealing with external services. """php $fiber = new Fiber(function (): void { echo "About to suspend\n"; $value = Fiber::suspend('fiber-suspended'); echo "Value after suspending: " . $value . "\n"; }); echo "Starting fiber\n"; $fiber->start(); var_dump($fiber->getStatus()); //Outputs int(2) corresponding to Fiber::STATUS_SUSPENDED echo "Resuming fiber\n"; $fiber->resume('fiber-resumed'); //Pass a value to the suspended Fiber """ These standards provide clear guidelines for managing state effectively in PHP applications. By following these standards, developers can build more maintainable, performant, and secure applications. Implementing them into the IDE's linting and auto-completion system provides the kind of immediate feedback needed for high development velocity and high quality.
# Security Best Practices Standards for PHP This document outlines the security best practices for PHP development. It aims to guide developers in writing secure, maintainable, and performant code, while also providing context for AI coding assistants. These standards emphasize modern approaches and patterns, utilizing the latest PHP features to mitigate common vulnerabilities. ## 1. General Security Principles ### 1.1. Input Validation and Sanitization **Standard:** All user inputs must be validated and sanitized before being used in any operation. **Why:** Prevents injection attacks (SQL, XSS, command injection) and ensures data integrity. **Do This:** * Use "filter_var()" with appropriate filters for different input types (e.g., "FILTER_VALIDATE_EMAIL", "FILTER_SANITIZE_STRING"). * Use prepared statements with parameterized queries for database interactions. * Implement input validation on both the client-side (for UX) and server-side (for security). * Sanitize data based on its intended use, rather than blanket sanitization. **Don't Do This:** * Directly use user input in SQL queries without escaping or parameterization. * Trust client-side validation alone. * Use deprecated "mysql_*" functions. * Assume input is safe because it "looks" harmless. **Example:** Sanitizing an email address """php <?php $email = $_POST['email'] ?? ''; if (filter_var($email, FILTER_VALIDATE_EMAIL)) { $sanitizedEmail = filter_var($email, FILTER_SANITIZE_EMAIL); echo "Valid and Sanitized Email: " . htmlspecialchars($sanitizedEmail) . PHP_EOL; // Escape for output } else { echo "Invalid Email Address" . PHP_EOL; } ?> """ **Anti-Pattern:** Directly using "$_POST" values in a database query. """php <?php // Vulnerable code $username = $_POST['username']; $query = "SELECT * FROM users WHERE username = '$username'"; // SQL Injection vulnerability // Avoid the above approach ?> """ **Modern Approach:** Use prepared statements """php <?php $username = $_POST['username'] ?? ''; // Assuming $pdo is a PDO database connection $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username"); $stmt->bindParam(':username', $username, PDO::PARAM_STR); $stmt->execute(); $user = $stmt->fetch(); if ($user) { echo "User found: " . htmlspecialchars($user['username']) . PHP_EOL; // Escape on output } else { echo "User not found." . PHP_EOL; } ?> """ ### 1.2. Output Encoding **Standard:** All data displayed to the user must be properly encoded to prevent XSS attacks. **Why:** Prevents malicious scripts from being injected into your website. **Do This:** * Use "htmlspecialchars()" or "htmlentities()" to escape HTML output. * Use appropriate escaping functions for other output formats (e.g., "json_encode()" for JSON, "urlencode()" for URLs). * Consider using a templating engine that automatically escapes output. Context aware escaping is optimal. **Don't Do This:** * Output user-controlled data without any encoding. * Rely solely on client-side escaping. * Disable escaping features in templating engines. **Example:** Encoding output """php <?php $userInput = "<script>alert('XSS');</script>"; $encodedOutput = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8'); echo "Encoded Output: " . $encodedOutput . PHP_EOL; ?> """ **Anti-Pattern:** Directly displaying user input without encoding. """php <?php // Vulnerable code echo $_GET['name']; // XSS Vulnerability // Avoid! ?> """ **Modern Approach:** Using "htmlspecialchars()": """php <?php $name = $_GET['name'] ?? ''; echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8'); // Properly encoded ?> """ ### 1.3. Authentication and Authorization **Standard:** Implement strong authentication and authorization mechanisms to control access to resources. **Why:** Prevents unauthorized access and protects sensitive data. **Do This:** * Use strong password hashing algorithms (e.g., "password_hash()" with "PASSWORD_DEFAULT" or "PASSWORD_ARGON2I"). * Implement proper session management (secure cookies, session fixation protection, session hijacking prevention). * Use role-based access control (RBAC) to define user permissions. * Implement multi-factor authentication (MFA) for critical accounts. Use modern authentication (e.g., WebAuthn). **Don't Do This:** * Store passwords in plain text or use weak hashing algorithms (e.g., MD5, SHA1). * Use default session settings without proper security configurations. * Grant excessive privileges to users. * Implement authentication yourself if a well vetted package (e.g. Laravel Sanctum/Passport, Symfony Security) can do it for you consistently. **Example:** Password hashing and verification. """php <?php $password = "SecretPassword123"; // NEVER hardcode actual passwords for testing in real projects. $hashedPassword = password_hash($password, PASSWORD_DEFAULT); echo "Hashed Password: " . $hashedPassword . PHP_EOL; $userInputPassword = "SecretPassword123"; if (password_verify($userInputPassword, $hashedPassword)) { echo "Password is valid!" . PHP_EOL; } else { echo "Invalid password." . PHP_EOL; } ?> """ **Anti-Pattern:** Storing passwords as plaintext. """php <?php // Vulnerable code $password = $_POST['password']; // $query = "INSERT INTO users (username, password) VALUES ('$username', '$password')"; //Avoid! ?> """ **Modern Approach:** Using "password_hash()" """php <?php $password = $_POST['password'] ?? ''; $hashedPassword = password_hash($password, PASSWORD_DEFAULT); // $query = "INSERT INTO users (username, password) VALUES ('$username', '$hashedPassword')"; // Correct way ?> """ ### 1.4. Session Management **Standard:** Employ secure session management techniques to protect user sessions from hijacking and fixation. **Why:** Prevents attackers from impersonating legitimate users. **Do This:** * Use "session_regenerate_id(true)" after successful login and periodically. * Set "session.cookie_httponly = true" to prevent JavaScript access to cookies. * Set "session.cookie_secure = true" to only transmit cookies over HTTPS. * Implement session timeouts. * Store session data server-side. **Don't Do This:** * Store sensitive information directly in cookies. * Rely solely on cookie values for authentication. * Use predictable session IDs. **Example:** Secure session configuration: """php <?php ini_set('session.cookie_httponly', true); ini_set('session.cookie_secure', true); session_start(); // Regenerate session ID upon login session_regenerate_id(true); ?> """ ### 1.5. Error Handling and Logging **Standard:** Implement robust error handling and logging mechanisms to identify and address security vulnerabilities. **Why:** Helps detect and respond to security incidents, and provides valuable debugging information. **Do This:** * Configure PHP to log errors to a secure location. * Use custom error handlers and exception handlers. * Log security-related events (e.g., login failures, unauthorized access attempts). * Avoid displaying sensitive error information to users in production environments. * Use a logging library (e.g., Monolog) for structured logging. **Don't Do This:** * Disable error reporting in production. * Display full error messages to users. * Store logs in a publicly accessible directory. **Example:** Custom error and exception handling """php <?php use Monolog\Logger; use Monolog\Handler\StreamHandler; // Create a logger $log = new Logger('app'); $log->pushHandler(new StreamHandler(__DIR__.'/app.log', Logger::WARNING)); set_error_handler(function($severity, $message, $file, $line){ global $log; $log->error("Error: $message in $file on line $line"); error_log("Error: $message in $file on line $line"); //Fallback, but prefer a well configured logger //return true; //Suppress standard error handling }); set_exception_handler(function($exception) use ($log) { $log->critical("Uncaught exception: " . $exception->getMessage() . " in " . $exception->getFile() . " on line " . $exception->getLine()); error_log("Uncaught exception: " . $exception->getMessage() . " in " . $exception->getFile() . " on line " . $exception->getLine()); // Optionally display a user-friendly error page http_response_code(500); echo "<h1>An unexpected error occurred. Please contact support.</h1>"; }); // Trigger an error try { throw new Exception("This is a test exception."); } catch (Exception $e) { // Exception is now handled by set_exception_handler throw $e; } ?> """ ### 1.6. File Handling Security **Standard:** Implement secure file handling practices to prevent unauthorized access to files and directories. **Why:** Prevents attackers from uploading malicious files, accessing sensitive data, or executing arbitrary code. **Do This:** * Validate file types based on content (mime type checking is not sufficient) and extensions. * Store uploaded files outside the webroot. Use a separate storage service (e.g. S3) where feasible. * Randomize file names to prevent filename guessing. * Set appropriate file permissions (e.g., 644 for files, 755 for directories). * Disable file execution in upload directories (using ".htaccess" or server configuration). **Don't Do This:** * Store sensitive files in the webroot. * Trust user-provided file names or extensions. * Allow unrestricted file uploads. * Use weak file permission settings. **Example:** Secure file upload """php <?php use Ramsey\Uuid\Uuid; $targetDir = __DIR__ . "/uploads"; if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); } $uploadedFile = $_FILES['file']; $originalFilename = basename($uploadedFile['name']); $fileSize = $uploadedFile['size']; $fileTmpName = $uploadedFile['tmp_name']; $fileType = $uploadedFile['type']; $fileError = $uploadedFile['error']; // Define allowed file types $allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']; // Generate a unique filename $safeName = Uuid::uuid4()->toString(); $fileExtension = pathinfo($originalFilename, PATHINFO_EXTENSION); $newFilename = $safeName . '.' . $fileExtension; $targetFile = rtrim($targetDir, '/') . '/' . $newFilename; // Check if the file type is allowed if (!in_array($fileType, $allowedTypes)) { throw new Exception("Invalid file type."); } //Check filesize if($fileSize > 1000000){ throw new Exception("File too big."); } // Move the uploaded file to the destination directory if(move_uploaded_file($fileTmpName, $targetFile)){ echo "File uploaded successfully. New filename is " . htmlspecialchars($newFilename); } else { throw new Exception("There was an error uploading your file."); } ?> """ ### 1.7. Cross-Site Request Forgery (CSRF) Protection **Standard:** Implement CSRF protection to prevent unauthorized actions on behalf of authenticated users. **Why:** Prevents attackers from tricking users into performing unintended actions. **Do This:** * Generate and validate CSRF tokens for all state-changing requests (e.g., form submissions, API endpoints). * Store CSRF tokens in the session. * Use the "SameSite" attribute for cookies to mitigate CSRF risks. **Don't Do This:** * Rely solely on referrer checking. * Use predictable CSRF tokens. * Expose CSRF tokens in URLs. **Example:** CSRF protection. """php <?php session_start(); function generateCSRFToken() { return bin2hex(random_bytes(32)); } if (!isset($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = generateCSRFToken(); } function validateCSRFToken($token) { return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token); } // In your form: $csrfToken = $_SESSION['csrf_token']; echo "<input type='hidden' name='csrf_token' value='$csrfToken'>"; // On form submission: if ($_SERVER['REQUEST_METHOD'] === 'POST') { $submittedToken = $_POST['csrf_token'] ?? ''; if (validateCSRFToken($submittedToken)) { // Process the form data safely echo "CSRF token is valid. Processing form data..." . PHP_EOL; } else { // CSRF attack detected http_response_code(400); die("CSRF token is invalid."); } } ?> """ ### 1.8. Dependency Management **Standard:** Use Composer to manage dependencies and keep them up to date with the latest security patches. **Why:** Ensures that your application relies on secure and well-maintained libraries. **Do This:** * Define dependencies in "composer.json". * Use "composer update" regularly to update dependencies. * Monitor dependencies for known vulnerabilities using tools like "Roave Security Advisor". * Pin dependencies to specific versions or use version constraints to avoid unexpected breaking changes. **Don't Do This:** * Manually install or update dependencies. * Ignore security updates for dependencies. * Use outdated or unmaintained libraries. **Example:** Using Composer. """json { "require": { "php": "^8.1", "monolog/monolog": "^3.0", "ramsey/uuid": "^4.0" }, "require-dev": { "roave/security-advisories": "dev-latest" }, "config": { "sort-packages": true }, "scripts": { "security-check": [ "@composer audit" ] } } """ Run "composer install" or "composer update" after updating "composer.json". Also important to set up a process to run "composer audit" on a regular schedule. ### 1.9. Secure Configuration **Standard:** Securely configure PHP and the web server to minimize attack surface. **Why:** Reduces the risk of vulnerabilities being exploited. **Do This:** * Disable unnecessary PHP extensions. * Set strict file permissions. * Disable "expose_php". * Limit "allow_url_fopen" and "allow_url_include". * Use a web server configuration that restricts access (e.g., Apache's ".htaccess" or Nginx configuration). **Don't Do This:** * Use default configuration settings. * Enable unnecessary features. * Expose sensitive configuration information. ### 1.10. Security Headers **Standard:** Configure web server to set security related HTTP headers. **Why:** Adds additional layers of protection against various attacks. **Do This:** * "Strict-Transport-Security": Enforces HTTPS connections. Example: "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload" * "X-Frame-Options": Protects against clickjacking attacks. Example: "X-Frame-Options: DENY" or "X-Frame-Options: SAMEORIGIN" * "X-Content-Type-Options": Prevents MIME-sniffing vulnerabilities. Example: "X-Content-Type-Options: nosniff" * "Content-Security-Policy": Controls resources the user agent is allowed to load. Example: "Content-Security-Policy: default-src 'self'" * "Referrer-Policy": Controls how much referrer information should be included with requests. Example: "Referrer-Policy: strict-origin-when-cross-origin" **Don't Do This:** * Rely solely on application-level security measures. * Ignore the defense-in-depth approach offered by security headers. * Use overly permissive policies that negate the protection offered by these headers. ## 2. Vulnerability-Specific Countermeasures ### 2.1. SQL Injection * **Use Prepared Statements:** Employ parameterized queries with PDO or MySQLi to prevent SQL injection. * **Escaping:** Use "PDO::quote()" or equivalent functions to properly escape user input when prepared statements aren't possible (rare). * **Least Privilege:** Database user should have least required privileges. ### 2.2. Cross-Site Scripting (XSS) * **Context-Aware Output Encoding:** Encode data based on its context (HTML, URL, JavaScript). Use specialized encoding functions like "htmlspecialchars()", "json_encode()", and "urlencode()". * **Content Security Policy (CSP):** Implement CSP to control the sources from which resources can be loaded. ### 2.3. Command Injection * **Avoid System Calls:** Minimize the use of "system()", "exec()", "shell_exec()", and similar functions. * **Input Validation:** If system calls are necessary, strictly validate and sanitize user input. Use "escapeshellarg()" and "escapeshellcmd()" carefully. ### 2.4. File Inclusion * **Avoid Dynamic Includes:** Avoid using variables in "include" or "require" statements. * **Whitelist:** If dynamic includes are unavoidable, create a strict whitelist of allowed files. * **Restrict Access:** Ensure the included files do not contain executable code or sensitive data. ### 2.5. Session Hijacking * **Regenerate Session IDs:** Call "session_regenerate_id(true)" after successful login and periodically. * **Secure Cookies:** Set "session.cookie_httponly" and "session.cookie_secure" in "php.ini". * **Session Timeouts:** Implement session timeouts to limit the lifespan of sessions. ## 3. Advanced Security Techniques ### 3.1. Encryption * **Use Libraries:** Use established encryption libraries like OpenSSL or Sodium. * **Authenticated Encryption:** Use authenticated encryption modes (e.g., AES-GCM) to ensure data integrity. * **Key Management:** Store encryption keys securely and use proper key management practices. Consider using a Key Management Service (KMS). ### 3.2. Security Auditing * **Code Reviews:** Conduct regular code reviews to identify potential security vulnerabilities. * **Static Analysis:** Use static analysis tools (e.g., Psalm, Phan) to detect code quality and security issues. * **Penetration Testing:** Perform periodic penetration testing to assess the security of your application in a real-world scenario. ### 3.3. Rate Limiting * **Implement Rate Limiting:** Protect against brute-force attacks and denial-of-service (DoS) by implementing rate limiting on critical endpoints. * **Monitor and Block:** Employ tools to monitor for suspicious activity and block malicious IP addresses. ## 4. Continuous Security Improvement ### 4.1. Stay Updated * **PHP Updates:** Regularly update PHP to the latest stable version to benefit from security patches and improvements. * **Security Newsletters:** Subscribe to security newsletters and mailing lists to stay informed about new vulnerabilities and best practices. * **Security Training:** Invest in security training for your development team to improve their knowledge and awareness. ### 4.2. Incident Response * **Incident Response Plan:** Develop a comprehensive incident response plan to handle security breaches and incidents effectively. * **Reporting:** Establish a clear process for reporting security vulnerabilities and incidents. * **Post-Mortem Analysis:** Conduct thorough post-mortem analysis after security incidents to identify root causes and implement preventive measures. This comprehensive coding standard provides a solid foundation for secure PHP development. By adhering to these guidelines, developers can significantly reduce the risk of vulnerabilities and build more robust and secure applications. Remember, security is an ongoing process that requires continuous attention and improvement. It's also critical to remember defense in depth - security measures should be layered, so if one fails, others still provide protection.
# Tooling and Ecosystem Standards for PHP This document outlines the recommended tools, libraries, and development practices within the PHP ecosystem to ensure maintainability, performance, security, and consistency across projects. It is designed to guide developers and provide context for AI coding assistants. This rule focuses specifically on the "Tooling and Ecosystem" aspects of PHP development, ensuring it is distinct from other rules. ## 1. Dependency Management with Composer ### Standard Use Composer for all project dependency management. Define dependencies explicitly in "composer.json" and utilize "composer.lock" to ensure consistency across environments. Take advantage of Composer's autoloading capabilities. **Do This:** * Use Composer for all dependencies. * Specify semantic versioning constraints (e.g., "^2.0", "~2.1") in "composer.json". * Commit "composer.json" and "composer.lock" to version control. * Use Composer's autoloading. * Keep dependencies up to date with "composer update" regularly, testing thoroughly after updates. * Leverage "composer install --optimize-autoloader --no-dev" in production. **Don't Do This:** * Manually download and include libraries. * Use wildcard version constraints (e.g., "*"). * Commit the "vendor" directory to version control. * Ignore Composer's autoloading and manually include files. * Neglect dependency updates for extended periods. **Why This Matters:** Composer centralizes dependency management, simplifying installation, updates, and resolving conflicts. Semantic versioning allows controlled updates while minimizing breaking changes. The "composer.lock" file ensures that all team members and environments use the exact same versions of dependencies, preventing unexpected behavior differences. Optimized autoloading enhances performance by reducing file system operations. **Code Example:** """json // composer.json { "require": { "php": "^8.1", "symfony/http-foundation": "^6.0", "doctrine/orm": "^3.0" }, "require-dev": { "phpunit/phpunit": "^9.0", "roave/security-advisories": "dev-latest" }, "autoload": { "psr-4": { "App\\": "src/" } }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } }, "config": { "sort-packages": true }, "scripts": { "test": "phpunit" } } """ This "composer.json" file specifies the required PHP version and project dependencies, along with development dependencies for testing. The "autoload" section defines the mapping between namespaces and directories. "scripts" allows you to define common commands. "config" allow you to define configurations. **Anti-Pattern:** Manually downloading libraries and including them in your project. This approach quickly leads to dependency conflicts, difficulty in updating libraries, and a lack of consistent dependency management. ## 2. Framework Selection ### Standard Choose a well-maintained PHP framework (e.g., Laravel, Symfony) that aligns with project requirements. Adhere to the framework's conventions and best practices. Evaluate the trade-offs of micro-frameworks (e.g., Slim, Lumen) vs. full-stack frameworks. **Do This:** * Select a framework that suits the project's scope and complexity. * Follow the framework's recommended project structure. * Utilize the framework's built-in features for routing, templating, ORM, etc., instead of reinventing the wheel. * Stay updated with the framework's latest releases and security patches. * Use the frameworks provided testing tools **Don't Do This:** * Build your own custom framework unless absolutely necessary. * Ignore the framework's conventions and create a disorganized project structure. * Try to mix and match components from different frameworks. * Use outdated or unsupported frameworks. **Why This Matters:** Established frameworks provide a solid foundation, promoting code reuse, consistency, and security. They handle common tasks, allowing developers to focus on business logic. Following framework conventions improves collaboration and maintainability. Selecting and using the right framework helps with efficiency and performance. **Code Example (Laravel):** """php // app/Http/Controllers/UserController.php namespace App\Http\Controllers; use App\Models\User; use Illuminate\Http\Request; class UserController extends Controller { public function index() { $users = User::all(); return view('users.index', ['users' => $users]); } public function store(Request $request) { $validatedData = $request->validate([ 'name' => 'required|max:255', 'email' => 'required|email|unique:users', 'password' => 'required|min:8', ]); $user = User::create($validatedData); return redirect('/users')->with('success', 'User created successfully.'); } } """ This Laravel controller demonstrates using the framework's ORM (Eloquent) to retrieve and create users. It also shows request validation. Following Laravel's MVC structure ("app/Http/Controllers", "app/Models", "resources/views") maintains consistency and improves readability. **Anti-Pattern:** Trying to build a web application from scratch without using a framework. This leads to duplicated effort, inconsistent code, increased security risks, and difficulty scaling. ## 3. Code Analysis Tools ### Standard Integrate static analysis tools (e.g., PHPStan, Psalm) into your development workflow to identify potential bugs, type errors, and code quality issues early on. Use code-sniffing tools (e.g., PHP_CodeSniffer) to enforce coding standards. **Do This:** * Configure static analysis tools with a strict level of checking. * Run static analysis regularly as part of your CI/CD pipeline. * Address all reported issues by either fixing the code or suppressing the error with a clear explanation. * Use a common coding standard like PSR-12 with PHP_CodeSniffer. * Automate code style checks with tools like PHP CS Fixer. **Don't Do This:** * Ignore warnings from static analysis tools. * Disable static analysis tools to avoid fixing issues. * Commit code with unresolved code style violations. **Why This Matters:** Static analysis and code-sniffing tools automatically detect potential problems in your code before runtime, improving code quality, reducing bugs, and ensuring adherence to coding standards. This leads to more reliable, maintainable, and consistent codebases. **Code Example (PHPStan):** 1. Install PHPStan using Composer: """bash composer require --dev phpstan/phpstan """ 2. Create a "phpstan.neon" configuration file: """yaml parameters: level: 7 paths: - src - tests excludePaths: - src/Migrations # Example exclude """ 3. Run PHPStan: """bash ./vendor/bin/phpstan analyse """ **Anti-Pattern:** Skipping static analysis because it takes time to fix the reported issues. The initial investment in fixing these issues will save significant time and effort in the long run by preventing bugs and improving code quality. ## 4. Debugging and Profiling ### Standard Use Xdebug for debugging and profiling PHP applications. Configure Xdebug to integrate with your IDE. Utilize profiling tools (e.g., Blackfire.io) to identify performance bottlenecks. **Do This:** * Install and configure Xdebug properly. * Use Xdebug's stepping and breakpoint features to understand code execution. * Set "xdebug.mode=debug" and "xdebug.start_with_request=yes" or "trigger" in your php.ini file (development only). Use "off" in production. * Use Blackfire.io or similar tools to profile slow requests. * Analyze profiling results to identify and optimize slow code paths. Use "xhprof" for server side profiling. * Use a code formatter that supports PSR-12 to keep the code readable and maintainable. **Don't Do This:** * Rely solely on "var_dump" or "print_r" for debugging. * Leave Xdebug enabled in production. * Ignore performance bottlenecks identified by profiling tools. **Why This Matters:** Xdebug provides advanced debugging features that make it easier to understand code execution and identify bugs. Profiling helps you pinpoint performance bottlenecks, allowing you to optimize your code for faster execution. **Code Example (Xdebug):** 1. Install Xdebug (usually via PECL). 2. Configure "php.ini": """ini zend_extension=xdebug.so xdebug.mode=debug xdebug.start_with_request=yes xdebug.client_host=host.docker.internal xdebug.client_port=9003 """ 3. Set breakpoints in your IDE. 4. Run your PHP script and trigger a debugging session from your IDE, or using a browser extension. The "xdebug.client_host=host.docker.internal" setting is crucial when running PHP in a Docker container, allowing Xdebug to connect back to the host machine. The port can be different on some setups too but is usually 9003 or 9000. **Anti-Pattern:** Leaving Xdebug enabled with "xdebug.mode=debug" and "xdebug.start_with_request=yes" in production. This can significantly impact performance and expose debugging information. ## 5. Templating Engines ### Standard Use a templating engine (e.g., Twig, Blade) to separate presentation logic from business logic. Avoid embedding PHP code directly within templates. Utilize template inheritance and components for code reuse. **Do This:** * Choose a templating engine that meets your project's needs. * Use template variables to pass data from PHP to templates. * Utilize template inheritance to create reusable layouts. * Use components or macros to encapsulate reusable UI elements. Encapsulate complex logic within a view composer. * Escapes all output to prevent XSS **Don't Do This:** * Embed complex PHP logic directly in templates. * Use "echo" or "print" statements directly in templates. * Ignore the templating engine's security features (e.g., auto-escaping). **Why This Matters:** Templating engines improve code organization, readability, and security by separating concerns. Template inheritance and components promote code reuse and maintainability. **Code Example (Twig):** """twig {# templates/users/index.html.twig #} {% extends 'base.html.twig' %} {% block body %} <h1>User List</h1> <ul> {% for user in users %} <li>{{ user.name }} - {{ user.email }}</li> {% endfor %} </ul> {% endblock %} """ """twig {# templates/base.html.twig #} <!DOCTYPE html> <html> <head> <title>{% block title %}My App{% endblock %}</title> </head> <body> <header> {% include 'header.html.twig' %} </header> <main> {% block body %}{% endblock %} </main> <footer> {% include 'footer.html.twig' %} </footer> </body> </html> """ This example demonstrates Twig template inheritance, where "users/index.html.twig" extends the "base.html.twig" layout and defines the content for the "body" block. **Anti-Pattern:** Embedding PHP code directly within templates. This makes templates difficult to read, maintain, and debug. It also increases the risk of security vulnerabilities. ## 6. Caching Strategies ### Standard Implement caching strategies to improve application performance. Use various caching mechanisms (e.g., object caching, page caching, opcode caching) based on the specific needs of your application. **Do This:** * Enable opcode caching (e.g., OpCache) in production. * Use object caching (e.g., Redis, Memcached) to store frequently accessed data. * Implement page caching for static or infrequently updated pages. * Use HTTP caching headers (e.g., "Cache-Control", "Expires") to leverage browser caching. * Invalidate the cache when data changes. Configure your ORM to use caching layer if possible. **Don't Do This:** * Neglect caching altogether. * Cache sensitive data without proper encryption. * Invalidate the cache too aggressively, leading to cache thrashing. * Use overly long cache durations for dynamic content. **Why This Matters:** Caching reduces database load, improves response times, and enhances the overall user experience. Opcode caching optimizes PHP execution by caching compiled code. Object caching stores frequently accessed data in memory. Page caching serves static content directly without executing PHP. Browser caching leverages the client's browser to store static assets. **Code Example (Redis Object Caching):** """php use Redis; $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $key = 'user:123'; $user = $redis->get($key); if (!$user) { // Fetch user data from database $userData = $db->getUser(123); // Serialize the data before storing in Redis $user = serialize($userData); $redis->set($key, $user, 3600); // Cache for 1 hour } else { //Unserialize cached data $user = unserialize($user); } // Now you can work with the $user object """ **Anti-Pattern:** Not using any caching mechanisms in a high-traffic application. This puts unnecessary load on the database and slows down the application, resulting in a poor user experience. ## 7. API Clients and HTTP Requests ### Standard Use a robust HTTP client library (e.g., Guzzle) for making API requests. Handle errors and exceptions gracefully. Implement retry mechanisms for transient failures. **Do This:** * Use Guzzle or Symfony's HttpClient component for HTTP requests. * Set appropriate timeouts for API requests. * Implement error handling for API responses (e.g., check status codes). * Use dependency injection to manage HTTP client instances. * Implement exponential backoff retry mechanisms for transient errors * Use PSR-7 interfaces when appropriate to define requests and responses. **Don't Do This:** * Use "file_get_contents" or "curl" directly for API requests. * Ignore errors from API responses. * Retry requests indefinitely without a limit. **Why This Matters:** HTTP client libraries provide a clean and consistent API for making HTTP requests, handling authentication, setting headers, and processing responses. Error handling ensures that your application can gracefully recover from API failures. Retry mechanisms improve resilience to transient errors. **Code Example (Guzzle):** """php use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; $client = new Client(['base_uri' => 'https://api.example.com']); try { $response = $client->request('GET', '/users/123', [ 'headers' => [ 'Authorization' => 'Bearer YOUR_API_KEY', ], 'timeout' => 5, // Timeout in seconds ]); $statusCode = $response->getStatusCode(); $body = $response->getBody()->getContents(); if ($statusCode === 200) { $userData = json_decode($body); // Process User Data } else { error_log("API request failed with status code: " . $statusCode); } } catch (RequestException $e) { error_log("API request exception: " . $e->getMessage()); // Handle request exception (e.g., network error, timeout) } """ **Anti-Pattern:** Using "file_get_contents" to make external API calls. This is insecure and lacks the flexibility of a dedicated HTTP client library. It doesn't handle complex scenarios such as timeouts, authentication or error handling easily. ## 8. Database Interaction and ORMs ### Standard Use an ORM (e.g., Doctrine, Eloquent) to interact with databases. Avoid writing raw SQL queries directly in your code. Utilize database migrations to manage schema changes. **Do This:** * Choose an ORM that suits your project's needs. * Use the ORM's query builder or repository pattern for data access. * Define database schema migrations for all schema changes. * Use prepared statements or parameterized queries to prevent SQL injection. * Use connection pools to improve performance **Don't Do This:** * Write raw SQL queries directly in your code. * Modify the database schema manually without using migrations. * Store sensitive data in plain text in the database. * Ignore the ORM's performance optimization features (e.g., eager loading, caching). **Why This Matters:** ORMs abstract database interactions, providing a more object-oriented and maintainable way to work with data. Database migrations ensure that schema changes are version-controlled and can be applied consistently across environments. Proper use of parametrized queries prevents SQL injection attacks. **Code Example (Doctrine):** """php use Doctrine\ORM\EntityManager; use App\Entity\User; /** @var EntityManager $entityManager */ $user = $entityManager->find(User::class, 123); if (!$user) { $user = new User(); $user->setName('John Doe'); $user->setEmail('john.doe@example.com'); $entityManager->persist($user); $entityManager->flush(); } echo $user->getName(); """ **Anti-Pattern:** Writing raw SQL queries directly within your PHP code. This makes your code harder to maintain, increases the risk of SQL injection vulnerabilities, and reduces database portability. ## 9. Queue Systems ### Standard Utilize queue systems (e.g., RabbitMQ, Redis Queue, Beanstalkd) for asynchronous task processing. Decouple time-consuming or non-critical tasks from the main request lifecycle **Do This:** * Select a reliable queue system that fits your project's architecture. * Push jobs to the queue for tasks that don't need immediate processing. * Implement robust error handling and retry mechanisms for failed jobs. * Monitor queue health and performance. * Use dedicated worker processes to consume tasks from the queue. **Don't Do This:** * Perform time-consuming tasks directly within the request lifecycle. * Ignore failed jobs or lose messages in the queue. * Overload the queue system with too many jobs. **Why This Matters:** Queue systems improve application responsiveness and scalability by offloading time-consuming tasks to background processes. They provide a reliable way to process asynchronous tasks, such as sending emails, processing images, or generating reports. **Code Example (Laravel Queue with Redis):** 1. Configure the "queue" connection in "config/queue.php" to use Redis. 2. Create a job class: """php namespace App\Jobs; use App\Models\User; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Mail; class SendWelcomeEmail implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $user; public function __construct(User $user) { $this->user = $user; } public function handle() { Mail::to($this->user->email)->send(new WelcomeEmail($this->user)); } } """ 3. Dispatch the queue job: """php use App\Jobs\SendWelcomeEmail; use App\Models\User; $user = User::find($userId); SendWelcomeEmail::dispatch($user); """ **Anti-Pattern:** Performing slow tasks inline within the web request. This causes the user to wait, reduces responsiveness, and can lead to timeouts. ## 10. Logging and Monitoring ### Standard Implement comprehensive logging and monitoring to track application behavior, identify errors, and detect performance issues. Use a centralized logging system and monitoring tools. **Do This:** * Use a logging library (e.g., Monolog) to generate structured log messages. * Log errors, warnings, and informational messages. * Use appropriate log levels to categorize messages. * Send logs to a centralized logging system (e.g., ELK stack, Graylog). * Use monitoring tools (e.g., New Relic, DataDog) to track application performance. * Set up alerts for critical errors or performance degradation. **Don't Do This:** * Rely solely on "var_dump" or "print_r" for debugging in production. * Log sensitive information without proper redaction. * Ignore error logs or performance alerts. **Why This Matters:** Logging and monitoring provide valuable insights into application behavior, helping you identify and resolve issues quickly. Centralized logging allows you to aggregate and analyze logs from multiple sources. Monitoring tools provide real-time performance metrics and alerts. **Code Example (Monolog):** """php use Monolog\Logger; use Monolog\Handler\StreamHandler; // Create a log channel $log = new Logger('my_app'); $log->pushHandler(new StreamHandler('/path/to/your.log', Logger::WARNING)); // Add records to the log $log->warning('Foo'); $log->error('Bar'); """ **Anti-Pattern:** Not logging errors or warnings in production. This makes it difficult to diagnose and resolve issues, leading to prolonged downtime and frustrated users. This document provides a comprehensive set of guidelines for using tooling and ecosystem components effectively in PHP development, focusing on current best practices for modern codebases. Use this document as a guide for developers and as context for AI coding assistants.