Hopefully i am asking this on the right stack exchange forum. If not please do let me know and I will ask somewhere else. I have also asked on Code Review, but the community seems a lot less active.
As I have self learned PHP and all programming in general, I have only recently found out about 'Data Mappers' which allows data to be passed into classes without said classes knowing where the data comes from. I have read some of the positives of using mappers and why they make it 'easier' to perform upgrades later down the line, however I am really struggling to find out the reccomended way of using mappers and their layouts in a directory structure.
Let's assume we have a simple application whos purpose is to echo out a first name and last name of a user.
The way I have been using/creating mappers (as well as the file structure is as follows):
index.php
include 'classes/usermapper.php';
include 'classes/user.php';
$user = new User;
$userMapper = new userMapper;
try {
$user->setData([
$userMapper->fetchData([
'username'=>'peter1'
])
]);
} catch (Exception $e) {
die('Error occurred');
}
if ($user->hasData()) {
echo $user->fullName();
}
classes/user.php
class User {
private $_data;
public function __construct() { }
public function setData($userObject = null) {
if (!$userObject) { throw new InvalidArgumentException('No Data Set'); }
$this->_data = $dataObject;
}
public function hasData() {
return (!$this->_data) ? false : true;
}
public function fullName() {
return ucwords($this->_data->firstname.' '.$this->_data->lastname);
}
}
classes/usermapper.php
class userMapper {
private $_db;
public function __construct() { $this->_db = DB::getInstance(); }
public function fetchData($where = null) {
if (!is_array($where)) {
throw new InvalidArgumentException('Invalid Params Supplied');
}
$toFill = null;
foreach($where as $argument=>$value) {
$toFill .= $argument.' = '.$value AND ;
}
$query = sprintf("SELECT * FROM `users` WHERE %s ", substr(rtrim($toFill), 0, -3));
$result = $this->_db->query($query); //assume this is just a call to a database which returns the results of the query
return $result;
}
}
With understanding that the users table contains a username, first name and last name, and also that a lot of sanitizing checks are missing, why are mappers convenient to use?
This is a very long winded way in getting data, and assuming that users aren't everything, but instead orders, payments, tickets, companies and more all have their corresponding mappers, it seems a waste not to create just one mapper and implement it everywhere in each class.
This allows the folder structure to look a whole lot nicer and also means that code isn't repeated as often.
The example mappers looks the same in every case bar the table the data is being pulled from.
Therefore my question is. Is this how data mappers under the 'domain model mappers' should look like, and if not how could my code be improved? Secondly is this model needed in all cases of needing to pull data from a database, regardless of the size of class, or should this model only be used where the user.php class in this case is very large?
Thank you in advance for all help.
The Data Mapper completely separates the domain objects from the persistent storage (database) and provides methods that are specific to domain-level operations. Use it to transfer data from the domain to the database and vice versa. Within a method, a database query is usually executed and the result is then mapped (hydrated) to a domain object or a list of domain objects.
Example:
The base class: Mapper.php
abstract class Mapper
{
protected $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
}
The file: BookMapper.php
class BookMapper extends Mapper
{
public function findAll(): array
{
$sql = "SELECT id, title, price, book_category_id FROM books;";
$statement = $this->db->query($sql);
$items = [];
while ($row = $statement->fetch()) {
$items[] = new BookEntity($row);
}
return $items;
}
public function findByBookCategoryId(int $bookCategoryId): array
{
$sql = "SELECT id, title, price, book_category_id
FROM books
WHERE book_category_id = :book_category_id;";
$statement = $this->db->prepare($sql);
$statement->execute(["book_category_id" => $bookCategoryId]);
$items = [];
while ($row = $statement->fetch()) {
$items[] = new BookEntity($row);
}
return $items;
}
/**
* Get one Book by its ID
*
* #param int $bookId The ID of the book
* #return BookEntity The book
* #throws RuntimeException
*/
public function getById(int $bookId): BookEntity
{
$sql = "SELECT id, title, price, book_category_id FROM books
WHERE id = :id;";
$statement = $this->db->prepare($sql);
if (!$result = $statement->execute(["id" => $bookId])) {
throw new DomainException(sprintf('Book-ID not found: %s', $bookId));
}
return new BookEntity($statement->fetch());
}
public function insert(BookEntity $book): int
{
$sql = "INSERT INTO books SET title=:title, price=:price, book_category_id=:book_category_id";
$statement = $this->db->prepare($sql);
$result = $statement->execute([
'title' => $book->getTitle(),
'price' => $book->getPrice(),
'book_category_id' => $book->getBookCategoryId(),
]);
if (!$result) {
throw new RuntimeException('Could not save record');
}
return (int)$this->db->lastInsertId();
}
}
The file: BookEntity.php
class BookEntity
{
/** #var int|null */
protected $id;
/** #var string|null */
protected $title;
/** #var float|null */
protected $price;
/** #var int|null */
protected $bookCategoryId;
/**
* Accept an array of data matching properties of this class
* and create the class
*
* #param array|null $data The data to use to create
*/
public function __construct(array $data = null)
{
// Hydration (manually)
if (isset($data['id'])) {
$this->setId($data['id']);
}
if (isset($data['title'])) {
$this->setTitle($data['title']);
}
if (isset($data['price'])) {
$this->setPrice($data['price']);
}
if (isset($data['book_category_id'])) {
$this->setBookCategoryId($data['book_category_id']);
}
}
/**
* Get Id.
*
* #return int|null
*/
public function getId(): ?int
{
return $this->id;
}
/**
* Set Id.
*
* #param int|null $id
* #return void
*/
public function setId(?int $id): void
{
$this->id = $id;
}
/**
* Get Title.
*
* #return null|string
*/
public function getTitle(): ?string
{
return $this->title;
}
/**
* Set Title.
*
* #param null|string $title
* #return void
*/
public function setTitle(?string $title): void
{
$this->title = $title;
}
/**
* Get Price.
*
* #return float|null
*/
public function getPrice(): ?float
{
return $this->price;
}
/**
* Set Price.
*
* #param float|null $price
* #return void
*/
public function setPrice(?float $price): void
{
$this->price = $price;
}
/**
* Get BookCategoryId.
*
* #return int|null
*/
public function getBookCategoryId(): ?int
{
return $this->bookCategoryId;
}
/**
* Set BookCategoryId.
*
* #param int|null $bookCategoryId
* #return void
*/
public function setBookCategoryId(?int $bookCategoryId): void
{
$this->bookCategoryId = $bookCategoryId;
}
}
The file: BookCategoryEntity.php
class BookCategoryEntity
{
const FANTASY = 1;
const ADVENTURE = 2;
const COMEDY = 3;
// here you can add the setter and getter methods
}
The table structure: schema.sql
CREATE TABLE `books` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`price` decimal(19,2) DEFAULT NULL,
`book_category_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `book_category_id` (`book_category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `book_categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
/*Data for the table `book_categories` */
insert into `book_categories`(`id`,`title`) values (1,'Fantasy');
insert into `book_categories`(`id`,`title`) values (2,'Adventure');
insert into `book_categories`(`id`,`title`) values (3,'Comedy');
Usage
// Create the database connection
$host = '127.0.0.1';
$dbname = 'test';
$username = 'root';
$password = '';
$charset = 'utf8';
$collate = 'utf8_unicode_ci';
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES $charset COLLATE $collate"
];
$db = new PDO($dsn, $username, $password, $options);
// Create the data mapper instance
$bookMapper = new BookMapper($db);
// Create a new book entity
$book = new BookEntity();
$book->setTitle('Harry Potter');
$book->setPrice(29.99);
$book->setBookCategoryId(BookCategoryEntity::FANTASY);
// Insert the book entity
$bookId = $bookMapper->insert($book);
// Get the saved book
$newBook = $bookMapper->getById($bookId);
var_dump($newBook);
// Find all fantasy books
$fantasyBooks = $bookMapper->findByBookCategoryId(BookCategoryEntity::FANTASY);
var_dump($fantasyBooks);
Related
I created this code where I create an instance of the database and work with it. Now I'm trying to convert the code to a static form, but I can't.
$pdo = new PDO('sqlite:src/chinook.db');
$sql = "CREATE TABLE IF NOT EXISTS uzivatele(
uzivatelId INTEGER PRIMARY KEY,
jmeno TEXT,
prijmeni TEXT,
body INTEGER
);";
$statement = $pdo->prepare($sql);
$statement->execute();
function dropTable($pdo,$name)
{
$sql = "DROP TABLE $name";
$statement = $pdo->prepare($sql);
$statement->execute();
}
...
static
This is how I have a class implemented for pdo (according to the manual) and I would like to implement static methods, such as createTable, but I can't redo it
class Db
{
protected static $pdo = null;
public static function get(): \PDO
{
return self::$pdo ?? (self::$pdo = new \PDO(
'sqlite:hw-06.db',
null,
null,
[
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
]
));
}
}
use App\Db;
class Account
{
...
public static function createTable(): void
{
$db = Db::get();
$sql = "CREATE TABLE IF NOT EXISTS uzivatele(
uzivatelId INTEGER PRIMARY KEY,
jmeno TEXT,
prijmeni TEXT,
body INTEGER
);";
$statement = $db->prepare($sql);
$statement->execute();
}
index.php
Account::createTable();
If u want to implement a simple singleton, u can use the "getInstance()" concept and combine with "__callStatic" and "call_user_func_array" to make a PDO functions to be static too, all PDO and Database class functions will become static:
<?php
declare(strict_types = 1);
/*
* PDO database class - only one connection alowed
*/
final class Database
{
/**
* #var PDO $connection The connection
*/
private $connection;
/**
* #var Database $instance The single instance
*/
private static $instance;
/**
* #var string $engine The engine of connection
*/
private $engine = 'sqlite:persistence.db'; // sqlite::memory:
/**
* #var array $options Default option to PDO connection
*/
private $options = [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false
];
/**
* Private constructor to prevent instance
*
* #throws \Throwable
* #return void
*/
private function __construct()
{
try {
$this->connection = new PDO($this->engine, null, null, $this->options);
}
catch (\Throwable $error) {
error_log("{$error->getMessage()}");
}
}
/**
* Get an instance of the Database
*
* #return PDO
*/
private static function getInstance(): PDO
{
// If no instance then make one
if (!self::$instance) {
self::$instance = new self;
}
return self::$instance->connection;
}
/**
* Transpiler of static methods for PDOStatements
*
* #var string $method The PDO static method
* #var array $args
* #return string|PDOStatement
*/
public static function __callStatic(string $method, array $args)
{
return call_user_func_array(array(self::getInstance(), $method), $args);
}
/**
* Destroying PDO connection
*
* #return void
*/
public function __destruct()
{
if (!empty($this->connection)) {
unset($this->connection);
}
}
/**
* Magic method clone is empty to prevent duplication of connection
*/
public function __clone() { }
public function __wakeup() { }
public function __toString() { }
}
to use there:
<?php
require_once __DIR__ . '/Database.php';
Database::exec('CREATE TABLE IF NOT EXISTS uzivatele (
uzivatelId INTEGER PRIMARY KEY,
jmeno TEXT,
prijmeni TEXT,
body INTEGER
);');
Database::exec("INSERT INTO uzivatele (jmeno, prijmeni, body) VALUES ('test', 'test', 1);");
var_dump(Database::lastInsertId());
$stmt = Database::prepare("SELECT * FROM uzivatele;");
$stmt->execute();
$data = $stmt->fetchAll();
var_dump($data);
note that "prepared statments objects" are still like objects!
i dont see any problem in using database connections as static, if they are not used in parallel, there is no problem, it even reduces the overhead of creating many connections with the database. but be careful, in some cases it may not be beneficial, as in cases where the code is not being executed by a CGI or FastCGI but by a wrapper, it can cause slowdowns and even give a problem!
I feel a bit stupid asking this question since there are a lot of resources talking and explaining mappers and repositories but I can't seem to get my head around it. So I've created some example code to explain my confusion. Please note that I don't know if this code would actually work I wrote this as an example.
This would be the entity / class (Quote.php)
class Quote {
private $id;
private $author;
private $content;
public function getId() {
return $this->id;
}
public function getAuthor() {
return $this->author;
}
public function getContent() {
return $this->content;
}
public function setId(int $id) {
$this->id = $id;
}
public function getAuthor(string $author) {
$this->author = $author;
}
public function setContent(string $content) {
$this->content = $content;
}
}
And this would be the mapper (QuoteMapper.php)
class QuoteMapper {
private $PDO;
public function __construct(PDO $PDO) {
$this->PDO = $PDO;
}
public function find(int $id = null, string $search = null) {
if (!empty($id) && !empty($search)) {
//Search for id and search word
$stmt = $this->PDO->prepare("SELECT `id`, `author`, `content` FROM `quotes` WHERE `id` = :id AND `content` LIKE :search LIMIT 1");
$stmt->bindParam('search', $search, PDO::PARAM_INT);
$stmt->bindParam('id', $id, PDO::PARAM_INT);
else if (!empty($id)) {
//search for id only
$stmt = $this->PDO->prepare("SELECT `id`, `author`, `content` FROM `quotes` WHERE `id` = :id AND `content` LIKE :search LIMIT 1");
$stmt->bindParam('id', $id, PDO::PARAM_INT);
} else if (!empty($search)) {
//search for search word only
$stmt = $this->PDO->prepare("SELECT `id`, `author`, `content` FROM `quotes` WHERE `id` = :id AND `content` LIKE :search LIMIT 1");
$stmt->bindParam('search', $search, PDO::PARAM_INT);
}
$stmt->execute();
$stmt->bindColumn('id', $id);
$stmt->bindColumn('author', $author);
$stmt->bindColumn('content', $content);
$stmt->fetch();
$quote = new Image();
$quote->setId($title);
$quote->setAuthor($source);
$quote->setContent($alternative);
return $image;
}
public function save(Quote $quote) {
//A save function
}
public function delete(Quote $quote) {
//A delete function
}
}
Last but not least, this would be the repository (QuoteRepository.php)
class ArticleRepository {
private $articleMapper;
public function __construct(ArticleMapper $articleMapper) {
$this->articleMapper = $articleMapper;
}
public function find(int $id = null, string $search = null) {
$article = $this->articleMapper->find($id, $search);
return $article;
}
public function save(Quote $quote) {
$this->articleMapper->save($user);
}
public function delete(Quote $quote) {
$this->articleMapper->delete($user);
}
}
As I understand my mapper isn't 'wrong' since the purpose of the mapper is to do things such as get and set data from persistent data storage (such as MySQL)
A Data Mapper is a Data Access Layer that performs bidirectional
transfer of data between a persistent data store (often a relational
database) and an in-memory data representation (the domain layer).
From Wikipedia
But my repository doesn't actually do anything. It just passes the function call along to the mapper? So I can only assume that my mapper contains code that should be in the repository, but what code would that be? Or perhaps I've completely misunderstood how data mappers and repositories would work together.
If there are any other things that I have done that are wrong or considered bad practice I would like to hear it. I'm really trying to figure this out! :)
DataMapper is a layer to isolate an application from a concrete database. It transforms an object into a record of a database and a record into an object. DataMapper gives us the ability to work with Database and be unaware of what RDBMS we use. Example:
interface DataMapperInterface
{
/**
* Find objects by a criteria
*
* #param array $criteria Search params
* #return Quote[] Found entities
*/
public function find(array $criteria);
/**
* Insert an object into a database
*
* #param Quote $object Object that will be inserted
*/
public function insert(Quote $object);
/**
* Update an object date in a database
*
* #param Quote $object Object that will be updated
*/
public function update(Quote $object);
/**
* Remove an object from a database
*
* #param Quote $object Object that will be removed
*/
public function delete(Quote $object);
}
Repository is a layer for encapsulation of logic of a query building. It gives us the ability to work with a collection of objects and be unaware to work with Database anything.
class Repository
{
/**
* #var DataMapperInterface Mapper to transform objects
*/
protected $mapper;
/**
* Constructor
*
* #param DataMapperInterface $mapper Mapper to transform objects
*/
public function __construct(DataMapperInterface $mapper)
{
$this->mapper = $mapper;
}
/**
* Find all objects
*
* #return Quote[] Found entities
*/
public function findAll()
{
return $this->mapper->find([]);
}
/**
* Find an object by an identifier
*
* #return Quote[] Found entities
*/
public function findById(integer $id)
{
$criteria = ['id' => $id];
return $this->mapper->find($criteria);
}
/**
* Find objects by an author name
*
* #return Quote[] Found entities
*/
public function findByAuthor($name)
{
$criteria = ['author' => $name];
return $this->mapper->find($criteria);
}
/**
* Save an object into the repository
*/
public function save(Quote $object)
{
if (empty($object->id)) {
$this->mapper->insert($object);
} else {
$this->mapper->update($object);
}
a }
/**
* Remove an object from the repository
*/
public function remove(Quote $object)
{
$this->mapper->delete($object);
}
}
This is the very simple instance and all is more difficult in a real application: queries are bigger, repository can cooperate with many other patterns (Query Object to query building, Unit of Work to track changes, Identity Map to avoid repeatedly-load of objects, etc.)
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I have thinking of this from past an hour, but unable to understand the best way to add update, Insert feature using a class in PHP.
I have an employee table which has around 5 columns, so i have created different properties for it in my Employees Class.
Now i am unable to understand where to set these Values, in __costructor(), in add_employees() method or some other way..
If i set them in __constructor() then user must remember and follow the same pattern in writing arguments that i followed in writing the parameters.
I am unable to understand that is it a right way or should i use different approach to do this.
I have searched this on net, and found very complex examples, where people are taking argument in form of array, separating it, add loop in it and then put it into database.
Class Employees(){
public $db;
public $conn;
public $emp_id;
public $first_name;
public $last_name;
public $gender;
public $added_by;
public function __construct(){
$this->db= new DatabaseConnection('localhost', 'root', '', 'employeesmanagement');
$this->conn= $this->db->connectDb();
}
public function get_employees(){
}
public function add_employees(){
}
I've written a class before, that does only things related to database things. Here is the class
/**
* CLASS: Database
*
* Description: This class deals with all database connection
* across the website. If any class needs to use the database
* it has to extends this one.
*
* #author: Andre Ferraz
* #copyright: ^
* #version: 2.0
*/
class Database
{
/**
* Holds Class Object instance
*
* #var Object
* #access: Private
* #static
*/
private static $_instace;
/**
* Holds PDO Object
*
* #var PDO
* #access: Private
*/
private $_pdo;
/**
* Used to keep track of how many columns
* has been found!
*
* #var int
* #access: Private
*/
private $_count;
/**
* Holds data from database
*
* #var array
* #access: Private
*/
private $_results = array();
/**
* Description: Instantiates PDO object
*
* #access: Protected
*/
protected function __construct()
{
$host = Config::get("database:host");
$user = Config::get("database:username");
$pass = Config::get("database:password");
$dbna = Config::get("database:dbname");
try
{
$this->_pdo = new PDO("mysql:dbname=".$dbna.";host=".$host.";", $user, $pass);
}
catch(PDOException $e)
{
Redirect::to(500);
}
}
/**
* Description: Gets data from the database
*
* #access: protected
* #return boolean
*/
protected function get($table, $columns, $condition = null)
{
if($condition != null)
{
$query = $this->_pdo->prepare("SELECT $columns FROM $table WHERE $condition");
if($query->execute())
{
$this->_count = $query->rowCount();
if($this->_count > 0)
{
$this->_results = $query->fetchAll();
return true;
}
return false;
}
}
return false;
//#todo condition == null
}
/**
* Description: Very similar to get function, but
* instead it just checks if data exists without storing
* any data.
*
* #access: protected
* #return boolean
*/
protected function query($table, $columns, $condition = null)
{
if($condition != null)
{
$query = $this->_pdo->prepare("SELECT $columns FROM $table WHERE $condition");
if($query->execute())
{
$this->_count = $query->rowCount();
return(($this->_count > 0)? true : false);
}
}
return false;
//#todo condition == null
}
/**
* Description: Updates information on the database
*
* #access: protected
*/
protected function update($table, $CV = array(), $condition)
{
if($CV !=null)
{
$columns = '';
$x = 1;
foreach($CV as $key => $value)
{
$columns .= "$key='$value'";
if($x < count($CV))
{
$columns .= ",";
}
$x++;
}
$query = $this->_pdo->prepare("UPDATE $table SET $columns WHERE $condition");
if($query->execute())
return true;
else
return false;
}
return false;
}
/**
* Description: Inserts data into database
*
* #access: protected
*/
protected function insert($table, $CV = array())
{
if($CV !=null)
{
// Join array elements with a string
$columns = implode(", ", array_keys($CV));
$values = '';
$x = 1;
// Put array key values into variables
foreach($CV as $value)
{
$values .= "'".$value."'";
if($x < count($CV))
{
$values .= ', ';
}
$x++;
}
$query = $this->_pdo->prepare("INSERT INTO $table ($columns) VALUES({$values})");
// Check execution is successful
if($query->execute())
return true;
else
return false;
}
return false;
}
/**
* Description: Deletes data from the database
*
* #access: protected
*/
protected function delete($table, $condition = null)
{
if($condition != null)
{
$query = $this->_pdo->prepare("DELETE FROM $table WHERE $condition");
if($query->execute())
return true;
else
return false;
}
else
{
$query = $this->_pdo->prepare("DELETE FROM $table");
if($query->execute())
return true;
else
return false;
}
}
protected function getResults()
{
return $this->_results;
}
/**
* Description: Singleton pattern, prevents multiple
* instantiations of the same class.
*
* NOTE: This is not needed. Only for "show of"
*
* #access: public
* #static
* #return Object
*/
public static function instance()
{
if(isset(self::$_instace))
return self::$_instace;
else
self::$_instace = new self;
}
}
Which other classes like User class would extend and use all the necessary function from the database to get data related to the user. Have a look at the project. There are some bugs in a few classes (which I can't be bothered to fix at this point), but database class is working fine. I don't mind if you get reference from it.
Visit my github for the full project.
Github
I'm writing PHPUnit tests to test MySQL database filling. My tests work fine locally, but are failing on Travis CI with the following error:
PHPUnit_Extensions_Database_Operation_Exception: COMPOSITE[TRUNCATE] operation failed on query:
TRUNCATE `simple_table`
using args: Array
[SQLSTATE[42S02]: Base table or view not found: 1146 Table 'test.simple_table' doesn't exist]
I have a DatabaseTestCase parent class and a FillCommandTest testing class.
DatabaseTestCase
class DatabaseTestCase extends PHPUnit_Extensions_Database_TestCase
{
private static $pdo = null;
private $connection = null;
/**
* Construct.
*/
public function __construct()
{
$env = new Environment();
$this->dsn = "mysql:dbname=". $env->get('DB_DATABASE', 'test') . ";host=" . $env->get('DB_HOST', '127.0.0.1');
$this->username = $env->get('DB_USERNAME', 'travis');
$this->password = $env->get('DB_PASSWORD', '');
$this->db_name = $env->get('DB_DATABASE', 'test');
}
/**
* Get database connection.
*
* #return PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection
*/
final public function getConnection()
{
if ($this->connection === null) {
if (self::$pdo == null) {
self::$pdo = new PDO($this->dsn, $this->username, $this->password);
}
$this->connection = $this->createDefaultDBConnection(self::$pdo, $this->db_name);
}
return $this->connection;
}
/**
* Get XML dataset.
*
* #param string $file
* #return string
*/
public function getDataSet($file = 'empty.xml')
{
$file = 'tests/datasets/' . $file;
return $this->createXmlDataSet($file);
}
/**
* Set up the test database table
*
* #param PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection $connection
*/
protected function setUpTable($connection)
{
$pdo = $connection->getConnection();
$sql = "CREATE TABLE IF NOT EXISTS simple_table (
id INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(50) NOT NULL,
address VARCHAR(100)
)";
$pdo->exec($sql);
}
}
FillCommandTest
class FillCommandTest extends DatabaseTestCase
{
protected $app;
protected $command;
protected $command_tester;
/**
*
*/
public function setUp()
{
$this->app = new Application();
$this->app->add(new FillCommand());
$this->command = $this->app->find('fill');
$this->command_tester = new CommandTester($this->command);
parent::setUp();
$connection = $this->getConnection();
$this->setUpTable($connection);
}
/**
*
*/
public function test_parse_and_fill_simple_table()
{
copy(getcwd() . '/tests/files/FillSimpleTable.php', getcwd() . '/src/Fillers/FillSimpleTable.php');
copy(getcwd() . '/tests/files/simple.txt', getcwd() . '/src/Files/simple.txt');
$this->command_tester->execute(array(
'command' => $this->command->getName(),
'name' => 'simple_table'
));
$actual = $this->getConnection()->createQueryTable('simple_table', 'SELECT * FROM simple_table');
$expected = $this->getDataSet('simple_table.xml')->getTable('simple_table');
$this->assertTablesEqual($expected, $actual);
unlink(getcwd() . '/src/Fillers/FillSimpleTable.php');
unlink(getcwd() . '/src/Files/simple.txt');
}
}
What am I doing wrong?
By the looks of it, it seems your code is referring to a database called 'test'.
But I am assuming since you are deploying, your database name should be different. Make sure your .env DB_DATABASE variable reflects the correct one on your production server.
I have a very weird behaviour with PDO. I won't go into much details as it would take up way too much time but basically what I observed is that when I re-use a \PDOStatement that performs a simple INSERT I sistematically get a wrong value when invoking PDO::lastInsertId().
The first time I execute the statement it works fine and I get back the right id. Subsequent executions will instead always return '0'. This is even more weird because it happens only between tests (PHPUnit ones). So say I execute the insert using the prepared statement in test1 (working), in test2 it will fail miserably.
When executing multiple times the prepared statement in a non unit-testing environment (in a simple php file fro instance) it all works fine and the last inserted ids are always accurate. Very weird indeed.
Here's the test (note that PersistencyManagerInstance is just a plain intsance of PersistencyManager):
<?php
class PersistencyManagerTest extends PHPUnit_Framework_TestCase {
const DELETE_ALL = "TRUNCATE user";
const ADD_USER = "INSERT INTO user values(null, :username, :password)";
const CHECK_USER_EXISTENCE = "SELECT * FROM user WHERE username = :username AND password = :password";
const DELETE_USER_BY_ID = "DELETE FROM user WHERE id = ?";
protected $manager = null;
public function __construct() {
$this->manager = new PersistencyManagerInstance(PDOFactory::build());
}
public function setUp() {
$this->manager->exec(self::DELETE_ALL);
}
public function tearDown() {
$this->manager->exec(self::DELETE_ALL);
}
public function testInsert() {
$user = new User("laurent", "password");
$id = $this->manager->insert(self::ADD_USER, $user->export());
$this->assertEquals("1", $id);
}
public function testInsertAgain() {
$user1 = new User("laurent1", "password1");
$id = $this->manager->insert(self::ADD_USER, $user1->export());
$this->assertEquals("1", $id);
}
public function testQuery() {
$user = new User("laurent", "password");
$this->manager->insert(self::ADD_USER, $user->export());
$results = $this->manager->query(self::CHECK_USER_EXISTENCE, $user->export());
$this->assertEquals(1, count($results));
}
public function testExec() {
$user = new User("laurent", "password---");
$id = $this->manager->insert(self::ADD_USER, $user->export());
$affected = $this->manager->exec(self::DELETE_USER_BY_ID, array($id));
$this->assertEquals(1, $affected);
}
}
testInsert works while testInsertAgain does not.
and here's the class:
<?php
namespace memory\manager;
use \PDO;
abstract class PersistencyManager {
/**
* #var array An array of \PDOStatement objects
*/
protected static $ps = array();
/**
* #var \PDO
*/
protected $connection = null;
protected function prepareStmt($sql) {
// return $this->connection->prepare($sql);
$key = md5($sql);
if (!isset(self::$ps[$key])) {
self::$ps[$key] = $this->connection->prepare($sql);
}
return self::$ps[$key];
}
public function __construct(PDO $connection) {
$this->connection = $connection;
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function __destruct() {
$this->connection = null;
}
/**
* Good for SELECT operations. By default it fetches using arrays.
* #param string $sql
* #param array $values
* #param integer $fetchStyle
* #return array A list of matching elements (The elements' type depends on $fetchStyle)
*/
public function query($sql, array $values = array(), $fetchStyle = PDO::FETCH_ASSOC) {
$prepared = $this->prepareStmt($sql);
$prepared->execute($values);
$prepared->setFetchMode($fetchStyle);
$all = $prepared->fetchAll();
$prepared->closeCursor();
return $all;
}
/**
* Good for INSERT operations.
* #param string $sql
* #param array $values
* #return string Last inserted element's id in string format
*/
public function insert($sql, array $values = array()) {
$prepared = $this->prepareStmt($sql);
$prepared->execute($values);
$prepared->closeCursor();
return $this->connection->lastInsertId();
}
/**
* Good for all the remaining routines.
* #param string $sql
* #param array $values
* #return integer The number of effected rows
*/
public function exec($sql, array $values = array()) {
$prepared = $this->prepareStmt($sql);
$prepared->execute($values);
$count = $prepared->rowCount();
$prepared->closeCursor();
return $count;
}
}
Any idea?
Cheers
guys I was starting a new connection at every test. That was the reason.