Checkout um Step erweitern

Hallo,

ich müsste den Checkout um einen Step erweitern der zwischen Adresseingabe (Registrieren) und Zahlungsart auswählen integriert werden soll.

In dem Step sollen noch Zusatzprodukte zum Artikel im Warenkorb gewählt werden können (bei gewissen Artikeln kann man zB. optional noch Sensoren bestellen und diese optional wiederum selbst montieren oder gegen Aufpreis montieren lassen).

Den Step habe ich schon ergänzt, aber wie zur Hölle krieg ich die Logik (Controller, Handler, etc) dafür jetzt in den aktuellen Checkoutprozess?

 

wie ich das sehe, müsste ich zuerst mal verhindern, dass man nach den Login direkt zu „Checkout/Confirm“ gelangt, also einen Redirect schalten.

Der passende Hook hierfür dachte ich könnte  ‚Shopware_Controllers_Frontend_Checkout::indexAction::replace‘ sein.

Ich habe also ein Plugin geschrieben und den Hook in getSubscribedEvents mit einer Function registriert… dort leite ich mit einem redirect auf meine eigene URL um.

aber es macht sich keine Änderung bemerkbar.

Es gibt für die URL noch keinen Controller, aber das war nur ein erster Test … wie bekomm ich denn die ganzen Daten aus dem bisherigen Checkout (Adresse, User, Cart, etc) in meinen neueun Controller für den neuen Checkout-Step?

Ich würde in meiner eigenen Function außerdem gern auf die selben Daten zugreifen können wie der Originalcontroller … bekomm ich die irgendwie aus meinem \Enlight_Hook_HookArgs raus?

 

ps: ich kann programmieren, mir fehlt nur der letzte Hinweis wo ich ansetzen muss…

 

viele Grüße

Habe ein wenig weiter experimentiert und erhalte nun

Fatal error : Uncaught Enlight_Exception: Class “Shopware_Controllers_Frontend_Checkout” has hooks, please use the instance method in /xxx/engine/Library/Enlight/Class.php:70 Stack trace: #0 /xxx/engine/Library/Enlight/Controller/Action.php(108): Enlight_Class->__construct() #1 /xxx/custom/plugins/MyPlugin/MyPlugin.php(44): Enlight_Controller_Action->__construct(Object(Enlight_Controller_Request_RequestHttp), Object(Enlight_Controller_Response_ResponseHttp)) #2 /xxx/engine/Library/Enlight/Event/Handler/Default.php(91): MyPlugin\MyPlugin->onPreDispatchCheckout(Object(Enlight_Hook_HookArgs)) #3 /xxx/engine/Library/Enlight/Event in  /xxxengine/Library/Enlight/Class.php  on line  70

Hallo,

 

warum machst du dir das Leben so schwer? Passe dein Theme an, oder deine Plugin View, sodass der Button nicht auf die eigentliche Seite führt, sondern auf deine eigene. Dann kannst du nämlich dafür einen eignen Controller mit eigener Action und eigener view definieren.

Der Fehler mit dem Hook kommt deswegen, weil du auf eine Klasse nur einmal einen Hook setzen kannst. Hier in deinem Fall scheint es schon einen Hook zu geben.

Hooks wirst du aber für dein Projekt nicht brauchen. Das nach dem Einloggen richtig auf deine Seite weiterleiten wäre eigentlich nur die sTarget Action anpassen im URL. Dafür müsste es Events geben.

 

 

MFG

 

derwunner

@derwunner‍

Aber ich muss doch von verschiedenen Seiten aus umleiten können. Das sind ja alles im Core festgelegte Redirects wie ich das sehe.

wenn man zB einen Artikel in den Warenkorb legt und nicht eingeloggt ist, muss nach dem Login natürlich auf MEINE Seite weitergeleitet werden statt auf Confirm.

Das Gleiche wenn man eingeloggt ist und auf „Zur Kasse“ klickt.

Ebenfalls muss ich anpassen, dass nach der Adresseingabe nicht auf den Step für Versand & Zahlung geleitet wird, sondern auf meinen Step und von diesem aus dann auf Versand & Zahlung

 

Ich muss also diese ganzen Weiterleitungen aushebeln und meine eigene integrieren, wenn ich nicht ganz auf dem Holzweg bin.

