OOP - Klassen Bsp.: User

02/17/2016 21:39 Masterkroko1#1
Abend Com,

ich habe ein kleines großes Problem.

Da ich hauptsächlich Prozedural Programmiere ist die OOP auf der Strecke geblieben.

Mein größtes Problem ist das ich nie weiß in welchen Klasse nun welche Methoden kommen und wann ich lieber eine neue Klasse erstellen soll.


Ich bräuchte einmal eine kleine Starthilfe von euch. Im Internet sind zwar viele Beispiele leider widersprechen sie sich gerne mal. Auch denke ich bei den vielen Beispielen das man das nicht so machen sollte / besser machen kann.


Fangen wir mal an mit meinen wirren Ideen.

Ich habe die Klasse "User"
In diese Klasse gibt es Methoden wie setId, setNickname und dementsprechend auch getId ...


Für die Datenbeschaffung aus der Datenbank gibt es eine extra Klasse.

Soweit alles gut.

Wo kommt aber nun die "Login-Verwaltung" hin? mit in die User-Klasse? oder eine eigene Klasse?
Ich selbst würde eine eigene Klasse erstellen.

Wohin kommen die Sachen hin für die Neuanlage von Usern?
checkPassword (Lang genug, genug zeichen ..)
checkNickname ...
Wieder in eine eigene Klasse?

Muss man bei der User Klasse sowas wie checkNickname vorhanden haben? Oder sollte man davon ausgehen wenn der Name in der Datenbank steht dann wird er schon in Ordnung sein.

Wie man vll. sieht ist es bei mir derzeit recht durcheinander und ich hoffe das mir hier wer weiterhelfen kann.

Mfg Masterkroko
02/17/2016 22:00 ~ JWonderpig ~#2
Wenn du nicht gerade mit einem Framework arbeitest, ist es dir eigentlich frei überlassen, wie du deine Klassen strukturieren willst. Vor deinem Problem stehen Viele, die gerade mit OOP anfangen. Das Prinzip von Gettern&Settern hast du ja anscheinend verstanden. Außerdem sei noch gesagt, dass du OOP und prozedurale Programmierung "mischen" kannst. Nun zu deinen Methoden: Deine Userklasse könnte z.B. so aussehen:

PHP Code:
class User
{
    private 
$db;
 
    function 
__construct($connection)
    {
      
$this->db $connection;
    }
 
    public function 
register(...)
    {
       
// Daten in Datenbank eintragen
    
}
 
   public function 
login($username,$password)
   {
       
// Mit Daten aus Datenbank vergleichen und ggf. die Session setzen mit z.B. der UserID
   
}
 
   public function 
hasValidLogin()
   {
      if(isset(
$_SESSION['user']))
      {
         return 
true;
      }
   }

