PHP Benötige eine Bewertung meiner Klasse

HighTec

Lieutenant
Registriert
Sep. 2006
Beiträge
761
Guten Tag liebe Forengemeinde,

ich habe mich nach langer Zeit endlich wieder mit PHP beschäftigt und mich zuvor eine noch viel längere Zeit vehement dagegen gesträubt mich in OOP einzuarbeiten.
Jetzt im Nachhinein muss ich sagen, dass ich diesen Schritt schon viel eher hätte gehen sollen.
Zu Übungs- und Lernzwecken habe ich nun eine Klasse programmiert, die sich um Registrierung und Login von Benutzern kümmern soll.

An dieser Stelle bitte ich euch nun um die Bewertung dieser Klasse in folgenden Punkten:

- Programmierstil
- Performance
- Sicherheit (Filterung von Benutzereingaben geschehen außerhalb der Klasse)
- Vorgehensweise
- Standards

Diese Bewertung benötige ich ebenfalls zu Übungs- und Lernzwecken, damit ich Verbesserungen erfahre bevor ich diese Fehler wieder in der nächsten Klasse einbaue.

Ich würde mich freuen, wenn ihr euch das ganze mal anschaut und mir berichtet was euch so auffällt.

(Da der Code ein bisschen zu lang für ein Forenbeitrag ist habe ich die Datei mal angehängt)

Ich Danke euch schon einmal im Vorraus und freue mich auf eure Hinweise.

Gruß
Christian
 

Anhänge

  • UserAuth.class.zip
    9 KB · Aufrufe: 241
Jap 1. und 2. was kling1 sagt und einen Autloader nutzen.

Immer eine gute Anlaufstelle: http://www.phptherightway.com

