borisbaer: Generierung von Properties zur Handhabung von POST-Daten

Hallo zusammen,

ich habe nun bereits einige Möglichkeiten gesehen, wie man zwecks der Erstellung einer Sign-up- bzw. Login-Funktion mit den POST-Daten des jeweiligen Formulars umgeht:

  1. Properties in der Model-Klasse setzen à la
public string $username = '';
public string $email = '';
public string $password = '';

Dann in der Controller-Klasse die Werte zuordnen:

$user -> username = $_POST['username'];
$user -> email = $_POST['email'];
$user -> password = password_hash( $_POST['password'], PASSWORD_DEFAULT );
  1. Eine Funktion in der Model-Klasse haben, welche die Daten bei Bedarf lädt, z.B.
public function loadData( array $data ): void
{
	foreach ( $data as $key => $value )
		if ( property_exists( $this, $key ) )
			$this -> { $key } = $value;
}
  1. Schon bei der Instanziierung der Model-Klasse die Properties mithilfe eines Constructors definieren lassen:
$user = new User( $_POST );
public function __construct( $body )
{
	foreach ( $body as $key => $value )
		$this -> $key = $value;
	$this -> password = password_hash( $this -> password, PASSWORD_DEFAULT );
}

Nun frage ich mich, welche denn die angemessene Umsetzung darstellt. Sicherlich verursacht die letzte Methode den geringsten Code. Jedoch scheint hier Type Hinting nicht möglich zu sein, ebensowenig ein Festlegen der Sichtbarkeit (public, protected, private). Bei Letzterem verstehe ich die Sinnhaftigkeit in Bezug auf POST-Daten aber ohnehin nicht wirklich. Auch wird bei der dritten Methode einfach alles an POST-Daten übermittelt und ich kann nicht entscheiden, welchen Wert man setzt und welchen nicht. Aber in der Regel sollen ja bei solchen Formularen eh alle Werte gesetzt werden. Ihr merkt, ich tendiere zur dritten Option.

Ich wäre sehr dankbar für ein wenig Aufklärung!

