Zugriff auf Oxid-Funktionen aus Klasse im Modul

Hallo zusammen,

zurzeit versuche ich ein Modul umzusetzen, welches Datenbankeinträge aus einer Newsletter-Engine (“Sendy”) in die Shop-Datenbank kopiert (also in eine Richtung “synchronisiert”). Den Ansatz habe ich zwar, jedoch scheitert es im Umgang mit der Oxid-Struktur.
Beim Eintragen in den Newsletter (form submit event) wird das Skript, welches als PHP-Datei in meinem Modul vorliegt, ausgeführt. Soweit sogut.
Nun möchte ich in diesem Skript mit der dafür vorgesehenen Oxid-Funktion “oxRegistry::getConfig()” die Werte der Moduleinstellungen abrufen. Leider findet der Parser Registry.php nicht, egal wie ich es versuche (über Namespace, Aufrufendes Skript in Klasse gepackt, …). Laut Error-Log sucht er im Verzeichnis meiner Modulklasse, welche die Funktion aufruft. Irgendwie logisch. Trotzdem scheint es in den Standard-Modulen, an denen ich mich orientiert habe, so problemlos zu funktionieren.

footer.tpl

<form action="modules/kcs/kcs_sendysync/classes/kcs_sendysync__subscribe_noname.php" class="d-flex" method="POST" accept-charset="utf-8">
  <div class="col d-flex p-0">
    <input type="email" name="newsletter_email" maxlength="200" placeholder="Ihre E-Mail-Adresse" required class="bg_dg b_1 bc_lg br_0 w-100 px-3 py-2"/>
  </div>

  <div class="col-auto px-0">
    <button class="bg_red b_0 fw-bold ff_headline py-3 px-4"
            type="submit"
            name="newsletter_submit">
      ABONNIEREN
    </button>
  </div>
</form>

→ aufgerufene kcs_sendysync__subscribe_noname.php des Moduls:

<?php
namespace kcs\kcs_sendysync\classes;

use \OxidEsales\Eshop\Core\Registry;
use oxRegistry;

// Konfigs auslesen
//$myConfig = \OxidEsales\Eshop\Core\Registry::getConfig();           // fails
//$myConfig = Registry::getConfig();                                  // fails
//$myConfig = oxRegistry::getConfig();                                // fails
//$sendyRoot = \OxidEsales\Eshop\Core\Registry::get("sSendyRoot");   // fails



// Sendy initiieren
//$sendyRoot =  $myConfig->getConfigParam('sSendyRoot');
$list = 'abc';
$api_key = 'abc123';
$boolean = 'false';
$success_url = 'success.php';
$fail_url = 'failed.php';


// Informationen aus POST holen
$email = $_POST['newsletter_email'];


// Query für Eintrag in SendyDB konstruieren
$postdata = http_build_query(
  array(
    'email' => $email,
    'list' => $list,
    'api_key' => $api_key,
    'boolean' => $boolean
  )
);

// Neuen Subscriber in SendyDB per Sendy-API eintragen
$opts = array('http' => array('method'  => 'POST', 'header'  => 'Content-type: application/x-www-form-urlencoded', 'content' => $postdata));
$context  = stream_context_create($opts);
$result = file_get_contents($sendyRoot.'/subscribe', false, $context);

// Verbindung zu SendyDB herstellen
//require_once $sendyRoot.'/api/_connect.php';

// Download des neuen Eintrags aus der SendyDB
// ...

// Verbindung zu SendyDB schließen
// ...

// Query für Eintrag in OxidDB konstruieren
// ...

// Neuen Subscriber in OxidDB per Oxid-API eintragen
// ...


// TESTING AREA - wrapped as class besser?
echo $sendyRoot;
echo 'Ich bin durchgelaufen!';

/*
public class kcs_sendysync__subscribe_noname {
  public function sendyTest() {
  $sendyRoot = \OxidEsales\Eshop\Core\Registry::get("sSendyRoot");
    echo 'sendyTest() wurde aufgerufen!';
  }
}
*/

Der Vollständigkeit halber hier auch die metadata.php des Moduls:

<?php
$sMetadataVersion = '2.1';

$aModule = array(
    'id'           => 'kcs_sendysync',
    'title'        => 'SendySync',
    'version'      => '1.0.1',
    'author'       => 'Mustermann',
    'url'          => 'http://www.musterdomain.de',
    'email'        => '[email protected]',
    'thumbnail'    => 'logo.png',

    'settings'     => array(
      array('group' => 'kcs_sendysync_general', 'name' => 'sSendyRoot', 'type' => 'str', 'value' => 'http://localhost/firma/sendy')
    )
);