Du vermischst hier extrem viel. Aber der Reihe nach...

  1. Nutzt du denn eine ordentliche IDE (PhpStorm, NetBeans, Eclipse, ... - wobei ich persönlich nur mit Erstem zufrieden bin)?
  2. Nutz für Member am Besten Upper camel case. Besser lesbar und in PhpStorm bspw. kannst du auf Member via Initialen zugreifen (bspw. auf $CellUserId via $this->CUI*tab* zugreifen, dann vervollständigt er automatisch).
  3. Eine Klasse für was genau? Hier ist mindestens Zugriff auf zwei Tabellen nötig (User, Session). Bau dir ergo lieber eine User und eine Session Klasse und misch nicht beides zusammen. Weiterhin scheinst du hier die DB-Kommunikation mit regeln zu wollen. Hierbei gibts das Active Record und das Data Mapper Pattern. Such dir eins aus und bau darauf auf, aber vermisch hier nicht beides.

    Weiterhin willst du wahrscheinlich noch Mails damit verschicken. Neue Klasse erstellen.

    OOP bedeutet nicht, dass du alles bisherige einfach nur in ein Objekt packst, sondern dass du eine ordentlich, strukturelle Trennung von verschiedenen Sachgebieten erreichst. Session, User, Mail, ... Unterschiedliche Anforderungen und Ziele. In deinen Klassen kannst du dann mittels DI entsprechende Objekte übergeben, auf welchen diverse Operationen ablaufen.

    Erstell dir lieber eine Model-Klasse (wenn du Active Record nutzen willst), wo nur Sachen zur Kommunikation mit der DB enthalten sind ($usertab, $celluserid, $cellusername, $cellemail, ...) und vererbe dann jeweils von dieser für jede einzelne Tabelle deiner DB. Oder nutz am Besten gleichen eine ordentliche Abstraktion: Doctrine (läuft allerdings als Data Mapper).
  4. Alles private? Bin ich persönlich überhaupt kein Fan davon. Keinerlei Vererbung möglich... Wäre die Klasse final, wärs noch zu verkraften.
  5. @access kannst du weg lassen, steht doch bereits explizit davor.
  6. :132 PEPPER? Wenn das ein String ist, schreib es in Anführungszeichen. Konstante? Dann deklarier es als Konstante. Ein Salt wird aber nicht fix festgelegt, sondern pro Hash jeweils einzeln generiert und zum Passwort mit gespeichert. Aber auch dafür gibts was Besseres: Password Hashing.
  7. $tokenexpiredays, $tokenresexpirehours, $tokenlenght (t <-> h), $hashlength, ... Ab in die DB damit, als ordentlicher Zeitstempel/Datetime zum User. Außerdem an Camel Case denken.

    Was ist, wenn die Daten plötzlich geändert werden müssen? Hast du abgesichert, dass bisherige Token weiterhin mit alten Einstellungen gültig sind? Müssen diese neu generiert werden?
  8. :276 @var ObjectReferenz -> Hinter @var erscheint immer der Typ der Variable. Da es sich um ein PDO-Objekt handelt, schreib ergo auch @var \PDO dahin. Sonst kannst du keine Auto Completion nutzen und tappst im Dunkeln beim Programmieren, welche Methoden jetzt ein Objekt besitzt. (fehlende IDE?)
  9. :385 Sehr unglücklicher Aufruf einer Exception. Du hast dir doch schon ne Klasse dafür gebaut. Spezialisier diese, dann kannst du für jeden Typ eine eigene Exception werfen. Und nutz bitte auch die integrierten Exceptions: InvalidArgumentException usw.
  10. :402 Helper-Methode fuer setAttribute(). Auswaehlen der zu aendernden Eigenschaft
    In Kommentaren kannst du ruhig Umlaute verwenden.
  11. switchandsetAttribute() Erstmal den Namen ordentlich gestalten - switchAndSetAttribute(). Weiterhin ist das ein Paradebeispiel dafür, wie man Abstraktion einsetzt. Erstell dir ein Interface (+ evtl. eine abstrakte Basis-Klasse) und jeweils mehrere spezialisierte Klassen, welche die Fälle UNameUserTable, UNameCellUserID usw. abhandeln. Da du anscheinend nur auf Strings abgleichst (was ist mit Zahlen u.ä.?) könntest zusätzlich auch gleich noch Traits mit einsetzen, da Mehrfachvererbung ja nicht möglich ist. So könntest du einen String-/DbValidator erstellen, der dir diese Prüfungen vereinfacht. Was machst du denn, wenn du plötzlich auch auf ints prüfen willst? So schreibst du jetzt gefühlte 5000 Mal && is_int( $value ) dazu. Mit einer eigenen Klasse dafür, änderst du das ein Mal und es prüft überall korrekt. Weiterhin ist das auch ein wunderbares Beispiel für eine Factory.
  12. setUserID() ORM oder nen Query Builder nutzen. Queries kannst du natürlich weiterhin selbst schreiben, aber ne weitere Abstraktion kann dir viel Arbeit abnehmen.
  13. Weiterhin solltest du die Positional parameters in der PDO ganz stark überdenken. Named parameters sind bedeutend übersichtlicher und wartbarer.
    Code:
    <?php
    $dbquery = '
    SELECT
    	'.$this->celluserid.'
    FROM
    	'.$this->usertab.'
    WHERE
    	'.$this->cellusername.' = ?
    OR
    	'.$this->cellemail.' = ?
    							';
    $dbstmt  = $this->db->prepare( $dbquery );
    $dbstmt->bindValue( 1, $this->username, PDO::PARAM_STR );
    $dbstmt->bindValue( 2, $this->emailadress, PDO::PARAM_STR );
    $dbstmt->execute();
    vs.
    Code:
    <?php
    $dbquery = '
    SELECT
    	'.$this->celluserid.'
    FROM
    	'.$this->usertab.'
    WHERE
    	'.$this->cellusername.' = :username
    OR
    	'.$this->cellemail.' = :email
    							';
    $dbstmt  = $this->db->prepare( $dbquery );
    $dbstmt->bindValue( ":username", $this->username, PDO::PARAM_STR );
    $dbstmt->bindValue( ":email", $this->emailadress, PDO::PARAM_STR );
    $dbstmt->execute();

    Warum wartbarer? Stell dir vor in einem fiktiven Query kommen nun noch drei Parameter hinzu. Bei der Positional Variante könntest du nun bei jedem Parameter neu durchzählen und diese setzen. Named parameter schmeißt du einfach hinzu, bindest sie entsprechend und fertig ist.
  14. Heredoc Strings für Queries überprüfen. Eine ordentliche IDE highlighted dir den Code entsprechend der Sprache des Heredoc-Tags:

    Code:
    <?php
    $dbquery = <<<SQL
    SELECT
    	{$this->celluserid}
    FROM
    	{$this->usertab}
    WHERE
    	{$this->cellusername} = :username
    OR
    	{$this->cellemail} = :email
    SQL;
    Und schon hättest du ordentliches Highlighting für SQL innerhalb deines PHP-Codes.
  15. :715 Sieh dir mal den Konstruktor der Exception Klasse an, besonders den Parameter $previous.
  16. setUsername(), setPassword(), setEmail(), ... Wo sind Getter? Kannst dich auch mal in Fluent Setter einlesen, womit du schönes Method Chaining betreiben kannst.
    Code:
    (new User())->setName( "foo" )
                ->setEmail( "bar" )
                ->setAge( 20 )
              //->set...( "..." )
                ->insertIntoDb();
  17. doRegister() -> Do? Nenn sie nach dem, was sie macht, ohne irgendwelche dubiosen Präfixe. AIO ist außerdem kontraproduktiv, aber dazu hab ich ja bereits oben was geschrieben. In der Methode ist weiterhin das Error Handling richtig gruselig (eben jener Indikator, dass du einfach nur deine bisherigen Kenntnisse irgendwie in Objekte verfrachtet hast).
  18. Code:
    $errorflag = 1;
    $errorRegister['usernametaken'] = 1;
    Komplett überflüssig. Wirf ne Exception, dazu sind sie da und handel diese außerhalb ab. Mit Flags in OOP zu hantieren ist wirklich nicht schön (Ausnahme natürlich bei Bit Flags/Mask u.ä.).
  19. createPasswordHash() Aha, du nutzt also $pepper als Salt. Brauchst du nicht, Finger weg. Das erledigt password_hash() und password_verify() von selbst - siehe Bcrypt dritter Absatz.
  20. generateActResToken() Warum eine Schleife? Ein User, ein Token. Nur dieser eine Token erlaubt dem User Zugriff. Doppelte Token sind irrelevant, denn nur die Kombination eMail <-> Token ist gültig.
  21. :1415 Überflüssiges switch... Gib das Format gleich korrekt in der Konstante an und zähl das ohne Verrenkungen (+ evtl. überflüssigen Bugs) drauf.
  22. doLogout() -> session_destroy()
  23. :1732 openssl_random_pseudo_bytes? Arbeitest du dort mit sensiblen Daten? rand() reicht hierbei vollkommen, ggf. gehasht (MD5, SHA1, ...) evtl. ne GUID übergeben oder oder.

Reicht für den Anfang. Wird nur besser mit mehr Erfahrung. ;)
 
