PHP is the best way to load a Database object from Model, but have only one instance?

This is my problem, I have a tiny PHP MVC framework that I created. Now that Im in Controller, I should be able to load the model. I have models called database tables such as Users.php, Pages.php, etc.

All controllers extend BaseController, and all models extend BaseModel, so I can use some methods for all controllers. Like any controller, I can use the loadModel method as follows:

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

Now $ usersModel will be an object representing the user database table, and from there I have to open the database connection and also extract, update, delete material from the user table.

To get the database connection from my models, baseModel has a loadDatabase () method, and I use it as follows:

 $database = $this->loadDatabase(); 

This will return a class that is a thin layer around the PDO, so from there I can use something like this:

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

Here is how I can return some $ data from Model to Controller.

Basically, a controller can load a model and then call methods on that model that return some data or update or delete, etc.

Now the controller can load more than one model. And each model must load the database, and it is there that it becomes complex. I need only one database connection.

This is what the loadDatabase method looks like:

  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); } 

Therefore, before loading the database object, I have to load the configuration to connect to the database, since the __constructor database objects expect this. Then I start a new database object and return it.

Now I am stuck and I do not know if it is right to load the database from the model? How do I configure this, how do I load the database from within the model, so there is always only one instance of the database object. Beacuse, if I do something like this from the 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 do I load a database from models, and what should this method look like in BaseModel? What if I would like to use 2 databases, I will have some big problems with this setting. How can I achieve this without using a 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 a registry template in which I could store shared objects. Therefore, when the model requests a connection to the database, I can check whether the DB class is in the registry and return it, if not, I would install and return ... The most confusing thing is that I need to load the configuration array ...

So what is the best way to load a database from models?

Thanks for reading, it was a very long question, but it bothers me so much, I hope someone can help me with this ... Thanks!

+7
source share
3 answers

You will not be mistaken in solving this issue.

Instead of manually creating a new β€œModel” each time and then customizing it, you should create a structure that will do this 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 that you would use in the index.php or bootstrap.php file or any other file that you use as an 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 are facing is actually caused by a misunderstanding of the model. In the right MVC Model is a layer, not a concrete class. The model layer consists of many classes / instances with two main responsibilities:

  • domain business logic
  • data access and storage

The instances in the first group are usually called Domain Objects or Business Objects (sort of like the situation with geeks and botanists ). They relate to validations, calculations, different conditions, but do not have a clue how and where information is stored. It does not change how you make an invoice, regardless of whether the data comes from SQL, a remote REST API, or a screenshot of an MS Word document.

Another group consists mainly of Data Mappers . They store and retrieve information from domain objects. This will usually be your SQL. But maps are not always mapped directly to the database schema. In the classic many-to-many structure, you can have 1 or 2 or 3 devices serving the storage. Mappers are usually one for each domain object .. but even this is optional.

In the controller, everything will 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 ); } 

Perhaps this will give you some guidance on the direction in which this can be done.

Some additional videos:

+9
source

The best way for these models is to use the dependency injection pattern.

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

where loadDatabase() should after initialization and after returning a simple instance of connecting to the database.

+2
source

Try the following:

 class Registry { /** @return Registry */ public static function getInstance() {} public function getObject($key) {} public function setObject($key, $value) {} /** @return bool */ public function isObjectExists($key) {} } class Db { const DB_ONE = 'db_one'; const DB_TWO = 'db_two'; protected static $_config = array( self::DB_ONE => array(), self::DB_TWO => array(), ); public static function getConnection($dbName) {} } abstract class BaseModel { abstract protected function _getDbName(); protected function _getConnection() { $dbName = $this->_getDbName(); if (!Registry::getInstance()->isObjectExists($dbName)) { Registry::getInstance()->setObject($dbName, Db::getConnection($dbName)); } return Registry::getInstance()->getObject($dbName); } } class UserModel extends BaseModel { protected function _getDbName() { return Db::DB_ONE; } } class PostModel extends BaseModel { protected function _getDbName() { return Db::DB_TWO; } } 

Good luck

0
source

All Articles