docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -0,0 +1,166 @@
<?php
declare(strict_types=1);
namespace App\Framework\Quality\PHPStan\Rules\Naming;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassNode;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
/**
* Enforces Controller Naming Convention:
* - View Controllers: Show{Feature} (only GET requests, ViewResult)
* - Action Controllers: {Verb}{Feature} (POST/PUT/DELETE, Business Logic)
* - NO "Controller" suffix
*
* @implements Rule<InClassNode>
*/
final class ControllerNamingRule implements Rule
{
public function getNodeType(): string
{
return InClassNode::class;
}
/**
* @param InClassNode $node
*/
public function processNode(Node $node, Scope $scope): array
{
$classReflection = $node->getClassReflection();
$className = $classReflection->getName();
$shortName = $classReflection->getNativeReflection()->getShortName();
// Skip if not in Application namespace (controllers are in Application)
if (!str_starts_with($className, 'App\\Application\\')) {
return [];
}
// Check if class has Route attributes (controller indicator)
$hasRouteAttribute = false;
foreach ($classReflection->getNativeReflection()->getMethods() as $method) {
foreach ($method->getAttributes() as $attribute) {
if (str_contains($attribute->getName(), 'Route')) {
$hasRouteAttribute = true;
break 2;
}
}
}
if (!$hasRouteAttribute) {
return []; // Not a controller
}
$errors = [];
// Rule 1: NO "Controller" suffix
if (str_ends_with($shortName, 'Controller')) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Controller "%s" should not have "Controller" suffix. Use Show{Feature} for views or {Verb}{Feature} for actions.',
$shortName
))
->identifier('naming.controller.noSuffix')
->build();
}
// Rule 2: Show* pattern validation
if (str_starts_with($shortName, 'Show')) {
$violations = $this->validateShowController($classReflection);
foreach ($violations as $violation) {
$errors[] = RuleErrorBuilder::message($violation)
->identifier('naming.controller.showPattern')
->build();
}
}
// Rule 3: Action controller pattern (Submit, Create, Update, Delete, etc.)
$actionVerbs = ['Submit', 'Create', 'Update', 'Delete', 'Handle', 'Process', 'Execute'];
$startsWithActionVerb = false;
foreach ($actionVerbs as $verb) {
if (str_starts_with($shortName, $verb)) {
$startsWithActionVerb = true;
break;
}
}
if (!str_starts_with($shortName, 'Show') && !$startsWithActionVerb) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Controller "%s" must start with "Show" (for views) or an action verb (Submit, Create, Update, Delete, Handle, Process, Execute).',
$shortName
))
->identifier('naming.controller.pattern')
->build();
}
return $errors;
}
/**
* @return string[]
*/
private function validateShowController(\PHPStan\Reflection\ClassReflection $classReflection): array
{
$violations = [];
$shortName = $classReflection->getNativeReflection()->getShortName();
foreach ($classReflection->getNativeReflection()->getMethods() as $method) {
if (!$method->isPublic() || $method->isStatic()) {
continue;
}
// Check for Route attribute
$routeAttributes = array_filter(
$method->getAttributes(),
fn($attr) => str_contains($attr->getName(), 'Route')
);
foreach ($routeAttributes as $routeAttr) {
$args = $routeAttr->getArguments();
// Check if method is not GET (Show* should only have GET routes)
if (isset($args['method'])) {
$methodName = is_object($args['method'])
? $args['method']->name
: $args['method'];
if ($methodName !== 'GET' && !str_contains((string)$methodName, 'GET')) {
$violations[] = sprintf(
'Show controller "%s" has non-GET route in method "%s()". Show controllers should only handle GET requests. Use %s for POST/PUT/DELETE.',
$shortName,
$method->getName(),
$this->suggestActionName($shortName, $methodName)
);
}
}
// Check return type (Show* should return ViewResult)
$returnType = $method->getReturnType();
if ($returnType && !str_contains($returnType->__toString(), 'ViewResult')) {
$violations[] = sprintf(
'Show controller "%s" method "%s()" should return ViewResult, not "%s".',
$shortName,
$method->getName(),
$returnType->__toString()
);
}
}
}
return $violations;
}
private function suggestActionName(string $showClassName, string $httpMethod): string
{
$feature = str_replace('Show', '', $showClassName);
return match(strtoupper($httpMethod)) {
'POST' => "Submit{$feature}",
'PUT', 'PATCH' => "Update{$feature}",
'DELETE' => "Delete{$feature}",
default => "{Verb}{$feature}"
};
}
}