Rachus: MySQL Loginsystem

Schönen guten Tag,

ich versuche gerade mithilfe von PHP (5.3) einen Loginschutz zu schreiben, das ähnlich funktionieren wird wie dieser. Jetzt hatte ich vor, die ganzen Informationen in einer MySQL (5.1) Datenbank zu speichern und bin dabei auf Fragen gestoßen, die ich mir nicht so ohne Weiteres beantworten konnte.

Da wären zum Beispiel die Fragen: Sollte ich auch die Tabellen- und Spaltennamen mit mysqli::real_escape_string(...) escapen bzw. bringt mir das was?
Kann ich mich mit dieser Funktion auch vor SQL-Injections schützen oder benötige ich da noch eine andere Funktion für?

Zudem interessiert mich noch, ob es richtig ist, wenn ich die "Einstellungen" des Loginsystems (sprich: MySQL-Server-Adresse, MySQL-Passwort, MySQL-Name, etc. etc.) in der Klasse in privaten statischen Variablen speichere oder ob ich da irgendwie eine andere Form der Konfiguration wählen sollte. (Allerdings glaube ich, dass es in "PHPmyAdmin" auch so ähnlich gewesen ist.)

Wäre nett, wenn Ihr mir helfen könntet.

Viele Grüße

Rachus

  1. Hallo,

    Da wären zum Beispiel die Fragen: Sollte ich auch die Tabellen- und Spaltennamen mit mysqli::real_escape_string(...) escapen

    nein, auf keinen Fall. Die *_real_escape_string-Funktionen und Methoden sind für Zeichenkettenwerte in SQL-Statements da, nicht für Identifier. Für diese gelten andere Escape-Mechanismen. Es ist jedoch eine gute Idee, von Deinem Code alle Tabellen- und Spaltennamen quoten zu lassen, weil Du somit nie ein Problem mit reservierten Namen bekommen wirst - so wie es zum Beispiel phpMyAdmin macht.

    Für den Test bzw. für Forumsbeiträge sehe ich es umgekehrt: Da quote ich nur, wenn es unbedingt sein muss. Das gilt besonders für MySQL, weil ich allergisch auf Backtickitis reagiere.

    bzw. bringt mir das was?

    Fehlermeldungen und eine nicht funktionierende Anwendung.

    Freundliche Grüße

    Vinzenz

  2. Hi!

    Sollte ich auch die Tabellen- und Spaltennamen mit mysqli::real_escape_string(...) escapen bzw. bringt mir das was?

    Wie lauten die Regeln zur Notation von Tabellen- und Spaltennamen? Welche Zeichen haben darin eine Sonderbedeutung und welche Zeichen werden von mysqli_real_escape_string() behandelt?

    Kann ich mich mit dieser Funktion auch vor SQL-Injections schützen oder benötige ich da noch eine andere Funktion für?

    Sich um SQL-Injection zu sorgen zäumt das Pferd von hinten auf. Beachte stets beim Einfügen von Werten in einen anderen Kontext dessen Regeln, besonders die Zeichen mit einer Sonderbedeutung. Wenn du die so notierst wie vom Kontext verlangt, kann keine Injection welcher Art auch immer auftreten. Die Beachtung der Notationsregeln ist nicht zur Verhinderung von *-Injection notwendig sondern für jegliche Daten. Auch ein gutmütiges ' beendet ein mit ' eingeleiteten String und sorgt mit dem nachfolgenden Zeichen in der Regel für Syntaxfehler.

    Außerdem kann dich die Funktion nicht schützen, wenn sie falsch angewendet wird. Siehe Abschnitt Zahlen im (My)SQL-Statement. Es ist wichtig, das Prinzip zu verstehen und nicht einfach nur eine Funktion aufzurufen.

    Zudem interessiert mich noch, ob es richtig ist, wenn ich die "Einstellungen" des Loginsystems (sprich: MySQL-Server-Adresse, MySQL-Passwort, MySQL-Name, etc. etc.) in der Klasse in privaten statischen Variablen speichere oder ob ich da irgendwie eine andere Form der Konfiguration wählen sollte. (Allerdings glaube ich, dass es in "PHPmyAdmin" auch so ähnlich gewesen ist.)

    Der PMA speichert die Zugangsdaten in Elementen seines allgemeinen Konfigurationsarrays. Es gibt im Prinzip kein richtig oder falsch bei der Auswahl des Ortes der Konfigurationsdaten. Wenn du auf Wiederverwendbarkeit deiner Komponenten Wert legst, dann mach sie nicht von festen Werten abhängig sondern übergib diese beim Initialisieren über Funktions- oder Konstruktor-Parameter. Wenn du "Konvention vor Konfiguration" bevorzugst, definiere eine oder mehrere Variablennamen (oder Konstanten), in der die Konfigurationsdaten zu finden sein sollen.

    Lo!

    1. Hallo,

      das waren für mich jetzt sehr schwere Lektüren, daher versuche ich jetzt mal eigene Fazits zu ziehen (sagt mir bitte, wenn ich etwas falsch verstanden habe):

      1. Die Identifiers brauchen nicht durch mysqli::real_escape_string() maskiert zu werden, weil es laut "Kontextwechsel erkennen und behandeln" nur die "Regel des doppelten Backticks" gibt.

      2. Die Funktion scheint in meinem Anwendungsfall, der da lautet:

      $this->mysqli->query('SELECT '.$settings['user_id_column'].' FROM '.$settings['user_table'].' WHERE '.$settings['user_name_column']."='".$this->mysqli->real_escape_string($_POST[$settings['post_name']])."' AND ".$settings['user_password_column']."='".$this->mysqli->real_escape_string($_POST[$settings['post_pw']])."'");

      richtig angewendet zu sein, richtig?

      1. Und das mit der Konfiguration ist also Geschmackssache (meine momentane Art ist ja jetzt einsehbar). Da mache ich mir vielleicht auch noch einmal Gedanken darüber.

      Dann bedanke ich mich schon einmal für eure Hilfe. Wenn man erst mal die Grundlagen versteht, sollte der Rest wie von selbst gehen.

      Schönen Abend

      Rachus

      1. Hi!

        1. Die Identifiers brauchen nicht durch mysqli::real_escape_string() maskiert zu werden, weil es laut "Kontextwechsel erkennen und behandeln" nur die "Regel des doppelten Backticks" gibt.

        Richtig. Und so sagt es auch das MySQL-Handbuch. Nur der Backtick hat bei Identifiers eine Sonderbedeutung, alle anderen Zeichen werden wörtlich genommen. Ein " im gebacktickten Identifier, durch mysqli_real_escape_string() zu " gemacht, wären aus Sicht von MySQL \ und ". Nur bei String-Werten wird " als " interpretiert.

        1. Die Funktion scheint in meinem Anwendungsfall, der da lautet:
          $this->mysqli->query('SELECT '.$settings['user_id_column'].' FROM '.$settings['user_table'].' WHERE '.$settings['user_name_column']."='".$this->mysqli->real_escape_string($_POST[$settings['post_name']])."' AND ".$settings['user_password_column']."='".$this->mysqli->real_escape_string($_POST[$settings['post_pw']])."'");
          richtig angewendet zu sein, richtig?

        Unter der Voraussetzung, dass $settings allein durch den Programmierer, Administrator oder ähnlich vertrauenswürdige Personen geschrieben werden kann, wäre das so in Ordnung. Verbessern kann man die Lesbarkeit durch den Einsatz von sprintf(), damit muss man den Statement-String nicht ständig wegen der Variablen unterbrechen. Außerdem spricht auch nichts dagegen, sich für die Identifier eine Funktion zu schreiben, die mindestens das Maskieren und optional auch das Quotieren übernimmt.

        1. Und das mit der Konfiguration ist also Geschmackssache (meine momentane Art ist ja jetzt einsehbar). Da mache ich mir vielleicht auch noch einmal Gedanken darüber.

        Ja. Konfigurationsdaten als PHP-Quellcode zu schreiben ist vorwiegend was für Programmierer. ini-Files sind eine Möglichkeit, bei der man nicht so sehr darauf achten muss, dass beim Ändern die PHP-Syntax eingehalten werden muss. Die Syntax von ini-Dateien ist da ja noch mal ein ganzes Stück einfacher.

        Lo!

        1. Hallo,

          Außerdem spricht auch nichts dagegen, sich für die Identifier eine Funktion zu schreiben, die mindestens das Maskieren und optional auch das Quotieren übernimmt.

          So eine Funktion könnte dann so aussehen:

          function escape_identifier($string, $quotations=true)  
          {  
             $escaped_string=str_replace('`', '``', $string, $count);  
             return ($quotations || count>0) ? '`'.$escaped_string.'`' : $escaped_string;  
          }
          

          Das müsste schon genügen, denke ich.

          Konfigurationsdaten als PHP-Quellcode zu schreiben ist vorwiegend was für Programmierer. ini-Files sind eine Möglichkeit, bei der man nicht so sehr darauf achten muss, dass beim Ändern die PHP-Syntax eingehalten werden muss. Die Syntax von ini-Dateien ist da ja noch mal ein ganzes Stück einfacher.

          Das hatte ich mir auch einmal überlegt, allerdings dachte ich, dass PHP-Dateien bei jedem Aufruf neu geparst und ausgeführt werden. Dann müssten die auch bei jedem Aufruf erst die INI-Datei neu parsen und da meinte ich, dass das zu viel Zeit in Anspruch nehmen würde. (Wobei meine Parser-Funktionen bisher auch eher ineffizient waren, da ich mit regulären Ausdrücken nicht zurecht komme). Andererseits gäbe das natürlich auch erweiterte Möglichkeiten wie das Bearbeiten im Browser.

          Dann werde ich mich morgen am besten an das Neuschreiben der Klasse machen.

          Sollten weitere Fragen auftauchen, melde ich mich wieder!

          Schönen Abend

          Rachus

          1. Hi!

            Außerdem spricht auch nichts dagegen, sich für die Identifier eine Funktion zu schreiben, die mindestens das Maskieren und optional auch das Quotieren übernimmt.

            So eine Funktion könnte dann so aussehen:

            function escape_identifier($string, $quotations=true)

            {
               $escaped_string=str_replace('', '``', $string, $count);    return ($quotations || count>0) ? ''.$escaped_string.'`' : $escaped_string;
            }

            
            > Das müsste schon genügen, denke ich.  
              
            Sehr schön. (Ein $ fehlt beim zweiten ($)count.)  
              
            
            > > [ini-Dateien]  
            > Das hatte ich mir auch einmal überlegt, allerdings dachte ich, [...] dass das zu viel Zeit in Anspruch nehmen würde. (Wobei meine Parser-Funktionen bisher auch eher ineffizient waren, da ich mit regulären Ausdrücken nicht zurecht komme).  
              
            PHP bringt [parse_ini_file()](http://de.php.net/manual/en/function.parse-ini-file.php) mit. Schneller und einfacher dürfte es nicht werden.  
              
              
            Lo!
            
            1. Hallo,

              PHP bringt parse_ini_file() mit. Schneller und einfacher dürfte es nicht werden.

              cool, ich wusste gar nicht, was PHP alles für schöne Funktionen mitbringt. Dann kann ich das natürlich sehr einfach machen.

              Nochmals danke für die Hilfe!

              Rachus

              1. Hallo nochmal,

                ich habe mir jetzt wenigstens schon einmal die Konfigurationseinlesung fertiggestellt und wollte Euch jetzt einfach mal fragen, ob das so okay ist.

                Insbesondere interessiert mich, ob ihr die verwendeteten Zugriffsarten und statischen Funktionen auch so angewendet hättet.
                Auch würde ich gerne wissen, ob man auf statische Methoden auch anders als mit KLASSENNAME::METHODENNAME() zugreifen kann.

                Dann poste ich einfach einmal meine Klasse (wie gehabt: nur das Einlesen der Konfiguration ist fertig):

                <?php  
                class sUserAuthSystem  
                {  
                	private static $settings=null;  
                	private $mysql;  
                	  
                	public static function prepare_settings()  
                	{  
                		if (sUserAuthSystem::$settings==null)  
                		{  
                			$sections_to_escape=array('USER_TABLE', 'LOGIN_TABLE', 'LOGIN_DENIED_TABLE', 'RIGHTS_TABLE');  
                			$escape_amount=4;  
                			  
                			sUserAuthSystem::$settings=parse_ini_file(__DIR__.'/suas.ini', true);  
                			  
                			for ($i=0; $i<$escape_amount; ++$i)  
                			{  
                				$section_length=count(sUserAuthSystem::$settings[$sections_to_escape[$i]]);  
                				  
                				foreach (sUserAuthSystem::$settings[$sections_to_escape[$i]] as $key => $value)  
                				{  
                					sUserAuthSystem::$settings[$sections_to_escape[$i]][$key]=sUserAuthSystem::escape_identifier($value);  
                				}  
                			}  
                			  
                			if (sUserAuthSystem::$settings['OTHER']['suas_relative_paths'])  
                			{  
                				sUserAuthSystem::path_correction(sUserAuthSystem::$settings['OUTPUT']['mysql_connection_fault_file']);  
                				sUserAuthSystem::path_correction(sUserAuthSystem::$settings['OUTPUT']['login_form_file']);  
                			}  
                		}  
                	}  
                	  
                	public static function escape_identifier(&$string)  
                	{  
                		return '`'.str_replace('`', '``', $string).'`';  
                	}  
                	  
                	private static function path_correction(&$path)  
                	{  
                		$path=__DIR__.'/'.$path;  
                	}  
                	  
                	private static function connect_error()  
                	{  
                		if (sUserAuthSystem::$settings['OUTPUT']['use_http_status']) header($_SERVER['SERVER_PROTOCOL'].' 500 Internal Server Error');  
                		  
                	}  
                	  
                	public function __construct()  
                	{  
                		$this::prepare_settings();  
                		@$this->mysql=new MySQLi($this::$settings['MYSQL']['name'], $this::$settings['MYSQL']['name'], $this::$settings['MYSQL']['password'], $this::$settings['MYSQL']['database']);  
                		if ($this->mysql->connect_error) $this::connect_error();  
                		  
                		  
                	}  
                }  
                ?>
                

                Wie Ihr sehen könnt, habe ich auch beschlossen, das escapen der Indentifier bereits in der Einlesefunktion durchzuführen - dann muss ich bei den SQL-Anfragen nicht mehr daran denken.

                Bitte sagt mir, was ihr davon haltet und klärt mich bitte auf!

                Schönen Abend

                Rachus

                1. Hi!

                  Insbesondere interessiert mich, ob ihr die verwendeteten Zugriffsarten und statischen Funktionen auch so angewendet hättet.

                  Erlaubt ist was gefällt, funktioniert und keine Fehler aufweist. Zusätzlich kannst du dir ja (gedanklich) Begründungen geben, warum du etwas auf eine Weise machst und nicht eine Alternative verwendest.

                  Auch würde ich gerne wissen, ob man auf statische Methoden auch anders als mit KLASSENNAME::METHODENNAME() zugreifen kann.

                  Innerhalb einer Klasse gibt es self, das den Klassennamen verkörpert, quasi ein statisches Pendant zu $this.

                    	$sections\_to\_escape=array('USER\_TABLE', 'LOGIN\_TABLE', 'LOGIN\_DENIED\_TABLE', 'RIGHTS\_TABLE');  
                    	$escape\_amount=4;  
                  

                  Und wenn du mal vergisst, beim Erweitern des Arrays den Zähler anzupassen? Besser count($sections_to_escape) statt einer händisch eingefügten Zahl. Das verdeutlicht auch gleich was es mit dem Wert auf sich hat, ohne dass man nachzählen muss.

                    	for ($i=0; $i<$escape\_amount; ++$i)  
                  

                  Allerdings würde ich generell auf eine extra Variablen verzichten und das count() gleich in die for-Schleife einbauen. Das bisschen Geschwindigkeit, das beim erneuten Zählen pro Schleifendurchlauf draufgeht, geht im Grundrauschen unter. Und da du eigentlich an den Werten interessiert bist und der Zähler egal und nur Hilfsmittel zum Indexieren ist, plädiere ich für ein foreach. Das was du beim separaten count() gespart hast (wenn es dir dabei um Performance ging), geht übrigens durch das pro Iteration dreimalige Auflösen-Müssen von sUserAuthSystem::$settings[$sections_to_escape[$i]] wieder drauf.

                  Wie Ihr sehen könnt, habe ich auch beschlossen, das escapen der Indentifier bereits in der Einlesefunktion durchzuführen - dann muss ich bei den SQL-Anfragen nicht mehr daran denken.

                  Keine gute Idee. Das richtige Maskieren ist lebenswichtig. Deshalb darf man es nie vergessen oder sich auch nur das Darüber-Nachdenkenmüssen sparen wollen. Wenn du mal irgendwann über einen anderen Weg als über die Einlesefunktion kommen willst, und dich nicht mehr richtig erinnerst, dass nur deshalb die SQL-Abfrage sicher ist, dann ...

                  Ein Kontextwechsel und die Behandlung dafür sollten eine Einheit bilden, damit die Übersichtlichkeit nicht darunter leidet.

                  Lo!

                  1. Hallo,

                    ich bekomme immer mehr das Gefühl meine früheren Skripte waren allesamt Fehlkonstruktionen. Jetzt scheine ich erst richtig PHP zu lernen.

                    Innerhalb einer Klasse gibt es self, das den Klassennamen verkörpert, quasi ein statisches Pendant zu $this.

                    Wunderbar, sowas habe ich gesucht! Das nimmt mir wirklich viel Schreibarbeit ab.

                    Und da du eigentlich an den Werten interessiert bist und der Zähler egal und nur Hilfsmittel zum Indexieren ist, plädiere ich für ein foreach.

                    Okay, ich habe sowas bislang immer von Hand gemacht, aber jetzt finde ich langsam einen Sinn in diesem Foreach-Konstrukt.

                    Keine gute Idee. Das richtige Maskieren ist lebenswichtig. Deshalb darf man es nie vergessen oder sich auch nur das Darüber-Nachdenkenmüssen sparen wollen. Wenn du mal irgendwann über einen anderen Weg als über die Einlesefunktion kommen willst, und dich nicht mehr richtig erinnerst, dass nur deshalb die SQL-Abfrage sicher ist, dann ...

                    Irgendwie widerstrebt es mir innerlich ein bisschen, dass ich auch für die Maskierung der Bezeichner bei jeder SQL-Anfrage eine Methode aufrufen zu müssen. (Insbesondere dann, wenn ich öfters diesen Bezeichner verwende, was hier aber wahrscheinlich nicht zutrifft) Daher hatte ich mich auch dafür entschieden, das ganze bereits beim einlesen durchzuführen. Wenn du mir jetzt aber ganz deutlich sagst, dass es zum guten PHP-Stil gehört, diese Behandlung erst beim SQL-Request durchzuführen, dann werde ich das abändern.

                    Mir sind jetzt aber auch neue Fragen gekommen: Soll ich die Pfade der Dateien anpassen (also relativ zur ausgeführten Datei machen), wenn in der INI-Datei ein gewisser Wert auf true (oder vergleichbare Werte) gesetzt wurde oder wie würdet ihr sowas behandeln?

                    Und als letzte Frage (in dieser Nachricht): Ich hatte es jetzt so vor zu handhaben, dass ich, bei einem Fehler oder wenn ein Benutzer nicht eingeloggt ist, per "require" eine PHP-Datei eingefügt wird, die dann das Anmeldeformular bzw. eine Fehlermeldung beinhaltet. Zuerst wollte ich es mit "readfile" machen, allerdings will ich das als grundlegendes System für meine zukünftigen Projekte machen und vielleicht soll da bei noch PHP-Code ausgeführt werden, wenn das Formular ausgeliefert werden soll.
                    Ist das so "guter Stil" oder hättet ihr das anders gemacht?

                    Danke, für die Geduld, aber das System soll einfach so ziemlich "perfekt" (wird es wohl nie werden) sein, da ich schon so oft ein Login-System gemacht habe, das ich dann aber nicht wiederverwenden konnte.
                    Außerdem lerne ich so PHP besser kennen (z.B. den foreach-Konstrukt).

                    Schöne Grüße

                    Rachus

                    1. Hi!

                      ich bekomme immer mehr das Gefühl meine früheren Skripte waren allesamt Fehlkonstruktionen. Jetzt scheine ich erst richtig PHP zu lernen.

                      Auch meine frühen Würfe sind mittlerweile ein Fall für den Giftschrank. Immerhin waren sie im Wesentlichen *-Injection-sicher.

                      Irgendwie widerstrebt es mir innerlich ein bisschen, dass ich auch für die Maskierung der Bezeichner bei jeder SQL-Anfrage eine Methode aufrufen zu müssen. (Insbesondere dann, wenn ich öfters diesen Bezeichner verwende, was hier aber wahrscheinlich nicht zutrifft)

                      Sind denn deine Abfragen alle dermaßen veränderlich im Code, dass du da ständig wechselnde Bezeichner reinbringen musst? Alles was statisch gemacht werden kann sollte so implementiert werden. Und was nur aus deinem eigenen Code kommt, benötigt nicht zwangsläufig Bezeichnermaskierungen. Du wirst vermutlich nicht absichtlich Backticks in die Tabellen- und Feldnamen einbauen.

                      Daher hatte ich mich auch dafür entschieden, das ganze bereits beim einlesen durchzuführen. Wenn du mir jetzt aber ganz deutlich sagst, dass es zum guten PHP-Stil gehört, diese Behandlung erst beim SQL-Request durchzuführen, dann werde ich das abändern.

                      Das ist kein PHP-spezifisches Ding. Nicht beachtete Kontextwechsel sind in allen Programmiersprachen ein gern gemachter Fehler.

                      Mir sind jetzt aber auch neue Fragen gekommen: Soll ich die Pfade der Dateien anpassen (also relativ zur ausgeführten Datei machen), wenn in der INI-Datei ein gewisser Wert auf true (oder vergleichbare Werte) gesetzt wurde oder wie würdet ihr sowas behandeln?

                      Ich konfiguriere die Pfade, mitunter auch in Teilen, wenn ein Basispfad um spezifische Pfade ergänzt werden soll. Der Basispfad kann dabei auch [Leerstring] oder / lauten.

                      Ich hatte es jetzt so vor zu handhaben, dass ich, bei einem Fehler oder wenn ein Benutzer nicht eingeloggt ist, per "require" eine PHP-Datei eingefügt wird, die dann das Anmeldeformular bzw. eine Fehlermeldung beinhaltet. Zuerst wollte ich es mit "readfile" machen, allerdings will ich das als grundlegendes System für meine zukünftigen Projekte machen und vielleicht soll da bei noch PHP-Code ausgeführt werden, wenn das Formular ausgeliefert werden soll.
                      Ist das so "guter Stil" oder hättet ihr das anders gemacht?

                      Kann man so machen. Ich bevorzuge bei der Verarbeitung (dem V vom EVA-Prinzip) nur Daten zu erstellen, möglichst ohne Ausgabeformatierung. Die Ausgabelogik prüft, ob bestimmte Strukturen oder Flags vorhanden sind und rendert dann entsprechend.

                      Lo!

                      1. Hallo,

                        Sind denn deine Abfragen alle dermaßen veränderlich im Code, dass du da ständig wechselnde Bezeichner reinbringen musst? Alles was statisch gemacht werden kann sollte so implementiert werden. Und was nur aus deinem eigenen Code kommt, benötigt nicht zwangsläufig Bezeichnermaskierungen. Du wirst vermutlich nicht absichtlich Backticks in die Tabellen- und Feldnamen einbauen.

                        Nun, die einzigen Bezeichner, die benutzt werden sollen, sind die aus der INI-Datei. Daher dachte ich ja, es wäre einfacher bereits beim Einlesen die Bezeichner zu maskieren.
                        Wirklich statisch wollte ich nichts machen, da ich das, wie schon gesagt, als grundlegendes Einlogsystem für zukünftige Projekte verwenden wollte.

                        Das ist kein PHP-spezifisches Ding. Nicht beachtete Kontextwechsel sind in allen Programmiersprachen ein gern gemachter Fehler.

                        Wenn ich mir das so überlege, würde ich aber in fast allen Programmiersprachen, die ich kann, die Überprüfung von Bezeichnern nach Möglichkeit bereits beim Einlesen durchzuführen. Vielleicht ist das einfach nur schlechter Stil von mir, aber die Daten, die ich bekomme, teste ich so schnell wie möglich auf Korrektheit bzw. mache sie evtl. unschädlich.

                        Ich konfiguriere die Pfade, mitunter auch in Teilen, wenn ein Basispfad um spezifische Pfade ergänzt werden soll. Der Basispfad kann dabei auch [Leerstring] oder / lauten.

                        Meinst du damit, dass du dir einen Basispfad in einer Konfiguration angeben lässt und diesen dann spezifisch erweiterst?
                        Welche Pfade wären bei dir dann "" oder eben "/"? Ich könnte mir vorstellen, dass "" für das aktuelle und "/" für das Wurzelverzeichnis steht.

                        Kann man so machen. Ich bevorzuge bei der Verarbeitung (dem V vom EVA-Prinzip) nur Daten zu erstellen, möglichst ohne Ausgabeformatierung. Die Ausgabelogik prüft, ob bestimmte Strukturen oder Flags vorhanden sind und rendert dann entsprechend.

                        Gut, ich denke, mein System ist so ähnlich aufgebaut.

                        Schönes Wochenende

                        Rachus

                        1. Hi!

                          Wenn ich mir das so überlege, würde ich aber in fast allen Programmiersprachen, die ich kann, die Überprüfung von Bezeichnern nach Möglichkeit bereits beim Einlesen durchzuführen. Vielleicht ist das einfach nur schlechter Stil von mir, aber die Daten, die ich bekomme, teste ich so schnell wie möglich auf Korrektheit bzw. mache sie evtl. unschädlich.

                          Eine (Datenintegritäts-)Überprüfung ist was anderes als die Behandlung für ein spezifisches Ausgabemedium. Wenn du gleich am Eingang für ein bestimmtes Ausgabemedium Veränderungen vornimmst, sind die Daten für andere Anwendungsfälle oftmals unbrauchbar. Siehe das Magic-Quotes-Feature von PHP. Gut gemeint maskiert es sämtliche Eingabedaten für SQL-Statments, nur kann damit kein Affenformular mehr bauen, bei dem nicht die "wundersame" Backslashvermehrung auftritt (jedenfalls nicht ohne eine Bereinigung). Auch eine Stringverarbeitung (Zeichen zählen und dergleichen) wird mindestens erschwert. Deswegen ist es nicht günstig, so früh wie möglich zu verunstalten, sondern lieber punktgenau zu behandeln. Ich lebe in meiner Wohnung lieber mit den Dingen in ihrer eigentlichen Form und packe sie erst dann ein, wenn ich umzuziehen gedenke.

                          Ich konfiguriere die Pfade, mitunter auch in Teilen, wenn ein Basispfad um spezifische Pfade ergänzt werden soll. Der Basispfad kann dabei auch [Leerstring] oder / lauten.

                          Meinst du damit, dass du dir einen Basispfad in einer Konfiguration angeben lässt und diesen dann spezifisch erweiterst?
                          Welche Pfade wären bei dir dann "" oder eben "/"? Ich könnte mir vorstellen, dass "" für das aktuelle und "/" für das Wurzelverzeichnis steht.

                          Aus einem aktuellen Projekt:

                          [files]
                          baseDir =
                          imagesDir = images
                          buttonsDir = images/buttons
                          navDir = images/nav
                          projectsDir = projects

                          baseDir ist in dem Fall leer, was bedeutet, dass alles relativ zum DocumentRoot ist, weil ich es so anwende:

                          <li><a href="##base##/%s"><img src="##base##/##images##/%s.jpg" alt="%s" /></a></li>

                          Die ##...##-Platzhalter werden später durch die Werte aus der ini-Datei ersetzt. Da wo das Stück Code steht, wird gerade ein Menü zusammengebaut und die %s-Platzhalter durch konkrete Werte ausgetauscht.

                          Der fertige Link zeigt dann auf /foo und das Bild fände der Browser unter /images/foo.jpg. Das Projekt arbeitet mit sprechenden URLs weswegen eine absolute Adressierung von einzubindenden Ressourcen notwendig ist, um nicht je nach Anzahl der Pfadteile unterschiedlich lange ../-Kolonnen berechnen zu müssen. Wenn sich das Basisverzeichnis verschöbe, müsste es à la /qux konfiguriert werden.

                          Lo!

                          1. Hallo,

                            [...] Deswegen ist es nicht günstig, so früh wie möglich zu verunstalten, sondern lieber punktgenau zu behandeln. Ich lebe in meiner Wohnung lieber mit den Dingen in ihrer eigentlichen Form und packe sie erst dann ein, wenn ich umzuziehen gedenke.

                            Nagut, damit überzeugst du mich! Ich escape erst bei der Abfrage.

                            Aus einem aktuellen Projekt:

                            [...]

                            Das ist interessant (insbesondere, wie du das in HTML einbaust). Allerdings geht es mir nicht um das, was der Client bekommen soll, sondern um die Pfadangaben, die ich bei "require" benötige. Denn relativ angegeben beziehen sie sich immer auf die aufgerufene Datei (nicht auf die aktuelle includierte Datei). Soll es sich jetzt auf die momentane Datei beziehen, muss ich den Pfad doch anpassen? Ist das dann so: $path=__DIR__.'/'.$path; korrekt?

                            Langsam komme ich mit dem Skript weiter. ;-)

                            Schönen Abend

                            Rachus

                            1. Hi!

                              Ist das dann so: $path=__DIR__.'/'.$path; korrekt?

                              __DIR__ gibt es nicht, aber mit dirname(__FILE__) bekommst du das Verzeichnis der aktuellen Datei.

                              Lo!

                              1. Hallo,

                                __DIR__ gibt es nicht, aber mit dirname(__FILE__) bekommst du das Verzeichnis der aktuellen Datei.

                                doch, diese Konstante gibt es ab PHP 5.3. Genauso wie Namensräume, die ich verwenden will und die eine weitere Frage aufwerfen:
                                Gibt es eine Möglichkeit in Namespaces wie in Klassen die Daten zu kapseln (z.B. mit public/private)? Irgendwie glaube ich, dass ich nur die richtige Stelle in der Dokumentation überlese, aber ich benötige dieses Feature zur "sauberen" Programmierung.

                                Schönen Abend

                                Rachus

                                1. Hi!

                                  __DIR__ gibt es nicht, aber mit dirname(__FILE__) bekommst du das Verzeichnis der aktuellen Datei.
                                  doch, diese Konstante gibt es ab PHP 5.3.

                                  Ja, sie lief mir kurze Zeit später über den Weg, als ich nach was anderem suchte.

                                  Gibt es eine Möglichkeit in Namespaces wie in Klassen die Daten zu kapseln (z.B. mit public/private)? Irgendwie glaube ich, dass ich nur die richtige Stelle in der Dokumentation überlese, aber ich benötige dieses Feature zur "sauberen" Programmierung.

                                  Mit Daten meinst du Variablen, die nicht Klassen- oder Objektvariablen sind? Siehe Defining namespaces erster Satz.

                                  Lo!

                                  1. Hallo,

                                    Mit Daten meinst du Variablen, die nicht Klassen- oder Objektvariablen sind? Siehe Defining namespaces erster Satz.

                                    Oh nein! Mir fällt gerade auf, dass tatsächlich die Variablen des Namespaces im globalen Geltungsbereich liegen. Das widerspricht meinem Verständnis von Namensräumen (kann mich aber auch irren, weil ich bisher nur JavaScript-"Pseudo"-Namensräume kenne).
                                    Wäre denn eine Singleton-Klasse (was es nicht alles gibt!) eine Alternative? Mein Wunsch wäre nämlich, dass die Variablen meines Systems nicht den globalen Namensraum "verschmutzen".

                                    Irgendwie komme ich mit dem System nicht voran!

                                    Schönen Tag noch

                                    Rachus

                                    1. Hi!

                                      Mir fällt gerade auf, dass tatsächlich die Variablen des Namespaces im globalen Geltungsbereich liegen.

                                      Sagen wir so: Variablen sind generell global, wenn sie nicht in Funktionen erzeugt werden.

                                      Das widerspricht meinem Verständnis von Namensräumen.

                                      Es gibt ja keine Gesetze, wie etwas zu implementieren ist, und so kann PHP eben definieren, dass es Variablen vom Namensraum-Handling ausnimmt. Es wird dafür möglicherweise Gründe gegen, aber andererseits gibt es - wenn sowieso schon Bedarf nach Namensräumen besteht - bessere Möglichkeiten als globale Variablen.

                                      Wäre denn eine Singleton-Klasse (was es nicht alles gibt!) eine Alternative? Mein Wunsch wäre nämlich, dass die Variablen meines Systems nicht den globalen Namensraum "verschmutzen".

                                      Es gibt das Entwurfsmuster Singleton. Eine spezielle Klasse dazu gibt es nicht. Man kann dieses Muster nämlich auch mit einer einfachen Funktion realsieren.

                                      Es kommt darauf an, was du insgesamt und jeweils speziell vorhast. Du solltest zuerst einmal alle Möglichkeiten der OOP ausschöpfen, bevor du Namensräume als Lösung für dein Projektstrukturierproblem ansiehst. Klassenvariablen (static properties) können eine Lösung sein, wobei ich nicht dafür plädiere, eine Klasse als Auffangbecken für die ansonsten global angelegte Variablen zu missbrauchen. Pattern - von denen es weit mehr als nur Singleton und Factory gibt - wären auch eine überlegenswerte Möglichkeit. Spontan fällt mir da das Muster Repository ein, das quasi ein besseres Array zum Ablegen von Objektinstanzen ist. Was letztlich sinnvoll ist, muss für jeden Einzelfall entschieden werden.

                                      Lo!

                                      1. Hallo,

                                        das ist ja mal wieder kompliziert.

                                        Es kommt darauf an, was du insgesamt und jeweils speziell vorhast.

                                        Vielleicht wäre es gut, wenn ich einfach schreibe, was ich vorhabe:
                                        Überlegt hatte ich mir, dass ich irgendeine Datenstruktur verwenden wollte, bei denen ich Variablen für mehrere Funktionen zur Verfügung habe, die aber nicht global sind (eben um nichts zu verschmutzen). Da es keinen Sinn machte, mehrere Objekte von einer "Login-Klasse" zu instanzieren, dachte ich mir, ich könnte dieses neue Feature von PHP, nämlich Namensräume, verwenden. Das Problem ist jetzt eben, dass diese die Variablen nicht miteinbeziehen. Nach einiger Suche bin ich dann eben auf das Singleton-Muster gestoßen bin, das ja eigentlich meine Anforderungen erfüllt: Eine Klasse, von der man nur ein Objekt erzeugen kann und schön "gepackte" Variablen.
                                        Jetzt frage ich einfach (wieder einmal): Wie würdest du so etwas realisieren?

                                        Du solltest zuerst einmal alle Möglichkeiten der OOP ausschöpfen, bevor du Namensräume als Lösung für dein Projektstrukturierproblem ansiehst.

                                        Welche Möglichkeiten habe ich denn mit OOP noch? Außerdem dachte ich, Namensräume wären Bestandteil davon.

                                        Pattern - von denen es weit mehr als nur Singleton und Factory gibt - wären auch eine überlegenswerte Möglichkeit. Spontan fällt mir da das Muster Repository ein, das quasi ein besseres Array zum Ablegen von Objektinstanzen ist. Was letztlich sinnvoll ist, muss für jeden Einzelfall entschieden werden.

                                        Leider habe ich da das Problem (so viele Probleme - Programmieren scheint wirklich nur das Lösen von Problemen zu sein), dass ich bereits "Factory" nicht verstehe, geschweige denn "Repository" (Wikipedia wird da nicht konkret genug - dadurch hätte ich hunderte (übertrieben) Ideen, das zu realisieren).

                                        Hoffentlich werde ich jetzt langsam dieses System überhaupt einmal anfangen können. Ich habe schon mehrere Entwürfe verwerfen müssen...

                                        Guten Abend

                                        Rachus

                                        1. Hi!

                                          Überlegt hatte ich mir, dass ich irgendeine Datenstruktur verwenden wollte, bei denen ich Variablen für mehrere Funktionen zur Verfügung habe, die aber nicht global sind (eben um nichts zu verschmutzen).

                                          Was machen denn diese Variablen und warum können sie nicht Eigenschaften eines Objekts sein?

                                          Da es keinen Sinn machte, mehrere Objekte von einer "Login-Klasse" zu instanzieren,

                                          Ist es nur nicht sinnvoll, oder muss verhindert werden, dass mehrere Instanzen entstehen? Oder willst du durch das Singleton-Pattern nur eine genau definierte Zugriffsmethode erstellen, die alle anderen Verwender nehmen können, ohne irgendeine Instanzvariable kennen zu müssen?

                                          Nach einiger Suche bin ich dann eben auf das Singleton-Muster gestoßen bin, das ja eigentlich meine Anforderungen erfüllt: Eine Klasse, von der man nur ein Objekt erzeugen kann und schön "gepackte" Variablen.

                                          Schön gepackte Variablen bekommt man durch die OOP, da braucht es noch kein Singleton dazu. Schön gepackt bekommt man auch alles, wenn man funktional programmiert.

                                          Jetzt frage ich einfach (wieder einmal): Wie würdest du so etwas realisieren?

                                          Dazu müsste ich erst einmal konkrete Vorstellungen haben, was alles für Anforderungen gelöst werden sollen. Am Ende wird sicher nicht nur eine Klasse entstehen, wenn man es "richtig" angehen will. Melkt eine Kuh sich nicht selbst? Eine Benutzer-Klasse kann Eigenschaften und Methoden haben, die den Benutzer und seine Fähigkeiten beschreiben. Wenn er aber irgendwo Zugang haben möchte, vollführt er mit seinen Ausweis eine Handlung an einem Zugangskontrollsystem. Schon hat man zwei Klassen. Wenn das ZKS seine Daten in einem DBMS speichert, kommt noch eine Datenbankabstraktion hinzu. Für einfache Fälle will man vielleicht nur eine Textdatei haben => sind wir schon bei 4 Klassen.

                                          Das kann man nun noch weiter treiben wenn man will. Die Frage ist: Muss es so umfangreich ausgestaltet sein? Ein wichtiges Prinzip bei der Entscheidungsfindung ist YAGNI - "Du wirst es nicht brauchen". Kapseln und definierte Schnittstellen schaffen für die Funktionalität, die du dir vorstellst, ist erst einmal wichtiger als das Ausgestalten dahinter.

                                          Du solltest zuerst einmal alle Möglichkeiten der OOP ausschöpfen, bevor du Namensräume als Lösung für dein Projektstrukturierproblem ansiehst.
                                          Welche Möglichkeiten habe ich denn mit OOP noch?

                                          Objekte. Punkt. Schau dir immer auch an, wie die Dinge in echt funktionieren, wenn du ein Abbild zu erschaffen versuchst.

                                          Außerdem dachte ich, Namensräume wären Bestandteil davon.

                                          Nö, ich sehe da keinen konkreten Zusammenhang. Es ist nur sinnvoll, Gliederungen zu erstellen, damit man sich nicht ins Gehege kommt. Schon Turbo Pascal kannte mit Units Namensräume, zwei Jahre und Versionen bevor es OOP lernte.

                                          Programmieren scheint wirklich nur das Lösen von Problemen zu sein

                                          Ja, von solchen, die man ohne Computer gar nicht hätte.

                                          Leider habe ich da das Problem [...], dass ich bereits "Factory" nicht verstehe, geschweige denn "Repository"

                                          Factory kann man in obgem Szenario anwenden, wenn das Zugangskontrollsystem einen Datenspeicher ansprechen will. Welcher das konkret ist, ist dem System egal, Hauptsache sie verhalten sich gleich. Dazu kann man die verschiedenen Implementationen von einem Interface oder einer Basisklasse abstammen lassen. Ein Objekt einer konkret zu verwendenden Klasse zu besorgen, ist Aufgabe der Fabrik. Sie bekommt anhand eines Dats (Einzahl von Daten) einen Auftrag und liefert etwas konkretes, so dass man nicht selbst beispielsweise ein switch-Konstrukt erstellen muss, um eine konkrete Klasse zu instantiieren.

                                          Repository will ich nicht weiter vertiefen, das wirst du sicher nicht benötigen.

                                          Hoffentlich werde ich jetzt langsam dieses System überhaupt einmal anfangen können. Ich habe schon mehrere Entwürfe verwerfen müssen...

                                          Wenn du dir Perfektionismus als Lebensziel gestellt hast, wirst du dich darauf einstellen müssen, ständig zu scheitern.

                                          Lo!

                                          1. Hallo,

                                            Was machen denn diese Variablen und warum können sie nicht Eigenschaften eines Objekts sein?

                                            sie speichern unter Anderem die MySQL-Verbindung und beinhalten das Konfigurationsarray. Und eigentlich könnten sie auch Attribute eines Objektes sein. (dann würde ich aber das gesamte System in einer Klasse realisieren)

                                            Ist es nur nicht sinnvoll, oder muss verhindert werden, dass mehrere Instanzen entstehen?

                                            Theoretisch macht es gar nichts aus, mehrere Objekte der Klasse zu erzeugen. Es macht aber einfach keinen Sinn, da dabei immer das gleiche passiert und zur Verfügung gestellt wird. Damit wären mehrere Instanzen reine Ressourcenverschwendung.
                                            Daher könnte ich auch eine Klasse machen, allerdings mit dem eben genannten Schönheitsfehler (der mir jetzt aber auch egal wäre).

                                            Oder willst du durch das Singleton-Pattern nur eine genau definierte Zugriffsmethode erstellen, die alle anderen Verwender nehmen können, ohne irgendeine Instanzvariable kennen zu müssen?

                                            Ich wollte nur verhindern, dass es mehr als eine Instanz der Klasse gibt.

                                            Schön gepackte Variablen bekommt man durch die OOP, da braucht es noch kein Singleton dazu. Schön gepackt bekommt man auch alles, wenn man funktional programmiert.

                                            Und da weiß ich wieder nicht, wie ich es in PHP schaffen sollte, nur mithilfe von Funktionen die Variablen (MySQL-Verbindung, Konfigurationsarray, ...) nicht jedes Mal bei einem Funktionsaufruf neu erzeugen zu müssen. Da sehe ich keinen Weg, das ohne globale Variablen hinzubekommen.
                                            Dann wäre es sogar mehr als logisch, das System als Klasse zu implementieren.
                                            Allerdings würde das bei mir fast wieder ein halbes Dutzend neue Fragen aufwerfen.

                                            Dazu müsste ich erst einmal konkrete Vorstellungen haben, was alles für Anforderungen gelöst werden sollen. Am Ende wird sicher nicht nur eine Klasse entstehen, wenn man es "richtig" angehen will. Melkt eine Kuh sich nicht selbst? Eine Benutzer-Klasse kann Eigenschaften und Methoden haben, die den Benutzer und seine Fähigkeiten beschreiben. Wenn er aber irgendwo Zugang haben möchte, vollführt er mit seinen Ausweis eine Handlung an einem Zugangskontrollsystem. Schon hat man zwei Klassen. Wenn das ZKS seine Daten in einem DBMS speichert, kommt noch eine Datenbankabstraktion hinzu. Für einfache Fälle will man vielleicht nur eine Textdatei haben => sind wir schon bei 4 Klassen.

                                            Das gibt mir jetzt wirklich zu denken. Es wäre tatsächlich eine gute Idee, das alles zu extrahieren und mehrere Klassen zu machen. Dazu muss ich aber meine Planung intensivieren. Langsam brummt mir der Kopf!

                                            Das kann man nun noch weiter treiben wenn man will. Die Frage ist: Muss es so umfangreich ausgestaltet sein? Ein wichtiges Prinzip bei der Entscheidungsfindung ist YAGNI - "Du wirst es nicht brauchen". Kapseln und definierte Schnittstellen schaffen für die Funktionalität, die du dir vorstellst, ist erst einmal wichtiger als das Ausgestalten dahinter.

                                            Daher habe ich mich jetzt entschieden, die (leicht nachrüstbare) Rechtetabelle aus dem Konzept zu streichen. Bislang finde ich auch noch keine wirklichen Einsätze dafür.

                                            Wenn du dir Perfektionismus als Lebensziel gestellt hast, wirst du dich darauf einstellen müssen, ständig zu scheitern.

                                            Naja, ich will eigentlich schon, dass alles, was ich anpacke super gelingt. Da ich aber motorisch relativ ungeschickt bin, habe ich bei handwerklichen Arbeiten meine Anforderungen schon sehr weit heruntergeschraubt.
                                            Bei Informatik ist mein Ziel schon etwas höher. Da möchte ich eigentlich schon fast die "sich selbst schreibende Programmiersprache".

                                            Mit der Zeit werden deine Antworten für mich leider immer unverständlicher. Allerdings freue ich mich, dass du mir trotzdem noch versuchst, alles zu erklären.
                                            Vermutlich fehlt mir einfach das nötige Vorwissen.

                                            Schönen Abend

                                            Rachus

                                            1. Hi!

                                              Was machen denn diese Variablen und warum können sie nicht Eigenschaften eines Objekts sein?
                                              sie speichern unter Anderem die MySQL-Verbindung und beinhalten das Konfigurationsarray. Und eigentlich könnten sie auch Attribute eines Objektes sein. (dann würde ich aber das gesamte System in einer Klasse realisieren)

                                              Datenbankabfragen brauchst du immer wieder. Deshalb ist es sinnvoll, die grundlegende Infrastruktur dafür wiederverwendbar zu gestalten. Und da sie am Ende Arbeit abnehmen soll, und nicht einfach nur die vorhandenen PHP-Funktionen kapseln soll, so dass man erst wieder 100 Zeilen Code tippen muss, muss sie also mindestens die Verbindung selbständig verwalten können und nicht viel mehr als eine Funktion zum Query-Absetzen und eine zum Maskieren (am besten inklusive Quotieren) von Werten nach außen hin anbieten (zumindest für den Start). Achja, die Connection-Daten muss man auch irgendwie initial übergeben können. Prinzipiell könnte das von außen so aussehen.

                                              class DB {
                                                private static $db;
                                                public static function Init($config_values) { ... }
                                                public static function Query($sql, $values = null) { ... }
                                                public static function Quote($value) { ... }
                                              }

                                              Das private $db ist natürlich nicht sichtbar, aber hier könnte die Instanz von mysqli abgelegt werden, die die anderen Methoden verwenden können.

                                              Theoretisch macht es gar nichts aus, mehrere Objekte der Klasse zu erzeugen. Es macht aber einfach keinen Sinn, da dabei immer das gleiche passiert und zur Verfügung gestellt wird. Damit wären mehrere Instanzen reine Ressourcenverschwendung.

                                              Wenn es nicht stört, dann lass es doch. Du als Autor bist nicht unbedingt schuld, wenn der Verwender es falsch einsetzt. Wenn er unbedingt will, lass ihn doch. Du hast nur mehr Aufwand auf deiner Seite. (Und dass du Autor und Verwender in Personalunion bist, ist auch nebensächlich.)

                                              Schön gepackte Variablen bekommt man durch die OOP, da braucht es noch kein Singleton dazu. Schön gepackt bekommt man auch alles, wenn man funktional programmiert.
                                              Und da weiß ich wieder nicht, wie ich es in PHP schaffen sollte, nur mithilfe von Funktionen die Variablen (MySQL-Verbindung, Konfigurationsarray, ...) nicht jedes Mal bei einem Funktionsaufruf neu erzeugen zu müssen. Da sehe ich keinen Weg, das ohne globale Variablen hinzubekommen.

                                              Das war ja nur mal so ein Beispiel. Aber wenn du dich erinnerst, erwähnte ich, dass man das Singleton-Pattern auch mit einer herkömmlichen Funktion implementieren kann, denn man kann darin statische Variablen anlegen.

                                              Aber gut, lassen wir diese kleine Abschweifung mal links liegen.

                                              Das gibt mir jetzt wirklich zu denken. Es wäre tatsächlich eine gute Idee, das alles zu extrahieren und mehrere Klassen zu machen. Dazu muss ich aber meine Planung intensivieren. Langsam brummt mir der Kopf!

                                              Alltagsweisheiten in Sprüche geklopft haben schon Generationen vor dir und mir: Meister fallen nicht vom Himmel. Erfahrungen sammelt man auch, wenn man erst mal den "falschen" Ansatz implementiert hat. Selbst wenn du dir noch so viel Mühe gibst und am Ende denkst, jetzt war es perfekt, kommst du ein paar Monde später wieder und findest mit inzwischen neu erworbenem Wissen und Erfahrungen, Dinge die verbesserungs- bis wegwerfwürdig sind.

                                              Wenn du dir Perfektionismus als Lebensziel gestellt hast, wirst du dich darauf einstellen müssen, ständig zu scheitern.
                                              Naja, ich will eigentlich schon, dass alles, was ich anpacke super gelingt. Da ich aber motorisch relativ ungeschickt bin, habe ich bei handwerklichen Arbeiten meine Anforderungen schon sehr weit heruntergeschraubt.
                                              Bei Informatik ist mein Ziel schon etwas höher. Da möchte ich eigentlich schon fast die "sich selbst schreibende Programmiersprache".

                                              Die gibts so wenig wie den Do-what-I-mean-Button für Anwender. Programmieren ist nicht selten wie das Rennen von einer Sackgasse in die nächste. Vielleicht fehlt dir beim Handwerklichen nur die nötige Übung. Programmieren benötigt sie auch, nur mehr auf mentaler Ebene.

                                              Mit der Zeit werden deine Antworten für mich leider immer unverständlicher. Allerdings freue ich mich, dass du mir trotzdem noch versuchst, alles zu erklären. Vermutlich fehlt mir einfach das nötige Vorwissen.

                                              Scheu dich nicht nachzufragen, ich finde dann sicher eine noch unverständlichere Erklärung.

                                              Lo!

                                              1. Hallo,

                                                [...] Prinzipiell könnte das von außen so aussehen.

                                                class DB {
                                                  private static $db;
                                                  public static function Init($config_values) { ... }
                                                  public static function Query($sql, $values = null) { ... }
                                                  public static function Quote($value) { ... }
                                                }

                                                ist nicht bereits die MySQLi-Klasse im Stande selbstständig die Verbindungen zu verwalten. So habe ich wirklich nur das Gefühl, als würde ich einen Wrapper schreiben. Auch ist mir unklar, warum es in diesem Fall statisch sein sollte, da es hier für mich durchaus Sinn machen würde, mehrere Objekte (mit unterschiedlichen Datenbankverbindungen) zu erzeugen.
                                                Hier einfach mal die Klasse, die ich anhand deines Schemas geschrieben habe:

                                                <?php  
                                                class DB  
                                                {  
                                                	private $db;  
                                                	  
                                                	public function __construct($host, $username, $passwd, $dbname, $port=3306)  
                                                	{  
                                                		$this->db=new mysqli($host, $username, $passwd, $dbname, $port);  
                                                	}  
                                                	  
                                                	public function query($sql, $resultmode)  
                                                	{  
                                                		$this->db->query($sql, $resultmode);  
                                                	}  
                                                	  
                                                	public function quote_identifier($value)  
                                                	{  
                                                		return '`'.str_replace('`', '``', &$value).'`';  
                                                	}  
                                                	  
                                                	public function quote_expression($value)  
                                                	{  
                                                		return $this->db->real_escape_string(&$value);  
                                                	}  
                                                }  
                                                ?>
                                                

                                                In meinen Augen bietet diese Klasse die gleichen Möglichkeiten wie die MySQLi-Klasse (nur dass ich noch die quote_identifier-Methode hinzugefügt habe).

                                                Wenn es nicht stört, dann lass es doch. Du als Autor bist nicht unbedingt schuld, wenn der Verwender es falsch einsetzt. Wenn er unbedingt will, lass ihn doch. Du hast nur mehr Aufwand auf deiner Seite. (Und dass du Autor und Verwender in Personalunion bist, ist auch nebensächlich.)

                                                Habe ich mir jetzt auch gedacht, nur hätte ich es nie so schön formulieren können.

                                                Schön gepackte Variablen bekommt man durch die OOP, da braucht es noch kein Singleton dazu. Schön gepackt bekommt man auch alles, wenn man funktional programmiert.
                                                Und da weiß ich wieder nicht, wie ich es in PHP schaffen sollte, nur mithilfe von Funktionen die Variablen (MySQL-Verbindung, Konfigurationsarray, ...) nicht jedes Mal bei einem Funktionsaufruf neu erzeugen zu müssen. Da sehe ich keinen Weg, das ohne globale Variablen hinzubekommen.

                                                Das war ja nur mal so ein Beispiel. Aber wenn du dich erinnerst, erwähnte ich, dass man das Singleton-Pattern auch mit einer herkömmlichen Funktion implementieren kann, denn man kann darin statische Variablen anlegen.

                                                Ahh! Jetzt verstehe ich, was du da meintest. Mir war nicht bewusst, dass ich auch in Funktionen statische Variablen anlegen kann. Daher hat dein Text für mich keinen Sinn ergeben. Ist das eigentlich in anderen Programmiersprachen (Java, C++) auch möglich?
                                                Aber somit stimmt es, dass man ein Singleton auch ohne Klasse erstellen kann!

                                                Alltagsweisheiten in Sprüche geklopft haben schon Generationen vor dir und mir: Meister fallen nicht vom Himmel. Erfahrungen sammelt man auch, wenn man erst mal den "falschen" Ansatz implementiert hat. Selbst wenn du dir noch so viel Mühe gibst und am Ende denkst, jetzt war es perfekt, kommst du ein paar Monde später wieder und findest mit inzwischen neu erworbenem Wissen und Erfahrungen, Dinge die verbesserungs- bis wegwerfwürdig sind.

                                                Dieses Phänomen ist schon öfters bei meinen Skripten (JavaScript und PHP) aufgetreten, bei denen ich mir gedacht habe, dass ich das ab jetzt besser (-> perfekt) machen müsste.

                                                Die gibts so wenig wie den Do-what-I-mean-Button für Anwender. Programmieren ist nicht selten wie das Rennen von einer Sackgasse in die nächste. Vielleicht fehlt dir beim Handwerklichen nur die nötige Übung. Programmieren benötigt sie auch, nur mehr auf mentaler Ebene.

                                                Da hast du wohl Recht! Tätigkeiten, die ich früher nicht konnte, kann ich mittlerweile ganz passabel, da ich Übung darin gewonnen habe.

                                                Mit der Zeit werden deine Antworten für mich leider immer unverständlicher. Allerdings freue ich mich, dass du mir trotzdem noch versuchst, alles zu erklären. Vermutlich fehlt mir einfach das nötige Vorwissen.

                                                Scheu dich nicht nachzufragen, ich finde dann sicher eine noch unverständlichere Erklärung.

                                                Das ist mal eine leicht verständliche und humorvolle Antwort. Damit ermunterst du mich gleich, dich immer weiter mit Fragen zu durchlöchern!

                                                Ein schönes Wochenende

                                                Rachus

                                                1. Hi!

                                                  ist nicht bereits die MySQLi-Klasse im Stande selbstständig die Verbindungen zu verwalten.

                                                  Nein, nicht Verbindung_en_, nur eine.

                                                  So habe ich wirklich nur das Gefühl, als würde ich einen Wrapper schreiben.

                                                  Jein. Sieh die Einfachheit meiner Klasse. Der Mehrwert liegt darin, dass kein Connect mit Test, kein Kodierungsaushandeln, nicht zwangsläufig ein Quoten und Zusammenbauen eines Statements stattfinden muss. Einfach nur (ein einmaliges Initialisieren am Scriptanfang und) ein query()-Aufruf in einem try-catch-Block. Der Verwender kann sich ganz auf seine Geschäftslogik konzentrieren.

                                                  Auch ist mir unklar, warum es in diesem Fall statisch sein sollte, da es hier für mich durchaus Sinn machen würde, mehrere Objekte (mit unterschiedlichen Datenbankverbindungen) zu erzeugen.

                                                  Ja, es ist sinnvoll, mehrere Verbindungen haben zu können, aber wann braucht man das schon? Selten. Deswegen kann ich hier auch den Aufwand aus Verwendersicht so gering wie möglich halten. Selbst das Instantiieren und Rumschleppen eines DB-Objekts spar ich mir durch den statischen Aufruf.

                                                  Die Quote()-Methode braucht es auch nur selten, denn, erwähnt hatte ich es noch nicht, aber die Query()-Methode nimmt das Statement mit Platzhaltern entgegen sowie ein Array mit den Werten und baut sich selbst ein ordentliches, sicheres Statement selbst zurecht. Ob es dazu Prepared Statements oder sprintf() nimmt, steht auf einem anderen Blatt.

                                                  Hier einfach mal die Klasse, die ich anhand deines Schemas geschrieben habe:

                                                  Das ist dann wirklich nur mehr oder weniger ein Wrapper. Wenn du das so machst, musst du immer ein DB-Objekt herumtragen. Oder du nimmst das Factory-Pattern, um dir bei tatsächlichem Bedarf eine Instanz zu holen. Die kannst du nach getaner Arbeit fallen lassen, weil sie sich der Nächste auch wieder über die Factory holt.

                                                  $db = DB::GetConnection('foo');
                                                  $db->...

                                                  Vorbereitend muss der DB-Klasse noch mitgeteilt werden, was die Verbindung foo ist.

                                                  DB::InitConnection('foo', Parameter_der_ersten_Verbindung);
                                                  DB::InitConnection('bar', Parameter_der_zweiten_Verbindung);

                                                  Der erste Parameter ist ein eindeutiger Name, der Rest - in welcher Form auch immer - die Parameter der Verbindung. DB merkt sich die Daten intern. Die Factory-Methode gibt die Verbindung mit passendem Namen heraus und erstellt sie vorher aus den Konfigurationsdaten, wenn das noch nicht geschehen ist.

                                                  In meinen Augen bietet diese Klasse die gleichen Möglichkeiten wie die MySQLi-Klasse (nur dass ich noch die quote_identifier-Methode hinzugefügt habe).

                                                  Der Mehrwert für den Verwender muss mindestens den Aufwand der Erstellung übersteigen, sonst wird das Ganze ineffizient.

                                                  Mir war nicht bewusst, dass ich auch in Funktionen statische Variablen anlegen kann. Daher hat dein Text für mich keinen Sinn ergeben. Ist das eigentlich in anderen Programmiersprachen (Java, C++) auch möglich?

                                                  In Java kann man, wenn ich mich recht erinnere, keine klassenlosen Funktionen erstellen. Also hast du mindestens eine Klasse, der du eine (private) statische Eigenschaft als Ablage verpassen kannst. Das selbe bei C#. C++ entzieht sich meiner Kenntnis.

                                                  Lo!

                                                  1. Hallo,

                                                    mein Kopf qualmt schon wieder!

                                                    Nein, nicht Verbindung_en_, nur eine.

                                                    Damit verdeutlichst du mir implizit, dass meine Klasse so ähnlich sein sollte wie MySQLi, nur dass sie mehrere Verbindungen verwaltet. Da ich aber aufgrund deines restlichen Textes das nicht glauben kann, denke ich, dass ich da etwa zu weit hineininterpretiert habe.

                                                    Jein. Sieh die Einfachheit meiner Klasse. Der Mehrwert liegt darin, dass kein Connect mit Test, kein Kodierungsaushandeln, nicht zwangsläufig ein Quoten und Zusammenbauen eines Statements stattfinden muss. Einfach nur (ein einmaliges Initialisieren am Scriptanfang und) ein query()-Aufruf in einem try-catch-Block. Der Verwender kann sich ganz auf seine Geschäftslogik konzentrieren.

                                                    Hieße das nicht eher, dass die Initiation bereits in einem try-catch-Block sein muss, damit man die Exception, die bei Fehlschlag des Verbindungsaufbaus geworfen wird, abfängt? Wozu benötige ich denn den try-catch-Block um query() bzw. wann wird da eine Ausnahme ausgelöst?

                                                    Ja, es ist sinnvoll, mehrere Verbindungen haben zu können, aber wann braucht man das schon? Selten. Deswegen kann ich hier auch den Aufwand aus Verwendersicht so gering wie möglich halten. Selbst das Instantiieren und Rumschleppen eines DB-Objekts spar ich mir durch den statischen Aufruf.

                                                    Sollte ich mal das Factory-Pattern außer Acht lassen, sagst du mir somit, dass ich das ganze statisch umsetzen soll. Ich sehe jetzt auch die Vorteile, die das bringen würde. Aber wie läuft es da wieder mit der Fehlerbehandlung? Bei einem erzeugten Objekt, weiß ich, dass es initialisiert wurde, bei der statischen Umsetzung kann es passieren, dass ein "Query()" vor der Initialisierung aufgerufen wird. Da würde ich dann eine zusätzliche if-Abfrage benötigen. Zwar gebe ich zu, dass das kein Problem ist, aber trotzdem meine ich, dass das irgendwie "schlampig" zu sein oder gar der OOP widerspricht. (Eine Klasse zu haben, von der man keine [sinnvollen] Objekte instanzieren kann.)
                                                    Dennoch könnte ich mich zu so einem Konstrukt durchringen.

                                                    Die Quote()-Methode braucht es auch nur selten, denn, erwähnt hatte ich es noch nicht, aber die Query()-Methode nimmt das Statement mit Platzhaltern entgegen sowie ein Array mit den Werten und baut sich selbst ein ordentliches, sicheres Statement selbst zurecht. Ob es dazu Prepared Statements oder sprintf() nimmt, steht auf einem anderen Blatt.

                                                    Also sähe dann ein Aufruf etwa so aus:
                                                    DB::query('SELECT x.foo, y.bar FROM x, y WHERE x.name=%s AND y.name=%s', array($_POST['name'], $_POST['name']));
                                                    (da interessiert mich auch, ob das SQL so richtig wäre)
                                                    Das ist eigentlich ein ganz gute Idee. Das Quotieren würde allerdings dann immernoch für die Identifier nötig werden. Außerdem könnte ich dann nicht wissen, ob ein %s ein Identifier oder ein String/eine Zahl/etc. ist.
                                                    Sonst könnte ich dafür ja sprintf() verwenden.

                                                    Das ist dann wirklich nur mehr oder weniger ein Wrapper. Wenn du das so machst, musst du immer ein DB-Objekt herumtragen. Oder du nimmst das Factory-Pattern, um dir bei tatsächlichem Bedarf eine Instanz zu holen. Die kannst du nach getaner Arbeit fallen lassen, weil sie sich der Nächste auch wieder über die Factory holt. [...]

                                                    Das würde doch eigentlich auch nur bei mehreren Verbindungen Sinn machen. Weil für jeweils eine Verbindung müsste ich dann nicht extra eine Factory schreiben. Außerdem sehe ich gerade wieder keinen Unterschied zwischen:

                                                    DB::InitConnection('foo', Parameter_der_ersten_Verbindung);  
                                                    $db=DB::GetConnection('foo');
                                                    

                                                    und
                                                    $db=new DB(Parameter_der_ersten_Verbindung);
                                                    Vom Sinn her ist das für mich fast das gleiche.

                                                    Der Mehrwert für den Verwender muss mindestens den Aufwand der Erstellung übersteigen, sonst wird das Ganze ineffizient.

                                                    Dann war das mehr als ineffizient!

                                                    In Java kann man, wenn ich mich recht erinnere, keine klassenlosen Funktionen erstellen. Also hast du mindestens eine Klasse, der du eine (private) statische Eigenschaft als Ablage verpassen kannst. Das selbe bei C#. C++ entzieht sich meiner Kenntnis.

                                                    Allerdings, ich erinnere mich, dass man in Java eine Klasse braucht. C# kenne ich nicht weiter, aber ich wage mich zu erinnern, dass es (zumindest in C) eine Möglichkeit gab, statische Funktionsvariablen zu erzeugen. Das sind so Features, die ich nie genutzt habe und daher vergaß.

                                                    Einen schönen Abend

                                                    Rachus

                                                    1. Hi!

                                                      Damit verdeutlichst du mir implizit, dass meine Klasse so ähnlich sein sollte wie MySQLi, nur dass sie mehrere Verbindungen verwaltet. Da ich aber aufgrund deines restlichen Textes das nicht glauben kann, denke ich, dass ich da etwa zu weit hineininterpretiert habe.

                                                      Nein, ich wollte damit sagen, dass sie derzeit nur wie ein einfacher Wrapper um mysqli aussieht mit einer Ergänzung fürs Quotieren/Maskieren. Die Mehr-DBMS-Fähigkeit war da noch nicht drin. Wenn du diese unbedingt haben willst (YAGNI!) dann musst du dir da meherere Instanzen erzeugen, die du irgendwie allen zur Verfügung stellen musst, wenn da nicht jeder seine Verbindung neu öffnen soll. Deswegen mein Factory-Vorschlag. Aber, wie gesagt: YAGNI - lass es weg, bis du echten Bedarf hast.

                                                      Jein. Sieh die Einfachheit meiner Klasse. Der Mehrwert liegt darin, dass kein Connect mit Test, kein Kodierungsaushandeln, nicht zwangsläufig ein Quoten und Zusammenbauen eines Statements stattfinden muss. Einfach nur (ein einmaliges Initialisieren am Scriptanfang und) ein query()-Aufruf in einem try-catch-Block. Der Verwender kann sich ganz auf seine Geschäftslogik konzentrieren.

                                                      Hieße das nicht eher, dass die Initiation bereits in einem try-catch-Block sein muss, damit man die Exception, die bei Fehlschlag des Verbindungsaufbaus geworfen wird, abfängt? Wozu benötige ich denn den try-catch-Block um query() bzw. wann wird da eine Ausnahme ausgelöst?

                                                      Das Init ist nur zur Entgegennahme der Konfigurationsparameter vorgesehen. Das eigentliche Öffnen der Verbindung passiert mit einem Lazy Connect. Das ist mit einem quasi internen Singleton realisiert. Wenn eine der öffentlichen Methoden eine Verbindung braucht, ruft es eine private Funktion auf, die die Verbindung (mysqli-Objekt) zurückgibt und sie gegebenenfalls vorher öffnet.

                                                      Natürlich ist es auch sinnvoll Fehler beim internen Umgang mit dem mysqli-Objekt abzufangen und dafür try-catch-Blöcke zu verwenden - wenn mysqli Exceptions werfen tät - tut es aber nicht. Man muss also selbst nach Fehlern Ausschau halten, durch Auswerten der Funktionsergebnisse. Zumm Verwender hin kann man ja ganz OOP-like eine Exception werfen. Aber mal angenommen, mysqli würfe Exceptions (PDO macht das, wenn man es lieb darum bittet), dann würde ich trotzdem try-catch intern einsetzen, aber nach außen hin nicht einfach die originale mysqli-Exceptions rethrowen, sondern nur eine allgemeine Exception werfen. Wenn man will, kann man ja die originale Exception als eine Eigenschaft der neu erstellten allgemeinen Exception anhängen. Zum Thema Exceptions folgt gleich noch etwas.

                                                      Sollte ich mal das Factory-Pattern außer Acht lassen, sagst du mir somit, dass ich das ganze statisch umsetzen soll. Ich sehe jetzt auch die Vorteile, die das bringen würde. Aber wie läuft es da wieder mit der Fehlerbehandlung? Bei einem erzeugten Objekt, weiß ich, dass es initialisiert wurde, bei der statischen Umsetzung kann es passieren, dass ein "Query()" vor der Initialisierung aufgerufen wird.

                                                      Wenn das passiert, muss es eine NotInitializedException geben. Das wäre nämlich ein Programmierfehler. Der Verwender muss dafür sorgen, dass Init() vor der ersten Verwendung aufgerufen wurde.

                                                      Da würde ich dann eine zusätzliche if-Abfrage benötigen. Zwar gebe ich zu, dass das kein Problem ist, aber trotzdem meine ich, dass das irgendwie "schlampig" zu sein oder gar der OOP widerspricht. (Eine Klasse zu haben, von der man keine [sinnvollen] Objekte instanzieren kann.)

                                                      Nö, bestes Beispiel für eine instanzlose Klasse wäre eine Math-Klasse. Von der braucht man in der Regel keine Instanz sondern nur deren Methoden. (Nicht unter PHP, da kann man gleich die math-Funktionen verwenden, aber unter C# und Java beispielsweise.) Math ist wieder ein recht extremes Beispiel, da da ein paar autarke Funktionen im Grunde genommen nur der Ordnung halber in einer Klasse aufgehängt worden sind. C#, ein durchweg objektorientiertes System, kennt extra das Schlüsselwort static nicht nur für Funktionen sondern auch für Klassen, eben wie solche wie die Math. Die Math-Klasse wird hier mehr oder weniger nur als Namensraum missbraucht und weil es keine klassenlosen Funktionen gibt.

                                                      Wie auch immer, bei der DB ist die Sachlage etwas anders, denn die hat intern ja so einige Methoden, die zusammenarbeiten. Und es gibt sogar eine Instanz, auch wenn diese nie das Licht der Öffentlichkeit erblickt. (Die wird im konkreten Fall vermutlich eine Instanz der mysqli-Klasse sein, aber wenn es mysqli nicht gäbe, müsste man dessen Funktionalität mit eigenen internen Methoden nachbauen.)

                                                      Nun zur Fehlerbehandlung: Dem Verwender ist es egal, aus welchem Grunde seine Abfrage fehlschlug. Programmierfehler (Syntax-Fehler im SQL-Statement) hat man durch ausreichende Tests ausgeschlossen. Den Rest kann man nicht direkt beeinflussen. Wenn das Netzwerk weg ist, der DBMS-Server down ist, jemand am Rechtesystem rumgefummelt hat, ..., nichts davon kann durch die Geschäftslogik korrigiert werden. Sie kann nur daraufhin ein Notprogramm fahren. Der eigentliche Fehler sollte schon von der DB-Klasse in Richtung Log-System und damit in Richtung Administrator gemeldet worden sein. Zum Verwender hin braucht es nur eine allgemeine Aussage, damit der den Anwender mit einem "Geht grad nicht" benachrichtigen oder ihm was anderes vorsetzen kann.

                                                      Die Quote()-Methode braucht es auch nur selten, denn, erwähnt hatte ich es noch nicht, aber die Query()-Methode nimmt das Statement mit Platzhaltern entgegen sowie ein Array mit den Werten und baut sich selbst ein ordentliches, sicheres Statement selbst zurecht. Ob es dazu Prepared Statements oder sprintf() nimmt, steht auf einem anderen Blatt.

                                                      Also sähe dann ein Aufruf etwa so aus:
                                                      DB::query('SELECT x.foo, y.bar FROM x, y WHERE x.name=%s AND y.name=%s', array($_POST['name'], $_POST['name']));

                                                      Ja, genauso dachte ich mir das. Das wäre sie sprintf-Variante.

                                                      (da interessiert mich auch, ob das SQL so richtig wäre)

                                                      Da fehlt vermutlich noch die Join-Bedingung (in WHERE: x.y_id = y.id - oder sowas in der Art).

                                                      Das ist eigentlich ein ganz gute Idee. Das Quotieren würde allerdings dann immernoch für die Identifier nötig werden.

                                                      Ja, kann man sich aber schenken, wenn man die Identifier nicht dynamisch einzufügen gedenkt oder sicherstellen kann, dass keine Backticks im Namen vorkommen (wer macht das schon?) sowie Eingaben, die Spaltennamen werden sollen, gegen eine Liste der erlaubten Namen geprüft werden. Wann hat man soch einen Fall? Vermutlich nur beim Sortieren nach einer vom Anwender ausgewählten Spalte. Da muss man sowieso testen, dass die Spalte eine der existierenden/erlaubten ist, damit man keinen Syntaxfehler riskiert. (Man kann auch nach der Spaltennummer (gemäß Reihenfolge in der SELECT-Klausel) sortieren, dann muss man nicht mit dem Spaltennamen hantieren.)

                                                      Außerdem könnte ich dann nicht wissen, ob ein %s ein Identifier oder ein String/eine Zahl/etc. ist.

                                                      Für String/Zahl/null kann man den Typ des Arguments auswerten und weiß dann, ob gequotet werden muss (String) oder nicht darf (null) (oder ob es egal ist (int/float)). Beim Identifier hat man ein Problem, den muss man irgendwie gänzlich separat behandeln - da fällt mir grad keine clevere Lösung ein.

                                                      Das ist dann wirklich nur mehr oder weniger ein Wrapper. Wenn du das so machst, musst du immer ein DB-Objekt herumtragen. Oder du nimmst das Factory-Pattern, um dir bei tatsächlichem Bedarf eine Instanz zu holen. Die kannst du nach getaner Arbeit fallen lassen, weil sie sich der Nächste auch wieder über die Factory holt. [...]
                                                      Das würde doch eigentlich auch nur bei mehreren Verbindungen Sinn machen. Weil für jeweils eine Verbindung müsste ich dann nicht extra eine Factory schreiben.

                                                      Zweimal ja.

                                                      Außerdem sehe ich gerade wieder keinen Unterschied zwischen:

                                                      DB::InitConnection('foo', Parameter_der_ersten_Verbindung);

                                                      $db=DB::GetConnection('foo');

                                                      
                                                      > und  
                                                      > `$db=new DB(Parameter_der_ersten_Verbindung);`{:.language-php}  
                                                      > Vom Sinn her ist das für mich fast das gleiche.  
                                                        
                                                      Die Factory kann sich vor der Rückgabe an den Verwender die erzeugte Instanz merken und so die selbe Instanz auch noch an andere Verwender rausgeben. Beim direkten Instantiieren bist du der alleinige Besitzer der Instanz und jeder new-Aufruf erstellt zwingend eine neue.  
                                                        
                                                      \-----  
                                                        
                                                      Übrigens, falls du dich fragst, warum ich nicht vorschlage, DB von mysqli abzuleiten ... nun, könnte man machen. Nur hat man dann alle öffentlichen Eigenschaften und Methoden in die DB-Klasse "hineingeerbt" und damit wäre sie nicht mehr so schön schlank wie ich sie mit ihren zwei (drei) statischen Methoden entworfen habe, denn was einmal public ist, lässt sich nicht wieder verstecken.  
                                                        
                                                        
                                                      Lo!
                                                      
                                                      1. Hallöchen,

                                                        Aber, wie gesagt: YAGNI - lass es weg, bis du echten Bedarf hast.

                                                        gut, ich lass den Mehrverbindungssupport weg!

                                                        Anhand deinen Tipps habe ich un mal meine Klasse modifiziert. Da ich nicht wusste, wie ich aus Arrays mehrere Funktionsparameter für sprintf() mache, habe ich das ganze etwas anders realisiert.
                                                        Ein query()-Aufruf kann nun so aussehen:
                                                        DBConnection::query("SELECT foo FROM bar WHERE bar.foo=?0 AND bar.x=?1", array($_POST['id'], $_POST['name']), array(DBConnection::NUMBER, DBConnection::LITERAL));
                                                        Hoffentlich ist es verständlich. Da mir mein Editor allerdings "INTEGER", "STRING" und "FLOAT" als bekannt angab, wollte ich diese Bezeichner nicht für die Klassenkonstanten verwenden.

                                                        Auch weiß ich nicht, wie ich bei Exceptions irgendwo den Fehler "hinlogge". Daher ist auch das nicht darin. Sonst würde ich diese Klasse jetzt schon recht angenehm finden. Eventuell noch etwas funktional modelieren, aber sonst habe ich mal das Gefühl, dass das so OK ist.

                                                        <?php  
                                                        class DBConnection  
                                                        {  
                                                        	static private $db;  
                                                        	static private $settings;  
                                                        	  
                                                        	const NOESCAPE=0;  
                                                        	const NUMBER=1;  
                                                        	const DECIMAL=2;  
                                                        	const LITERAL=3;  
                                                        	const IDENTIFIER=4;  
                                                        	  
                                                        	static public function Init($host, $username, $passwd, $dbname, $port=3306)  
                                                        	{  
                                                        		self::$settings=array($host, $username, $passwd, $dbname, $port);  
                                                        	}  
                                                        	  
                                                        	static public function query($sql, $values, $types, $resultmode=MYSQLI_STORE_RESULT)  
                                                        	{  
                                                        		if (!isset(self::$settings)) throw new Exception('not initialized', 1);  
                                                        		if (!isset(self::$db))  
                                                        		{  
                                                        			if (!self::connect()) throw new Exception('could not connect', 2);  
                                                        		}  
                                                        		  
                                                        		for ($i=count($values); $i>=0; --$i)  
                                                        		{  
                                                        			if ($types[$i]==self::NUMBER)  
                                                        			{  
                                                        				$sql=str_replace('?'.$i, intval($values[$i]), $sql);  
                                                        			}  
                                                        			else if ($types[$i]==self::DECIMAL)  
                                                        			{  
                                                        				$sql=str_replace('?'.$i, floatval($values[$i]), $sql);  
                                                        			}  
                                                        			else if ($types[$i]==self::LITERAL)  
                                                        			{  
                                                        				$sql=str_replace('?'.$i, self::quote_expression($values[$i]), $sql);  
                                                        			}  
                                                        			else if ($types[$i]==self::IDENTIFIER)  
                                                        			{  
                                                        				$sql=str_replace('?'.$i, self::quote_identifier($values[$i]), $sql);  
                                                        			}  
                                                        			else  
                                                        			{  
                                                        				$sql=str_replace('?'.$i, $values[$i], $sql);  
                                                        			}  
                                                        		}  
                                                        		$sql=str_replace('??', '?', $sql);  
                                                        		  
                                                        		return self::$db->query($sql, $resultmode);  
                                                        	}  
                                                        	  
                                                        	static public function quote_identifier($value)  
                                                        	{  
                                                        		return '`'.str_replace('`', '``', &$value).'`';  
                                                        	}  
                                                        	  
                                                        	static public function quote_expression($value)  
                                                        	{  
                                                        		return '\''.self::$db->real_escape_string(&$value).'\'';  
                                                        	}  
                                                        	  
                                                        	static private function connect()  
                                                        	{  
                                                        		self::$db=new MySQLi(self::$settings[0], self::$settings[1], self::$settings[2], self::$settings[3], self::$settings[4]);  
                                                        		if (self::$db->connect_error) return false;  
                                                        		else return true;  
                                                        	}  
                                                        }  
                                                        ?>
                                                        

                                                        Wahrscheinlich könnte man die Einstellungen auch anders handhaben, aber das wirst du mir sicher wieder mitteilen.

                                                        Da fehlt vermutlich noch die Join-Bedingung (in WHERE: x.y_id = y.id - oder sowas in der Art).

                                                        In Ordnung, an sowas habe ich bei meinem Beispiel nicht gedacht...

                                                        Für String/Zahl/null kann man den Typ des Arguments auswerten und weiß dann, ob gequotet werden muss (String) oder nicht darf (null) (oder ob es egal ist (int/float)). Beim Identifier hat man ein Problem, den muss man irgendwie gänzlich separat behandeln - da fällt mir grad keine clevere Lösung ein.

                                                        Dafür habe ich jetzt ja eine Lösung ganz ohne sprintf() gefunden (Gründe siehe oben). Hat lange Überlegungszeit gekostet, aber ichbin jetzt einigermaßen zufrieden.

                                                        Die Factory kann sich vor der Rückgabe an den Verwender die erzeugte Instanz merken und so die selbe Instanz auch noch an andere Verwender rausgeben. Beim direkten Instantiieren bist du der alleinige Besitzer der Instanz und jeder new-Aufruf erstellt zwingend eine neue.

                                                        Dadurch, dass ich jetzt aber das ganze für nur eine Verbindung erledige, erübrigt sich die Factory.

                                                        Übrigens, falls du dich fragst, warum ich nicht vorschlage, DB von mysqli abzuleiten [...]

                                                        Hatte ich nicht vor. Auf so eine Idee wäre ich, um ehrlich zu sein, auch nicht gekommen.

                                                        Einen schönen Abend

                                                        Rachus

                                                        1. Hi!

                                                          Da ich nicht wusste, wie ich aus Arrays mehrere Funktionsparameter für sprintf() mache, habe ich das ganze etwas anders realisiert.

                                                          Die umständliche Variante: call_user_func_array(). Die einfache Variante: vsprintf().

                                                          Auf jeden Fall müsste nach meiner Version vor dem vsprintf()-Aufruf noch das Array durchlaufen werden und je nach Typ eine entsprechende Behandlung erfolgen.

                                                          Ein query()-Aufruf kann nun so aussehen:
                                                          DBConnection::query("SELECT foo FROM bar WHERE bar.foo=?0 AND bar.x=?1", array($_POST['id'], $_POST['name']), array(DBConnection::NUMBER, DBConnection::LITERAL));

                                                          Das ist unsicher wenn du nicht garantieren kannst, dass beispielsweise $_POST['id'] wirklich eine Zahl ist. Üblicherweise ist nämlich alles in den EGPCS-Variablen vom Typ String.

                                                          Was dann bleibt, ist den Unterschied zwischen String und Identifier festzustellen. Ich würde das mit unterschiedlichen Platzhaltern realisieren, zum Beispiel: ?ziffer und ?!ziffer. Jedenfalls so, dass ? weiterhin als Platzhaltereinleitung steht, alle anderen Zeichen sind mehr oder weniger schon als normaler Operator verbraucht.

                                                          Auch weiß ich nicht, wie ich bei Exceptions irgendwo den Fehler "hinlogge".

                                                          Die Exception-Klasse hat schon von sich aus einige Eigenschaften. Im einfachsten Fall kannst du den Text in $message ablegen. Du kannst aber auch die Klasse beerben und eigene Eigenschaften hinzufügen. (Selbst unbeerbt ginge das, PHP legt schließlich alles an, was noch nicht da ist.)

                                                          static public function query($sql, $values, $types, $resultmode=MYSQLI_STORE_RESULT)
                                                          {
                                                          if (!isset(self::$settings)) throw new Exception('not initialized', 1);
                                                          if (!isset(self::$db))
                                                          {
                                                          if (!self::connect()) throw new Exception('could not connect', 2);
                                                          }

                                                          Das würde ich beschränken auf ein self::connect(); und alles andere dort unterbringen. Denn wenn du mal weiterdenkst für später eventuell hinzukommende Methoden, die eine Verbindung brauchen, dann brauchst du dieses Handling genauso, und Copy&Paste-Programmierung ist Mist. (Das widerspricht auch nicht dem YAGNI, denn es kommt ja nichts "Überflüssiges" hinzu sondern das Vorhandene wird nur zukunftssicher platziert.)

                                                          Die query() muss einfach nur dafür Sorge tragen, dass die Verbindung besteht. Wie das geschieht und was dafür alles notwendig ist (Konfigdaten) ist nicht ihr Bier, darum kann sich die connect() kümmern.

                                                          Wahrscheinlich könnte man die Einstellungen auch anders handhaben, aber das wirst du mir sicher wieder mitteilen.

                                                          Kein Einwand meinerseits.

                                                          Lo!

                                                          1. Hallo,

                                                            Die umständliche Variante: call_user_func_array(). Die einfache Variante: vsprintf().

                                                            komisch, dass ich sowas immer nicht finde.

                                                            Ein query()-Aufruf kann nun so aussehen:
                                                            DBConnection::query("SELECT foo FROM bar WHERE bar.foo=?0 AND bar.x=?1", array($_POST['id'], $_POST['name']), array(DBConnection::NUMBER, DBConnection::LITERAL));

                                                            Das ist unsicher wenn du nicht garantieren kannst, dass beispielsweise $_POST['id'] wirklich eine Zahl ist. Üblicherweise ist nämlich alles in den EGPCS-Variablen vom Typ String.

                                                            Warum das? Ich parse das doch per intval() als Zahl. Wenn das tatsächlich ein String ohne Zahl als erstes Zeichen einlese, dann wird 0 zurückgegeben. Ist damit ein Problem verbunden? Sollte ich dann wohl auch eine Exception werfen, wenn das eigentlich numerisch erwartete Argument nicht numerisch ist?

                                                            Was dann bleibt, ist den Unterschied zwischen String und Identifier festzustellen. Ich würde das mit unterschiedlichen Platzhaltern realisieren, zum Beispiel: ?ziffer und ?!ziffer. Jedenfalls so, dass ? weiterhin als Platzhaltereinleitung steht, alle anderen Zeichen sind mehr oder weniger schon als normaler Operator verbraucht.

                                                            Dann würde ich aber vsprintf() nicht mehr verwenden können, da der das ganze ja anders handhabt. Damit wäre wohl meine Bearbeitung des Queries korrekt?

                                                            Die Exception-Klasse hat schon von sich aus einige Eigenschaften. Im einfachsten Fall kannst du den Text in $message ablegen. Du kannst aber auch die Klasse beerben und eigene Eigenschaften hinzufügen. (Selbst unbeerbt ginge das, PHP legt schließlich alles an, was noch nicht da ist.)

                                                            Eigentlich meint ich damit, dass ich dem Administrator mitteile, dass etwas falsch ist. So ähnlich wie die Log-Dateien vom Apache.

                                                            static public function query($sql, $values, $types, $resultmode=MYSQLI\_STORE\_RESULT)  
                                                            {  
                                                              if (!isset(self::$settings)) throw new Exception('not initialized', 1);  
                                                              if (!isset(self::$db))  
                                                              {  
                                                              	if (!self::connect()) throw new Exception('could not connect', 2);  
                                                              }  
                                                            

                                                            Das würde ich beschränken auf ein self::connect(); und alles andere dort unterbringen. Denn wenn du mal weiterdenkst für später eventuell hinzukommende Methoden, die eine Verbindung brauchen, dann brauchst du dieses Handling genauso, und Copy&Paste-Programmierung ist Mist.

                                                            Okay, wird so gemacht. Hatte ich auch erst vor, wusste aber nicht wie eine Exception bei tieferen Funktionsaufrufverschachtelungen reagiert.

                                                            Die query() muss einfach nur dafür Sorge tragen, dass die Verbindung besteht. Wie das geschieht und was dafür alles notwendig ist (Konfigdaten) ist nicht ihr Bier, darum kann sich die connect() kümmern.

                                                            Dazu würde mich auch noch einmal interessieren, warum das eigentlich per "Lazy Connect" stattfinden soll. Wenn die Init() aufgerufen wird, dürfte doch eigentlich klar sein, dass früher oder später eine DB-Verbindung benötigt wird. Warum dann nicht also gleich die Verbindung aufbauen und später dafür sich nicht mehr darum kümmern müssen?

                                                            Schönen Sonntag

                                                            Rachus

                                                            1. Hi!

                                                              DBConnection::query("SELECT foo FROM bar WHERE bar.foo=?0 AND bar.x=?1", array($_POST['id'], $_POST['name']), array(DBConnection::NUMBER, DBConnection::LITERAL));
                                                              Das ist unsicher wenn du nicht garantieren kannst, dass beispielsweise $_POST['id'] wirklich eine Zahl ist. Üblicherweise ist nämlich alles in den EGPCS-Variablen vom Typ String.
                                                              Warum das? Ich parse das doch per intval() als Zahl.

                                                              Stimmt, dass das intval() im weiteren Verlauf nimmt die Unsicherheit raus. Da hab ich hier zu schnell gemeckert.

                                                              Wenn das tatsächlich ein String ohne Zahl als erstes Zeichen einlese, dann wird 0 zurückgegeben. Ist damit ein Problem verbunden? Sollte ich dann wohl auch eine Exception werfen, wenn das eigentlich numerisch erwartete Argument nicht numerisch ist?

                                                              Ich würde mir aus der Sicht von query() da keine Gedanken weiter machen. Wenn jemand Mist eingibt, kann er nicht erwarten, das was Sinnvolles rauskommt, Hauptsache es entsteht keine Sicherheitslücke. Natürlich kann man sich mit einer Exception beschweren, aber was soll's? Wenn jemand die Eingangsdaten auf fachliche Plausibilität prüfen möchte/muss, dann soll er das vorher tun.

                                                              Was dann bleibt, ist den Unterschied zwischen String und Identifier festzustellen. Ich würde das mit unterschiedlichen Platzhaltern realisieren, zum Beispiel: ?ziffer und ?!ziffer. Jedenfalls so, dass ? weiterhin als Platzhaltereinleitung steht, alle anderen Zeichen sind mehr oder weniger schon als normaler Operator verbraucht.
                                                              Dann würde ich aber vsprintf() nicht mehr verwenden können, da der das ganze ja anders handhabt. Damit wäre wohl meine Bearbeitung des Queries korrekt?

                                                              Ja, kannst du so lassen.

                                                              Eigentlich meint ich damit, dass ich dem Administrator mitteile, dass etwas falsch ist. So ähnlich wie die Log-Dateien vom Apache.

                                                              Als Idee hätte ich im Angebot, mit den Konfigurationsdaten ein optionales Callback zu übergeben. Beim Feststellen eines Fehlers würde ich zuerst bei is_callable() vom Callback selbiges mit detaillierter Information aufrufen und dann die allgemeine Exception werfen. In der Callback-Funktion kann dann was auch immer zur Admin-Benachrichtigung stehen.

                                                              [...] wusste aber nicht wie eine Exception bei tieferen Funktionsaufrufverschachtelungen reagiert.

                                                              Sie geht schnurstracks den Aufruf-Stack nach oben, alles andere ignorierend, bis irgendwo in einem ein try-Block ein passendes catch sie fängt.

                                                              Dazu würde mich auch noch einmal interessieren, warum das eigentlich per "Lazy Connect" stattfinden soll. Wenn die Init() aufgerufen wird, dürfte doch eigentlich klar sein, dass früher oder später eine DB-Verbindung benötigt wird. Warum dann nicht also gleich die Verbindung aufbauen und später dafür sich nicht mehr darum kümmern müssen?

                                                              Ohne Lazy Connect musst du vorher wissen, ob wirklich eine DB-Verbindung gebraucht wird. Kann man so machen. Dann musst du aber zu jedem Query-Aufruf sichergestellt haben, dass Init aufgerufen wurde. Und wenn du mehr als ein Query hast, die an unterschiedlichen Stellen der Programmlogik stehen und nichts voneinander wissen, musst du irgendwie das Init koordinieren. Ich würde das Init einfach auf Verdacht am Scriptanfang oder in einer allgemeinen Initialisierungsphase aufrufen. Es ist je erst einmal nicht mehr als eine Variablenzuweisung, was selbst bei Nichtverwendung so gut wie nichts kostet.

                                                              Ich würde allerdings die Connect-Logik anders als bei dir umdrehen. Pseudocode:

                                                              if (!self::$db) { // keine Verbindung vorhanden
                                                                if (!konfigurationsdaten vorhanden)
                                                                  Exception
                                                                $self::db = connect...;
                                                              }
                                                              return $self::db;

                                                              Wenn die Verbindung da ist, wunderbar, rausgeben und fertig. Konfigdatencheck brauchen wir nur, wenn sie erst noch aufgebaut werden muss.

                                                              Lo!

                                                              1. Hallo,

                                                                damit hoffe ich, dass die Klasse dann so vorerst fertig ist:

                                                                <?php  
                                                                class DBConnection  
                                                                {  
                                                                	static private $db;  
                                                                	static private $settings;  
                                                                	  
                                                                	const NOESCAPE=0;  
                                                                	const NUMBER=1;  
                                                                	const DECIMAL=2;  
                                                                	const LITERAL=3;  
                                                                	const IDENTIFIER=4;  
                                                                	  
                                                                	static public function Init($host, $username, $passwd, $dbname, $port=3306)  
                                                                	{  
                                                                		self::$settings=array($host, $username, $passwd, $dbname, $port);  
                                                                	}  
                                                                	  
                                                                	static public function query($sql, $values, $types, $resultmode=MYSQLI_STORE_RESULT)  
                                                                	{  
                                                                		if (!isset(self::$db)) self::connect();  
                                                                		  
                                                                		for ($i=count($values); $i>=0; --$i)  
                                                                		{  
                                                                			if ($types[$i]==self::NUMBER)  
                                                                			{  
                                                                				$sql=str_replace('?'.$i, intval($values[$i]), $sql);  
                                                                			}  
                                                                			else if ($types[$i]==self::DECIMAL)  
                                                                			{  
                                                                				$sql=str_replace('?'.$i, floatval($values[$i]), $sql);  
                                                                			}  
                                                                			else if ($types[$i]==self::LITERAL)  
                                                                			{  
                                                                				$sql=str_replace('?'.$i, self::quote_expression($values[$i]), $sql);  
                                                                			}  
                                                                			else if ($types[$i]==self::IDENTIFIER)  
                                                                			{  
                                                                				$sql=str_replace('?'.$i, self::quote_identifier($values[$i]), $sql);  
                                                                			}  
                                                                			else  
                                                                			{  
                                                                				$sql=str_replace('?'.$i, $values[$i], $sql);  
                                                                			}  
                                                                		}  
                                                                		$sql=str_replace('??', '?', $sql);  
                                                                		  
                                                                		return self::$db->query($sql, $resultmode);  
                                                                	}  
                                                                	  
                                                                	static public function quote_identifier($value)  
                                                                	{  
                                                                		return '`'.str_replace('`', '``', &$value).'`';  
                                                                	}  
                                                                	  
                                                                	static public function quote_expression($value)  
                                                                	{  
                                                                		return '\''.self::$db->real_escape_string(&$value).'\'';  
                                                                	}  
                                                                	  
                                                                	static private function connect()  
                                                                	{  
                                                                		if (!isset(self::$settings)) throw new Exception('not initialized', 1);  
                                                                		self::$db=new MySQLi(self::$settings[0], self::$settings[1], self::$settings[2], self::$settings[3], self::$settings[4]);  
                                                                		if (self::$db->connect_error) throw new Exception('could not connect', 2);  
                                                                	}  
                                                                }  
                                                                ?>
                                                                

                                                                Ich würde mir aus der Sicht von query() da keine Gedanken weiter machen. Wenn jemand Mist eingibt, kann er nicht erwarten, das was Sinnvolles rauskommt, Hauptsache es entsteht keine Sicherheitslücke. Natürlich kann man sich mit einer Exception beschweren, aber was soll's? Wenn jemand die Eingangsdaten auf fachliche Plausibilität prüfen möchte/muss, dann soll er das vorher tun.

                                                                Gut, dann werde ich das so belassen.

                                                                Dann würde ich aber vsprintf() nicht mehr verwenden können, da der das ganze ja anders handhabt. Damit wäre wohl meine Bearbeitung des Queries korrekt?

                                                                Ja, kannst du so lassen.

                                                                Habe ich so belassen.

                                                                Eigentlich meint ich damit, dass ich dem Administrator mitteile, dass etwas falsch ist. So ähnlich wie die Log-Dateien vom Apache.

                                                                Als Idee hätte ich im Angebot, mit den Konfigurationsdaten ein optionales Callback zu übergeben. Beim Feststellen eines Fehlers würde ich zuerst bei is_callable() vom Callback selbiges mit detaillierter Information aufrufen und dann die allgemeine Exception werfen. In der Callback-Funktion kann dann was auch immer zur Admin-Benachrichtigung stehen.

                                                                Das muss ich aber nicht implementieren, oder? Sonst würde ich das herauslassen. Ist einfacher ohne, denke ich. Wie sollte denn die Admin-Benachrichtigung ankommen? Einfach in eine Textdatei schreiben?

                                                                Ohne Lazy Connect musst du vorher wissen, ob wirklich eine DB-Verbindung gebraucht wird. Kann man so machen. [...]

                                                                Habe ich jetzt auch erstmal so gelassen.

                                                                Wenn die Verbindung da ist, wunderbar, rausgeben und fertig. Konfigdatencheck brauchen wir nur, wenn sie erst noch aufgebaut werden muss.

                                                                Dito. Auch versucht zu übernehmen.

                                                                Noch eine letzte Frage: Würde es hier Sinn machen, die Klassen, die ich für mein(e) System(e) erstelle, in einen Namensraum zu packen, oder sollte ich sie einfach global lassen?

                                                                Schönen Nachmittag

                                                                Rachus

                                                                1. Hi!

                                                                  Eigentlich meint ich damit, dass ich dem Administrator mitteile, dass etwas falsch ist. So ähnlich wie die Log-Dateien vom Apache.
                                                                  Als Idee hätte ich im Angebot, mit den Konfigurationsdaten ein optionales Callback zu übergeben. [...]
                                                                  Das muss ich aber nicht implementieren, oder? Sonst würde ich das herauslassen. Ist einfacher ohne, denke ich.

                                                                  Das war nur eine Idee, wie man einerseits die Adminbenachrichtigung von der DB-Klasse weitgehend abkoppeln kann, sie andererseits auch nicht direkt in der Geschäftslogik drin hat und obendrein so flexibel ist, sie ganz wegzulassen, wenn man es nicht braucht.

                                                                  Wie sollte denn die Admin-Benachrichtigung ankommen? Einfach in eine Textdatei schreiben?

                                                                  So wie du das gern hättest. Ich hab das lieber als E-Mail, die gehe ich regelmäßiger durch als daran denken zu müssen, Logfiles zu studieren, was auch langweilig ist, wenn sie leer bleiben.

                                                                  Noch eine letzte Frage: Würde es hier Sinn machen, die Klassen, die ich für mein(e) System(e) erstelle, in einen Namensraum zu packen, oder sollte ich sie einfach global lassen?

                                                                  Kommt drauf an. Stell dir die Frage nach dem Sinn und den Vor- und Nachteilen, die das mit sich bringt. Ich kann das nicht beantworten, da ich den Gesamtkontext nicht kenne, in dem du das einsetzen willst.

                                                                  Lo!

                                                                  1. Hallo,

                                                                    Das war nur eine Idee, wie man einerseits die Adminbenachrichtigung von der DB-Klasse weitgehend abkoppeln kann, sie andererseits auch nicht direkt in der Geschäftslogik drin hat und obendrein so flexibel ist, sie ganz wegzulassen, wenn man es nicht braucht.

                                                                    Das kann ich ja bei Bedarf nachrüsten. Du hast mich ja oft genug auf YAGNI hingewiesen. Da ich das nicht brauchen werde, kommt es auch nicht in die Klasse!

                                                                    Noch eine letzte Frage: Würde es hier Sinn machen, die Klassen, die ich für mein(e) System(e) erstelle, in einen Namensraum zu packen, oder sollte ich sie einfach global lassen?

                                                                    Kommt drauf an. Stell dir die Frage nach dem Sinn und den Vor- und Nachteilen, die das mit sich bringt. Ich kann das nicht beantworten, da ich den Gesamtkontext nicht kenne, in dem du das einsetzen willst.

                                                                    Sollten es mehr Klassen/Funktionen werden, die ich produziere, werde ich sie dann doch in einen Namensraum einschließen, da sie dann einfach logisch zusammengehalten werden. Bleibt es bei einer oder zwei Klassen, lasse ich diese im globalen Namensraum.

                                                                    Danke für deine Hilfe. Sollte ich wiedermal Fragen haben, melde ich mich wieder!

                                                                    Schönen Abend

                                                                    Rachus