   public function 
logout()
   {
        
session_destroy();
        unset(
$_SESSION['user']);
        return 
true;
   }


Deine index.php könnte z.B. so aussehen

PHP Code:
<?php

$connection 
= new mysqli(...);

$user = new User($connection);

if (
$user->hasValidLogin())
{
   
// User weiterleiten
}
02/17/2016 22:56 Masterkroko1#3
Ich arbeite mit keinem Framework. Die Funktionalität der Klassen hab ich eig. drauf ich weiß halt nur nicht was man wo hin schreiben muss. Wie du schon geschrieben hast typisches Anfängerproblem.

Die Kommunikation mit der Datenbank wollte ich ja wie bereits erwähnt in eigenen Klassen auslagern. Hier verwende ich das öhm "DAO Pattern".

Ich möchte eig auch soweit wie möglich nur OOP. Natürlich wird hier und da mal ein bisschen prozedurale Programmierung auftauchen jedoch möchte ich das so gut es geht vermeiden.

Du hast jetzt alles in die User-Klasse geworfen.
Diverse Foren und Beiträge sagen z.B. das die Login-Verwaltung bei dem oberen Beispiel login, haslogin, logout gesondert in eine extra Klasse kommen sollte.



Auch stellt mir die Frage mit dem "checkNickname".
Eig darf diese Methode ja nur verwendet werden wenn ich ein neuen User anlegen.
Sobald der Name in der Datenbank steht darf man den Nickname eig nicht mehr prüfen. Ich kann ja nicht auf einmal sagen määäp dein Nickname passt nicht obwohl er schon Jahrelang den Namen hat.

Da würde in meinen Augen folgendes für die User-Klasse raus kommen:

und dann eine weiter Klasse für die Login-Verwaltung? Oder doch da rein wahhhh
02/17/2016 23:51 ~ JWonderpig ~#4
Quote:
Originally Posted by Masterkroko1 View Post
Die Kommunikation mit der Datenbank wollte ich ja wie bereits erwähnt in eigenen Klassen auslagern. Hier verwende ich das öhm "DAO Pattern".
Wenn ich es richtig in Erinnerung habe, benötigst du trotzdem eine User Klasse mit Userattributen. Meine Klasse würde z.b. die UserDAO Klasse darstellen, da dort die "Userfunktionen" beinhaltet sind und mit Objekten der Klasse User arbeitet.


Quote:
Originally Posted by Masterkroko1 View Post
und dann eine weiter Klasse für die Login-Verwaltung? Oder doch da rein wahhhh
Zur Not einfach testen. Es kristallisiert sich meistens schnell heraus, ob es sinnvoll ist ;)


Quote:
Originally Posted by Masterkroko1 View Post
Auch stellt mir die Frage mit dem "checkNickname".
Eig darf diese Methode ja nur verwendet werden wenn ich ein neuen User anlegen.
Sobald der Name in der Datenbank steht darf man den Nickname eig nicht mehr prüfen. Ich kann ja nicht auf einmal sagen määäp dein Nickname passt nicht obwohl er schon Jahrelang den Namen hat.
Die Methode einfach nicht nochmal aufrufen, oder sehe ich das falsch?
02/17/2016 23:56 False#5
Meiner Meinung nach macht es kein Sinn register und login in den User zu tun !Mach dir eine Klasse namens(wobei der Name ja eh egal ist) UserController, dort hast du dann Methoden wie register, create, update, delete..
Diese Methoden verlangen dann ein User somit sagst du einfach UserController->register($user) und in der register klasse holst du dir die Daten aus dem User und erstellst ein(natürlich noch checks ob er schon existiert etc..).
Das ganze kannst du für alles machen z.b. ein ProductController mit den Methoden :
- create, update, delete, findById, findAll, findByProductName etc...


Ps. solltest du Methoden zum suchen wie z.b. findById haben erstell dir dann ein Repository wo du dir die Daten aus der Datenbank holst also z.b. UserRepository->findById($id) und die Methode gibt dir dann ein User wieder(Objekt).


Ps²: Schau dir mal Doctrine und Entitys an, ist praktisch ;)
02/17/2016 23:58 algernong#6
Jetzt nur meine Meinung, ohne Anspruch auf Richtigkeit:

Ich würde das eindeutig trennen, denn ich sehe das typische User Objekt mit Attributen wie Name, E-Mail, Passwort ... als Ergebnis vom Authentifizierungsprozess, und nicht als daran teilnehmender Akteur. Ein Benutzer authentifiziert sich schließlich nicht selber, sondern wird authentifiziert (Passiv).

Angenommen, wir modellieren ein Forum mit OOP. Dann sind Klassen wie "Post", "Topic", "Subforum", ... vielleicht sinnvoll. Jeder Post benötigt einen Autor: Ein Objekt der Klasse User. Welchen Sinn macht nun etwa folgender Aufruf, wenn logout() wie im zweiten Beitrag implementiert wird?

Quote:
Post post = ...;
post.getAuthor().logout();
Der Aufruf suggeriert, dass wir den Autor vom Beitrag abmelden. Eigentlich hat das aber jetzt wirklich überhaupt nichts mit dem Autor zu tun, sondern es meldet uns selber ab. Komische Semantik.

