I'm using ActiveRecord and I need switching databases. When I log-in I select a database.
The databases are the same schema.
I tried:
$connections = array(
'1' => 'mysql://root:pass#localhost/db1;charset=utf8',
'2' => 'mysql://root:pass#localhost/db2;charset=utf8',
'test' => 'mysql://root:password#localhost/database_name'
);
$current_db = $_SESSION['db'] ? $_SESSION['db'] : '2';
ActiveRecord\Config::initialize(function($cfg) use ($connections, $current_db)
{
$cfg->set_model_directory(MODEL_PATH);
$cfg->set_connections($connections);
$cfg->set_default_connection($current_db);
});
db '2' is default. But does not work.
I generally use a method like this;
$all = new SomeModel(); // class of ActiveRecord\Model
$all->table()->class->setStaticPropertyValue("connection","test"); //test - defined in cfg
$all->table()->reestablish_connection();
//then use it as usual.
$all_data = $all->all();
I found a link with an excellent solution for switching databases here
EDIT:
I define a method called switch_connection in my model
public static function switch_connection($name) {
$cfg = ActiveRecord\Config::instance();
$valid = $cfg->get_connections();
if ( ! isset($valid[$name])) {
throw new ActiveRecord\DatabaseException('Invalid connection specified');
}
// Get the name of the current connection
$old = self::$connection;
$cm = ActiveRecord\ConnectionManager::instance();
$conn = $cm::get_connection($name);
static::table()->conn = $conn;
return $old;
}
If you see in the link, note that I added the static keyword. I think that is better idea.
Related
I'm still pretty new with PHPUnit, and I'm trying to figure out how to best test methods as shown in the 3rd class.
I understand how the mock databases work (I think), in that they can return values based on input, from an XML file, etc. I'm not sure how to provide data for the 3rd example, when SQL queries are run inside the methods itself.
I'm trying to test code that accesses info from a DB and performs operations on it. There is no way to feed mocked DB data (e.g., arrays) to these methods currently.
Question: What is the best way to provide data to a method that handles all of its SQL queries internally?
<?php
class ThisMakesSense {
public function checkPassword($original, $hash) {
return (md5($original) == $hash);
}
}
class ThisMakesSenseTest {
public function testCheckPassword() {
$tms = new ThisMakesSense();
$data = array('jdoe#example.com' => 'password1', 'bsmith#example.com' => 'password2');
foraech ($data as $email => $password) {
$hash = $this->mockDB()->doStuff()->getPasswordHashByEmail($email);
$this->assertTrue($tms->checkPassword($password, $hash), "Password for {$email} is invalid");
}
$tms->checkPassword($password, $hash);
}
}
/* The '$this->_db' object is basically an OOP way of using the
* mysql_* /mysqli_* functions. Replacing it is not an option
* right now.
*/
class DontUnderstand {
public function checkPassword($email, $password) {
$this->_db->query("SELECT password_hash FROM users WHERE email = '{$email}'");
$row = $this->_db->fetchAssoc();
return (md5($password) == $row['password_hash']);
}
}
class DontUnderstandTest extends PHPUnit_Framework_TestCase {
public function testCheckPassword() {
$du = new DontUnderstand();
$data = array('jdoe#example.com' => 'password1', 'bsmith#example.com' => 'password2');
foreach ($data as $email => $pass) {
$this->assertTrue($du->checkPassword($email, $pass), "Password for {$email} is invalid");
}
}
}
(To save someone the trouble of commenting, the md5 and query methods are just for a simple example)
I'm not sure what is the best approach, but here's my way. It's based on the assumption of a class that connects internally with a database and a single table. Access is through INSERT, UPDATE, DELETE, SELECT and similar, so no complex JOINs or UNIONs. Another assumption is that I erect the database once (not as part of the testing routine) before I run phpunit. I looked at PHPUnit's Database extension but it appeared to me as too cumbersome to use here, so I quickly mocked this:
class UserProfileTest extends PHPUnit_Framework_TestCase {
protected static
$options,
$dbHandle;
public static function setUpBeforeClass() {
self::$options = array(
'dbHostName' => 'localhost',
'dbUserName' => 'tester',
'dbPassword' => 'pw4tester',
'dbName' => 'test',
'dbTableName' => 'Test',
);
self::$dbHandle = new mysqli( self::$options[ 'dbHostName'], self::$options[ 'dbUserName'], self::$options[ 'dbPassword'], self::$options[ 'dbName'] );
if( self::$dbHandle->connect_errno)
exit( 'Error: No DB connection.');
}
protected function fillDb() {
$query = 'INSERT INTO ' . self::$options[ 'dbTableName'] . ' VALUES (default,"foo","bar",7)';
if( !self::$dbHandle->query( $query) )
exit( 'Error: Could not fill DB.');
}
protected function setUp() {
// always start a TC with empty DB
$query = 'DELETE FROM ' . self::$options[ 'dbTableName'];
if( !self::$dbHandle->query( $query) )
exit( 'Error: Could not empty DB.');
}
// -- test --
public function testGetNumberOfProfiles() {
$profileMgr = new UserProfile( self::$options);
$this->assertEquals( 0, $profileMgr->getNumberOfProfiles() );
$this->fillDb();
$this->assertEquals( 1, $profileMgr->getNumberOfProfiles() );
$this->fillDb();
$this->assertEquals( 2, $profileMgr->getNumberOfProfiles() );
}
So, you connect to the DB when the class is instantiated (setUpBeforeClass), and empty the table before each testcase (in setUp). There is a helper function which inserts a row into the table; it is called when needed.
I've been unable to successfully retrieve records between two dates in Zend Framework 2.
My code looks as follows:
$select = $this->getTimeTable();
$predicate = new \Zend\Db\Sql\Where();
$select->where($predicate->between("start_date", $week_sunday_date , $satruday_date));
public function getTimeTable()
{
if (!$this->timeTable)
{
$this->timeTable = new TableGateway(
'time',
$this->getServiceLocator()->get('Zend\Db\Adapter\Adapter')
);
}
return $this->timeTable;
}
"start_date" is the column in my DB that is of type DATETIME, and $week_sunday_date , $satruday_date are both generated as follows
$week_sunday_date = date("Y-m-d\TH:i:sP",strtotime('this sunday'));
$satruday_date = date("Y-m-d\TH:i:sP",strtotime("+6 day", strtotime($week_sunday_date)));
The database connection is good and I can otherwise get data. Dumping both $week_sunday_date and $satruday_date variables above I see:
string(25) "2014-11-08T00:00:00+00:00"
string(25) "2014-11-02T00:00:00+00:00"
I've also tried several other ways of in between but ultimately the page is blank and won't load due to an error.
Seems like I need something like this:
$statement = $sql->prepareStatementForSqlObject($select);
$time_list = $statement->execute();
But not sure how to initialize $sql properly.
I think the correct syntax would be:
$select->where->between('start_date', $week_sunday_date, $saturday_date);
Oh, but since, in your case $select is an instance of TableGateway, then you probably need $select->getSql()->where->between(...).
I'm a little rusty on TableGateway, we've strayed away from using it. But I think you can do something like:
$timeTable = new TableGateway(/*...*/);
$select = $timeTable->getSql()->select();
$select->columns(array('col1', 'col2'));
$select->where->between('start_date', $week_sunday_date, $saturday_date);
$resultSet = $timeTable->selectWith($select);
var_dump($resultSet->toArray());
Note: I do not recommend always converting the result set to an array, but it's nice to be able to do this for debugging purposes.
Example not using TableGateway:
Query class:
namespace Example\Model\Sql\Select;
use Zend\Db\Sql\Select;
class Time extends Select {
public function __construct($week_sunday_date, $saturday_date) {
parent::__construct(['t' => 'Time']);
$this->columns([
'col1',
'col2',
]);
$this->where->between('start_date', $week_sunday_date, $saturday_date);
}
}
Data model: (basically a glorified array object, just holds data, no business logic)
namespace Example\Model\DataContainer;
class Time extends \ArrayObject {
protected $col1;
protected $col2;
public function exchangeArray($data) {
$this->col1 = $data['col1'];
$this->col2 = $data['col2'];
}
public function getCol1() {
return $col1;
}
public function getCol2() {
return $col2;
}
}
Controller:
namespace Example\Controller;
use Example\Model\DataContainer;
use Example\Model\Sql\Select;
use Zend\Db\ResultSet\ResultSet;
class IndexController {
public function myExampleAction() {
$request = $this->getRequest();
$sm = $this->getServiceLocator();
$query = new Select\Time($request->getQuery('sunday'), $request->getQuery('saturday'));
$resultSet = new ResultSet(ResultSet::TYPE_ARRAYOBJECT, new DataContainer\Time());
$executer = $sm->get('Executer');
$resultSet = $executer->execute($query, $resultSet);
return [
'time' => $resultSet, // loop through results in your view
];
}
}
So, we create an instance of our query class, which is set up when you call the constructor implicitly by creating a new instance of it. We pass our parameters into it. Then, we create an instance of our result set class, and we specify the array object prototype. Each row returned by our query will be populated as an instance of the array object prototype. When the result set is initialized, it will automatically call exchangeArray() with the data returned for the current row. That method populates your data container, and so the result set is populated with an array of these populated data container objects. So when you loop through the result set, each row will be represented by an instance of your array object prototype.
You will have to define your own Executer class. That's not a built-in. You will have to create it and add it to the service config, so that you can get it out of the service manager like I've done in the example.
A quick example for the $sql property as you requested at the end of your question.
You should use:
use Zend\Db\Sql\Sql;
use Zend\Db\Sql\Select;
$adapter = $this->tableGateway->getAdapter();//get connection
$sql = new Sql($adapter);
$select = $sql->select();
$select->from($this->tableGateway->getTable())->where(array('active' => '1'));
$selectString = $sql->getSqlStringForSqlObject($select);
//echo $selectString;die;//this will show the quesry string build above.
$results = $adapter->query($selectString, $adapter::QUERY_MODE_EXECUTE);
$resultSet = new ResultSet();
$resultSet->initialize($results);
return $resultSet->toArray();//return array(use $resultSet->current() for one row)
i hope this helps
WHAT I'M TRYING TO DO:
So I'm trying to implement a multi-tenant database architecture using SQL Azure, PHP 5.4, Zend Framework 2, and Doctrine 2. I'm going with the "Shared Database, Separate Schemas" architecture as mentioned in this article: http://msdn.microsoft.com/en-us/library/aa479086.aspx
Unlike simple multi-tenant environments, my environment has certain use cases where a User from Tenant A should be able to access information from a table in Tenant B. So because of this there are "root" or "global" tables that aren't made for each tenant and are instead used by all tenants. So as an example, I could have a table called users that exists for each tenant each with a unique schema name (e.g. tenanta.users and tenantb.users). I would then also have a root schema for things like global permissions (e.g. root.user_permissions).
WHAT I'VE DONE:
In Module.php's onBootstrap() function I've set up a loadClassMetadata event for dynamically changing the schemas of tables, like so:
$entityManager = $serviceManager->get('doctrine.entitymanager.orm_default')->getEventManager();
$entityManager->addEventListener(array( \Doctrine\ORM\Events::loadClassMetadata ), new PrependTableEvent() );
The PrependTableEvent object uses session data to know which schema to use, it looks like so:
namespace Application\Model;
use Zend\Session\Container;
class PrependTableEvent {
private $session;
public function __construct() {
$this->session = new Container('base');
}
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) {
$classMetadata = $eventArgs->getClassMetadata();
$table = $classMetadata->table;
$table_name = explode('.', $table['name']);
if ( 'root' != $table_name[0] && NULL !== $this->session->queryschema ) {
$table['name'] = $this->session->queryschema . '.' . $table_name[1];
}
$classMetadata->setPrimaryTable($table);
}
}
In order for loadClassMetadata to be called everytime the queryschema changes I built a QuerySchemaManager that looks like so:
namespace Application\Model;
use Doctrine\ORM\Events,
Doctrine\ORM\Event\LoadClassMetadataEventArgs,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\EntityManager,
Zend\Session\Container;
class QuerySchemaManager {
private static $session;
private static $initialized = FALSE;
private static function initialize() {
QuerySchemaManager::$session = new Container('base');
QuerySchemaManager::$initialized = TRUE;
}
public static function reload_table_name( EntityManager $em, $class, $schema) {
if ( ! QuerySchemaManager::$initialized ) {
QuerySchemaManager::initialize();
}
QuerySchemaManager::$session->queryschema = $schema;
if ($em->getEventManager()->hasListeners(Events::loadClassMetadata)) {
$eventArgs = new LoadClassMetadataEventArgs($em->getClassMetadata($class), $em);
$em->getEventManager()->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
}
public static function reload_all_table_names( EntityManager $em, $schema) {
if ( ! QuerySchemaManager::$initialized ) {
QuerySchemaManager::initialize();
}
QuerySchemaManager::$session->queryschema = $schema;
if ($em->getEventManager()->hasListeners(Events::loadClassMetadata)) {
$metadatas = $em->getMetadataFactory()->getAllMetadata();
foreach($metadatas as $metadata) {
$eventArgs = new LoadClassMetadataEventArgs($metadata, $em);
$em->getEventManager()->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
}
}
}
All that code works great and properly updates the ClassMetadata files for each entity.
THE PROBLEM:
I have an issue with Doctrine 2 where when I insert values into a table for Tenant A and then try to insert values into the same table for Tenant B, all the rows get inserted into Tenant A's table.
I spent a lot of time following break points to find the problem, but I still have no idea how to solve it.
(Note: All the following Code is from Doctrine, so it can't/shouldn't be edited by me)
The problem is that EntityManager->unitOfWork has a private array called $persisters that stores (in my case) BasicEntityPersister objects. Every time one of the BasicEntityPersisters are needed UnitOfWork's getEntityPersister($entityName) is called which looks like so:
public function getEntityPersister($entityName)
{
if ( ! isset($this->persisters[$entityName])) {
$class = $this->em->getClassMetadata($entityName);
if ($class->isInheritanceTypeNone()) {
$persister = new Persisters\BasicEntityPersister($this->em, $class);
} else if ($class->isInheritanceTypeSingleTable()) {
$persister = new Persisters\SingleTablePersister($this->em, $class);
} else if ($class->isInheritanceTypeJoined()) {
$persister = new Persisters\JoinedSubclassPersister($this->em, $class);
} else {
$persister = new Persisters\UnionSubclassPersister($this->em, $class);
}
$this->persisters[$entityName] = $persister;
}
return $this->persisters[$entityName];
}
So it will create one BasicEntityPersister per entity (i.e. Application\Model\User will have one BasicEntityPersister even though its schema name will dynamically change), which is fine.
Each BasicEntityPersister has a private member called $insertSql which stores the insert SQL statement once it has been created. When the insert statement is needed this method is called:
protected function getInsertSQL()
{
if ($this->insertSql !== null) {
return $this->insertSql;
}
$columns = $this->getInsertColumnList();
$tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
if (empty($columns)) {
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
$this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
return $this->insertSql;
}
$values = array();
$columns = array_unique($columns);
foreach ($columns as $column) {
$placeholder = '?';
if (isset($this->class->fieldNames[$column])
&& isset($this->columnTypes[$this->class->fieldNames[$column]])
&& isset($this->class->fieldMappings[$this->class->fieldNames[$column]]['requireSQLConversion'])) {
$type = Type::getType($this->columnTypes[$this->class->fieldNames[$column]]);
$placeholder = $type->convertToDatabaseValueSQL('?', $this->platform);
}
$values[] = $placeholder;
}
$columns = implode(', ', $columns);
$values = implode(', ', $values);
$this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values);
return $this->insertSql;
}
These three lines are the culprit:
if ($this->insertSql !== null) {
return $this->insertSql;
}
If those lines were commented out then it would work perfectly as the metadata it uses to create the insertSql statement updates properly. I can't find a way to delete/overwrite the insertSql variable, or to even delete/overwrite the whole BasicEntityPersister.
Anyone who's implemented a multi-tenant environment using Doctrine 2 I would like to know how you did it. I don't mind redoing all or large parts of my work, I just need to know what the best way to go about doing this is. Thanks in advance.
You are fighting against the ORM (which is meant to be generating the SQL for you). This should be a signal that you perhaps are going about things in the wrong way.
Rather than modify the persisters (that generate the SQL strings) you should be adding an additional EntityManager. Each entity EntityManager (and therefore UnitOfWork) are designed to persist to one schema; so your second one would simple handle the persistence to the second database - No need to change Doctrine internals!
I have not personally tried to connect to two schemas; however reading into it it seems that it should be possible with the DoctrineModule v1.0.
I have following code for multiple database connections. It's not good design. Everything is static. But don't know how to improve it. i can add more functions like prepare query, but presently I want good/clean design. I tried to make multiton design pattern. The requirements is like, first I will connect with 1 database, then get database details of all other mysql clients, then loop and connect with each database and do something. So, I need multiple connections.
<?php
class db_class{
private static $instance = array();
private function __construct(){ }
public static function get_instance($type , $db_detail_array=array()){
$host = $db_detail_array['host'];
$username = $db_detail_array['username'];
$database = $db_detail_array['database'];
$password = $db_detail_array['password'];
if(empty($host) or empty($username) or empty($database) or empty($password)){
return;
}
if(empty(self::$instance[$type])) {
self::$instance[$type] = new mysqli($host, $username, $password, $database);
if (#self::$instance[$type]->connect_errno) {
echo self::$last_err = "Connect failed";
}
}
}
static function fetch_assoc($query,$type){
$db_query = self::run_query($query,$type);
$rows = array();
while($row = #$db_query->fetch_assoc()){
$rows[] = $row;
}
$db_query->free();
return($rows);
}
static function escape($type,$value){
$value = self::$instance[$type]->real_escape_string($value);
return($value);
}
static function run_query($query,$type){
self::$instance[$type]->ping();
$db_query = self::$instance[$type]->query($query);
if(self::$instance[$type]->error){
echo self::$last_err = self::$instance[$type]->error;echo "<p>$query, $type</p>";
return;
}
return($db_query) ;
}
static function num_rows($query,$type){
$db_query = self::run_query($query,$type);
$num_rows = $db_query->num_rows;
return($num_rows);
}
static function disconnect($type){
#self::$db_obj[$type]->close();
}
}
?>
Please have a look at PDO.
It is an unifier database object exposing a common and effective interface.
It supports server types other than mysql too.
Even using it plainly will be satisfactory.
I am very new to PHP Object Orientated Programming so was wondering if I could get some good advice on a database object I created.
I call the class db and include my class into every page load and initiate the database object using $db = new db. I then call the method inside this for each action I may need (building a menu from the database, getting login information etc.) with different parameters depending on what it is i want to do.
It takes its first parameter as the query with the ? symbol as replacements for values I want to bind, the second parameter is the values to bind to it in an array that is then looped through inside the prepared_statement method and the third parameter is the type ( FETCH_ARRAY returns an array of a SELECT statements rows, NUM_ROWS returns the amount of rows affected and INSERT returns the last inserted ID).
An example of how I would call this function is below:
$db->prepared_execute( "SELECT * FROM whatever WHERE ? = ? ", array( 'password', 'letmein' ), NUM_ROWS );
The second and third parameters are optional for if there are no parameters to be bound or no return is needed.
As I am new to OOP I am finding it hard to get my head around exactly when to use correctly and what are public, private, static functions/variables and design patterns (Singleton etc.).
I've read many tutorials to get as far as I hav but I feel now I need to take it here to get further answers or advice on where to go next with OOP and with this class I've built.
It works as it is which for me is a good starting place except for any error handling which I will add in next but I want to make sure I am not making any obvious design errors here.
The code for the class is below:
class db {
var $pdo;
public function __construct() {
$this->pdo = new PDO('mysql:dbname=' . DB_NAME . ';host=' . DB_HOST . ';charset=utf8', DB_USER, DB_PASS);
$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function prepared_execute( $query, $bind_values = null, $type = null ) {
$preparedStatement = $this->pdo->prepare( $query );
if( $bind_values ) {
$i = 1;
foreach( $bind_values as $bind_value ) {
$preparedStatement->bindValue($i, $bind_value);
$i++;
} }
$preparedStatement->execute();
if( $type == FETCH_ARRAY ) { return $preparedStatement->fetchAll(); }
elseif( $type == NUM_ROWS ) { return $preparedStatement->rowCount(); }
elseif( $type == INSERT ) { return $this->pdo->lastInsertId(); }
else{ return true; }
}
Your code is a bit outdated. You should use one of the visibility keywords instead of var to declare your properties. In this case you probably want to use protected so that it cant be modified from outside the class but so that any future sub classes can modify it internally. Youll also probably want to add a getter in-case you need to work with PDO directly (which you will - see my final statements below my class example).
Its bad to hard code you PDO connection information in the class. You should pass these in as parameters same as you would if using PDO directly. I would also add the ability to pass in a pre-configured PDO instance as well.
While not required, its a good idea to conform to PSR-0 through PSR-2; Sepcifically in your case im speaking about Class and method naming which should both be camelCase and the first char of the class should be capital. Related to this your code formatting is also ugly particularly your block statements... If thats jsut an issue with copy and paste then ignore that comment.
So overall i would refactor your code to look something like this:
class Db {
protected $pdo;
public function __construct($dsn, $user, $pass, $options = array()) {
if($dsn instanceof PDO) {
// support passing in a PDO instance directly
$this->pdo = $dsn;
} else {
if(is_array($dsn)) {
// array format
if(!empty($options)) {
$dsn['options'] = $options;
}
$dsn = $this->buildDsn($options);
} else {
// string DSN but we need to append connection string options
if(!empty($options)) {
$dsn = $this->buildDsn(array('dsn' => $dsn, 'options' => $options));
}
}
// otherwise just use the string dsn
// ans create PDO
$this->pdo = new PDO($dsn, $user, $pass);
}
// set PDO attributes
$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function getConnection()
{
return $this->pdo;
}
protected function buildDsn($options) {
if($isDbParts = isset($options['dbname'], $options['hostname']) || !($isDsn = isset($option['dsn']))) {
throw new Exception('A dsn OR dbname and hostname are required');
}
if($isDsn === true) {
$dsn = $options['dsn'];
} else if {
$format = '%s:dbname=%s;host=%s';
$driver = isset($options['dbtype']) ? $options['dbtype'] : 'mysql';
$dsn = sprintf($format, $options['dbtype'], $options['dbname'], $options['host']);
}
if(isset($options['options'])) {
$opts = array();
foreach($options['options'] as $name => $value) {
$opts[] = $name . '=' . $value;
}
if(!empty($opts)) {
$dsn .= ';' . implode(';', $opts);
}
}
return $dsn;
}
public function preparedExecute( $query, $bind_values = null, $type = null ) {
$preparedStatement = $this->pdo->prepare( $query );
if( $bind_values ) {
$i = 1;
foreach( $bind_values as $bind_value ) {
$preparedStatement->bindValue($i, $bind_value);
$i++;
}
}
$preparedStatement->execute();
if( $type == FETCH_ARRAY ) {
return $preparedStatement->fetchAll();
}
elseif( $type == NUM_ROWS ) {
return $preparedStatement->rowCount();
}
elseif( $type == INSERT ) {
return $this->pdo->lastInsertId();
}
else {
return true;
}
}
}
Lastly, unless this is just for educational purposes I wouldnt do this. There are a ton of different queries that have varying part assemblies which arent considered here so at some point this is not going to support what you need it to do. Instead i would use Doctrine DBAL, Zend_Db or something similar that is going to support greater query complexity through its API. In short, dont reinvent the wheel.
I have developed something similar and it can helps you.
public function select($sql, $array = array(), $fetchMode = PDO::FETCH_ASSOC){
$stmt = $this->prepare($sql);
foreach ($array as $key => $value){
$stmt->bindValue("$key", $value);
}
$stmt->execute();
return $stmt->fetchAll();
}
public function insert($table, $data){
ksort($data);
$fieldNames = implode('`,`', array_keys($data));
$fieldValues = ':' .implode(', :', array_keys($data));
$sql = "INSERT INTO $table (`$fieldNames`) VALUES ($fieldValues)";
$stmt = $this->prepare($sql);
foreach ($data as $key => $value){
$stmt->bindValue(":$key", $value);
}
$stmt->execute();
}