Heim > Backend-Entwicklung > PHP-Tutorial > Etwas fortgeschrittenerer Code als das Beispiel in der Framework-Dokumentation.

Etwas fortgeschrittenerer Code als das Beispiel in der Framework-Dokumentation.

Patricia Arquette
Freigeben: 2024-12-27 19:06:12
Original
387 Leute haben es durchsucht

Slightly more advanced code than the example in the frameworks documentation.

Als Programmierer bin ich im Laufe der Jahre auf zahlreiche Projekte gestoßen, die ich übernommen, verwaltet, erweitert, weiterentwickelt und übergeben habe. Bei vielen handelte es sich um Spaghetti-Code oder, wie es auch genannt wird, um einen „großen Schlammball“. Dieses Problem betrifft häufig Projekte, die auf einem Framework basieren und bei denen der Code ähnlich wie Beispiele in der Dokumentation des Frameworks organisiert ist.

Leider wird in der Dokumentation von MVC-Frameworks häufig nicht darauf hingewiesen, dass Codebeispiele in erster Linie der Veranschaulichung der Funktionalität dienen und nicht für reale Anwendungen geeignet sind. Daher integrieren reale Projekte oft alle Schichten in Controller- oder Presenter-Methoden (im Fall von MVP), die Anfragen (typischerweise HTTP-Anfragen) verarbeiten. Wenn das Framework ein Komponentenobjektmodell wie Nette enthält, sind Komponenten häufig Teil des Controllers oder Präsentators, was die Situation weiter verkompliziert.

Probleme mit nicht optimaler Codestruktur

Der Code solcher Projekte nimmt schnell an Länge und Komplexität zu. In einem einzigen Skript werden Datenbankoperationen, Datenbearbeitung, Komponenteninitialisierung, Vorlageneinstellungen und Geschäftslogik gemischt. Während Autoren gelegentlich Teile der Funktionalität in eigenständige Dienste (normalerweise Singletons) extrahieren, hilft dies selten viel. Ein solches Projekt wird schwer zu lesen und schwer zu warten.

Meiner Erfahrung nach werden standardisierte Entwurfsmuster selten verwendet, insbesondere in kleineren Projekten (5–50.000 Codezeilen), wie beispielsweise einfachen CRUD-Anwendungen für kleine Unternehmen, die die Verwaltung vereinfachen möchten. Dennoch könnten diese Projekte stark von Mustern wie CQRS (Command Query Responsibility Segregation) und DDD (Domain-Driven Design) profitieren.

  1. HTTP-Anfrage -> Controller/Moderator
  2. Eingabevalidierung -> In Anfrage konvertieren
  3. Anfrage -> Befehl
  4. Befehl -> Rendern/Antwort

Ich werde zeigen, wie dieser Ansatz mithilfe des Nette-Stacks, Contributte (mit Symfony Event Dispatcher-Integration) und Nextras ORM aussieht.

// Command definition
final class ItemSaveCommand implements Command {
    private ItemSaveRequest $request;

    public function __construct(private Orm $orm) {
        //
    }

    /** @param ItemSaveRequest $request */
    public function setRequest(Request $request): void {
        $this->request = $request;
    }

    public function execute(): void {
        $request = $this->request;

        if ($request->id) {
            $entity = $this->orm->items->getById($request->id);
        } else {
            $entity = new Item();
            $entity->uuid = $request->uuid;
        }

        $entity->data = $request->data;

        $this->orm->persist($entity);
    }
}

// Command Factory
interface ItemSaveCommandFactory extends FactoryService {
    public function create(): ItemSaveCommand;
}

// Request
#[RequestCommandFactory(ItemSaveCommandFactory::class)]
final class ItemSaveRequest implements Request {
    public int|null $id = null;
    public string $uuid;
    public string $data;
}

/**
 * Command execution service
 * Supports transactions and request logging
 */
final class CommandExecutionService implements Service {
    private \DateTimeImmutable $requestedAt;

    public function __construct(
        private Orm $orm,
        private Container $container,
        private Connection $connection,
    ) {
        $this->requestedAt = new \DateTimeImmutable();
    }

