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.
Related
In a production environment when using prepared statements and all other validation is done, do I need to error check every every step of the way or can I just check end result of $stmt for true or false?
I am trying to clean up about 2000 lines of a function file and a lot of it just seems like wasted space when there is so much validation already done(ie checking for empty values, required values, empty fields etc).
Here is a rough, extremely simple, example of what I would like to do.
$sql = "SELECT count(*) FROM foo WHERE somecol = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s",$value);
$stmt->execute();
$stmt->bind_result($c);
$stmt->fetch();
if(false === $stmt){
//My error report
trigger_error("Something bad happened!" );
//Error user sees
$userErrorMsg[] 'Some generic msg here';
}
EDIT: I probably should have mention the $conn has been checked previously.
You have to decide if it's necessary in your case or not. But some programmers would say, that the code to catch an error is almost that much like the normal code.
In short: If there can be an error CATCH IT ;)
Otherwise I would recommend you to create a Wrapper class for your DB functions.
Just a small example to point you in the right direction:
class MyDBClass {
private static $con = null;
private static $instance = null;
public static function getInstance() {
if( null === self::$instance) {
// add error handling ;)
self::$instance = new MyDBClass();
self::$instance->setConnection();
}
return self::$instance;
}
private function setConnection() {
if( null === self::$con) {
// add error handling ;)
self::$con = mysqli_connect("localhost","my_user","my_password","my_db");
}
}
private function __construct() {}
public function select( $tableName, $columns = "*", $conditions = array(), $numRows = null ) {
// add your stuff with error handling
}
public function selectRow( $tableName, $columns = "*" , $conditions = array() ) {
// add your stuff with error handling
}
}
// use of class
$db = MyDBClass::getInstance();
$db->select( "mytable" );// would (for example) select * from mytable
NOTE: This is not a working example and I would recommend to get use a good framework or a small wrapper class
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 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();
}
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.
I'm not sure what's a better practice or a more real-world practice. I'm looking to create a Catalog System from scratch, but unsure what the best approach would be.
I was thinking that I use objects when I need to display information like, info.php?id=100. Have code like this for displaying
Game.class.php file
class Game {
private $name;
private $type;
private $database;
public function __construct($database, $id) {
$information = $database->select($id); // returns mysql_fetch_array
$this->database = $database;
$this->setName($information['name']);
$this->setType($information['type']);
}
public function __destruct() {
unset($this->name);
...
}
...
private function setName($name) {
$this->name = $name;
}
...
public function getName() {
return $this->name;
}
...
public function addToDatabase() {
// stuff here to check if it exists in the database or not
$database->insert('test', array('id' => '', 'name' => $this->name, 'type' => $this->type));
}
}
info.php?id=100 file
// already have __autload()
$id = sanitize($_POST['id']);
$item = new Game($db, $id);
echo "Item name: " . $item->getName() . " <br />";
But I don't really need to create objects for like when I add or update things add.php?name=XMen&type=GameBoy
I wouldn't want to do this:
$name = sanitize($_POST['name']); // sanitize
$type = sanitize($_POST['type']); // sanitize
$newObject = new Game($name, $type);
$newObject->addToDatabase();
I should instead I should just skip creating an object and just insert directly
$name = sanitize($_POST['name']); // sanitize
$type = sanitize($_POST['type']); // sanitize
$sql = "INSERT INTO test ('id', 'name', 'type') VALUES ('', $name, $type)";
mysql_query($sql);
or if I had a database class
$name = sanitize($_POST['name']); // sanitize
$type = sanitize($_POST['type']); // sanitize
$db->insert('test', array('id' => '', 'name' => $name, 'type' => $type));
To get complete abstraction you might look into 3rd-party ORMs. At the very, very least you should create your own database class. I usually use a Singleton design pattern if I only plan on having one database connection.
You don't want raw SQL statements all over the place because you may need to switch to and/or support different versions of MySQL or even a different database all together. It'd be easier to replace a single database management object than to fix all those queries throughout your code.
One of my database classes is much like that third option you posted and it works quite well. It has a bunch of conveniences like insertArray(), getOneRow(), getOne(), getOneFromAllRows(), executeExternalFile(), and an iteratable result set for all SELECT-like queries.
For this example, it doesn't seem necessary to create the object first. Just insert straight into the database.