# TYPO3 Coding Guidelines **Source:** TYPO3 Core API Reference - Coding Guidelines **Purpose:** PHP code style, formatting standards, and PSR-12 compliance for TYPO3 extensions ## PSR-12 Compliance TYPO3 follows **PSR-12: Extended Coding Style** as the foundation for PHP code style. **Key PSR-12 Requirements:** - 4 spaces for indentation (NO tabs) - Unix line endings (LF) - Maximum line length: 120 characters (soft limit), 80 recommended - Opening braces for classes/methods on same line - One statement per line - Visibility MUST be declared on all properties and methods ## Identifier Naming Conventions ### Variables and Methods: camelCase ```php // ✅ Right $userName = 'John'; $totalPrice = 100; public function calculateTotal() {} public function getUserData() {} // ❌ Wrong $user_name = 'John'; // snake_case $UserName = 'John'; // PascalCase public function CalculateTotal() {} // PascalCase public function get_user_data() {} // snake_case ``` ### Classes: UpperCamelCase (PascalCase) ```php // ✅ Right class UserController {} class PaymentService {} class ProductRepository {} // ❌ Wrong class userController {} // camelCase class payment_service {} // snake_case class productRepository {} // camelCase ``` ### Constants: SCREAMING_SNAKE_CASE ```php // ✅ Right const MAX_UPLOAD_SIZE = 1024; const API_ENDPOINT = 'https://api.example.com'; private const DEFAULT_TIMEOUT = 30; // ❌ Wrong const maxUploadSize = 1024; // camelCase const ApiEndpoint = '...'; // PascalCase ``` ### Namespaces: UpperCamelCase ```php // ✅ Right namespace Vendor\ExtensionKey\Domain\Model; namespace Vendor\ExtensionKey\Controller; // ❌ Wrong namespace vendor\extension_key\domain\model; namespace Vendor\extension_key\Controller; ``` ## Function and Method Naming ### Descriptive Names with Verbs ```php // ✅ Right: Verb + noun, descriptive public function getUserById(int $id): ?User {} public function calculateTotalPrice(array $items): float {} public function isValidEmail(string $email): bool {} public function hasPermission(string $action): bool {} // ❌ Wrong: No verb, ambiguous public function user(int $id) {} public function price(array $items) {} public function email(string $email) {} public function permission(string $action) {} ``` ### Boolean Methods: is/has/can/should ```php // ✅ Right public function isActive(): bool {} public function hasAccess(): bool {} public function canEdit(): bool {} public function shouldRender(): bool {} // ❌ Wrong public function active(): bool {} public function access(): bool {} public function checkEdit(): bool {} ``` ## Array Formatting ### Short Syntax Only ```php // ✅ Right: Short array syntax $items = []; $config = ['foo' => 'bar']; $users = [ ['name' => 'John', 'age' => 30], ['name' => 'Jane', 'age' => 25], ]; // ❌ Wrong: Long array syntax (deprecated) $items = array(); $config = array('foo' => 'bar'); ``` ### Multi-line Array Formatting ```php // ✅ Right: Proper indentation and trailing comma $configuration = [ 'key1' => 'value1', 'key2' => 'value2', 'nested' => [ 'subkey1' => 'subvalue1', 'subkey2' => 'subvalue2', ], // Trailing comma ]; // ❌ Wrong: No trailing comma, inconsistent indentation $configuration = [ 'key1' => 'value1', 'key2' => 'value2', 'nested' => [ 'subkey1' => 'subvalue1', 'subkey2' => 'subvalue2' ] ]; ``` ## Conditional Statement Layout ### If/ElseIf/Else ```php // ✅ Right: Proper spacing and braces if ($condition) { doSomething(); } elseif ($otherCondition) { doSomethingElse(); } else { doDefault(); } // ❌ Wrong: Missing spaces, wrong brace placement if($condition){ doSomething(); } else if ($otherCondition) { doSomethingElse(); } else { doDefault(); } ``` ### Switch Statements ```php // ✅ Right switch ($status) { case 'active': processActive(); break; case 'pending': processPending(); break; default: processDefault(); } // ❌ Wrong: Inconsistent indentation switch ($status) { case 'active': processActive(); break; case 'pending': processPending(); break; default: processDefault(); } ``` ## String Handling ### Single Quotes Default ```php // ✅ Right: Single quotes for simple strings $message = 'Hello, World!'; $path = 'path/to/file.php'; // ❌ Wrong: Unnecessary double quotes $message = "Hello, World!"; // No variable interpolation $path = "path/to/file.php"; ``` ### Double Quotes for Interpolation ```php // ✅ Right: Double quotes when interpolating $name = 'John'; $message = "Hello, {$name}!"; // ❌ Wrong: Concatenation instead of interpolation $message = 'Hello, ' . $name . '!'; // Less readable ``` ### String Concatenation ```php // ✅ Right: Spaces around concatenation operator $fullPath = $basePath . '/' . $filename; $message = 'Hello ' . $name . ', welcome!'; // ❌ Wrong: No spaces around operator $fullPath = $basePath.'/'.$filename; $message = 'Hello '.$name.', welcome!'; ``` ## PHPDoc Comment Standards ### Class Documentation ```php // ✅ Right: Complete class documentation /** * Service for calculating product prices with tax and discounts * * This service handles complex price calculations including: * - Tax rates based on country * - Quantity discounts * - Promotional codes * * @author John Doe * @license GPL-2.0-or-later */ final class PriceCalculationService { // ... } ``` ### Method Documentation ```php // ✅ Right: Complete method documentation /** * Calculate total price with tax for given items * * @param array $items * @param string $countryCode ISO 3166-1 alpha-2 country code * @param float $discountPercent Discount percentage (0-100) * @return float Total price including tax * @throws \InvalidArgumentException If country code is invalid */ public function calculateTotal( array $items, string $countryCode, float $discountPercent = 0.0 ): float { // ... } // ❌ Wrong: Missing or incomplete documentation /** * Calculates total */ public function calculateTotal($items, $countryCode, $discountPercent = 0.0) { // Missing param types, descriptions, return type } ``` ### Property Documentation ```php // ✅ Right /** * @var UserRepository User data repository */ private readonly UserRepository $userRepository; /** * @var array Configuration options */ private array $config = []; // ❌ Wrong: No type hint or description /** * @var mixed */ private $userRepository; ``` ## Curly Brace Placement ### Classes and Methods: Same Line ```php // ✅ Right: Opening brace on same line class MyController { public function indexAction(): ResponseInterface { // ... } } // ❌ Wrong: Opening brace on new line (K&R style) class MyController { public function indexAction(): ResponseInterface { // ... } } ``` ### Control Structures: Same Line ```php // ✅ Right if ($condition) { doSomething(); } foreach ($items as $item) { processItem($item); } // ❌ Wrong: Opening brace on new line if ($condition) { doSomething(); } ``` ## Namespace and Use Statements ### Namespace Structure ```php // ✅ Right: Proper namespace declaration title; } public function setTitle(string $title): void { $this->title = $title; } } ``` ## PHPStan and Static Analysis TYPO3 extensions should use **PHPStan level 10** (strictest) for maximum type safety and code quality. ### PHPStan Baseline Hygiene **Critical Rule:** New code must NEVER add errors to `phpstan-baseline.neon`. The baseline file exists only for legacy code that hasn't been refactored yet. All new code must pass PHPStan level 10 without baseline suppression. **Validation:** ```bash # Check if your changes added to baseline git diff HEAD~1 Build/phpstan-baseline.neon # If count increased, you MUST fix the underlying issues # Example: count: 8 → count: 9 means you added 1 new error ``` ### Type-Safe Mixed Value Handling **Common PHPStan Error:** "Cannot cast mixed to int/string/bool" **Occurs with:** TypoScript configuration, user input, API responses **❌ Wrong (adds to baseline):** ```php // PHPStan: Cannot cast mixed to int $maxSize = (int) ($conf['maxSize'] ?? 0); ``` **✅ Right (passes level 10):** ```php // Type-guard before casting $value = $conf['maxSize'] ?? 0; if (is_numeric($value)) { $maxSize = (int) $value; } else { $maxSize = 0; } ``` ### Common Mixed Type Patterns **Arrays from configuration:** ```php // ❌ Wrong $items = (array) $conf['items']; // ✅ Right $items = []; if (isset($conf['items']) && is_array($conf['items'])) { $items = $conf['items']; } ``` **Strings from user input:** ```php // ❌ Wrong $name = (string) $_POST['name']; // ✅ Right $name = ''; if (isset($_POST['name']) && is_string($_POST['name'])) { $name = $_POST['name']; } ``` **Boolean from configuration:** ```php // ❌ Wrong $enabled = (bool) $conf['enabled']; // ✅ Right $enabled = isset($conf['enabled']) && (bool) $conf['enabled']; ``` ### Pre-Commit PHPStan Check Always run PHPStan before committing: ```bash # Run PHPStan composer ci:php:stan # Verify no new baseline entries git diff Build/phpstan-baseline.neon # If baseline changed, fix the issues instead of committing the baseline ``` ## Conformance Checklist - [ ] All PHP files use 4 spaces for indentation (NO tabs) - [ ] Variables and methods use camelCase - [ ] Classes use UpperCamelCase - [ ] Constants use SCREAMING_SNAKE_CASE - [ ] Array short syntax [] used (not array()) - [ ] Multi-line arrays have trailing commas - [ ] Strings use single quotes by default - [ ] String concatenation has spaces around `.` operator - [ ] All classes have PHPDoc comments - [ ] All public methods have PHPDoc with @param and @return - [ ] Opening braces on same line for classes/methods - [ ] declare(strict_types=1) at top of all PHP files - [ ] Proper namespace structure matching directory - [ ] Use statements grouped and sorted - [ ] Type declarations on all properties and method parameters - [ ] Maximum line length 120 characters - [ ] Unix line endings (LF) - [ ] PHPStan level 10 passes with zero errors - [ ] No new errors added to phpstan-baseline.neon - [ ] Type-guards before casting mixed values (is_numeric, is_string, is_array)