kling1 schrieb:
- .class weg
- diene klasse beinhaltet 2 klassen.. sollte nur die UserAuth beimnhalten..
- schrecklicher coding style http://www.php-fig.org/psr/psr-2/ durchlesen
- versuchen nicht mehr 2 verschachtelungsebenen tief zu gehen

Schon einmal Vielen Dank für deine Antwort.

Drei Fragen habe ich zu deinem Beitrag:

1. Wieso sollte der Name der Datei nicht die .class-deklaration enthalten?
Hatte diese Art der Namensvergabe in diversen Tutorials gesehen und fand es zur Erkennung ganz nützlich.


2. Die Exceptionklasse sollte demnach also eine eigene Datei bekommen die nur diese eine Zeile beinhaltet:
PHP:
class UserAuthExceptions extends Exception{}
Ist es denn dann in Ordnung diese zusätzliche Datei in die "Hauptklasse" zu inkludieren oder sollte ich sie im Hauptscript mit einbinden?

3. Ist die Vorgabe von nicht mehr als 2 Verschachtellungsebenen auch Teil eines Standards um die Lesbarkeit zu erhöhen?
Da sehe ich erstmal ein Problem für mich. Aufgrund von Überprüfungen von Zuständen rutsche ich bei einigen Methoden automatisch in die 3. Ebene.
Nur ein kleines Beispiel (Sind Methoden dabei die noch tiefer verschachtelt sind):

PHP:
public function checkUsername(){                                        //Ebene Eins
    if(isset($this -> username)){                                            //Ebene Zwei 
	try{                                                                               //Ebene Drei
	      $dbquery = '
				SELECT 
					     * 
				FROM 
					     '.$this -> usertab.' 
			        WHERE 
					     '.$this -> cellusername.' = ?
				';
				$dbstmt = $this -> db -> prepare($dbquery);			
				$dbstmt -> bindValue(1, $this -> username,PDO::PARAM_STR);			
				$dbstmt -> execute();
				if($result = $dbstmt -> fetchall()){          //Ebene Vier  
					return FALSE;
				}else{
					return TRUE;
				}
	}
	catch (PDOException $e){
		$error = $e -> getMessage();				
		$trace = $e -> getTrace();
		$failquery = $trace[0]['args'][0];
		$line = $e -> getLine();
		throw new UserAuthExceptions('Fehler bei der Datenbankabfrage ! Fehler: "'.$error.'"; Zeile: "'.$line.'"; Query: "'.$failquery.'"');
				
			}	
    }else{
			
			throw new UserAuthExceptions ('Zum Pruefen des Benutzernamens muss ein Benutzername gesetzt werden. Methode setUsername($username) benutzen');
			
		}
}
(Sorry die Einrückung scheint hier ein wenig ausser Kontrolle zu geraten und deine Verbesserungsvorschläge wurden noch nicht auf obigen Code angewandt)

In diesem Beispiel bin ich (meines Verständnisses nach) bereits bei 4 Ebenen. Oder zähle ich dort zu viel?
Jetzt auf die Schnelle sähe ich auch keine Möglichkeit Verschachtellungsebenen einzusparen. Magst du mir da noch eine kleine Hilfestellung geben?


Zu dem anderen Punkt:

Danke dir für den Link. wenn ich das richtig verstehe ist das ein allgemeingültiger Programmierstil-Standard. Diesen werde ich mir natürlich zu Gemüte führen.



Fällt dir/euch denn sonst noch etwas zu den anderen "Bewertungspunkten" wie Sicherheit,Performance, Funktion etc. auf?
Ich werde dann versuchen eure Verbesserungsvorschläge auf die Klasse anzuwenden.

Dankeschön soweit

Gruß
Christian
Ergänzung ()

Yuuri schrieb:
Reicht für den Anfang. Wird nur besser mit mehr Erfahrung. ;)

Wow ja dein Beitrag ist harter Tobak für mich :evillol:

Ich danke dir recht herzlich, dass du dir die Zeit für diese doch recht detaillierte Ausführung genommen hast.
Ich muss auch ehrlich sagen, dass ich von einigen Begriffen und Möglichkeiten die du genannt hast (Heredoc, Fluent Setter,...) bisher leider noch nichts gehört habe. Da gibt es auf jeden Fall noch Nachholbedarf.

- Zu deiner Frage: Momentan nutze ich Notepad++. Ich tu mich schwer mich für eine IDE zu entscheiden.

-Zu 3.: Das Active Record Pattern ist eine fertige Klasse die ich implementieren kann wenn ich es richtig verstehe oder?
Würde es Sinn machen, wenn ich mir meine Klassen soweit selbst schreibe oder ist es auch als Lernzweck in Ordnung fertige Klassen zu benutzen?

- Zu 6.: Danke für den Hinweis. Ich wusste, dass password_hash sich eigenständig um das Salt kümmert und wollte damit nur noch etwas "Pfeffer" in die Suppe bringen. Also ist es ok wenn ich ein Passwort einzig und allein mit password_hash($passwort, PASSWORD_DEFAULT); hashe?

- Zu 7.: Die Eigenschaften$tokenexpiredays, $tokenresexpirehours, $tokenlength und $hashlength sind die festen Standardwerte für, zum Beispiel, den Zeitraum der auf das aktuelle Timestamp aufaddiert wird. Diese Voreinstellungen auch in die Datenbank schreiben?
Ja eventuelle Änderungen an diesen Voreinstellungen sind in diesem Fall abgesichert. Anhand der darin enthaltenden Werte wird ein Timestamp (DATETIME) in der Datenbank gespeichert anhand dessen das Ablaufdatum verglichen wird. Das alte Timestamp ist hier also auch noch gültig nachdem die Werte geändert wurden.


