This file defines the coding standards and working rules for AI agents and developers contributing to this PHP codebase. It is based on the principles from PHP: The Right Way and adapted into practical project instructions.
Source reference: https://phptherightway.com/
Write PHP that is:
PHP does not have only one canonical “right way,” so prefer widely accepted standards, documented project conventions, and clear tradeoffs over personal style.
Use the current stable PHP version supported by the project.
Default expectation:
PHP 8.x+
Do not introduce code that depends on unsupported PHP versions unless the project explicitly targets a legacy runtime.
When adding a language feature, verify that it is supported by the project’s configured PHP version in composer.json.
Example:
{
"require": {
"php": ">=8.2"
}
}
Follow recognized PHP standards unless the repository already defines stricter rules.
Preferred standards:
Use automated tooling rather than manual formatting arguments.
Recommended tools:
composer require --dev squizlabs/php_codesniffer
composer require --dev friendsofphp/php-cs-fixer
Example checks:
vendor/bin/phpcs --standard=PSR12 src tests
vendor/bin/php-cs-fixer fix --dry-run --diff
Example fix:
vendor/bin/php-cs-fixer fix
Use English names for code symbols and infrastructure.
Use:
class InvoiceRepository
{
public function findByCustomerId(int $customerId): array
{
// ...
}
}
Avoid unclear abbreviations:
class InvRepo
{
public function fbcid($cid)
{
// ...
}
}
<?php tags. Do not use short open tags.declare(strict_types=1);
public, protected, or private.Prefer a predictable structure.
Example:
project-root/
public/
index.php
src/
Controller/
Service/
Repository/
Entity/
ValueObject/
templates/
config/
tests/
var/
cache/
logs/
vendor/
composer.json
Rules:
public/ is the web root.src/, config/, tests/, vendor/, or .env files through the web server.src/.var/ or another ignored runtime directory.All new application classes must use namespaces.
Use PSR-4 autoloading through Composer.
Example composer.json:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}
After changing autoload rules, run:
composer dump-autoload
Example class:
<?php
declare(strict_types=1);
namespace App\Service;
final class InvoiceCalculator
{
public function calculateTotal(array $items): int
{
// Return cents, not floating-point dollars.
return array_sum(array_column($items, 'amountCents'));
}
}
Use Composer for PHP dependencies.
Rules:
composer require or composer require --dev.composer.json and composer.lock for applications.vendor/.Commands:
composer install
composer update vendor/package
composer audit
composer validate
Use composer update intentionally. Do not casually update every dependency in unrelated work.
Prefer clear object-oriented code for domain and application logic.
Use classes for cohesive behavior:
final class CustomerName
{
public function __construct(private string $value)
{
if (trim($value) === '') {
throw new InvalidArgumentException('Customer name is required.');
}
}
public function value(): string
{
return $this->value;
}
}
Guidelines:
Prefer dependency injection over creating dependencies inside classes.
Good:
final class RegisterUser
{
public function __construct(
private UserRepository $users,
private PasswordHasher $passwords
) {
}
public function handle(string $email, string $plainPassword): void
{
$hash = $this->passwords->hash($plainPassword);
$this->users->create($email, $hash);
}
}
Avoid:
final class RegisterUser
{
public function handle(string $email, string $plainPassword): void
{
$users = new UserRepository();
$passwords = new PasswordHasher();
// ...
}
}
Rules:
Use PDO or a well-maintained database abstraction layer/ORM.
Never concatenate untrusted input into SQL.
Bad:
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];
Good:
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
Rules:
Transaction example:
$pdo->beginTransaction();
try {
$orders->create($order);
$auditLog->record('order.created', $order->id());
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
Treat all external data as untrusted.
Untrusted data includes:
$_GET$_POST$_REQUEST$_COOKIE$_SERVERExample:
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if ($email === false || $email === null) {
throw new InvalidArgumentException('A valid email address is required.');
}
For HTML output:
function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
Usage:
<p><?= e($user->name()) ?></p>
Rules:
escapeshellarg() when passing controlled values to shell commands, and avoid shell execution when possible.../, /, \, and null bytes.Never store plain-text passwords.
Use PHP’s password API:
$hash = password_hash($plainPassword, PASSWORD_DEFAULT);
if (! password_verify($plainPassword, $hash)) {
throw new RuntimeException('Invalid credentials.');
}
Rules:
password_hash() for new password hashes.password_verify() for login checks.password_needs_rehash() when algorithm/cost settings change.md5, sha1, or raw sha256 for passwords.Do not call unserialize() on untrusted data.
Prefer JSON for data exchange:
$data = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
$json = json_encode($data, JSON_THROW_ON_ERROR);
Rules:
JSON_THROW_ON_ERROR for new code.Rules:
.env.example.Example .gitignore entries:
.env
.env.local
/config/local.php
/var/cache/
/var/log/
/vendor/
Example .env.example:
APP_ENV=local
APP_DEBUG=true
DATABASE_URL=mysql://user:password@localhost:3306/app
Use exceptions for exceptional failure paths.
Development:
Production:
Do not leak:
Example:
try {
$service->handle($request);
} catch (Throwable $e) {
$logger->error('Order processing failed.', [
'exception' => $e,
'requestId' => $requestId,
]);
http_response_code(500);
echo 'An unexpected error occurred.';
}
Keep presentation separate from business logic.
Rules:
Plain PHP template example:
<h1><?= e($pageTitle) ?></h1>
<ul>
<?php foreach ($users as $user): ?>
<li><?= e($user->name()) ?></li>
<?php endforeach; ?>
</ul>
Rules:
X-Forwarded-For unless configured behind a trusted proxy.Example POST guard:
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit('Method Not Allowed');
}
Before completing any feature, verify:
password_hash() and password_verify().eval, exec, shell_exec, system, passthru, unserialize.composer audit.Automated tests are expected for new behavior.
Preferred tools:
Rules:
var_dump() or manual browser testing as the only verification.Example PHPUnit test:
final class InvoiceCalculatorTest extends TestCase
{
public function testItCalculatesTotalInCents(): void
{
$calculator = new InvoiceCalculator();
$total = $calculator->calculateTotal([
['amountCents' => 1000],
['amountCents' => 2500],
]);
self::assertSame(3500, $total);
}
}
Run tests:
vendor/bin/phpunit
Use static analysis when available.
Recommended tools:
composer require --dev phpstan/phpstan
composer require --dev vimeo/psalm
Common quality commands:
composer validate
composer audit
vendor/bin/phpcs --standard=PSR12 src tests
vendor/bin/phpunit
vendor/bin/phpstan analyse src tests
Do not ignore tool failures without documenting why.
Use PHPDoc where it adds clarity, especially for arrays, generics-like structures, complex return values, and public APIs.
Good:
/**
* @return list<Customer>
*/
public function findActiveCustomers(): array
{
// ...
}
Avoid noisy comments that repeat the code:
// Increment i by one.
$i++;
Rules:
Rules:
When modifying this codebase, the AI agent must:
Before considering work complete:
If this project contains legacy PHP:
Legacy code should still move toward:
The agent must not:
md5, sha1, or raw fast hashes for passwords.unserialize() untrusted data.vendor/.A project may include scripts like this:
{
"scripts": {
"test": "phpunit",
"style": "phpcs --standard=PSR12 src tests",
"style:fix": "php-cs-fixer fix",
"analyse": "phpstan analyse src tests",
"quality": [
"@style",
"@test",
"@analyse"
]
}
}
Then run:
composer quality
When in doubt, choose the boring, obvious, secure PHP solution:
This project is a small PHP MVC framework called MindVisionCode PHP.
It is intentionally inspired by a Classic ASP MVC framework style:
Do not turn this into Laravel, Symfony, Slim, or another large framework.
Install dependencies:
composer install
Regenerate autoload files:
composer dump-autoload
Run local server:
php -S localhost:8000 -t public
Run basic tests:
php tests/run.php
Browser → public/index.php → Request → Dispatcher → Router → Route → Controller → ViewModel/Repository → View → Response
Powered by TurnKey Linux.