wie ich das sehe, muss ich dann auch den Frontend_Checkout_Controller erweitern um meine Action, analog zu Frontend_Checkout_paymentAction oder Frontend_Checkout_confirmAction. Aber wie?

Außerdem muss ich mich doch irgendwie in diesen „Checkout-Context“ hängen, damit ich in meinem zusätzlichen Step auch auf alle Daten des Checkouts zugreifen und sie verändern kann, oder nicht?

Viel zu kompliziert! Jedes Formular hat eine Action, jeder Link hat ein Linkziel. Wie wäre es, wenn du einfach diese Ziele bearbeitest?! Du weißt ja, wo du im Theme eingreifen musst, also nach welchen Steps deine eigenen Sachen kommen sollen. Und genau diese Formular- bzw. Linkziele passt du einfach auf deine an. Fertig!

Falls das nicht bei allen gehen sollte, kannst du immer noch deinen eigenen Router erstellen. Ein Router bestimmt, wohin welcher URL intern leitet, bzw. welcher URL auf welchen weiterleitet. Das ist ein sehr mächtiges Feature, mit dem man viel machen kann, beispielsweise von einem festen URL zu einen anderen festen URL umleiten:

public static function getSubscribedEvents()
{
    return [
        'Enlight_Controller_Router_Route' => 'onRouteRewrite'
    ];
}

public function onRouteRewrite (\Enlight_Event_EventArgs $args)
{
    /**
     * @var \Enlight_Controller_Request_RequestHttp $request
     */
    $request = $args->get('request');

    /**
     * @var \Shopware\Bundle\StoreFrontBundle\Struct\ProductContextInterface
     */
    $context = $args->get('context');

    $requestUri = mb_strtolower($request->getPathInfo());

    return array(
        'module' => 'frontend',
        'controller' => 'meincontroller',
        'controllerName' => 'MeinController',
        'action' => 'index'
    );
}

In $requestUri steht dann z. B. „frontend/checkout/cart“. Du musst ein array zurück geben mit den Daten, wohin es umleiten soll. In diesem Fall leitet er auf einen eigenen Controller im Modul frontend auf die Action index um.

Steht hier dokumentiert: The Shopware SEO engine

Shopware setzt hier auf das Framework Slugify (den Link dazu aus der Doku habe ich leider nicht gefunden…).

Okay danke schon mal @derwunner‍!

heißt  ich suche in den Templates meine Links ala „checkout/confirm“ und ändere diese zu „checkout/meinStep“ ?

Dann sind die Links schon mal korrekt, OK!

Wie aber krieg ich dann meinen eigenen Controller für „checkout/meinStep“ da rein?

Ich weiß, wie ich einen Controller erzeuge der auf eine bestimmte URL hört. Aber wie ich das sehe, suchen sich diese Checkout-Steps immer im Frontend_Checkout-Controller eine passende Action und haben keinen eigenen Controller pro Action.

Und letztendlich: wie erhalte ich in meinem eigenen Controller dann auch die Checkout-Prozess-Infos wie Warenkorb, Versandart, etc bzw. kann diese manipulieren?

Also wegen dem Checkout Controller Problem hast du zwei Möglichkeiten:

  • Möglichkeit 1: Ändere mittels dem Event Controller_GetPath (oder so ähnlich, einfach das selbe Event für Checkout verwenden, mit dem Du Shopware den Pfad zu deinen eigenen Controllern bekannt machst) den Pfad zum Checkout Controller. Leite deinen Checkout Controller dann vom normalen ab. So solltest du deine Action ergänzen können (ungetestet)
  • Möglichkeit 2: Benenne den Controller einfach anders. Also ändere die Links z. B. auf meinController/meinStep anstatt von checkout/meinStep

Die Infos solltest du alle aus der Datenbank erhalten. Schau dir an, was alles in der User Session gespeichert ist, da rein schreibt Shopware vieles. Zum Beispiel auch die userID, die sessionId, usw. mit dem du die Daten aus dem Warenkorb von der Datenbank bekommst.

Aber am besten schaust du dir das im Checkout Controller selbst an, die müssen das ja auch irgendwie machen…

@derwunner‍  Danke für die Hinweise!