Dann hab ich ja erst mal einiges zu tun, zu lesen, zu verbessern, zu lesen ... ach und zu lesen ;-)

Ich schätze mal ich darf in Anbetracht der zu Verbessernden Menge die Klasse stumpf neu schreiben. Aber nicht schlimm.. ist halt so. Ist ja schliesslich zu lernzwecken gedacht. Und so lernt man halt.

Nochmal zu deinem ersten Satz:

- Wieso nicht .class? Gibt es dort wesentliche Aspekte auf die Kennzeichnung zu verzichten?
- Sollte die Exception Klasse der UserAuth also mit nur einer Zeile Code eine eigene Datei bekommen?
Und inkludiere ich diese dann in der Hauptklasse, im Hauptscript oder gar nicht?

Nochmal vielen Dank für die detaillierte Ausführung. Das war schon weitaus detaillierter als ich es mir erhofft hatte :)

Wenn irgendjemandem noch etwas auffällt dann bitte nur heraus damit.

Ich werde mich wieder melden ;-)

Gruß
Christian
 
Zuletzt bearbeitet:
Die Klasse ist vor allem eines: zu groß. MMn sollte eine Klasse nach Möglichkeit nicht mehr als 200-300 Zeilen enthalten. Wie Yuuri schon angemerkt hat, bedient die Klasse zu viele verschiedene Funktionen. Ein bekanntes Prinzip der OOP ist das sog. Single Responsibility Principle, das besagt, das jede Klasse nur genau eine Verantwortlichkeit abdecken soll - und "Alles, was irgendwie mit Auth zu tun hat" ist halt keine sauber definierte Verantwortlichkeit :) Ich würde also empfehlen, als erstes zu checken, welche Verantwortlichkeiten es gibt, und danach die Klasse aufsplitten.

Auch einige deiner Methoden sind extrem lang, z.B. switchandsetAttribute (~ 270 Zeilen), setSessionLoggedIn (~130 Zeilen) usw. Meine Faustregel hier ist: 10 Zeilen für eine Methode sind super, 20 sind ok, 30 schon viel, und sobald ich sie nicht mehr ohne Scrollen überblicken kann, ist sie definitiv zu lang. Um dem entgegenzuwirken, kannst du lange Methoden in mehrere kürzere aufteilen. Gib ihnen sprechende Namen, und du bekommst wesentlich verständlicheren Code.

In setSessionLoggedIn() könntest du z.B. die Berechnung von $hashexpiredate in eine eigene Methode auslagern, ebenso die Abfrage des Session-Hashes und das Updaten / Einfügen der neuen Session-Daten. Alle diese Methoden zusammen könntest du dann in eine eigene Klasse verschieben, und schon wäre ein erster Schritt zur Modularisierung deines Codes getan.

Ein Teil der Länge liegt auch daran, dass du es mMn bei den SQL-Abfragen mit dem Umbrechen übertreibst. Es muss nicht jeder Spaltenname, jeder Platzhalter in eine eigene Zeile.

Die Methode switchandsetAttribute() ist ein Fall, wo immer wieder der gleiche Code steht, nur mit anderen Attributnamen und mal is_int statt is_string. Das könntest du erheblich verkürzen. Ich würde z.B. ein Array mit allen Attributnamen und -typen anlegen:

PHP:
    private $attributes = [
        'UNameUserTable' => ['fieldName' => 'usertab', 'type' => 'string'],
        'UNameCellUserId' => ['fieldName' => 'celluserid', 'type' => 'string'],
        // usw.
    ];

Statt dieses riesigen switch-case-Blocks könntest du dann schreiben:

PHP:
    if (!isset($this->attributes[$att])) {
        return '- "'.$att.'" ist keine gueltige Eigenschaft';
    }
    $attribute = $this->attributes[$att];
    $typeCheck = 'is_' . $attribute['type'];
    if (!$typeCheck($value)) {  // ja, das geht. bisschen hacky, aber hier mMn vertretbar :)
        return '- Wert "'.$att.'" muss ein '.ucfirst($attribute['type']).' sein';
    }
    $fieldName = $attribute['fieldName'];
    $this->$fieldName = $value;

Das ist ein Verfahren, das nicht nur in diesem konkreten Fall funktioniert, sondern allgemeingültig ist: Vermeide es, viele Beinahe-Kopien desselben Codes zu haben; stattdessen zieh den gemeinsamen Code heraus und mach die Teile, die verschieden sind, zu Parametern.

Hier würde ich allerdings in Frage stellen, ob es überhaupt sinnvoll ist, eine Methode wie setAttribute() zu haben. Ich rate davon ab, da sie mE mehrere Nachteile hat. Zum einen würde ich generell nicht alle Membervariablen einer Klasse von außen beschreibbar machen, sondern nur die, bei denen es tatsächlich sinnvoll ist (wann ändern sich im laufenden Betrieb schon die Namen von Datenbankspalten?). Zum zweiten hat diese Implementation den Nachteil, dass du beim Aufruf wissen musst, wie die Attribute genau heißen. Bei so vielen Attributen und bei Namen wie 'PwResetTokenExpireHours' und 'ActivationTokenExpireDays' vertippt oder vertut man sich leicht: War es jetzt "Pw" oder "Password"? "Days" oder "Hours"? Wenn du tatsächlich Setter haben willst, solltest du sie mMn für jede Membervariable einzeln schreiben. Dann kann dir deine IDE nämlich helfen, den richtigen zu finden, und dir Tippfehler anstreichen.

