OOP fouten + records

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Pagina: « vorige 1 2

Wouter J

Wouter J

15/07/2012 12:13:14
Quote Anchor link
Raoul, maar die werkt niet overal goed:
Quote:
If the last SQL statement executed by the associated PDOStatement was a SELECT statement, some databases may return the number of rows returned by that statement. However, this behaviour is not guaranteed for all databases and should not be relied on for portable applications.
(bron)
 
PHP hulp

PHP hulp

24/01/2025 04:50:18
 
Roel -

Roel -

16/07/2012 00:19:25
Quote Anchor link
Daarom. Wouter, heb je verder nog antwoord op (een van) mijn vragen?
 
Erwin H

Erwin H

16/07/2012 09:27:13
Quote Anchor link
Heb je wel de errormode van PDO gezet? Goede aanvulling van Wouter op mijn verhaal over exception handling:
Wouter J op 04/07/2012 11:20:15:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
<?php
$pdo
->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTIONS);
?>


Roel PHP op 14/07/2012 23:45:30:
Ten tweede, ik heb nu een abstracte mapper class gemaakt, want elke mapper moet toch bepaalde methods krijgen, anders is het geen mapper. Ik wou er eigenlijk een interface van maken

Dit zou ik beide doen. Een abstracte basis class en een interface zijn twee verschillende dingen die (deels) een ander doel dienen.

De abstracte basis class is handig om een deel van de functionaliteit te centraliseren zodat je niet hetzelfde stuk code in verschillende classes hebt. Scheelt code, scheelt werk en maakt het eenvoudiger om fouten weg te werken.

Een interface heeft als primaire doel om bepaalde functionaliteit voor te defineren. Door via een interface bepaalde methodes af te dwingen kan je op een veilige manier afhankelijkheden leggen. Omdat een class een bepaalde interface implementeert weet je dat je de methodes uit die interface in de class kan aanroepen (vanuit een andere class). Stel je wil vandaag je log in systeem bouwen op basis van een database, maar morgen wil je over stappen op tekst bestanden. Als beide datamappers dezelfde interface implementeren (met of zonder dezelfde abstracte basis class te hebben) kan je dat eenvoudig doen.

In het kort is het dus handig om alle datamappers dezelfde abstracte basis class te geven om bepaalde code te centraliseren en is het handig om interfaces te schrijven voor elke functionele datamapper (je hebt dus verschillende interfaces voor een user datamapper en een product datamapper).

Wat betreft de implementatie van een abstracte methode. Bij mijn weten moet je een abstracte methode precies zo implementeren als aangeven in de abstracte definitie. Je kan dus niet een variabele toevoegen.
 
Wouter J

Wouter J

16/07/2012 10:08:54
Quote Anchor link
Ik ben het eens met Erwin behalve dat ik denk dat je gewoon 1 datamapper interface moet maken en dan verschillende abstracte klassen zoals DbDatamapper, FileDataMapper, ect. Daaronder komen weer de datamappers als UserDataMapper en LogDataMapper.

Quote:
Wat betreft de implementatie van een abstracte methode. Bij mijn weten moet je een abstracte methode precies zo implementeren als aangeven in de abstracte definitie. Je kan dus niet een variabele toevoegen.

Laatst had ik iets interessants gelezen. Je moet namelijk een abstracte definitie ervoor zorgen dat je het op dezelfde manier kan aanroepen. Dus bijv:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
abstract class Foo
{
    // ...

    abstract public function bar($a, $b);
}

class Baz extends Foo
{
    public function bar($a, $b, $c)
    {

        // ...
    }
}

?>

Dit zal niet werken. PHP zal Baz::bar aanroepen met 2 argumenten en een error krijgen omdat je nog een derde argument hebt toegevoegd. Maar maak je die 3e optioneel dan kun je Baz::bar gewoon aanroepen met 2 argumenten, maar ook met 3:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
<?php
// ...
class Baz
{
    public function bar($a, $b, $c = null)
    {

        // ...
    }
}

?>


En je kan een functie altijd zoveel argumenten meegeven als je wilt en dan kun je ook nog gaan werken met func_get_args, mocht je dat handiger/leuker vinden.
 
Roel -

Roel -

