Hoi,

ik ben terug aan het programmeren geslagen en wou nog eens proberen een OOP member systeem maken. Ik heb daarvoor het dataMapper patern gebruikt dat WouterJ me eens had geleerd.

Kunnen jullie eens kijken of mijn structuur goed zit?



User_User

<?php

/* user class */

class User_User
{
	private $name;
	private $password;
	private $email;
	private $id;

	public function __construct($name, $password, $email)
	{
		$this->name = (string) $name;
		$this->password = (string) $password;
		$this->email = (string) $email;
	}

	public function setId($value)
	{
		$this->id = $value;
	}

	public function setName($value)
	{
		$this->name = $value;
	}

	public function setPassword($value)
	{
		$this->password = $value;
	}

	public function setEmail($value)
	{
		$this->email = $value;
	}

	public function getId()
	{
		return $this->id;
	}

	public function getName()
	{
		return $this->name;
	}

	public function getPassword()
	{
		return $this->password;
	}

	public function getEmail()
	{
		return $this->email;
	}
}


?>


User_UserMapper

<?php

class User_UserMapper
{
	protected $db; // db abstractielaag

	public function __construct(PDO $db)
	{
		$this->db = $db;
	}


	/**
     * Uit de database komt een array, populate maakt van de array een User object
     */
    public function populate(array $data)
    {
        $user = new User_User($data['name'], $data['password'], $data['email']);
        $user->setId($data['id']);

        return $user;
    }

	public function getUserById($id)
	{
		$qry = $this->db->prepare("SELECT name, password, email FROM users WHERE id = ?");

        $qry->execute(array((int) $id));
        $result = $qry->fetch(PDO::FETCH_ASSOC);
        $result['id'] = (int) $id;

        return $this->populate($result);
	}

	public function getUserByPassAndEmail($email, $password)
	{
		$qry = $this->db->prepare("SELECT id, name FROM users WHERE email = ? AND password = ?");

        $qry->execute(array($email, $password));
        $result = $qry->fetch(PDO::FETCH_ASSOC);
        $result['email'] = $email;
        $result['password'] = $password;

        return $this->populate($result);
	}

    public function saveUser(User $user)
    {

    }

    public function deleteUserById($id)
    {
        
    }
}

?>


Session_Session
<?php

class Session_Session
{
public function __construct($id)
{
$_SESSION['user_id'] = $id;
}
}

?>

Public/index.php
<?php
include('../lib/autoLoader.php');

$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$userMapper = new User_UserMapper($db);
$jasper = $userMapper->getUserByPassAndEmail('jasp.***@gmail.com', 'test123');
echo $jasper->getName(); // geeft jasper -> OK
new Session_Session($jasper->getId());
echo $_SESSION['user_id']; // geeft 1 -> OK
?>

Bedankt!
User_User is een beetje raar. Dat kan je gewoon vervangen door User en User_UserMapper door UserMapper.
Hetzelfde voor Session_Session. Als je zo wilt werken gebruik dan beter namespaces.
Raoul, het is niet raar eerder een oude versie van namespaces. Ik raad Jasper dan ook te gaan kijken naar het nieuwe namespace support in PHP, lees bijv. eens deze tutorial: http://net.tutsplus.com/tutorials/php/namespacing-in-php-2/

Verder ziet de code er goed uit, een beetje zoals ik hem ook al een paar had gemaakt. Je bent zeker enorme stappen opgeschoten sinds je laatste OO script!
Ik heb 4 tips voor je:

1. Programmeer naar een interface[sup]1[/sup]
Het is altijd goed om naar een interface te programmeren, naar iets wat je klassen groepeert, wat je zekerheid biedt welke methods je kunt gebruiken en iets waar je op kunt vertrouwen. Maak bijv. een DataMapperInterface die methods als Mapper::populate(), Mapper::create(), Mapper::read(), Mapper::update(), Mapper::delete() verplicht stelt. En zo ook voor alle andere soorten.

2. Zie klassen meer als components dan als deel van een applicatie
Je ziet de klasse nog net iets te veel als deel van een applicatie. Je moet klassen zien als componenten die je overal kunt gebruiken. Vervolgens plaats je al deze componenten bij elkaar en maak je een klasse of een stukje flat PHP om al deze componenten om te zetten in een applicatie.

Dat zie je overal gebeuren in de OO wereld: Zend Framework bestaat uit 48 modules die los te gebruiken zijn en 1 module + wat flat PHP die deze vervolgens allemaal in elkaar zet en er een framework van maakt. Symfony werkt net zo.

Bij jou is dit nog niet zo. De sessie opsla klasse is nu alleen voor het opslaan van een user_id sessie, wat een beetje beperkt is. Ik zou sowieso de key variabel maken en misschien zou ik wel willen gaan voor een soort SessionMapper met de CRUD functies.