Der Empfehlung, eine vollwertige IDE zu benutzen, kann ich mich nur anschließen, auch der Ansicht, dass nichts über PhpStorm geht. Das kostet zwar Geld, aber vielleicht kommst du ja für eine kostenlose Studentenlizenz in Frage. Ich kenne jedenfalls keinen professionellen PHP-Entwickler, der etwas anderes benutzt (außer den paar Freaks, die mit selbst konfigurierten Vim/Emacs unterwegs sind :D). Es ist einfach super mächtig, zeigt dir viele potentielle Fehler auf, hilft dir beim Refactoring und kann sogar deinen Code nach PSR-2 formatieren :)

Die Konvention, PHP-Dateien, die Klassen enthalten, mit .class.php zu benennen, kommt noch aus der Zeit von PHP 4, als Klassen noch nicht so weit verbreitet waren. Heutzutage ist es normal, dass alle PHP-Dateien Klassen enthalten, außer vllt. der index.php, die quasi den "Bootloader" für die eigentliche Applikation darstellt. Deswegen benutzt man heute .class.php normalerweise nicht mehr.

password_hash($passwort, PASSWORD_DEFAULT); reicht vollkommen aus. Damit sind deine Passwörter schon wesentlich sicherer als vermutlich in so manchem Großunternehmen :D

Bei deinem Beispiel zur Verschachtelungstiefe gibt es einen einfachen, aber wirkungsvollen Trick, mit dem du dir eine Ebene sparen kannst. Da der else-Block ja nur die Exception wirft, kannst du die if-Bedingung folgendermaßen umdrehen:

PHP:
if(!isset($this -> username)) { // wir behandeln zuerst den Ausnahmefall
    throw new UserAuthExceptions ('Zum Pruefen des Benutzernamens muss ein Benutzername gesetzt werden. Methode setUsername($username) benutzen');
}
// wenn das if zugeschlagen hat, kommen wir hier gar nicht mehr an, brauchen also kein else und damit auch keine neue Verschachtelungsebene
try{                                                     
    // ...
}
catch (PDOException $e){
    // ...
}

Das gleiche könntest du noch an einigen anderen Stellen machen.

Du benutzt häufig 0/1 als Wahrheitswerte statt true/false. Technisch geht das natürlich, aber empfehlen würde ich trotzdem letzteres - es macht den Code noch verständlicher. (Nebenbei bemerkt: In PHP schreibt man true, false und null standardmäßig klein.)

Inkludieren musst du gar nichts, wenn du wie von Yuuri empfohlen mit Autoloading arbeitest. Das musst du nur einmal einrichten, und dann hast du überall alle Klassen zur Verfügung. Sobald du mal Code von Anderen einbinden willst, wirst du Composer benutzen wollen; dieses (extrem nützliche und wichtige) Tool kann dir auch einen Autoloader für deine eigenen Klassen bauen. Vorher solltest du dich mit Namespaces vertraut machen (solltest du aber sowieso tun).

Mit welcher PHP-Version arbeitest du? Die aktuelle ist 7.1. Falls du 5.6 oder gar eine noch ältere hast, würde ich dir empfehlen, auf 7 zu wechseln, da hier eindeutig die Zukunft von PHP liegt.

Falls du dich noch weiter in die Webentwicklung mit PHP vertiefen willst, würde ich dir mittelfristig Symfony ans Herz legen. Das ist ein Framework, das dir viele Dinge schon mitbringt, die du brauchst, und dir auch dabei hilft, deinen Code zu strukturieren. Klar kann es spannend und manchmal sogar lehrreich sein, alles von Grund auf selbst zu schreiben, aber den praktischen Nutzen halte ich doch für begrenzt, da heute die meisten realen PHP-Anwendungen auf einem solchen Framework aufbauen. Symfony mag auf den ersten Blick schrecklich komplex aussehen, und es hat so einige Features, an die man sich als Anfänger erst einmal gewöhnen muss - Dependency Injection zum Beispiel. Aber es lohnt sich mMn, sich damit anzufreunden, denn diese Features sind keine Eigenarten dieses Frameworks, sondern weit verbreitete Standardpraktiken. Falls du mal beruflich PHP entwickeln willst, dann sind Symfony-Kenntnisse auf jeden Fall ein Vorteil.

Einige der Empfehlungen des werten Kollegen Yuuri möchte ich auch noch kommentieren:

- Upper camel case für Membervariablen kenne ich persönlich nur aus dem Microsoft-/.NET-Umfeld. Ist extrem Geschmackssache, ich z.B. finde es gar nicht schön. In PHP ist lowerCamelCase der Standard.

- Alles private zu machen ist super! Vererbung ist wohl der - gerade bei Anfängern - am meisten überbewertete Aspekt der OOP. Versuch so weit wie sinnvoll ohne Vererbung auszukommen. Faustregel: Was private ist, kannst du gefahrlos ändern, ohne dass außerhalb der Klasse etwas kaputtgehen kann. Bei public und protected ist das nicht der Fall.

- Von Active Record möchte ich dringend abraten. Das Pattern ist mMn eine "Noobfalle", das guten Programmierprinzipien (Trennung von Geschäfts- und Persistenzlogik) total im Weg steht. Bei einfachen Programmen merkt man das noch nicht so, aber wenn es komplexer wird und man mit Unit Testing anfangen will, bringt Active Record einen in Teufels Küche. Wenn du dein SQL nicht mehr selber schreiben willst, schau dir Doctrine an - das ist auch bei Symfony standardmäßig dabei.

- Heredocs sind nice, ich möchte aber noch eine weitere Möglichkeit in den Raum werfen, Strings mit Parametern zu versehen: sprintf(). Das führt mMn in einigen Fällen zu noch besser lesbarem Code.