16/07/2012 13:59:00
Quote Anchor link
Yep, dat heb ik gedaan in een config die ik overal include:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
<?php
$pdo
= new PDO('mysql:host=localhost;dbname=roelvdwater', 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db = new Database($pdo);
?>

Ik vind het overigens eerlijk gezegd overdreven om zoveel classes en interfaces te maken voor één object. Je hebt straks per objects drie classes nodig: het object zelf, een datamapper en datadatamapper...?
Kan ik wel zoiets doen? Dit lijkt mij de beste oplossing:
MapperInterface
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
/**
 * Interface for the abstract mapper class
 *
 * @author Roel van de Water
 */

interface MapperInterface
{
    public function __construct(Database $db);
    public function create();
    public function save();
    public function delete();
    public function populate(array $result);
}

?>

Mapper
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
require 'mapperinterface.php';

/**
 * Basic functionality for mapper objects
 *
 * @author Roel van de Water
 */

abstract class Mapper implements MapperInterface
{
    protected $db;
    
    public function __construct(Database $db)
    {

        $this->db = $db;
    }

    
    public function create()
    {

        // Implementation at child class
    }
    
    public function save()
    {

        // Implementation at child class
    }
    public function delete()
    {

        // Implementation at child class
    }
    
    public function populate(array $result)
    {

        // Implementation at child class
    }
}

?>

UserMapper
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
require 'mapper.php';
/**
 * Class for communicating between the user and database object
 *
 * @author Roel van de Water
 */

class UserMapper extends Mapper
{    
    // getByUsernameAndPassword...
    
    public function create(User $user)
    {
        
    }

    
    public function save(User $user)
    {
        
    }

    
    public function delete(User $user)
    {
        
    }

    
    public function populate(array $result)
    {
        
    }
}

?>

Dit ziet er in mijn ogen vrij correct uit, alleen krijg ik zo deze error:
SCREAM: Error suppression ignored for
Fatal error: Declaration of UserMapper::create() must be compatible with MapperInterface::create() in \classes\usermapper.php on line 9
 
Wouter J

Wouter J

16/07/2012 14:22:33
Quote Anchor link
Quote:
Ik vind het overigens eerlijk gezegd overdreven om zoveel classes en interfaces te maken voor één object. Je hebt straks per objects drie classes nodig: het object zelf, een datamapper en datadatamapper...?
Kan ik wel zoiets doen?

Ja, hoe meer klassen hoe beter eigenlijk. Laatst had ik eens getest hoeveel klassen je eigenlijk gebruikt voor een simpele pagina aanroep in Symfony: 256. Je moet dus echt niet bang zijn veel klassen te maken. Kijk bijv. ook eens hoe ik dat scriptje heb gemaakt om klassen te tellen: http://snipplr.com/view/66144/classfilteriterator/ Daarin zie je ook dat ik 6 klassen/interfaces gebruik om dat voor elkaar te krijgen.

En goed, kijk eens wat je doet. In de MapperInterface zeg je:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
public function create()

In de UserMapper zeg je:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
public function create(User $user)

Zie jij het verschil? Ik wel.

En nu ziet PHP die Interface die geen argumenten heeft. Om te testen of UserMapper wel aan het interface voldoet gebruikt PHP:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
UserMapper::create()

En woeps, hij krijgt een error.

Je moet er dus voor zorgen dat je UserMapper::create() kan aanroepen zonder argumenten of je moet in de interface aangeven dat de functie create() 1 argument nodig heeft.

Wat ik zelf doe in mijn Storage klassen die ik nu aan het maken ben is dat ik alle elementen die ik kan opslaan een interface meegeef: Storable. Vervolgens geef ik in het hoofdinterface van de datamapper (in mijn geval StorageInterface) aan dat de functie create een Storable moet zijn:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
// DataMappers
interface MapperInterface
{
    // ...
    public function create(Storable $store);
}


abstract class DatabaseMapper implements MapperInterface
{
    // ... methods die database opzetten, ect.
}

class UserMapper extends DatabaseMapper
{
    public function create(Storable $user)
    {

        // ... sla nieuwe gebruiker op
    }
}


// Users
interface Storable
{
    // overeenkomstige methods
}

class User implements Storable
{
    // ... user methods
}

// Bootstrap
$pdo = new PDO(...);
$database = Database::getDBO('pdo:mysql');
$userMapper = new UserMapper($database);

$user = new User(...);

$userMapper->create($user);
?>


Edit:
En de DatabaseMapper verwijst natuurlijk weer naar een Database object die weer verschillende kinderen heeft als MySQLiDatabase, abstract PDODatabase, PDOMySQLDatabase, PDOOracleDatabase, ect. Die de database klasse aanmaakt door een factory method.
Hierdoor kom je uit op zo'n 9 klassen/interfaces voor het opslaan van een gebruiker :)
Gewijzigd op 16/07/2012 14:27:06 door Wouter J
 
Roel -

Roel -

16/07/2012 14:47:03
Quote Anchor link
Dus in feite geef je nu gewoon altijd de parent class mee aan je methods?
Dus ipv User zeg je Storable, ipv Page zeg je Storable?

Precies wat ik zoek denk ik! Ik ga ermee aan de slag en als ik vragen heb laat ik het weten!
Bedankt voor de tips.

Overigens snap ik je Database::getDBO()-method niet echt. Wat is hier het nut van?
 
Wouter J

Wouter J

16/07/2012 14:55:15
Quote Anchor link
Quote:
Dus in feite geef je nu gewoon altijd de parent class mee aan je methods?
Dus ipv User zeg je Storable, ipv Page zeg je Storable?

En dat is nou precies waarom Erwin en ik zo zitten te hameren op het maken van parent klassen/superklassen. Het maakt je niks uit welke klasse je nou gaat opslaan, als het maar een instance is van Storable. In Storable leg je vast welke methods die klassen per se nodig heeft en die methods kun je dus gewoon altijd gebruiken. Stel je voor dat je een method getData maakt om de data te krijgen dan weet je in de UserMapper::create() method nu zeker dat je die method kan gebruiken.

Quote:
Overigens snap ik je Database::getDBO()-method niet echt. Wat is hier het nut van?

Dat is een factory zoals we dat noemen. In die method maak je de juiste database klasse aan die je kan gebruiken. Bijv:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Database
{
    public static function getDBO($adapter)
    {

        switch (strtolower($adapter)) {
            case
'mysql' :
                return new MySQLDatabase();
                break;

            case
'mysqli' :
                return new MySQLiDatabase();
                break;

            case
'pdo:mysql' :
                return new PDOMySQLDatabase();
                break;

            // ...
        }
    }
}
 
Roel -

Roel -

16/07/2012 15:39:28
Quote Anchor link
Dus je maakt dan niet meer zelf een Database object aan, maar je maar een static method die er geen een aanlevert. Interessant. En die PDOMySQLDatabase etc. maak je gewoon zelf?
 
Wouter J

Wouter J

16/07/2012 15:56:44
Quote Anchor link
Quote:
En die PDOMySQLDatabase etc. maak je gewoon zelf?

Ja.
 
Roel -

Roel -

16/07/2012 16:36:56
Quote Anchor link
En wat voor methods staan er bij jou dan bijvoorbeeld in?
En dat zijn als ik het goed begrijp zeker ook weer een childs van een andere interface om te kunnen bepalen wat voor methods erin moeten komen, alleen moet de implementatie per database anders zijn?
 
Wouter J

Wouter J

16/07/2012 17:07:19
Quote Anchor link
Ja, je hebt 1 database klasse met queries. Die database klasse heeft weer subklassen die per adapter ervoor zorgen dat het uitgevoerd word. Want een query uitvoeren is natuurlijk verschillend per adapter.
 
Roel -

Roel -

17/07/2012 17:39:11
Quote Anchor link
Hmm, oke. Maar hoe voer jij een query uit dan?
Je doet normaal toch via een method in de mapper iets als $this->db->query().

Maar het argument van die method is dan altijd hetzelfde, welke database hem ook afhandelt. Hoe doe je dat dan verder?

Ik heb trouwens m'n try-catch probleem nog niet opgelost. Weet jij hoe dit te fixen is?

Overigens heb ik nu een Storable interface zoals je zei. Moet ik nu m'n methods in m'n UserMapper die een User object veranderen in Storable, of gewoon zo laten? Het kan beide, maar wat is het beste?

Edit:
Laat maar, het moet perse Storable zijn.
Gewijzigd op 17/07/2012 17:47:49 door Roel -
 
Wouter J

Wouter J

17/07/2012 18:14:36
Quote Anchor link
(heel simpel) voorbeeldje:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?php

/**
 * A interface for all Database classes
 */

interface Database
{
    public function connect($host, $user, $pass, $db);
    public function query($query, array $parameters = array());
    public function fetch($result);
}


/**
 * The MySQL class
 */

class MySQLDatabase implements Database
{
    protected $link;

    public function connect($host, $user, $pass, $db)
    {

        $this->link = mysql_connect($host, $user, $pass);
        // error handling
        
        mysql_select_db($db, $this->link);
        // error handling
    }

    public function query($query, array $parameters = array())
    {

        $query = vsprintf(str_replace('?', '%s', $query), $parameters);

        return $this->fetch(mysql_query($query, $this->link));
    }


    public function fetch($result)
    {

        return mysql_fetch_assoc($result);
    }
}


/**
 * The PDO superclass
 */

abstract class PDODatabase implements Database
{
    protected $link;

    public function query($query, array $parameters = array())
    {

        $stmt = $this->link->prepare($query);

        return $this->fetch($stmt->execute($parameters));
    }


    public function fetch($result)
    {

        return $result->fetch(PDO::FETCH_ASSOC);
    }
}


/**
 * The MySQL PDO class
 */

class PDOMySQLDatabase extends PDODatabase
{
    public function connect($host, $user, $pass, $db)
    {

        $this->link = new PDO('mysql:host='.$host.';dbname='.$db, $user, $pass);
        // error handling
    }
}


/**
 * The Oracle PDO class
 */

class PDOOracleDatabase extends PDODatabase
{
    public function connect($host, $user, $pass, $db)
    {

        // maak connectie op de Oracle manier
        $this->link = new PDO(...);
        // error handling
    }
}
Gewijzigd op 17/07/2012 18:14:50 door Wouter J
 
Roel -

Roel -

18/07/2012 15:01:00
Quote Anchor link
Ik word gek van dit probleem. Ik heb een method om users op te halen op basic van username en password en daarin wordt gecontroleerd of deze user bestaat en aan de hand daarvan stuurt de method een user of een boolean terug, maar ik snap er geen ruk van.
UserMapper
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
require 'mapper.php';

/**
 * Class for communicating between the user and database object
 *
 * @author Roel van de Water
 */

class UserMapper extends Mapper
{
    public function getByUsernameAndPassword($username, $password)
    {

        $query = "SELECT id, username, password, ip FROM users WHERE username = :username AND password = :password";
        $result = $this->db->query($query, array(
            ':username' => $username,
            ':password' => $password
        ));
        
        if ($this->db->num_rows($result) == 0)
        {

            return false;
        }

        else
        {
            $record = $this->db->fetch($result);
            
            return $this->populate($record);
        }
    }


    // overige methods
    
    public function populate(array $data)
    {

        $user = new User($data['username'], $data['password'], $data['ip']);
        $user->setId($data['id']);
        
        return $user;
    }
}

?>

Database
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
/**
 * Database object for communicating between the mapper and the PDO database object
 *
 * @author Roel van de Water
 */

class Database
{
    protected $pdo;
    
    public function __construct(PDO $pdo)
    {

        $this->pdo = $pdo;;
    }

    
    public function query($query, array $args = null)
    {

        try
        {
            $statement = $this->pdo->prepare($query);
            $statement->execute($args);

            return $statement;
        }

        catch (PDOException $e)
        {

            throw new Exception('Query failed');
        }
    }

    
    public function lastInsertId()
    {

        return $this->pdo->lastInsertId;
    }

    
    public function num_rows($result)
    {

        return count($result->fetchAll(PDO::FETCH_ASSOC));
    }

    
    public function fetch($result)
    {

        return $result->fetch(PDO::FETCH_ASSOC);
    }
}

?>

Als ik nu probeer in te loggen krijg ik de volgende error:
Catchable fatal error: Argument 1 passed to UserMapper::populate() must be of the type array, boolean given, called in C:\wamp\www\RoelVanDeWater\classes\usermapper.php on line 27 and defined in C:\wamp\www\RoelVanDeWater\classes\usermapper.php on line 65

Als ik op regel 38 van Database instel dat er altijd 1 gereturned wordt doet ie het wel, maar dat klopt natuurlijk niet. Ik trek zowat de haren uit m'n hoofd want ik snap gewoon niet wat er verkeerd gaat...
 
Erwin H

Erwin H

18/07/2012 16:04:05
Quote Anchor link
Ik *denk* dat het komt omdat je eigenlijk twee keer probeert te fetchen. Eerst met fetchAll om het aantal rijen te krijgen, daarna nog een keer fetch om alleen de eerste te krijgen waarmee je iets wilt doen. Het staat niet in de documentatie, maar ik vermoed dat fetchAll de pointer in de resulset helemaal achteraan zet, waardoor je de tweede keer niet meer kunt fetchen.

Oplossing: fetch all records direct na het uitvoeren van je query, sla het in je DB class op (of in een andere class die met resultaten om kan gaan) en doe alle bewerkingen op die opgeslagen set.
 
Roel -

Roel -

18/07/2012 16:09:43
Quote Anchor link
Dat vermoedde ik ook al. Maar is dat wel een nette oplossing? Je kunt dan namelijk niet meer met het statement doet wat je wilt buiten de database class.
 

Pagina: « vorige 1 2



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.