3. Maak gebruik van je setters/getters
Je setters moeten de enige methoden zijn die aan de properties mogen komen. Zij moeten er voor zorgen dat je de juiste waarde opslaat in de properties. Gebruik de setters en getters ook in een klasse, bijv. in de User constructor:
<?php
class User
{
// ...

public function __construct($name, $password)
{
$this->setName($name);
$this->setName($password);
}

// ...
}
?>

4. Gebruik PHPdocs
Je code wil je zo duidelijk mogelijk maken, gebruik PHPdocs om aan te geven wat er allemaal in de functie moet gebeuren, bijv:
<?php
/**
* A class that can handle sessions.
*
* @author Wouter J <http://wouterj.nl>
*/
class SessionStorage
{
/**
* Edits a session.
*
* @param string $id The session name
* @param mixed $value The new value of the session
*
* @throws \InvalidArgumentException When the session did not exists
*
* @return string The new session
*/
public function update($id, $value)
{
// ...
}
}
?>
[hr]
Nog een wat kleiner tipje: Ik zou wat precies zijn in je variabele. $value zegt niet heel veel, gebruik welke value dat is. Bijv niet setName($value) maar setName($name).

[sup]1)[/sup] Met een interface wordt vaak een echt Interface bedoelt, het kan echter ook voorkomen dat er een abstracte klasse voor de 'superklasse' functie zorgt.
Wouter, ontzettend bedankt voor de tips! Als ik je ooit eens tegen kom heb je een pintje van mij te goed. ;-)

Ik ga aan de slag. Die namespaces is inderdaad nog wel een belangrijk punt bij mij want User_User staat inderdaad niet.

bedankt!

[edit]
Je bedoelt dus met de CRUD-functions dat ik mijn sessie ook zou moeten kunnen wijzigen, lezen, maken, ... ? En eventueel ook koppel met de database (door middel van de mapper dan)?
[/edit]
Hoi,

ik denk dat ik de namespaces nu wel goed gebruik. Ik heb ook geprobeerd een mapperInterface toe te voegen maar ik denk dat deze nog niet echt klopt...

<?php

namespace lib;

interface MapperInterface
{
// protected $db; // db abstractielaag

public function __construct(\PDO $db);


public function populate(array $data);

public function save();

// public function delete();
}

?>

De usermapper implementeert deze dan.
Ik heb ook geprobeerd PHPdocumentor te installeren maar dat is niet gelukt en ik snap ook nog niet 100% hoe het wel moet. De .php installer werkt niet op mijn pc.
Ik raad je aan de PSR-0 standaards voor namespaces te gebruiken. Die zegt dat namespaces er zou uit horen te zien:

namespace <app name/prefix>/<part>;

Ik prefix bijv. alles met Wj. Je krijgt dan bijv.:

namespace Wj\AdminBundle;

Vervolgens kan dit nog wat verder oplopen tot bijv. Wj\AdminBundle\Controller.

In jouw geval kan je bijv. dit doen:

namespace Jds\User;

De MapperInterface moet je ook weer in een namespace verder doen. Ik zou je mappen structuur zo maken:

src/ (of lib)
    Jds/
        User/
            User.php
            UserMapper.php
        Mapper/
            MapperInterface.php
        Storage/
            StorageInterface.php
            SessionStorage.php
            DataBaseStorage.php
            ...

[hr]
Je interface ziet er gewoon goed uit, waarom zou hij niet werken?
[hr]
Je hoeft niet eens PHPdocumentor the installeren, ik gebruik dat programma bijna nooit. Ik bedoel dat je de manier van documenteren, wat de standaard is in PHP, moet gaan gebruiken. Dus met annotations (@author, @return, @param, @version, @since, @throws, ...).
[hr]
Je bedoelt dus met de CRUD-functions dat ik mijn sessie ook zou moeten kunnen wijzigen, lezen, maken, ... ? En eventueel ook koppel met de database (door middel van de mapper dan)?

Ik zou allemaal storage klassen maken die je kunt gebruiken. Bijv: https://gist.github.com/4016844 (kijk naar de bestandsnaam om te zien in welke map ze staan).
Zo we zijn weer wat verder.

Wat is gelukt:
- Namespaces (PSR-0 methode) gebruiken
- Commentaar toevoegen op de PHPdoc methode
- MapperInterface maken

Wat mij niet lukt:
- De juiste objecten vinden (niet voor de hand liggende objecten)

Waar ik graag meer info over wil:
- Waarom moet classX een interface hebben en ClassY niet
- Waarom moet ClassX een parent hebben en ClassY niet

Stel nu dat ik een systeem maak met een auto een bestuurder en een copiloot dan begrijp ik dat er een klasse Auto is met als parent voertuig en een klasse bestuurder en copiloot die een child zijn van de klasse persoon.

Wat ik moeilijker vind is om dit te doen met bijvoorbeeld een Sessie of een database.

Graag zou ik dus samen met jullie bekijken welke Classen / interfaces / ... er allemaal nodig zijn in een login-systeem zonder speciale rechten of users. Ik wil dit eerst en vooral graag doen zonder code zodat ik de logica snap want daar ben ik soms nog niet helemaal met mee.