In diesem Fall würde ich persönlich sogar noch einen Schritt weiter gehen und die Namen der Datenbanktabellen und -spalten hart in die Queries hineinschreiben, ohne den Umweg über Membervariablen. Dadurch wird das Schreiben und Lesen der Queries vereinfacht, und der Code wird durch das Wegfallen der Membervariablen auch ein gutes Stück kürzer. Außerdem kann eine gute IDE (PhpStorm auf jeden Fall) auch deine Datenbank einbinden und dann die Tabellen- und Spaltennamen checken und highlighten.

Das soll es für heute abend gewesen sein :)
 
Zuletzt bearbeitet:
HighTec schrieb:
- Zu deiner Frage: Momentan nutze ich Notepad++. Ich tu mich schwer mich für eine IDE zu entscheiden.
Probier sie einfach durch, sie erleichtern dir immens die Arbeit und warten mit Features wie u.a. Debugging auf (einfach mal in die Featurelisten gucken). Wenn dir Eclipse oder NetBeans reichen, kannst du ja dabei bleiben. Wenn du allerdings gern mal mehr ausprobieren willst, nimmst du mal ein paar Monate 8,90 € in die Hand und probierst mal PhpStorm aus. Ist wie die eierlegende Wollmilchsau. Testversion gibts natürlich auch, aber vielleicht kommst du mit den Gratislösungen gut hin (für den Anfang sowieso).
HighTec schrieb:
-Zu 3.: Das Active Record Pattern ist eine fertige Klasse die ich implementieren kann wenn ich es richtig verstehe oder?
Nein, Active Record beschreibt im Prinzip, dass ein/dein konkretes Objekt einer Zeile in der DB entspricht, mit welchem du direkt agierst. Du musst also aktiv deine Daten ändern und diese zurück in die DB schreiben.

Mal schematisch:
Code:
<?php

for( $i = 0; $i < 100; ++$i )
{
  (new User())->setUsername( "User #$i" )
              ->setMail( "mail$i@example.org" )
              ->setPassword( "abc$i" )
              ->insert();
}

Jedes Objekt besitzt hier die entsprechenden Variablen und Methoden der Elternklasse, damit die Kommunikation mit der DB klappt.

Mehr (bspw.): http://www.phpactiverecord.org/projects/main/wiki/Basic_CRUD

Data Mapper arbeitet prinzipiell mit leichtgewichtigen Objekten (quasi 1:1 nur die Variablen für die jeweiligen Spalten in der Tabelle) und mit einem Repository, welches sich im Nachhinein um Inserts/Updates/Deletes/etc. kümmert.
Code:
<?php

for( $i = 0; $i < 100; ++$i )
{
  $user = new User();
  $user->setUsername( "User #$i" )
       ->setMail( "mail$i@example.org" )
       ->setPassword( "abc$i" );
  $entityManager->persist( $user ); // signalisiert eine Änderung des Objekts
}

$entityManager->flush(); // schreibt die Daten in die DB

U.a. ein großer Vorteil vom Data Mapper ist, dass alles automatisch transaktionsbasiert in die DB geschrieben werden kann (nicht muss). Beim AR musst du dich ggf. selbst um die Transaktionen kümmern.

Mehr: http://designpatternsphp.readthedocs.io/en/latest/Structural/DataMapper/README.html

Hier noch ein Vergleich: http://culttt.com/2014/06/18/whats-difference-active-record-data-mapper/
tl;dr: Beide sind zu gebrauchen, keins ist falsch. Nimm das was du ggf. für besser handhabbar hälst oder was für das Projekt besser ist.
HighTec schrieb:
Würde es Sinn machen, wenn ich mir meine Klassen soweit selbst schreibe oder ist es auch als Lernzweck in Ordnung fertige Klassen zu benutzen?
Kommt drauf an. Wenn du schnelle Ergebnisse und das Projekt umsetzen willst, nimm fertige Libs. Libs sind einfach durchgetestet und bieten eigentlich kaum grobe Fallstricke. Wenn du natürlich wissen willst, wie was funktioniert, bau dir selbst was. Bringt ja nichts nur mit Libs zu hantieren und du fragst dich tagtäglich, wie die schwarze Magie überhaupt funktioniert.

Das AR-Pattern ist ziemlich leicht nachzubauen. Prinzipiell gibts ne Model-Klasse, welche eine generische Tabelle darstellt. Hier kommen dann die Methoden insert(), update(), delete(), fetch() usw. rein, je nach SQL Operationen und je nachdem wie du es handhaben willst (fetch() bspw. implizit im Konstruktor oder nur explizit?). Für jede Tabelle in der DB erbst du dann einfach von dieser generischen Klasse und fügst dann jeweils eine Instanzvariable für jede Spalte hinzu. insert(), update() usw. müssen dann natürlich mit der Datenbank-Klasse interagieren und die Daten, die die Model-Klasse selbst hält an die DB-Klasse weiterreichen und diese passt sie in der Datenbank an.
HighTec schrieb:
- Zu 6.: Danke für den Hinweis. Ich wusste, dass password_hash sich eigenständig um das Salt kümmert und wollte damit nur noch etwas "Pfeffer" in die Suppe bringen. Also ist es ok wenn ich ein Passwort einzig und allein mit password_hash($passwort, PASSWORD_DEFAULT); hashe?
Du kannst an Eingabedaten natürlich hashen was du willst, aber es ist nicht nötig, weil password_hash() bereits automatisch einen Salt hinzufügt. So sparst du dir u.a. auch den Pfeffer ebenso irgendwo zu speichern und ständig mitzuführen. Aber wie gesagt: Nicht irgendwo statisch hinterlegen, sondern immer dynamisch zum Einsatz speichern.

