Entwickler, der außergewöhnliche CRM- und Laravel-Lösungen liefert

Als erfahrener Entwickler spezialisiere ich mich auf Laravel- und Vue.js-Entwicklung, die Implementierung von Vtiger CRM sowie auf vielfältige WordPress-Projekte. Meine Arbeit zeichnet sich durch kreative, dynamische und benutzerzentrierte Weblösungen aus, die individuell an die Bedürfnisse meiner Kunden angepasst werden.

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.

Custom PHPStan-Regel: Geschäftslogik in Controllern verbieten

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.

Voraussetzungen

  • PHPStan installiert:
  composer require --dev phpstan/phpstan
  • Grundlegende PHPStan-Konfiguration (phpstan.neon):
  includes:
      - ./vendor/nunomaduro/larastan/extension.neon

  parameters:
      paths:
          - app/
      level: 9

Regel-Implementierung

1. Rule-Klasse

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_;
    }
}

2. Controller-Erkennung

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);
    }
}

Regel registrieren

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 schreiben

1. Test-Klasse

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()
        );
    }
}

2. Test-Fixture

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'));
    }
}

Regel ausführen

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!  
 ------ --------------------------------------------------------- 

Fazit

  • Vorteile:
  • Erzwingt saubere MVC-Architektur.
  • Automatische Code-Reviews via CI/CD.
  • Erweiterungen:
  • Eigene Detektion für REST-Controller.
  • Support für andere Frameworks (Symfony, Laravel).

🔗 Weiterführende Links:

„Guter Code ist wie ein Witz – er braucht keine Erklärung.“Corey House