Mit so einem Ansatz wirft man den Authentifizerungsprozess und einen Benutzer - zwei Dinge - in eine Klasse. Und das ist nicht so gut. Eher würde ich die Klasse von JWonderpig zum Beispiel in "UserManager" oder so umbenennen und dann bei "login()" ein Objekt von User zurückgeben, mit dem dann die restliche Anwendung arbeitet (also trennen).

Edit: Ich würde dir raten, etwas mit Symfony oder so zu arbeiten. Wenn man sieht, wie richtige Frameworks OOP nutzen, macht alles auf einmal super viel Sinn. Das war zumindest meine Erfahrung.
Das ist zum Beispiel ein super Entwurf für die ganze Geschichte: [Only registered and activated users can see links. Click Here To Register...]
Überflieg das vielleicht mal ein bisschen.
02/18/2016 00:13 ~ JWonderpig ~#7
Wenn du mit Symfony den EntityManager und das ORM nutzt, kommt dein gewünschtes DAO Pattern durch Entities und Repositories zum Einsatz. Die Entities entsprechen dann den Tabellen in deiner Datenbank und die Repositories den Managerklassen, wie sie im Post über mir beschrieben wurden.
02/18/2016 09:58 Masterkroko1#8
Also würde ich dann 3 Klassen verwenden

1. Klasse nur für einen vorhanden User (id, email, nickname ...)
2. Klasse kümmert sich um den Login, Registrierung & co.
3. Klasse die nur mit der Datenbank redet (create,update,select)

PHP Code:
class User {
    private 
$iId;
    private 
$sNickname;
    private 
$sEmail;

    public function 
setId() {}
    public function 
getId() {}

    public function 
setNickname() {}
    public function 
getNickname() {}
    
    public function 
getEmail() {}
    ...

PHP Code:
class UserDao {
    private 
$rDbConn;

    public function 
__construct() {}

    public function 
getUserById() {
        
$oUser = new User();
        
// Selektiert die Daten
        
$oUser->setNickname($row['nickname'])
        return 
$oUser;
    }

    public function 
createUser() {}

    public function 
deleteUser() {}

    public function 
changePassword() {}

PHP Code:
class UserController {
    private 
$sMinPassword 5;
    private 
$sMaxPassword 25;

    public function 
checkPassword() {}
    
    public function 
login() {}
    public function 
logout() {}