Grüße
Boris 🧸

  1. Frage der Umsetzung

    password_hash() hat keine Funktion um auch nur irgendeine Komplexität des Passwortes (Länge, genutzter Zeichenvorrat, ggf. Anzahl Wörter "Pferd miaut Diesel Vogel") festzustellen und (womöglich noch nach einem Test gegen häufig genutzte Passwörter) zurück zu weisen. Wie wär's damit, sowas in Deiner Klasse zu verbauen?

    public function __construct( $body )

    Neverever. Erst mal ist $body „schwer selbstverwirrend“, denn ich denke bei dem Name und im Kontext „PHP“ eher an einen HTML-Body. Ich sehe auch nichts, was es notwendig machen könnte, die Daten als Hash/Array zu übergeben. Soweit ich das sehe sind es auch nur drei Werte: Benutzernamem, Klartextpasswort, Mailadresse - und die stehen in der Superglobalen $_POST.

    Soll heißen, Du könntest bequem, einfach und ohne jedes verwirrendes Umherkopieren

        public function __construct(
            $postUsernameKey  = 'user',
            $postPasswordKey1 = 'pass1',
            $postPasswordKey2 = 'pass2',
            $postEmailKey     = 'mail'
        )
    

    verwenden und im Idealfall (die Formularfelder tragen gleich diese Namen) ohne Parameter aufrufen. Außerdem kannst Du dann auch gleich eine Methode bauen, welche das Anmeldeformular „austut“, bei einem Konflikt (bereits verwendeter Username, zurück gewiesenes Passwort, zurück gewiesene Mailadresse) nochmals (dann ergänzt um den Hinweis und angereichert mit den Daten, welche die Prüfung bestanden haben.) aus/oder zurück gibt.

    Das Passwort brauchst Du zweimal oder willst Du diese Prüfung etwa weglassen oder an anderer Stelle durchführen? Meine Philosophie von „Objektorientierung“ wäre da eine andere.

    1. password_hash() hat keine Funktion um auch nur irgendeine Komplexität des Passwortes (Länge, genutzter Zeichenvorrat, ggf. Anzahl Wörter "Pferd miaut Diesel Vogel") festzustellen und (womöglich noch nach einem Test gegen häufig genutzte Passwörter) zurück zu weisen. Wie wär's damit, sowas in Deiner Klasse zu verbauen?

      Ja, du hast natürlich recht. Das war hier falsch gesetzt. Ich habe es nun direkt vor $user -> create().

      public function __construct( $body )

      Neverever. Erst mal ist $body „schwer selbstverwirrend“, denn ich denke bei dem Name und im Kontext „PHP“ eher an einen HTML-Body.

      Ja, ich verstehe, was du meinst. Wird halt eben vielfach als POST-body bezeichnet bzw. parsed body. Aber es kann bei anderen für Verwirrung sorgen.

      Ich sehe auch nichts, was es notwendig machen könnte, die Daten als Hash/Array zu übergeben.

      Was meinst du mit Hash/Array?

      Soweit ich das sehe sind es auch nur drei Werte: Benutzernamem, Klartextpasswort, Mailadresse - und die stehen in der Superglobalen $_POST.

      Soll heißen, Du könntest bequem, einfach und ohne jedes verwirrendes Umherkopieren

          public function __construct(
              $postUsernameKey  = 'user',
              $postPasswordKey1 = 'pass1',
              $postPasswordKey2 = 'pass2',
              $postEmailKey     = 'mail'
          )
      

      verwenden und im Idealfall (die Formularfelder tragen gleich diese Namen) ohne Parameter aufrufen.

      Ja, die Formularfelder tragen alle dieselben Namen. Aber weshalb nicht einfach:

      public function __construct( $body )
      {
      	foreach ( $body as $key => $value )
      		$this -> $key = $value;
      }
      

      Das stellt mir doch direkt alle eingegebenen POST-Daten als Properties zur Verfügung.

      Außerdem kannst Du dann auch gleich eine Methode bauen, welche das Anmeldeformular „austut“, bei einem Konflikt (bereits verwendeter Username, zurück gewiesenes Passwort, zurück gewiesene Mailadresse) nochmals (dann ergänzt um den Hinweis und angereichert mit den Daten, welche die Prüfung bestanden haben.) aus/oder zurück gibt.

      Daran habe ich die letzten Tage gearbeitet. Die grundlegende server-seitige Validierung funktioniert mittlerweile. Wenn ich damit fertig bin, widme ich mich der Javascript-Validierung.

      Das Passwort brauchst Du zweimal oder willst Du diese Prüfung etwa weglassen oder an anderer Stelle durchführen? Meine Philosophie von „Objektorientierung“ wäre da eine andere.

      Nein, confirmPassword brauche ich auch. Ging nur ums Prinzip.

      Grüße
      Boris

  2. Hallo borisbaer,

    $user -> username = $_POST['username'];
    $user -> email = $_POST['email'];
    $user -> password = password_hash( $_POST['password'], PASSWORD_DEFAULT );
    

    ist am einfachsten und klarsten. Für häufige Konvertierungen oder Prüfungen schreibe Helper-Funktionen.

    Wenn Du unbedingt ein Architekturwunder vollbringen willst, dann verwende das Reflection API, um die Properties der Klasse aufzufinden, und klebe an die Properties Attribute, um deklarativ festzulegen, aus welchem POST-Wert sie zu lesen sind und welche Plausibilitäten oder Konvertierungen laufen müssen. Auf diese Weise kannst Du einen generischen POST zu Object Mapper erstellen.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo borisbaer,

      $user -> username = $_POST['username'];
      $user -> email = $_POST['email'];
      $user -> password = password_hash( $_POST['password'], PASSWORD_DEFAULT );
      

      ist am einfachsten und klarsten. Für häufige Konvertierungen oder Prüfungen schreibe Helper-Funktionen.

      Okay, ich verstehe. Unter dem Primat der Klarheit stimmt das sicherlich.

      Wenn Du unbedingt ein Architekturwunder vollbringen willst, dann verwende das Reflection API, um die Properties der Klasse aufzufinden, und klebe an die Properties Attribute, um deklarativ festzulegen, aus welchem POST-Wert sie zu lesen sind und welche Plausibilitäten oder Konvertierungen laufen müssen. Auf diese Weise kannst Du einen generischen POST zu Object Mapper erstellen.

      Wunder werde ich sicher keine vollbringen. Ich glaube, das ist für mich noch Zukunftsmusik. Erst mal müssen die Grundlagen bei mir sitzen, bevor ich mich mit Attribute und Reflection auseinandersetzen kann. Zumal diese noch so neu sind, dass es kaum gute Tutorials dazu gibt.

      Aber danke für den Hinweis!

      Grüße
      Boris

  3. Moin mein Bärchen :),

    <mein-senf>

    1. Properties in der Model-Klasse setzen à la
    $user -> username = $_POST['username'];
    $user -> email = $_POST['email'];
    $user -> password = password_hash( $_POST['password'], PASSWORD_DEFAULT );
    

    Sowas versuche ich zu vermeiden. Es ist zwar möglich, aber es gibt ganz ganz selten den Fall wo du dich fragst "wo wird eigentlich 'username' gesetzt" (vielleicht steht ja was falsches drin). Bei dieser Umsetzung musst du nach den Stellen im Code suchen.

    Deshalb würde ich IMMER get/set verwenden um etwas in ein Objekt zu bekommen. Denn wenn du dich jetzt fragst wo wird das aufgerufen, haust du einfach ein "backtrace" in den Code und schaust nach. Eventuell willst du auch mehr machen so z.B. nicht nur das Passwort übergeben sondern innerhalb der set-Methode auch gleich das Passwort verschlüsseln. Das geht auch Problemlos. Sprich der Code ist erweiterbar.

    1. Eine Funktion in der Model-Klasse haben, welche die Daten bei Bedarf lädt, z.B.
    public function loadData( array $data ): void
    {
    	foreach ( $data as $key => $value )
    		if ( property_exists( $this, $key ) )
    			$this -> { $key } = $value;
    }
    

    Das wäre eine Ergänzung zu den set/get Methoden um den Aufruf möglichst schlank zu halten.

    1. Schon bei der Instanziierung der Model-Klasse die Properties mithilfe eines Constructors definieren lassen:
    $user = new User( $_POST );
    
    public function __construct( $body )
    {
    	foreach ( $body as $key => $value )
    		$this -> $key = $value;
    	$this -> password = password_hash( $this -> password, PASSWORD_DEFAULT );
    }
    

    Das wäre wie Version 2 nur im Konstruktor. Hier hatte ich öfters das Problem, dass bevor ich etwas im Konstruktor gesetzt habe ich noch eine Logik "vorher" haben möchte. Sprich, ein setzen vor dem Konstruktor. Deshalb bin ich kein Freund von zu großer Logik im Konstruktor. Wichtige Dinge ja, große Berechnungen auf keinen Fall. Hier tendiere ich zu einem setzen der Werte via Konstruktor (sofern diese zwingend benötigt werden) und einem Aufrufen einer Methode für die Logik z.B. init().

    Hier nochmal meine "Ideallösung" im Ganzen:

    public function setMail( $strMail )
    {
       $this->strMail = $strMail;
       return $this;
    }
    
    public function getMail()
    {
       return $this->strMail;
    }
    
    public function setData( $arData )
    {
      if( isset($arData["mail"]) )
      {
         $this->setMail( $arData["mail"] );
      }
    }
    
    public function init()
    {
    //--- mach irgendwas
    }
    

    Kann natürlich alles noch durch Typisierung erweitert werden. Da bin ich aber gar kein so großer Freund von.

    </mein-senf>

    Gruß
    T-Senf

    1. Hallo T-Rex,

      Deshalb würde ich IMMER get/set verwenden um etwas in ein Objekt zu bekommen. Denn wenn du dich jetzt fragst wo wird das aufgerufen, haust du einfach ein "backtrace" in den Code und schaust nach. Eventuell willst du auch mehr machen so z.B. nicht nur das Passwort übergeben sondern innerhalb der set-Methode auch gleich das Passwort verschlüsseln. Das geht auch Problemlos. Sprich der Code ist erweiterbar.

      ja, Getter und Setter sind vor dem Hintergrund sicher eine sehr gute Sache, blähen aber auch ein wenig die Klasse auf, nicht? Wenn jede Property ihren eigenen Getter und Setter benötigt?

      Das wäre wie Version 2 nur im Konstruktor. Hier hatte ich öfters das Problem, dass bevor ich etwas im Konstruktor gesetzt habe ich noch eine Logik "vorher" haben möchte. Sprich, ein setzen vor dem Konstruktor. Deshalb bin ich kein Freund von zu großer Logik im Konstruktor. Wichtige Dinge ja, große Berechnungen auf keinen Fall. Hier tendiere ich zu einem setzen der Werte via Konstruktor (sofern diese zwingend benötigt werden) und einem Aufrufen einer Methode für die Logik z.B. init().

      Ich passe den Code in der Regel erst dann den Bedürfnissen an, wenn sich diese auch ergeben. Ich mache es selten so, dass ich allein wegen der Möglichkeit Code „auseinanderreiße“. Wenn ich merke oder manchmal schon vorher weiß, dass ich eine Logik mehrmals benötige, dann kopple ich diese natürlich aus, um mich nicht zu wiederholen. Aber eben immer erst dann. Ansonsten tendiere ich dazu, alles an einem Platz zu haben. Ich mache mir damit sicher mehr Arbeit als nötig, aber ich persönlich bin kein großer Freund davon, den Code „aufzublähen“, um für alle zukünftigen Anforderungen gewappnet zu sein. Vielleicht ändere ich meine Meinung dazu ja.

      Hier nochmal meine "Ideallösung" im Ganzen:

      public function setMail( $strMail )
      {
         $this->strMail = $strMail;
         return $this;
      }
      
      public function getMail()
      {
         return $this->strMail;
      }
      
      public function setData( $arData )
      {
        if( isset($arData["mail"]) )
        {
           $this->setMail( $arData["mail"] );
        }
      }
      
      public function init()
      {
      //--- mach irgendwas
      }
      

      Kann natürlich alles noch durch Typisierung erweitert werden. Da bin ich aber gar kein so großer Freund von.

      Danke für den Input, T-Senf! 😉

      Grüße
      Boris

      1. Moin,
        generell ein guter Gedanke. Programmiere nur das was du weißt. So handle ich auch.

        Es gibt aber Dinge, da greift man einmal ins Messer und dann hat man einen Lerneffekt. Wenn du Eigenschaften definierst, dann verwendest du sie auch. Und das im Nachgang vom Zugriff der reinen Eigenschaft auf eine Methode um zu stellen kann unter umständen sehr schmerzhaft sein. Aber wahrscheinlich musst du einmal in dieses Messer greifen.

        Aber ja, getter und setter blähen den Code auf, da hast du einfach recht.

        Gruß
        Messer am Griff Benutzer
        T-Rex

        1. Hallo T-Rex,

          Aber ja, getter und setter blähen den Code auf, da hast du einfach recht.

          deswegen liebe ich C#.

          class RolfSeine
          {
             public string Familienname { get; set; }
             public string Vorname { get; set; }
             public string Name => Vorname + " " + Familienname;
          ...
          }
          

          Das ganze Boilerplate-Geraffel macht der Compiler. Wenn ich ein öffentlich lesbares, aber intern änderbares Property haben will, schreibe ich { get; private set; }. Wenn ich ein readonly-Property will, dass nur im Konstruktor gesetzt werden kann, schreibe ich { get; }.

          Und wenn ich dann doch entscheide, dass der Vorname eine Prüfung braucht, kann ich es jederzeit umschreiben und außerhalb der Klasse fällt es niemandem auf.

             private string _vorname;
             public string Vorname {
                get { return _vorname; }
                set {
                   if (value == "Rolf"
                      throw new ArgumentException("There Can Be Only One");
                   _vorname = value;
                }
             }
          

          Dagegen sind Java und PHP einfach nur lästig.

          Rolf

          --
          sumpsi - posui - obstruxi