Een voorbeeldje van WouterJ dat ik bijvoorbeeld niet snap. Waarom moet er voor een Sessie nog een Storage class gemaakt worden? Ik begrijp dat een sessie inderdaad iets "bij houd" en dat doet de Storage class ook maar dan is de Mapper toch ook een storage want een database doet dat ook?

Dit is wat ik op deze moment zou doen:


Alvast bedankt voor de vele hulp!
Session class maakt de sessie aan.
SessionStorage bewaart de sessie.

Je zou er eventueel een interface aan kunnen hangen voor de CRUD methodes.
Wat je nu hebt in dat uml diagram ziet er goed uit en is gewoon goed OO.

Je kan echter nog een stapje verder en dat zou ik ook doen, het hoeft niet ik zou het doen.

Je hebt nu namelijk nog 2 zwakke plekken:
1) elke klasse heeft in principe een parent, nu heeft Session dat niet;
2) als je nu MySQLi ipv PDO wilt gebruiken heb je een probleem, aangezien je dan alle mappers moet aanpassen.

Beide zijn in 1 klapt te vangen:
Het zijn allebei manieren om iets op te slaan, de sessie in een sessie en bij een database in een database. Waarom groeperen we ze niet onder een StorageInterface?
Die bevat dan de basis methoden om er zeker van te zijn dat je die kan gebruiken. Ik stel voor de CRUD methods te pakken.

Deze Storage klassen zijn dan een database abstractie layer, zoals we dat zo mooi noemen. Ze zijn de laag die de database voorstelt. Die kunnen we dan gebruiken in de Mapper en op andere plaatsen waar we iets willen opslaan. De Mapper is een klasse voor communicatie tussen het object en een database. Wat die database is maakt die mapper niks uit, als hij maar ergens wordt opgeslagen. Daar hebben we dus een speciaal object voor gemaakt. Je UML wordt dus:


      +------------------+                          +------------+
      |   <<interface>>  |                          | UserMapper |
      | StorageInterface |                          +------------+
      +------------------+                          | ...        |
      | create(obj)      |        HEEFT_EEN         |            |
      | read(obj)        |  --------------------->  |            |
      | update(obj)      |                          +------------+
      | delete(obj)      |
      +------------------+
        ^               ^
 IS_EEN |"              | IS_EEN
+----------------+   +-----------------+
| SessionStorage |   | DatabaseStorage |
+----------------+   +-----------------+
| create(obj)    |   | create(obj)     |
| read(obj)      |   | read(obj)       |
| update(obj)    |   | update(obj)     |
| delete(obj)    |   | delete(obj)     |
+----------------+   | connect(...)    |
                     +-----------------+
Voor het opslaan van een session heb ik een session en sessionMapper klasse.

Ik het het zo gemaakt dat ik gewoon het object in de database opsla, dit doe ik met "serialize". Verder maak ik daarbij een cookie die voor het inloggen bijvoorbeeld "user" heet aan met een "hash". Deze "hash" staat ook weer opgeslagen in de database en op deze manier kan ik dus het user object ophalen. Maar zou ik andere dingen willen opslaan geen probleem, ik geef een andere naam mee aan de klasse en klaar.

Opslaan van een session:

$session = new Session( 'user', $userOBJECT );
$sessionMapper->save( $session );


Ophalen van een session:

$loginData = $sessionMapper->getSession( 'user', $requestOBJECT );
@Wouter, oké goed ik ben weer mee! Mijn UML ziet er nu zo uit:


Als ik dan toch even inga op de code. Mijn UserMapper of andere Mappers mogen nu geen PDO "commando's" meer gebruiken nu hé? Anders gaan we ons doel voorbij. Ik veronderstel dat de PDO "commando's" nu in de DatabaseStorage moeten en dat we die classe dan aanspreken in de Mappers zodat als we overschakelen op Mysqli dan we alleen de DatabaseStorage moeten aanpassen klopt?

Hoe zit dat nu bij de SessionStorage? Deze Class vervangt mijn class Session niet? Hoe zit dat nu concreet hoe moet ik de methode create bezien? Kan ik een volledig User Object in een Sessie steken? Nee dit moet zeker een int / string ofzo worden waarmee ik de User terug kan ophalen?

Dan nog even terugkomend op de UserMapper, je pijl loopt van de interface naar de Mapper, kan het zijn dat dat omgekeerd moet of interpreteer ik dat verkeerd?
We gaan door dat te doen afdwingen dat de UserMapper ook die methodes heeft?

@Tom, ik snap je manier van werken maar ik denk dat de methode van wouter abstracter is en dus dichter tegen het doel van OOP programmeren aanleunt dat jouw methode of zie ik dat verkeerd? Alleszins bedankt voor je inbreng, het deel van het opslaan in de database kan ik waarschijnlijk ook nog verwerken in wouter zijn manier. Dat is een kwestie van hoe je de functies vult denk ik...

Reageren