    public function 
isLoggedIn() {}

Hier wird geprüft ob der User eingeloggt ist und selektiert dann die Userdaten.
PHP Code:
$oUserController = new UserController();
$oUserDao = new UserDao();

$iUserId $oUserController->isLoggedIn();
$oUser $oUserDao->getUserById($iUserId); // Vll. als 2 Parameter ein User-Objekt mitgeben falls vorhanden 
Lauf ich damit "richtig" oder sollte ich es abändern?
02/18/2016 21:06 algernong#9
Ich finde das sinnvoll, oder sehe zumindest nicht, was dagegen spraeche.
02/18/2016 23:53 Masterkroko1#10
Danke.
Meine User-Klasse hat schon die ersten Attribute & Methoden.
Fange nun mit der UserDao-Klasse an.

Hat hier vll. noch wer eine verständliche Anleitung bezüglich Fehlerbehandlung (Exception) zur Hand? Ansonsten werde ich morgen nochmal das web danach durchsuchen. Als Beispiel hätte ich diese Methode:
PHP Code:
    public function getUserById($iId 0) {
        if ( 
is_integer($iId) && $iId ) {
            
$sqlUserData "SELECT `user_id`, `username`, `email`, `right`";
            
$sqlUserData .= " FROM `users`";
            
$sqlUserData .= " WHERE `user_id` = " . (string) $iId;
            
$resUserData $this->oDbConn->query($sqlUserData);

            if ( 
$resUserData === false ) {
                throw new 
Exception(".....1");
            }

            if ( 
$resUserData->num_rows != ) {
                throw new 
Exception(".....2");
            }

            
$rowUserData $resUserData->fetch_assoc();
            
$oUser = new User();

            
$oUser->setId($rowUserData['user_id']);
            
$oUser->setUsername($rowUserData['username']);
            
$oUser->setMailAddress($rowUserData['email']);
            
$oUser->setRight($rowUserData['right']);

            return 
$oUser;
        }
        else {
            throw new 
Exception("....3");
        }
    } 
02/19/2016 00:19 algernong#11
Lieber von Exception erben und dann eine spezifischere Exception werfen. PHP bringt auch schon ein paar von Haus aus mit, siehe [Only registered and activated users can see links. Click Here To Register...]
Statt der dritten Exception könntest du zum Beispiel eine InvalidArgumentException werfen.

Oder du erbst selber von Exception, das ist für spezifische Fehler, die in deinem Code auftreten können, auch immer gut. Der Sinn dahinter ist dann, dass du auch nur spezifische Exceptions fangen kannst. Angenommen, du hast etwa eine Methode

parseXmlDocument($file) throws IOException, MalformedDocumentException, InvalidArgumentException { ... }

IOException wird geworfen, wenn die Datei nicht gelesen werden kann, MalformedDocumentException wenn die Datei kein richtiges XML enthält, InvalidArgumentException wenn null für $file übergeben wird.

Die InvalidArgumentException willst du gar nicht fangen, denn wenn die kommt, hast du einen Bug im Code. Würde die Funktion aber für jeden Fehler nur Exception werfen, könntest du nur alle Fehler oder keinen Fehler fangen. Jetzt kannst du einen catch-Block für IOExceptionen und einen für MalformedDocumentException haben, und die InvalidArgumentException einfach durchfallen lassen.

Wenn kein getUserById() keinen Benutzer findet, könntest du auch null zurück geben. Das würde ich davon abhängig machen, wie du die Methode später verwenden möchtest (dann aber auch konsistent bleiben): Rufst du die get...By...() nur auf, wenn du dir schon absolut sicher bist, dass es auch ein Ergebnis gibt? Dann wäre es irgendein Fehler, wenn es doch kein Ergebnis gibt, und eine Exception angebracht.
Wenn du die Methoden aber auch dazu verwendest möchtest, um zu prüfen, ob es ein derartiges Objekt überhaupt existiert, fände ich persönlich null besser.

Der Fall num_rows != 1 ist wieder eindeutig, wenn es mehrere Einträge mit dem selben PK gibt ist das ein Fehler und eine Exception ist angebracht.
02/19/2016 09:29 Masterkroko1#12
Das ich mit mit vererbten Exception arbeiten muss war mir bewusst.
Die oben im Beispiel angegeben Exception waren hauptsächlich da um zu zeigen wo welche zum Einsatz kommen würden.

Derzeitiger Gedanke war das die get...By...() Funktionen nur aufgerufen werden wenn der die Daten auch da sein sollten. Also im Fehlerfall mit ner exception behandeln.

Ich hab bewusst leider noch keine vererbte Exception Klasse gesehen. Deswegen weiß ich nicht genau was in so einer alles enthalten sollte / was die alles macht.
02/19/2016 13:13 algernong#13
Dann finde ich deinen gewaehlten Einsatz gut.

Eine eigene Exception muss gar nicht mehr koennen; es geht wirklich hauptsaechlich darum, den catch Block auf spezifischere Fehler beschraenken zu koennen.

Nebenbei: Bei privaten Methoden bietet es sich uebrigens auch an, die Argumente mit assert() statt mit einer Exception zu sichern.
02/19/2016 13:40 Masterkroko1#14
Wuat

Ich hätte da noch sowas wie Logging / automatische Fehlermail an admin & co reingehauen.
02/19/2016 18:38 ~ JWonderpig ~#15
Falls du Symfony nutzt, gibt es da den Monologger. Der kann auch Mails verschicken.