Ein Salt wurde ja eingeführt, damit Rainbow Tables keinen Angriffsvektor mehr darstellen. Ohne Salt gibts bspw. bereits fixe Rainbow Tables für eine gewisse Anzahl an Eingaben. Nimmst du jetzt einen fixen Salt, kann mit ausreichend Rechenpower eine Rainbow Table anhand dieses Salts generiert und somit wieder geknackt werden. Nimmst du jetzt aber für jede Eingabe einen unterschiedlichen Salt, musst für jeden einzigartigen Salt eine Rainbow Table erstellt werden. Und so viel Rechenpower hat momentan keiner zur Verfügung stehen.
HighTec schrieb:
- Zu 7.: Die Eigenschaften$tokenexpiredays, $tokenresexpirehours, $tokenlength und $hashlength sind die festen Standardwerte für, zum Beispiel, den Zeitraum der auf das aktuelle Timestamp aufaddiert wird. Diese Voreinstellungen auch in die Datenbank schreiben?
Ja eventuelle Änderungen an diesen Voreinstellungen sind in diesem Fall abgesichert. Anhand der darin enthaltenden Werte wird ein Timestamp (DATETIME) in der Datenbank gespeichert anhand dessen das Ablaufdatum verglichen wird. Das alte Timestamp ist hier also auch noch gültig nachdem die Werte geändert wurden.
Solche Sachen kommen eigentlich eher in Methoden als Standardwerte. createToken( $expireDays = 10, $expiresHours = 1 ) wäre der bessere Ansatz, da du so Abhängigkeiten dynamisch zur Funktion geben kannst, denn prinzipiell ist die Erstellung eines Tokens ja nicht ans Objekt selbst gekoppelt, sondern eben eine Funktion/Operation basierend auf dem Objekt. Morgen kannst du bspw. eine Klasse dafür schreiben, welche mehr Möglichkeiten gibt und du musst nur diese Methode anpassen, statt die Klasse im Gesamten und all dessen Vorkommen in jeder einzelnen Methode. Übermorgen erstellst du dir dann eine eigene Token Klasse, welche sich ums Management davon kümmert usw. ;)

Aber: Lern erstmal. Du kannst dir bspw. mal Projekte wie Symfony ansehen. Dort kann man sehr sehr viel lernen, wie man Sachen am besten umsetzen kann. Frag dich einfach mal, warum etwas gemacht wurde, wie es gemacht wurde. Wenn du nach Design Patterns suchst, gibts auch etliche Seiten, die dir erklären warum etwas so gemacht wird und was dessen Vorteile sind. Es sind einfach Best Practices in Designentscheidungen.

Dependency Injection steht z.B. ganz oben in der Liste, da du dich so von Abhängigkeiten lösen kannst und nicht Klassen zueinander aufbaust und Spaghetticode fabrizierst.

Eins vorweg: Du machst es bereits in deiner Klasse. Du übergibst die Datenbank-Verbindung direkt im Konstruktur der Klasse. Somit ist die Klasse nicht auf eine Datenbankverbindung fixiert, sondern du kannst hierbei die Verbindung einfach austauschen (wäre mit nem Setter natürlich noch dynamischer, aber für den Anfang reichts). Wenn du nun einen User in zwei getrennten Systemen registrieren willst, könntest du theoretisch einfach die DB-Verbindung austauschen und insert() nochmal aufrufen. Schon hättest du einen User ganz einfach in zwei Systemen. Oder du könntest für Tests einfach die DB tauschen oder oder oder ...
HighTec schrieb:
Ich schätze mal ich darf in Anbetracht der zu Verbessernden Menge die Klasse stumpf neu schreiben. Aber nicht schlimm.. ist halt so. Ist ja schliesslich zu lernzwecken gedacht. Und so lernt man halt.
Rom wurde auch nicht an einem Tag erbaut. Vieles kommt mit der Erfahrung und wenn du fleißig übst, wird das schon. Rotz nicht einfach irgendwas in den Editor, sondern überleg vorher einfach mal kurz, wie man was am besten lösen kann (u.a. durch Anwendung von Design Patterns). So schreibt man irgendwann einfach wartbaren und bugfreieren Code.
 
Hey hier melde ich mich mal wieder.

Noch einmal Danke für eure ausführlichen Beiträge ( Ich hab mir eure Posts nun mal ausgedruckt damit ich mir das ganze nochmal in Ruhe genau durchlesen kann :D )

Ich habe mir heute mal Netbeans heruntergeladen und muss nun erstmal die Software einstellen und mich darin zurechtfinden. Grade diese Projektstrukturfunktion ist für mich noch ein kleines Hindernis, aber das wird schon.
Zu aller erst werde ich mir erst mal die Syntaxhighlightings einstellen und dann mal schauen.
Bisher erscheint mir die Software noch unvollständig nach dem ersten herumprobieren.

Beispiel:
bei der Eingabe von "is" schlägt Netbeans mir direkt die Funktion isset() vor. Soweit so gut.
schreib ich weiter "is_i" schlägt es mir nix mehr vor und auch der komplette Befehl "is_int()" wird nicht hervorgehoben. Irgendwie muss ich das der Software alles noch beibrigen.
Hat jemand von euch Erfahrung mit Netbeans und weiss woran das liegt?

Wie Handhabt ihr selbst es denn mit dem Highlighting? Bei Notepad++ setzte ich auf deftige Farben für Identifier, Strings und Variablen. Schätze mal das werde ich wieder so machen.

Ob ich dann auf Active Record oder auf ein Data Mapper setze muss ich noch schauen, was mir dort besser gefällt.

Das Umbrechen der SQL-Querys habe ich mir erst jüngst so angewöhnt, weil mich die elendig langen SQL-Querys aus der prozeduralen Programmieren auf den S*** gingen ;)
Muss mir da noch den richtigen Stil raus filtern.

Mit einem Autoloader arbeite ich natürlich bereits. Dieser sieht momentan bei mir folgendermaßen aus:

PHP:
function __autoload ($class) {  
	if (strpos ($class, '.') !== false || strpos ($class, '/') !== false
		|| strpos ($class, '\\') !== false || strpos ($class, ':') !== false) {
		return;
	}
	if (file_exists ('../files/class/'.$class.'.class.php')) {
		include_once '../files/class/'.$class.'.class.php';
	}else{
		die ('Klasse '.$class.' nicht gefunden');
	}
}
Ist daran auch noch etwas zu verbessern?

Ich habe schon mal ein wenig nach Composer gesucht, bin mir aber nicht wirklich im klaren darüber, welche Aufgabe dieser nun erfüllt....

Ich arbeite momentan auf einem VServer mit Debian 8 und Php5.6
Das Upgrade auf PHP 7.1 werde ich wahrscheinlich erst im nächsten Quartal durchführen, da sonst eine Menge händische Änderungen nötig sind um Administrationssoftware und weitere Komponenten 100% lauffähig zu halten.
Ich hab mich auch bisher noch leider etwas zu wenig mit den PHP7 Neuerungen beschäftigt. Werde ich aber nachholen. Bisher weiss ich nur, dass es wesentlich schneller läuft.


So weit so gut :-)

Gruß

Christian
 
Mit Composer kannst du Abhaengikeiten zu anderen Libs verwalten (oder spaeter auch fuer eigene Libs die du vielleicht veroeffentlichest).

Wenn du dich z.B. entschieden hast Doctrine zu verwenden (wie vorgeschlagen) dann kannst du dies einfach per Befehl zu deinem Projekt hinzufuegen.
> composer require doctrine/orm
Danach musst du noch die 'vendor/autoload.php' einbinden (require_once) und kannst auf Doctrine zugreifen. (Siehe Getting Started with Doctrine)
Wenn du dann noch einen Template Engine (wie z.B. Twig) benutzen moechtest, dann kannst diese dann einfach dazuinstallieren:
> composer require twig/twig
Mit 'composer update' kannst du diese Abhaengikeiten auch aktualisieren.
Wenn du dein Projekt auf einen anderen Server/Rechner umziehst, brauchst du den 'vendor' Ordner nicht mitkopieren (und auch nicht in Versionsverwaltung einchecken [Git/SVN etc.]) sondern kannst einfach 'composer install' alle Abhaengigkeiten wieder nachinstallieren.

Auf https://packagist.org/explore/ findet man alles moegliche an PHP Paketen die man mit Composer installieren kann.

Ich verwende gerne dunkle Themes. Monokai, Solarized Dark oder Tomorrow Night.
UserAuth.class.png
 
HighTec schrieb:
bei der Eingabe von "is" schlägt Netbeans mir direkt die Funktion isset() vor. Soweit so gut.
schreib ich weiter "is_i" schlägt es mir nix mehr vor und auch der komplette Befehl "is_int()" wird nicht hervorgehoben. Irgendwie muss ich das der Software alles noch beibrigen.
Hat jemand von euch Erfahrung mit Netbeans und weiss woran das liegt?
NetBeans hab ich zuletzt vor vielen vielen Jahren benutzt. Ich glaub irgendwann früher hab ich mal mit phpDesigner angefangen, dann NetBeans, Eclipse (PDT), Aptana, Zend Studio (war auch recht i.O.), vs.php usw. durchprobiert. Seit mittlerweile rund vier/fünf Jahren bin ich bei PhpStorm hängen geblieben. Ist quasi die einzige IDE, die mit Visual Studio vergleichbar ist und mit der ich momentan glücklich und zufrieden bin.
HighTec schrieb:
Wie Handhabt ihr selbst es denn mit dem Highlighting? Bei Notepad++ setzte ich auf deftige Farben für Identifier, Strings und Variablen. Schätze mal das werde ich wieder so machen.
colors.png

Ich hasse Dark Themes.
HighTec schrieb:
Mit einem Autoloader arbeite ich natürlich bereits. Dieser sieht momentan bei mir folgendermaßen aus:

Ist daran auch noch etwas zu verbessern?
Sieh dir mal die Doku zu __autoload an.
http://php.net/manual/en/function.autoload.php schrieb:
This feature has been DEPRECATED as of PHP 7.2.0. Relying on this feature is highly discouraged.
Und weiter:
http://php.net/manual/en/language.oop5.autoload.php schrieb:
Although the __autoload() function can also be used for autoloading classes and interfaces, it's preferred to use the spl_autoload_register() function. This is because it is a more flexible alternative (enabling for any number of autoloaders to be specified in the application, such as in third party libraries). For this reason, using __autoload() is discouraged and it may be deprecated in the future.
Weiterhin schmeiß das die() raus. spl_autoload_register() arbeitet prinzipiell wie ein Stack. Du kannst hiermit viele viele Autoloader registrieren, welche PHP dann alle durchgeht, bis es den Pfad zur Klasse aufgelöst bekommt. Falls nicht kommt dir ein Fatal Error entgegen.
 
Zurück
Oben