PHP DataMapper pattern: My class needs an instance of PDO, I want to wrap it inside a Db class
Asked Answered
B

2

3

here's what I have:

class Entry
{
    public $id;
    public $name;
    public $seoName;
    public $timeCreated;

    public function someFunction()
    {

    }

}

class EntryMapper
{
    protected $db;

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

    public function saveEntry(Entry &$entry)
    {
        if($entry->id){
            $sql = "";
    }
    else {
        $sql = "INSERT INTO tbl_entry (name, seo_name, time_created) VALUES (:name, :seo_name, :time_created)";
        $stmt = $this->db->prepare($sql);
        $stmt->bindParam("name", $entry->name);
        $stmt->bindParam("seo_name", $entry->seoName);
        $stmt->bindParam("time_created", $entry->timeCreated);
        $stmt->execute();
        $entry->id = $this->db->lastInsertId();
        }
    }

}

Now, here's how I use it in my view file (currently just testing insert command):

$entry = new Entry();

$entry->name = "Some Company LLC";
$entry->seoName = "some-company-llc";
$entry->timeCreated = date("Y-m-d H:i:s");

$entryMapper = new EntryMapper(new PDO("mysql:host=....."));
$entryMapper->saveEntry($entry);

I want to have the $entryMapper line like this:

$entryMapper = new EntryMapper(new Database());

meaning I should have a separate class Database.php where I would establish the connection.

I tried that, but since my class EntryMapper.php needs an instance of PDO directly, i'm getting an error. I have tried Database extend from PDO but that also raises error saying that PDO constructor was not called in EntryMapper

Any thoughts?

EDIT: if you see any signs of code coupling or similar, let me know because I want to learn to code properly. Thank you very much!

Benge answered 1/3, 2014 at 21:19 Comment(1)
you might find this useful: https://mcmap.net/q/216904/-how-to-properly-set-up-a-pdo-connectionLuo
B
1

This is how I finally solved it (if a better implementation arises, I will for sure recode). It is an implementation of solution under the accepted answer here: Global or Singleton for database connection?

My ConnFactory.php

include('config/config.php');

class ConnFactory
{
    private static $factory;

    public static function getFactory()
    {
        if(!self::$factory){
            self::$factory = new ConnFactory();
            return self::$factory;
        }

    }

    private $db;

public function pdo()
{
    if(!$this->db){
        $options = array(
            PDO::ATTR_PERSISTENT => true,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
        );
        $this->db = new PDO("mysql:host=".DB_HOST.";port=".DB_PORT.";dbname=".DB_SCHEMA."", DB_USER, DB_PASS, $options);
    }
    return $this->db;
    }

}

Usage in my view/html file (just a test of insert functionalty):

$entry = new Entry();
$entry->name = "Kartonaža ad Gradačac";
$entry->seoName = "kartonaza-ad-gradacac";
$entry->timeCreated = date("Y-m-d H:i:s");

$entryMapper = new EntryMapper(ConnFactory::getFactory()->pdo());
$entryMapper->saveEntry($entry);
Benge answered 2/3, 2014 at 19:36 Comment(1)
However, I would prefer to have be able to call anything shorter for db connection instead of "ConnFactory::getFactory()->pdo()". If anyone knows how to improve on that, it will be appreciated.Benge
R
1

You can use Factory pattern and create the PDO object within a function in the Database class.

class Database {
    private const connStr = 'mysql:host=.....';

    public static function createPDODatabase() {
        return new PDO(connStr);
    }
}

So you may call your EntryMapper constructor as:

$entryMapper = new EntryMapper(Database::createPDODatabase());

EDIT: If you want to do it by instantiating the Database object, you should call the PDO constructor in the constructor of the Database class.

class Database extends PDO {
    public function __construct($dbname='db_name', $server='localhost', $username='db_user', $password='db_password') {
        parent::__construct("mysql:host=$server;dbname=$dbname", $username, $password);
        parent::setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    }
}

Then you may just instantiate the Database object.

$entryMapper = new EntryMapper(new Database());
Rainout answered 1/3, 2014 at 21:47 Comment(7)
Do you think it would be wise to have this connStr in my config file? Or leave it here but use config items in it?Benge
It is better to use a configuration file most of the times.Rainout
OK, so I got it working: I didn't use the conn string as a constant but kept it directly in new PDO() and it's comprised of my config items. Also, above return line it's my array of options for the connection. I will mark your answer as accepted because you suggested the Factory Pattern. Thank you!Benge
-1: you obviously have no idea what factory actually is. Horrible advice with even worse code (global scope, violations of OCP, vulnerable initialization of PDO). I wonder why OP accepted it as an answer.Luo
@tereško What do you suggest here? Thank youBenge
@tereško This is just a simple example and a simplified version. Just do a quick search and look at the samples, phptherightway.com/pages/Design-Patterns.html for example. If you have a better example you may share with us.Rainout
@LandoCalrissian I'm afraid he's right. Factory Pattern seems to assume another class named FooFactory with a single static method that is called whenever you need to create an object (with variations provided through supplied arguments). That's how I see it, not claiming what I say is correct.Benge
B
1

This is how I finally solved it (if a better implementation arises, I will for sure recode). It is an implementation of solution under the accepted answer here: Global or Singleton for database connection?

My ConnFactory.php

include('config/config.php');

class ConnFactory
{
    private static $factory;

    public static function getFactory()
    {
        if(!self::$factory){
            self::$factory = new ConnFactory();
            return self::$factory;
        }

    }

    private $db;

public function pdo()
{
    if(!$this->db){
        $options = array(
            PDO::ATTR_PERSISTENT => true,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
        );
        $this->db = new PDO("mysql:host=".DB_HOST.";port=".DB_PORT.";dbname=".DB_SCHEMA."", DB_USER, DB_PASS, $options);
    }
    return $this->db;
    }

}

Usage in my view/html file (just a test of insert functionalty):

$entry = new Entry();
$entry->name = "Kartonaža ad Gradačac";
$entry->seoName = "kartonaza-ad-gradacac";
$entry->timeCreated = date("Y-m-d H:i:s");

$entryMapper = new EntryMapper(ConnFactory::getFactory()->pdo());
$entryMapper->saveEntry($entry);
Benge answered 2/3, 2014 at 19:36 Comment(1)
However, I would prefer to have be able to call anything shorter for db connection instead of "ConnFactory::getFactory()->pdo()". If anyone knows how to improve on that, it will be appreciated.Benge

© 2022 - 2024 — McMap. All rights reserved.