©Sergey Emelyanov 2025 | Alle Rechte vorbehalten
In diesem Artikel erstellen wir eine PHPStan-Regel, die Business-Logik in Controllern erkennt. Ziel ist es, Entwickler zu zwingen, Logik in Services oder Actions auszulagern.
composer require --dev phpstan/phpstan
phpstan.neon
): includes:
- ./vendor/nunomaduro/larastan/extension.neon
parameters:
paths:
- app/
level: 9
Erstelle src/NoBusinessLogicInController.php
:
<?php
declare(strict_types=1);
namespace Utils\PHPStan;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\RuleErrorBuilder;
final class NoBusinessLogicInController implements \PHPStan\Rules\Rule
{
public function __construct(
private readonly ControllerDetermination $determination
) {}
public function getNodeType(): string
{
return \PhpParser\Node\Stmt\Class_::class;
}
public function processNode(Node $node, Scope $scope): array
{
$className = (string) $node->namespacedName;
if (!$this->determination->isController($className)) {
return [];
}
foreach ($node->getMethods() as $method) {
foreach ($method->getStmts() ?? [] as $stmt) {
if ($this->isBusinessLogic($stmt)) {
return [
RuleErrorBuilder::message(sprintf(
'Method %s in Controller %s enthält Geschäftslogik. Auslagern in Service!',
$method->name->toString(),
$className
))->build()
];
}
}
}
return [];
}
private function isBusinessLogic(Node $stmt): bool
{
return $stmt instanceof Node\Stmt\If_
|| $stmt instanceof Node\Stmt\While_
|| $stmt instanceof Node\Stmt\For_
|| $stmt instanceof Node\Stmt\Foreach_
|| $stmt instanceof Node\Stmt\Switch_;
}
}
Interface src/ControllerDetermination.php
:
<?php
declare(strict_types=1);
namespace Utils\PHPStan;
interface ControllerDetermination
{
public function isController(string $className): bool;
}
Implementierung (z. B. Suffix-Check):
<?php
declare(strict_types=1);
namespace Utils\PHPStan;
final class SuffixControllerDetermination implements ControllerDetermination
{
public function __construct(private readonly string $suffix = 'Controller') {}
public function isController(string $className): bool
{
return str_ends_with($className, $this->suffix);
}
}
Füge die Regel zur phpstan.neon hinzu:
services:
-
class: Utils\PHPStan\NoBusinessLogicInController
tags: [phpstan.rules.rule]
arguments:
$determination: @controllerDetector
-
class: Utils\PHPStan\SuffixControllerDetermination
id: controllerDetector
arguments:
$suffix: 'Controller'
tests/NoBusinessLogicInControllerTest.php
:
<?php
declare(strict_types=1);
namespace Utils\PHPStan\Tests;
use PHPStan\Testing\RuleTestCase;
use Utils\PHPStan\NoBusinessLogicInController;
use Utils\PHPStan\SuffixControllerDetermination;
final class NoBusinessLogicInControllerTest extends RuleTestCase
{
public function testRule(): void
{
$this->analyse(
[__DIR__ . '/Fixtures/ControllerWithLogic.php'],
[
['Method index in Controller App\Http\Controllers\UserController enthält Geschäftslogik.', 10]
]
);
}
protected function getRule(): \PHPStan\Rules\Rule
{
return new NoBusinessLogicInController(
new SuffixControllerDetermination()
);
}
}
tests/Fixtures/ControllerWithLogic.php
:
<?php
namespace App\Http\Controllers;
class UserController
{
public function index()
{
// Verbotene Logik!
if (auth()->user()->isAdmin()) {
$users = User::query()->where('active', true)->get();
}
return view('users.index', compact('users'));
}
}
vendor/bin/phpstan analyse
Beispielausgabe:
------ ---------------------------------------------------------
Line app/Http/Controllers/UserController.php
------ ---------------------------------------------------------
10 Method index in Controller App\Http\Controllers\UserController
enthält Geschäftslogik. Auslagern in Service!
------ ---------------------------------------------------------
🔗 Weiterführende Links:
„Guter Code ist wie ein Witz – er braucht keine Erklärung.“ – Corey House
©Sergey Emelyanov 2025 | Alle Rechte vorbehalten