ich habe jetzt in meinem Plugin auf das Event  Enlight_Controller_Dispatcher_ControllerPath_Frontend_Checkout  eine Methode gelegt, die auf meinen Controller Shopware_Controllers_Frontend_Checkout umleitet.

Das funktioniert einwandfrei! Allerdings sind jetzt alle anderen Checkout-Actions wie checkout/confirm oder checkout/paymentShipping kaputt, weil mein Controller diese nicht unterstützt schätze ich… hab schon versucht vom Core-Checkout-Controller abzuleiten aber das krieg ich nicht hin … 

extends Shopware_Controllers_Frontend_Checkout

–> klappt nicht, da die Core-Klasse genau so heißt wie meine (aber das muss sie ja laut Namenskonvention für Controller)

 

ich habe auch schon einen eigenen Controller mit anderer URL versucht, das funktioniert auch. Allerdings fehlen mir dann bestimmte Settings und der Controller is falsch, dann fehlt zB. im Body eine entscheidende Klasse und damit die Styles…

 

wie mach ichs denn richtig? Bzw wie hast du deine Beschreibung Möglichkeit 1 genau gemeint?

 

Hi Flo,

muss es denn ein eigener Controller sein?

Mit einem Subscriber geht das einfacher. Hier das BestPractice Beispiel: https://forum.shopware.com/discussion/comment/193172/#Comment_193172

Viele Grüße

@simkli‍

nein wie das funktioniert is mir eigentlich egal… aber ich brauche halt alle Daten aus dem bestehenden Controller. Da war meiner Meinung nach das einfachste von diesem abzuleiten, aber das krieg ich irgendwie nicht hin.

Ich brauche halt eine Klasse die auf die URL checkout/meinStep reagiert um darin dann zusätzliche Daten in den View zu hängen (zB Artikel in den Warenkorb legen, Versandkosten erhöhen, Versandart anpassen, etc). Dafür sind Controller doch da?

@FloC3 schrieb:

@simkli‍

nein wie das funktioniert is mir eigentlich egal… aber ich brauche halt alle Daten aus dem bestehenden Controller. Da war meiner Meinung nach das einfachste von diesem abzuleiten, aber das krieg ich irgendwie nicht hin.

Ich brauche halt eine Klasse die auf die URL checkout/meinStep reagiert um darin dann zusätzliche Daten in den View zu hängen (zB Artikel in den Warenkorb legen, Versandkosten erhöhen, Versandart anpassen, etc). Dafür sind Controller doch da?

Nein, eigentlich sollten die Controller so wenig Logik wie möglich beinhalten. http://symfony.com/doc/current/best\_practices/controllers.html

Na das solltest du doch alles mit meinem Link von oben realiseren können, oder wo hängt es? :slight_smile:

Viele Grüße 

@simkli‍

daran, wie ich an alle Daten und Methoden aus dem ursprünglichen Checkout-Controller komme.

Controller sollten keine Logik enthalten? Der Checkout-Controller im Shopware-Core ist pure Logik … Laden, Validieren, Errorhandling, Datenbankzugriffe, Speichern… alles is da drin

Controller sollten keine Logik enthalten? Der Checkout-Controller im Shopware-Core ist pure Logik … Laden, Validieren, Errorhandling, Datenbankzugriffe,

Der Checkout-Controller ist auch noch nicht refaktorisiert sondern sehr stark legacy. Das heißt aber nicht, dass du das auch so machen sollst. Schau dir mal den AddressController an. Der kam erst mit Shopware 5.2. dazu und zeigt, wie man es machen soll. Die BusinessLogic steckt hier z.B. im AddressService. Der Controller holt lediglich die Daten aus dem Request, gibt sie an den Service weiter und gibt die Antwort/Fehlermeldung aufbereitet ans Frontend zurück. „Symfony follows the philosophy of „thin controllers and fat models“. This means that controllers should hold just the thin layer of glue-code needed to coordinate the different parts of the application.“ - Quelle

daran, wie ich an alle Daten und Methoden aus dem ursprünglichen Checkout-Controller komme.

Welche genau brauchst du denn? Mit $args->getSubject() kommst du an den Controller und erreichst alle public-Methoden. 

um darin dann zusätzliche Daten in den View zu hängen (zB Artikel in den Warenkorb legen, Versandkosten erhöhen, Versandart anpassen, etc). Dafür sind Controller doch da?

  • Dinge in den Warenkorb zu legen: sBasket (da brauchst du den Controller nicht) 
  • Versandkosten erhöhen geht soweit ich weiß auch nicht im Controller
  • Versandart anpassen -> sBasket
  • Daten in den View zu hängen -> geht auch über den Subscriber $subject->View()

Viele Grüße

@simkli‍ 

eine abschließende Frage noch weil ich es grad auf die schnelle nicht testen kann…

d.h. ich regstriere mich, wenn ich die URL checkout/meinStep abbilden will, auf das Event Enlight_Controller_Action_Frontend_Checkout_MeinStep ?

 

bleibt noch das Problem, dass ich dann denke ich aus dem „Checkout-Kontext“ fliege wie oben beschrieben oder?

Hi Flo,

d.h. ich regstriere mich, wenn ich die URL checkout/meinStep abbilden will, auf das Event Enlight_Controller_Action_Frontend_Checkout_MeinStep ?

yep. Das hättest du ja einfach ausprobieren können :stuck_out_tongue:

bleibt noch das Problem, dass ich dann denke ich aus dem „Checkout-Kontext“ fliege wie oben beschrieben oder?

Ich weiß nicht genau was du mit „Checkout-Context“ meinst. Der Warenkorb wird mit einer Session-ID verknüpft. Auf diese hast du eigentlich überall Zugriff (Cache beachten!*). Manipulieren kannst du das auch überall*. Das hängt nicht an einem Controller. Die Klasse sBasket macht eigentlich fast die ganze Warenkorblogik. Zugriff erhältest du u.a. über den Dependency Container. $this->container->get(‚modules‘)->getModule(‚Basket‘); (in deiner Plugin-Klasse, früher Shopware()->Modules()->Basket())

Viele Grüße

* viele Controller werden gecached. D.h. die Logik wird u.U. nicht ausgeführt. Eigene Controller sind davon grundsätzlich ausgenommen. Man muss diese explizit hinzufügen. Die Checkout/Account/Register/Address-Controller sind alle ausgenommen => werden nicht gecached.

Ich rede davon, dass der Body-Tag zB spezielle Klassen (Controller und Action) bekommt, wenn man sich im Checkout-Controller befindet.

Beispielsweise is–ctl-checkout und is–act-confirm  für den Fall /checkout/confirm. Daran hängen sehr viele Stylings.

Wenn ich einen eigenen Controller schreibe mit eigener URL, so fehlt mir das.

Ja, das tust du ja mit der oben beschriebenen Subscriber-Methode nicht. (Hier nutzt man einen Event-Subscriber, keinen eigenen Controller) https://forum.shopware.com/discussion/comment/193172/#Comment_193172

Mach’ es doch am besten einfach mal. Dann siehst du es selbst  Sticking-out-tongue

Viele Grüße  Smile

@derwunner‍ 

vielen Dank euch beiden!

Aus Zeitgründen musste ich es jetzt so lösen, dass ich den Core-Controller in mein Plugin kopiert und angepasst / erweitert hab.

Das Projekt muss raus…

Ich werden nach dem Livegang wenn mal Luft ist jedoch eure Sachen ausprobieren!

 

konkret muss ich folgende Dinge machen: 

  • eigene Seite aufrufen bei /checkout/meinStep
  • nach dem Step gehts weiter zur Zahlung, es muss also eine Save-Action für den Step geben
  • in der Save-Action muss ich die Daten aus dem MeinStep-Formular auslesen und ggf. Artikel zum Basket hinzufügen oder entfernen
  • Außerdem muss in der Save-Action etwas in die Session geschrieben werden
  • Nach der Save-Action muss redirected werden zu PaymentShipping
  • Beim Aufruf jedes Checkout-Steps muss ein Wert aus der Session gelesen, an den View gehängt und via Smarty ausgelesen werden können
  • Beim Abschluss des Kaufes im Confirm muss ich die von MeinStep in die Session geschriebenen Dinge zur Bestellung in die Datenbank schreiben
     

krieg ich das alles mit der Lösung hin?

Mein Problem is aktuell noch, dass ich nicht so recht weiß, wie ich erkennen kann, welches Event ich grad für den jeweiligen Anwendungsfall brauche…