Hier der aktuelle Output:

Fatal error: Uncaught Error: Class 'oxRegistry' not found in C:\XAMPP\htdocs\firma\oxid\source\modules\kcs\kcs_sendysync\classes\kcs_sendysync__subscribe_noname.php:8

Was mache ich falsch, das ich in meiner Klasse keinen Zugriff auf die Klasse habe? Selbst wenn ich den Namespace als use angebe (was selbst widerrum funktioniert!), sucht er offensichtlich am falschen Ort.

$myConfig = \OxidEsales\Eshop\Core\Registry::getConfig();

$sSendyRoot = $myConfig->getConfigParam('sSendyRoot');
2 Likes

Hallo rubbercut,

danke für deine Antwort.
Leider entspricht dein Vorschlag meinem ersten (auskommentierten) Versuch in der kcs_sendysync__subscribe_noname.php.

Auch hier der Output, dass die Klasse Registry nicht gefunden wird, da er scheinbar nur im Arbeitsverzeichnis der ausführenden Datei sucht:

Fatal error: Uncaught Error: Class 'OxidEsales\Eshop\Core\Registry' not found in C:\XAMPP\htdocs\***\oxid\source\modules\kcs\kcs_sendysync\classes\kcs_sendysync__subscribe_noname.php:9 Stack trace: #0 {main} thrown in C:\XAMPP\htdocs\***\oxid\source\modules\kcs\kcs_sendysync\classes\kcs_sendysync__subscribe_noname.php on line 9

Selbst wenn ich die Registry.php per require_once einbinde, funktioniert es nicht. Irgendwo scheint sich der Wurm eingeschlichen zu haben, denn die Syntax ist ja offensichtlich korrekt.
Leider kann ich das Problem beim besten Willen nicht ausfindig machen :thinking:
Hatte bisher nie Probleme bei der Nutzung der Oxid-Funktionen (es findet übrigens keine, auch nicht OxNew() o.ä.), nur waren das bisher immer Klassen, die vorhandene extended haben. Was im Grunde ja aber kein Unterschied machen sollte, immerhin sind die Namespaces für den Zugriff ja konstant, egal wie oder von wo man auf sie zugreifen möchte.

Alles ein wenig durcheinander.
Dann füge oberhalb von class “use OxidEsales\Eshop\Core\Registry;” hinzu.

Bereits versucht, leider immernoch kein Zugriff.
Habe eben alles nochmal in ein frisches Testmodul eingetragen, um das Ganze etwas zu abstrahieren. Leider mit dem gleichen Ergebnis. Irgendwas scheine ich grundlegend falsch zu machen bzw. zu vergessen.
Hatte schon die metadata.php im Verdacht, aber wenn da etwas nicht stimmen würde, würde er ja gar nicht erst die eigene PHP-Datei finden. Aber die findet er ja.

Die Dateien, die Du benutzt sollten in der metadata eingebunden sein.

Du solltest Dir dringend anschauen, wie man Module aufbaut:

Und dazu vielleicht ein einfaches Bespiel anschauen: downloads.foxido.de/Mobiile-Detect.zip
Inhalt für 4.10.x und 6.2x. Es wird eine Datei eingebunden.

1 Like

Habe in der Doku gelesen, das Dateien, die keine OXID-Klasse extenden ab Metadata v2.x automatisch vom Autoloader erfasst werden. Da das Skript an sich ausgeführt wird, hatte ich einen Fehler an dieser Stelle ausgeschlossen.
Danke für dein Beispiel, werde direkt reinschauen!

// Edit:
Das Beispiel beinhaltet leider auch nur eine Klasse, die eine vorhandene Oxid-Klasse (ViewConfig) erweitert. In solchen Fällen funktioniert das Ganze auch tadellos. Nur wenn ich komplett eigene Klassen schreibe bzw in Dateien ausgelagerte PHP-Skripte vorliegen habe, habe ich in diesen keinen Zugriff auf die Oxid-Klassen.

Schau mal in den Link für Dummies. Eigenständige Controller kannst über die Metadata we folgt einbinden (Bsp: neuer Admin-Controller):

'controllers' => array(
	//Admin-Article-Tab
		'xxx_article_pictures' => \foxido_de\pl_xxx\Controller\Admin\xxx_article_pictures::class,
	//Frontend
		//
		),