    /** @throws \Throwable */
    public function execute(AbstractCommandRequest $request, bool $logRequest = true, bool $transaction = true): mixed {
        $factoryClass = RequestHelper::getCommandFactory($request);
        $factory = $this->container->getByType($factoryClass);

        $cmd = $factory->create();
        $clonedRequest = clone $request;
        $cmd->setRequest($request);

        try {
            $cmd->execute();

            if (!$transaction) {
                $this->orm->flush();
            }

            if (!$logRequest) {
                return;
            }

            $logEntity = RequestHelper::createRequestLog(
                $clonedRequest,
                $this->requestedAt,
                RequestLogConstants::StateSuccess
            );

            if ($transaction) {
                $this->orm->persistAndFlush($logEntity);
            } else {
                $this->orm->persist($logEntity);
            }

            return;
        } catch (\Throwable $e) {
            if ($transaction) {
                $this->connection->rollbackTransaction();
            }

            if (!$logRequest) {
                throw $e;
            }

            $logEntity = RequestHelper::createRequestLog(
                $clonedRequest,
                $this->requestedAt,
                RequestLogConstants::StateFailed
            );

            if ($transaction) {
                $this->orm->persistAndFlush($logEntity);
            } else {
                $this->orm->persist($logEntity);
            }

            throw $e;
        }
    }
}

// Listener for executing commands via Event Dispatcher
final class RequestExecutionListener implements EventSubscriberInterface {
    public function __construct(
        private CommandExecutionService $commandExecutionService
    ) {
        //
    }

    public static function getSubscribedEvents(): array {
        return [
            RequestExecuteEvent::class => 'onRequest'
        ];
    }

    /** @param ExecuteRequestEvent<mixed> $ev */
    public function onRequest(ExecuteRequestEvent $ev): void {
        $this->commandExecutionService->execute($ev->request, $ev->logRequest, $ev->transaction);
    }
}

// Event definition for command execution
final class ExecuteRequestEvent extends Event {
    public function __construct(
        public Request $request,
        public bool $logRequest = true,
        public bool $transaction = true,
    ) {
        // Constructor
    }
}

// Event Dispatcher Facade
final class EventDispatcherFacade {
    public static EventDispatcherInterface $dispatcher;

    public static function set(EventDispatcherInterface $dispatcher): void {
        self::$dispatcher = $dispatcher;
    }
}

// Helper function for simple event dispatching
function dispatch(Event $event): object {
    return EventDispatcherFacade::$dispatcher->dispatch($event);
}

// Usage in Presenter (e.g., in response to a component event)
final class ItemPresenter extends Presenter {
    public function createComponentItem(): Component {
        $component = new Component();
        $component->onSave[] = function (ItemSaveRequest $request) {
            dispatch(new ExecuteRequestEvent($request));
        };

        return $component;
    }
}
Nach dem Login kopieren

Diese Lösung hat mehrere Vorteile. Die datenbankbezogene Logik ist von MVC/P getrennt, was zu einer besseren Lesbarkeit und einfacheren Wartung beiträgt. Das Request-Objekt, das als Datenträger fungiert, eignet sich ideal für die Protokollierung in der Datenbank, beispielsweise einem Ereignisprotokoll. Dadurch wird sichergestellt, dass alle datenverändernden Benutzereingaben in chronologischer Reihenfolge gespeichert werden. Im Fehlerfall können diese Protokolle überprüft und bei Bedarf erneut abgespielt werden.

Zu den Nachteilen dieses Ansatzes gehört die Tatsache, dass der Befehl keine Daten zurückgeben sollte. Wenn ich also weiter mit den neu erstellten Daten arbeiten muss (z. B. sie an eine Vorlage übergeben muss), muss ich sie mithilfe ihrer UUID abrufen, weshalb sie sowohl in der Anfrage als auch in der Entität enthalten ist. Ein weiterer Nachteil besteht darin, dass bei Änderungen am Datenbankschema alle Anforderungen aktualisiert werden müssen, damit sie mit dem neuen Schema übereinstimmen, was zeitaufwändig sein kann.

Das obige ist der detaillierte Inhalt vonEtwas fortgeschrittenerer Code als das Beispiel in der Framework-Dokumentation.. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage