Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP - The best way to load Database object from Model, but to have only one instance?

This is my problem, I have a tiny PHP MVC framework i built. Now when Im in Controller, i should be able to load a Model. I have Models named like database tables like Users.php, Pages.php etc.

All Controllers extend BaseController, and all Models extend BaseModel, this way I can have some methods available to all Controllers. Like from any Controller I can use loadModel method like this:

$usersModel = $this->loadModel("Users");

Now $usersModel will be object that represents users database table, and from there I should open database connection, and fetch, update, delete stuff from users table.

To get database connection from my Models, baseModel has method loadDatabase() and I use it like this:

$database = $this->loadDatabase();

This would return class that is thin layer around PDO so from there I can use something like this:

$data = $database->fetchAssoc("SELECT * FROM users WHERE name = ?", array("someone"));

This is how I would return some $data from Model to the Controller.

So basicly, Controller can load a model, and then call methods on that model that would return some data or update or delete etc.

Now, Controller can load more then one model. And each model should load database, and this is where it gets complicated. I need only one connection to the database.

This is how loadDatabase method looks:

    public function loadDatabase()
    {
        // Get database configuration  
        require_once("Application/Config/Database.php");

        // Build configuration the way Database Object expects it
        $dns      = "{$db_config['db_driver']}:host={$db_config['db_host']};dbname={$db_config['db_name']}";
        $username = $db_config['db_user'];
        $password = $db_config['db_pass'];
        $options  = $db_config['db_options'];

        // Return new Database object
        return new \Core\Database\Database($dns, $username, $password, $options);

    }

So before I load a Database object, i must load configuration for database connection, as Database objects __constructor expects it. Then I instanciate new database object and return it.

Now Im stuck and I dont know is this the right way to loadDatabase from model? How should I set this up, how should I load database from inside the model so there is always only one instance of database object. Beacuse if I do something like this from Controller:

$users = $this->loadModel("Users");
$pages = $this->loadModel("Pages");

$users->doSomethingThatNeedsDatabase();
$users->doSomethingThatNeedsDatabase();
$pages->doSomethingThatNeedsDatabase();

I would create 3 database objects :(

So my question is, how should I load Database from inside the Models, and how should that method look in BaseModel? What if I would like to use 2 databases, I will have some big problems with this setup. How can I achive this without using singleton pattern?

At the moment, I also have something like this:

public function getDatabase()
{

    $reg = \Core\Registry\Registry::getInstance();
    $db = $reg->getObject("database");
    if(!isset($db))
    {
        require_once("Application/Config/Database.php");

        $dns      = "{$db_config['db_driver']}:host={$db_config['db_host']};dbname={$db_config['db_name']}";
        $username = $db_config['db_user'];
        $password = $db_config['db_pass'];
        $options  = $db_config['db_options'];                

        $db = new \Core\Database\Database($dns, $username, $password, $options); 

        $reg->setObject('database', $db);
    }
    return $reg->getObject('database');

}

This is Registry pattern, where I could hold shared objects. So when Model asks for DB connection I could check if DB Class is in Registry, and return it, if not I would instaciate and then return... The most confusing thing is that I need to load configuration array...

So what is the best way, to load Database from Models?

Thanks for reading, this was a very long question, but this is bothering me so much, i hope someone could help me with this one... Thanks!

like image 828
Limeni Avatar asked Mar 01 '12 22:03

Limeni


2 Answers

You are going in the wrong way about solving this.

Instead of each time manually making a new "Model" and then configuring it, you should create a structure that does it for you ( extremely simplified version ):

class ModelFactory
{
    protected $connection = null;
    // --- snip --

    public function __construct( $connection )
    {
        $this->connection = $connection;
    }
    // --- snip --

    public function buildMapper( $name )
    {
        $instance = new {$name}( $this->connection );
        return $instance;
    }
    // --- snip --

}

This class you would be using in index.php or bootstrap.php , or any other file you use as entry point for your application:

// at the bootstrap level
$modelFactory = new ModelFactory( new PDO(...) );

// i am assuming that you could get $controllerName 
// and $action from routing mechanism
$controller = new {$controllerName}( $modelFactory );
$controller->{$action}();

The main problem you have is actually cause by misunderstanding what Model is. In a proper MVC the Model is a layer, and not a specific class. Model layer is composed from multitude of class/instances with two major responsibilities:

  • domain business logic
  • data access and storage

The instances in first group are usually called Domain Objects or Business Objects (kinda like situation with geeks and nerds). They deal with validations, computation, different conditions, but have no clue how and where information is stored. It does not change how you make an Invoice , whether data comes from SQL, remote REST API or a screenshot of MS Word document.

Other group consists mostly of Data Mappers. They store and retrieve information from Domain Objects. This is usually where your SQL would be. But mappers do not always map directly to DB schema. In a classic many-to-many structure you might have either 1 or 2 or 3 mappers servicing the storage. Mappers usually one per each Domain Object .. but even that is not mandatory.

In a controller it would all look something like this.

public function actionFooBar()
{
    $mapper = $this->modelFactory->buildMapper( 'Book' );
    $book = $this->modelFactory->buildObject( 'Book' );
    $patron = $this->modelFactory->buildObject( 'Patron' );

    $book->setTitle('Neuromancer');
    $mapper->fetch( $book );

    $patron->setId( $_GET['uid']);

    if ( $book->isAvailable() )
    {
        $book->lendTo( $user );
    }

    $mapper->store( $book );

}

Maybe this will give you some indications for the direction in which to take it.

Some additional video materials:

  • Advanced OO Patterns (slides)
  • Global State and Singletons
  • Don't Look For Things!
like image 188
tereško Avatar answered Nov 07 '22 01:11

tereško


Best way for these models to use dependency injection pattern.

public function loadModel() {
    $db = $this->loadDatabase();
    $model = new Model();
    $model->setDatabase($db);
    return $model;
}

where loadDatabase() should once init and after return simple instance of database connection.

like image 35
Electronick Avatar answered Nov 07 '22 02:11

Electronick