Danke für deine Geduld. Ich bin mit OXID offensichtlich komplett überfordert.
Also muss ich jede Datei, die in früheren Metadata-Versionen in ‘files’ gelandet ist, jetzt unter ‘Controller’ eintragen? Aber ich möchte doch gar kein neuen Controller implementieren…?
Im Endeffekt möchte ich ja einfach nur etwas PHP-Code, den ich in meinem Template für ein onClick-Ereignis benötige, in eine eigene PHP-Dateien eines eigenen Moduls auslagern. Innerhalb dieser ausgelagerten Dateien brauche ich Zugriff auf die Oxid-Klassen, um die Moduleinstellungen zu erhalten. Also das, was man mit “normalen” PHP mit einem simplen include erreicht.

Habe die Klasse nun als Controller wie folgt eingetragen:

'kcs_sendysync__subscribe_noname' => \kcs\kcs_sendysync\classes\kcs_sendysync__subscribe_noname::class,

Und die PHP-Datei enthält jetzt eine Klasse. Leider mit dem gleichen Ergebnis.

<?php
namespace kcs\kcs_sendysync\classes;

use OxidEsales\Eshop\Core\Registry;

class kcs_sendysync__subscribe_noname {
  

public function machWas()
  {
  echo "Test";  
    
  // Konfigs einlesen
  $myConfig = \OxidEsales\Eshop\Core\Registry::getConfig();
  $sendyRoot = $myConfig->getConfigParam('sSendyRoot');
  }

}

// Edit: Werde bis zum Feierabend versuchen, die Klasse Registry.php (und alle weiteren Oxid-Klassen die ich danach noch benötigen werde) in mein Modul mitreinzukopieren und einfach diese zu nutzen. Erzeugt zwar ne Menge Redundanz und ist sicher nicht nötig, wenn man es richtig macht - aber bin froh, wenn es überhaupt mal läuft.

Metadata 1.x hat den files-array. Da wurden zwei Arten von Klassen registriert: Controller und andere Klassen (z.B. Models oder Core). Die anderen Klassen macht jetzt der Autoloader, aber Controller brauchen ja einen eindeutigen Namen der von außen angesprochen werden kann, deshalb gibt es dafür jetzt das array Controllers.

Also wenn du im Browser etwas aufrufen willst, dann brauchst du dafür einen Controller der über das Oxid-Framework geladen wird. Das passiert mit index.php?cl=controllername. Der Controller hat immer eine Methode “render”, deren Rückgabe ist der Name des Templates das geladen werden soll. Wenn kein Template geladen werden soll kann man in der render-Methode den Output mit echo ausgeben und mit exit aussteigen.

Du kannst also nicht eine PHP-Klasse in deinem Modul direkt per form action anspringen, sondern müsstest über index.php?cl=controllername einen in deiner metadata registrierten Controller aufrufen.

Die einzige Möglichkeit eine Datei direkt aufzurufen ohne MVC-Pattern, und trotzdem das Oxid-Framework zu benutzen ist die Datei bootstrap.php zu includen, so wie in den Scripts im bin-Ordner.

1 Like

Hallo leofonic,

danke für die sehr informative Erläuterung!
Das hilft mir sehr weiter :slight_smile:
Wenn ich das richtig verstanden habe, ist der Grund, weshalb die Oxid-Funktionen aus meiner PHP-Datei nicht erreichbar sind, das mir schlichtweg ein Controller fehlt, der diesen Zugriff regelt, ich also das MVC-Pattern verletzt habe?

Okay, das Controller so unumgänglich sind, war mir nicht klar. Da ich das Controller-Konzept noch nicht ganz verstanden hatte, wollte ich das vorerst meiden - aber in dem Fall führt wohl kein Weg daran vorbei.

Wenn die unzähligen Doku’s/Tutorials/etc das nur auch so gut erklären würden.
Ist dir zufällig gute (idealerweise deutschsprachige) Literatur zu OXID/MVC/PHP/Smarty bekannt, die genauso anfängergerecht ist, wie deine Schilderung?

Ich denke, mit diesem Wissen kann ich mein Problem nun lösen. Ich werde hier berichten bzw. Rückfragen zur Controller-Implementierung stellen :grin: Aber heute widme ich mein Feierabend ausnahmsweise mal wieder der Freizeit, statt der Weiterbildung :grin:

Nochmal vielen Dank, das ihr euch die Zeit hierfür genommen habt!

Smarty.net gibt’s teilweise auf Deutsch. Es enthält neben dem Forum hier, alles was Du brauchst.

Noch ein Tipp, weil Du oben eine <form></form> hast. Schaue Dir action und die hidden fields cl und fnc im shop an. Analog dazu solltest Deine Form aufbauen.

Wenn du die PHP-Datei direkt aufrufst wird halt der Autoloader gar nicht geladen. Aber es stimmt was rubbercut meinte, eigentlich brauchst du wahrscheinlich keine eigene Seite sondern nur eine function, also das form lädt die gerade aktuelle Seite erneut und führt eine Funktion aus, wie z.B. im Login-Formular.

1 Like

Ja, die deutschsprachige Doku von Smarty ist echt okay. Durch die arbeite ich mich parallel seit einigen Tagen durch. Als PHP- bzw. allg. WebDev-Anfänger allerdings ziemlich mühselig. Da qualmt schonmal der Kopf, aber wird von Tag zu Tag besser :slight_smile:
Nur für OXID habe ich leider noch keine ordentliche Literatur gefunden. Die Bücher, die ich dazu gefunden habe, richten sich eher an Shopbetreiber bzw administrierende als an Entwickler.

Noch ein Tipp, weil Du oben eine <form></form> hast. Schaue Dir action und die hidden fields cl und fnc im shop an. Analog dazu solltest Deine Form aufbauen.

Danke für den Hinweis. Mir ist schon aufgefallen, das die inputs in den default themes fast ausschließlich mit diesen beiden Feldern arbeiten. Habe jedoch die Stelle noch nicht gefunden, wo diese aufgegriffen werden. Das müsste ja eigentlich irgendwo in einem FrontEnd-Controller und/oder JS passieren, richtig?

hier am Beispiel der Newsletter Anmeldung:
im Template sind alle Felder in einem “editval” array zusammengefasst

und im Controller wird dann die Daten von “editval” aus dem Request abgerufen

1 Like

Hallo zusammen,

aus den neuen Erkenntnissen ist folgender Code entstanden. Habe das Gefühl, ich nähere mich der Lösung, aber ganz funktionieren will es noch nicht.

Die <form> enthaltende footer.tpl des themes:
(Denke die passt soweit inzwischen, zumindest die erzeugten POST-Parameter sehen soweit okay aus)

<div id="footer_newsletter_email" class="col ff_text fc_white pe-0 my-auto">
  [{* Newsletter ohne Namensangabe abonnieren *}]
  <form class="form" action="[{$oViewConf->getSelfActionLink()}]" methode="post" role="form" novalidate="novalidate">
    <input type="hidden" name="cl" value="sendysyncmain">        [{* Controller, an den submitted wird *}]
    <input type="hidden" name="fnc" value="subscribe_noname">    [{* Funktion im Controller, die zu verwenden ist *}]
    <div class="input-group col d-flex p-0">
      <input id="subscribeParam" class="form-control bg_dg" type="email" name="editval[email]" value="" placeholder="Ihre E-Mail-Adresse" required>
      <span class="input-group-btn col-auto px-0">
      <button class="btn btn-primary bg_red b_0 fw-bold ff_headline py-3 px-4"
              type="submit">
          ABONNIEREN
      </button>
      </span>
    </div>
  </form>
</div>

Das obige Form sendet gemäß den Parametern cl und fnc die POST-Daten an meinen nachfolgenden MainController.php:

<?php
namespace kcs\kcs_sendysync\Controller;

use OxidEsales\Eshop\Core\Registry;

class MainController extends \OxidEsales\Eshop\Application\Controller\NewsletterController
  {

  public function subscribe_noname()
    {
    // Moduleinstellungen auslesen
    $Configs   = Registry::getConfig();
    $sendyRoot = $Configs->getConfigParam('sSendyRoot');
    $api_key   = $Configs->getConfigParam('sApiKey');
    $list      = $Configs->getConfigParam('sListId');
    $plainText = $Configs->getConfigParam('sPlainText');

    // Zu Testzwecken. Wo erscheint diese Ausgabe? Wo müsste sie erscheinen?
    echo "Echo aus MainController::subscribe_noname()";

    // Informationen aus POST holen und in Class Member _aRegParams
    $aParams = Registry::getRequest()->getRequestEscapedParameter("editval");
    $this->_aRegParams = $aParams;

    // Query für Eintrag in SendyDB konstruieren
    $postdata = http_build_query(
      array(
        //'email' => $aParams["email"],
        'email'   => $this->getRegParams()["email"],        // via getter = failsafe
        'list'    => $list,
        'api_key' => $api_key,
        'boolean' => $plainText
      )
    );

    // Neuen Subscriber in SendyDB per Sendy-API eintragen
    $opts = array('http' => array('method'  => 'POST', 'header'  => 'Content-type: application/x-www-form-urlencoded', 'content' => $postdata));
    $context  = stream_context_create($opts);
    $result = file_get_contents($sendyRoot.'/subscribe', false, $context);

    // ...
    }
  }

Und abschließend noch die neue metadata.php des Moduls:

<?php
/**
 * Metadata version
 */
$sMetadataVersion = '2.0';

/**
 * Module information
 */
$aModule = array(
    'id'           => 'kcs_sendysync',
    'title'        => 'SendySync',
    'version'      => '1.0.2',
    'author'       => 'Max Mustermann',
    'url'          => 'http://www.mustersite.de',
    'email'        => '[email protected]',
    'thumbnail'    => 'logo.png',
    'description'  => 'Kopiert die Subscriber-Einträge der Sendy-Datenbank redundant in die Shopdatenbank.<br>Hierfür werden fehlende Felder in den Oxid-Tables ergänzt.',

    'controllers'  => array(
      'sendysyncmain' => \kcs\kcs_sendysync\Controller\MainController::class,
    ),

    'settings'     => array(
      array('group' => 'kcs_sendysync_general', 'name' => 'sSendyRoot', 'type' => 'str', 'value' => 'http://localhost/kiebel/sendy'),
      array('group' => 'kcs_sendysync_general', 'name' => 'sApiKey', 'type' => 'str', 'value' => '***'),
      array('group' => 'kcs_sendysync_general', 'name' => 'sListId', 'type' => 'str', 'value' => '***'),
      array('group' => 'kcs_sendysync_general', 'name' => 'sPlainText', 'type' => 'bool', 'value' => 'false')
    )
);

Soweit so gut. Ich würde einmal behaupten, es wird die passende Funktion des Controllers aufgerufen. Das wollte ich mit einem echo-Befehl im Controller bestätigen, kann die Ausgabe jedoch nirgends finden.
Auch der Datenbankeintrag in der Sendy-DB wird nicht(mehr) angestoßen. Gut möglich, das das aber am entsprechenden Code liegt, da muss ich erst nochmal die Doku zur Sendy-API befragen.

Dennoch drängen sich mich dabei folgende Fragen auf:

  • Wo müsste denn die Ausgabe des echo-Befehls zu finden sein? Im Grunde doch im FrontEnd direkt unter/in dem Formular, welches den Controller aufgerufen hat, oder?

  • Müssen eigene Controller zwangsläufig einen vorhandenen Controller erweitern? In meinem Fall habe ich recht willkürlich den NewsletterController erweitert, damit ich bei Bedarf auch Zugriff auf dessen Funktionen habe, da mir diese im weiteren Programmablauf nützen könnten. Aber wenn man ein ganz neues Feature entwickelt? Würde man dann direkt den FrontEnd-/BaseController erweitern? Oder gar keinen? Und warum?

// Edit: Okay, jetzt werden auch die echo’s angezeigt. Hätte mal den tmp-Ordner leeren sollen :grin: Habe mal ein Skript implementiert, das das automatisch bei Modul(de)aktivierung macht. Komisch, dass Oxid das nicht im Core eingebaut hat, aber wird schon seine Gründe haben.
Die
Das Sendy-Gedöns klappt zwar noch nicht ganz, das soll hier aber nicht das Thema sein.
Wenn mir noch jemand abschließend meine letzte Frage bzgl Controller-Vererbung beantworten könnte, wäre ich diesbezüglich wunschlos glücklich :slight_smile:
->Also die hier:

  • Müssen eigene Controller zwangsläufig einen vorhandenen Controller erweitern? In meinem Fall habe ich recht willkürlich den NewsletterController erweitert, damit ich bei Bedarf auch Zugriff auf dessen Funktionen habe, da mir diese im weiteren Programmablauf nützen könnten. Aber wenn man ein ganz neues Feature entwickelt? Würde man dann direkt den FrontEnd-/BaseController erweitern? Oder gar keinen? Und warum?

Vielen Dank an alle fleißigen Helfer hier im Forum!

So ist es!

1 Like