phpunit test a mapper with silex php - php

I would like test my class LocationMapper whose the aim is make CRUD operations:
<?php
class LocationMapper {
/*
* Database connection.
*/
var $db = NULL;
/*
* Constructor function. Loads model from database if the id is known.
*
* #param $db
* Database connection
*/
function __construct($app) {
$this->db = $app['db'];
}
/**
* Load data from the db for an existing user.
*/
function read($app, $id) {
$statement = $this->db->prepare('SELECT * FROM location WHERE id = :id');
$statement->execute(array(':id' => $id));
$data = $statement->fetch(PDO::FETCH_ASSOC);
$comments = $app['CommentMapper']->loadAllByLocation($id);
$location = new Location($data['latitude'], $data['longitude'], $data['address'], $data['name'], $data['checked'], $comments);
$location->set('id', $data['id']);
return $location;
}
...
}
And in my index.php, I create my database connection and my mapper like this :
require_once __DIR__ . '/../config.php';
// Add the database connection as a shared service to the container.
$db_string = 'mysql:host=' . $app['db.hostname'] .';dbname=' . $app['db.dbname'];
$app['db'] = $app->share(function ($app) use ($db_string) {
try {
return new PDO($db_string, $app['db.username'], $app['db.password']);
}
catch (PDOException $e) {
$app->abort(500, 'Unable to connect to database. Check your configuration');
}
});
// Initialize mappers
$app['LocationMapper'] = $app->share(function () use ($app) {
return new LocationMapper($app);
});
How test the class LocationMapper with a real database ?
I tested this :
class LocationMapperTest extends PHPUnit_Framework_TestCase
{
protected $locationMapper;
protected $app;
public function setUp() {
$app = new Silex\Application();
// Add the database connection as a shared service to the container.
$db_string = 'mysql:host=' . $app['db.hostname'] .';dbname=' . $app['db.dbname'];
$app['db'] = $app->share(function ($app) use ($db_string) {
try {
return new PDO($db_string, $app['db.username'], $app['db.password']);
}
catch (PDOException $e) {
$app->abort(500, 'Unable to connect to database. Check your configuration');
}
});
$locationMapper = new LocationMapper();
}
public function createTest() {
$this->assertEquals(0, 0);
}
}
but I have an error PHP Fatal error: Class 'Silex\Application' not found

I guess you've installed Silex using Composer, so you are including the Composer autoload at the very beginning of your application (probably inside your config.php) to automatically include all needed files.
The problem is that PHPUnit is not using your config file, so it's not autoloading the classes: PHPUnit won't use an autoload file unless you told it to do so.
To tell PHPUnit to load your autoload file you have two options:
The first one is to use the bootstrap switch while executing the tests on the command line. That way, PHPUnit will include the bootstrap file at the beginning of the tests execution. If you pass it your autoload file, it will autoload your classes.
The second options is to use a configuration file, where you specify your bootstrap file.
Apart from that, a few observations about your code. Your unit tests shouldn't use your real database for testing. That way, your tests will modify the real database, making the tests unrepeteable (you'd have to re-populate the database with data if you delete rows, for example), slow, and, more importantly, you may affect real data.
I'd recommend you to mock your database, or use a database for testing purposes at least.

Related

Symfony 4: Keep SQLite PDO connection in test + controller + twig extensions

My situation
I have a Symfony 4.2 project with the following structure:
src
Controller
Service
Twig Extensions
Templates
Tests
I use a database class, which internally creates a PDO connection. If i run my tests with PHPUnit, my database class has to switch from mysql to sqlite. Everything works fine here.
My problem
I can not keep the once created Database instance, when running just one test. Symfony seems to recreate it during the test: when inside a Twig template which uses a Twig extension. Because the database class uses
new \PDO('sqlite::memory:');
it loses created tables and therefore the test fails. I am aware of, that the Database instance (with PDO reference) gets reseted after each test, but in my situation i only have one test. How can i ensure, that it re-uses the Database instance?
Here the related code
InstanceExtension class provides the function title, which is used in a Twig template and has to access the database.
<?php
namespace App\TwigExtension;
use App\Service\Database;
use Twig\TwigFunction;
use Twig\Extension\AbstractExtension;
class InstanceExtension extends AbstractExtension
{
protected $db;
/**
* #param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* Tries to return the title/name for a given ID.
*
* #param string $subject
* #param string|array $tables
* #param string $lang Default is 'de'
*
* #return string label or id, if no title/name was found
*/
public function title(string $subject, $tables, string $lang = 'de'): string
{
return $this->db->get_instance_title($subject, $tables, $lang);
}
}
In my services.yaml the Database class is set to public (which should enable reusing it, doesn't it?):
App\Service\Database:
public: true
Database class
Here is the part of the Database class which initializes the PDO connection. Production code, which uses MySQL instead, removed:
<?php
declare(strict_types=1);
namespace App\Service;
class Database
{
/**
* #param Config $app_config
*
* #throws \Exception
*/
public function __construct(Config $app_config)
{
global $sqlite_pdo;
try {
// non test
// ..
// test environment
} else {
$this->db_engine = 'sqlite';
// workaround to ensure we have the same PDO instance everytime we use the Database instance
// if we dont use it, in Twig extensions a new Database instance is created with a new SQLite
// database, which is empty.
if (null == $sqlite_pdo) {
$pdo = new \PDO('sqlite::memory:');
$sqlite_pdo = $pdo;
} else {
$pdo = $sqlite_pdo;
}
}
} catch (\PDOException $e) {
if (\strpos((string) $e->getMessage(), 'could not find driver') !== false) {
throw new \Exception(
'Could not create a PDO connection. Is the driver installed/enabled?'
);
}
if (\strpos((string) $e->getMessage(), 'unknown database') !== false) {
throw new \Exception(
'Could not create a PDO connection. Check that your database exists.'
);
}
// Don't leak credentials directly if we can.
throw new \Exception(
'Could not create a PDO connection. Please check your username and password.'
);
}
if ('mysql' == $this->db_engine) {
// ...
}
// default fetch mode is associative
$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
// everything odd will be handled as exception
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->db = $pdo;
}
// ...
}
One test looks kinda like:
<?php
class SalesChannelControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
// init Database class, using SQLite
$this->init_db();
// further setup function calls
// SalesChannelController is a child of
// Symfony\Bundle\FrameworkBundle\Controller\AbstractController
$this->fixture = new SalesChannelController(
$this->db
// ...
);
}
/**
* Returns a ready-to-use instance of the database. Default adapter is SQLite.
*
* #return Database
*/
protected function init_db(): Database
{
// config parameter just contains DB credentials
$this->db = new Database($this->config);
}
public function test_introduction_action()
{
// preparations
/*
* run action
*
* which renders a Twig template, creates a Response and returns it.
*/
$response = $this->fixture->introduction_action($this->request, $this->session, 'sales_channel1');
/*
* check response
*/
$this->assertEquals(200, $response->getStatusCode());
}
}
My current workaround is to store the PDO instance in a global variable and re-use it, if required.
<?php
global $sqlite_pdo;
// ...
// inside Database class, when initializing the PDO connection
if (null == $sqlite_pdo) {
$pdo = new \PDO('sqlite::memory:');
$sqlite_pdo = $pdo;
} else {
$pdo = $sqlite_pdo;
}
If you need more info, please tell me. Thanks for your time and help in advance!

Is there a way to get pass .env varables to my app.json file?

I have deployed my app to heroku but I want to get my .json file(where I store my database information) to heroku's webserver without pushing my file to github(file is ignored). I got the recommendation to use .env file. Is it possible to pass the information from a .env file to a .json file or do I need do change my code in some way?
I have tried logging into heroku to add the file on the server but it doesn't work because when the app restarts the app it resets.
app.json
{
"db": {
"dsn": "mysql:host=adress;dbname=database;charset=utf8",
"user": "user",
"password": "pass"
}
}
Connection.php
<?php
namespace TwitterClone\Core;
use \PDO;
use TwitterClone\Core\Config;
use TwitterClone\Utils\Singleton;
class Connection extends Singleton
{
public $handler;
protected function __construct()
{
try {
$config = Config::getInstance()->get('db');
$this->handler = new PDO(
$config['dsn'],
$config['user'],
$config['password']
);
$this->handler->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo $e->getMessage();
}
}
}
Config.php
<?php
namespace TwitterClone\Core;
use TwitterClone\Utils\Singleton;
use TwitterClone\Exceptions\NotFoundException;
/**
* Singleton
*/
class Config extends Singleton
{
private $data;
protected function __construct()
{
$json = file_get_contents(__DIR__ . '/../../config/app.json');
$this->data = json_decode($json, true);
}
public function get($key)
{
if (!isset($this->data[$key])) {
throw new NotFoundException("Key $key not in config.");
}
return $this->data[$key];
}
}
I want to push the file to be on heroku's server but not on github. or a way to get information to the app.json file without showing sensitive information.

Singleton alternative for PHP PDO

This is my class that I'm using to connect to my MySQL database.
As you can see I'm using the Singleton Pattern but almost every post says it is a very bad pattern. What is the best approach to create a database connection class? Is there a better pattern?
class DB extends PDO {
function __construct() {
try {
parent::__construct('mysql:host=' . 'localhost' . ';dbname=' . 'kida', 'root', 'root', array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'");
parent::setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo $e->getMessage();
}
}
public static function get_instance() {
static $instance = false;
if(!$instance) $instance = new self;
return $instance; //returns pdo object.
}
}
Using the singleton-pattern (or antipattern) is considered bad practice because it makes testing your code very hard and the depencies very convoluted until the project becomes hard to manage at some point. You can only have one fixed instance of your object per php-process. When writing automated unit-tests for your code you need to be able to replace the object the code you want to test uses with a test-double that behaves in a prdictable manner. When the code you want to test uses a singleton, then you cannot replace that with a test double.
The best way (to my knowlege) to organize the interaction between objects (like your Database-Object and other objects using the database) would be to reverse the direction of the depencies. That means that your code is not requesting the object it needs from an external source (in most cases a global one like the static 'get_instance' method from your code) but instead gets its depency-object (the one it needs) served from outside before it needs it. Normally you would use a Depency-Injection Manager/Container like this one from the symfony project to compose your objects.
Objects that use the database-object would get it injected upon construction. It can be injected either by a setter method or in the constructor. In most cases (not all) is it better to inject the depency (your db-object) in the constructor because that way the object that uses the db-object will never be in an invalid state.
Example:
interface DatabaseInterface
{
function query($statement, array $parameters = array());
}
interface UserLoaderInterface
{
public function loadUser($userId);
}
class DB extends PDO implements DatabaseInterface
{
function __construct(
$dsn = 'mysql:host=localhost;dbname=kida',
$username = 'root',
$password = 'root',
) {
try {
parent::__construct($dsn, $username, $password, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'");
parent::setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo $e->getMessage();
}
}
function query($statement, array $parameters = array())
{
# ...
}
}
class SomeFileBasedDB implements DatabaseInterface
{
function __construct($filepath)
{
# ...
}
function query($statement, array $parameters = array())
{
# ...
}
}
class UserLoader implements UserLoaderInterface
{
protected $db;
public function __construct(DatabaseInterface $db)
{
$this->db = $db;
}
public function loadUser($userId)
{
$row = $this->db->query("SELECT name, email FROM users WHERE id=?", [$userId]);
$user = new User();
$user->setName($row[0]);
$user->setEmail($row[1]);
return $user;
}
}
# the following would be replaced by whatever DI software you use,
# but a simple array can show the concept.
# load this from a config file
$parameters = array();
$parameters['dsn'] = "mysql:host=my_db_server.com;dbname=kida_production";
$parameters['db_user'] = "mydbuser";
$parameters['db_pass'] = "mydbpassword";
$parameters['file_db_path'] = "/some/path/to/file.db";
# this will be set up in a seperate file to define how the objects are composed
# (in symfony, these are called 'services' and this would be defined in a 'services.xml' file)
$container = array();
$container['db'] = new DB($parameters['dsn'], $parameters['db_user'], $parameters['db_pass']);
$container['fileDb'] = new SomeFileBasedDB($parameters['file_db_path']);
# the same class (UserLoader) can now load it's users from different sources without having to know about it.
$container['userLoader'] = new UserLoader($container['db']);
# or: $container['userLoader'] = new UserLoader($container['fileDb']);
# you can easily change the behaviour of your objects by wrapping them into proxy objects.
# (In symfony this is called 'decorator-pattern')
$container['userLoader'] = new SomeUserLoaderProxy($container['userLoader'], $container['db']);
# here you can choose which user-loader is used by the user-controller
$container['userController'] = new UserController($container['fileUserLoader'], $container['viewRenderer']);
Notice how the different classes no not know about each other. There are no direct depencies between them. This is done by not require the actual class in the constructor, but instead require the interface that provides the methods it needs.
That way you can always write replacements for your classes and just replace them in the depency-injection container. You do not have to check the whole codebase because the replacement just has to implement the same interface that is used by all other classes. You know that everything will continue to work because every component using the old class only knows about the interface and calls only methods known by the interface.
P.S.: Please excuse my constant references to the symfony project, it is just what i am most used to. Other project's like Drupal, Propel or Zend probably also have concepts like this.

PHPUnit RabbitMQ: write test for create connection function

I'm facing the following problem. I've wrote a function that create a connection object (AMQPConnection) given the required parameters. Now I want to write the corresponding unit test. I just don't know how to do it without having the RabbitMQ broker running. Here is the function in question:
public function getConnection($hostKey, array $params)
{
$connection = null;
try {
$connection = new AMQPConnection(
$params['host'],
$params['port'],
$params['username'],
$params['password'],
$params['vhost']
);
// set this server as default for next connection connectionAttempt
$this->setDefaultHostConfig($hostKey, $params);
return $connection;
} catch (\Exception $ex) {
if ($this->isAttemptExceeded()) {
return $connection;
} else {
// increment connection connectionAttempt
$this->setConnectionAttempt($this->getConnectionAttempt() + 1);
return $this->getConnection($hostKey, $params);
}
}
}
You usually don't test code like this as a Unittest since the result would more likely tell you that your server is installed correctly and not that your code is working.
Do you test if PDO returns a valid database connection?
It could make sense if you test your installation but not to test if the php c libs are working correctly.
You should add an ability to change the class being instantiated.
Add an ability to change the connector class via constructor or setter, with a default AMQPConnector class by default. Use it to create a connector object. Example
Create a mocked connector class for unit tests. Example
Use it setting the mock class via constructor or setter in tests. Example
You could also do assertions on what arguments are passed to the connector constructor.
Another way would be to use amqp interop so you are not coupled to any implementation. Much easier to tests as you deal with pure interfaces only.
#chozilla
#zerkms
So thanks to your hints, I've decided to use Closure in order to isolate the code portion connecting to the server.
Here is the closure:
$connectionFunction = function ($params) {
return new AMQPStreamConnection(
$params['host'],
$params['port'],
$params['username'],
$params['password'],
$params['vhost']
);
};
And here the modified getConnection() function
/**
* #param string $hostKey The array key of the host connection parameter set
* #param array $params The connection parameters set
* #return null|AMQPStreamConnection
*/
public function getConnection($hostKey, array $params)
{
$connection = null;
try {
$connection = call_user_func($connectionFunction, $params);
// set this server as default for next connection connectionAttempt
$this->setDefaultHostConfig($hostKey, $params);
return $connection;
} catch (\Exception $ex) {
if ($this->isAttemptExceeded()) {
return $connection;
} else {
// increment connection connectionAttempt
$this->setConnectionAttempt($this->getConnectionAttempt() + 1);
return $this->getConnection($hostKey, $params);
}
}
}
For the unit test I did the following:
$mockConnection = $this->getMockBuilder('PhpAmqpLib\Connection\AMQPStreamConnection')
->disableOriginalConstructor()
->getMock();
$connectionFunction = function ($params) use ($mockConnection) {
return $mockConnection;
};
Or this to have an Exception.
$connectionFunction = function ($params) {
throw new \Exception;
};
N.B.: I'm using AMQPStreamConnection in the getConnection() function since AMQPConnection is marked as deprecated in PhpAmqpLib

Dynamic class name in PHP

I'm trying to create a system that it has a GeneralObj. The GeneralObj allows user to initiate special objects for database's tables with a String of the table name. So far, it works perfect.
class GeneralObj{
function __construct($tableName) {
//...
}
}
However, it is too tired to type new GeneralObj(XXX) every time.
I am wondering is that possible to simplify the process to like new XXX(), which is actually running the same as new GeneralObj(XXX)?
I spot PHP provided __autoload method for dynamic loading files in the setting include_path but it requires a the actually definition file existing. I really don't want to copy and copy the same definition files only changing a little.
For cause, eval is not an option.
Maybe you can just auto-create the files in the autoloader:
function __autoload($class_name) {
// check for classes ending with 'Table'
if (preg_match('/(.*?)Table/', $class_name, $match)) {
$classPath = PATH_TO_TABLES . '/' . $match[1] . '.php';
// auto-create the file
if (!file_exists($classPath)) {
$classContent = "
class $class_name extends GeneralObj {
public __construct() {
parent::__construct('{$match[1]}');
}
}";
file_put_contents($classPath, $classContent);
}
require_once $classPath;
}
}
Use inheritance. Make GeneralObj the superclass of the table specific classes. This way you can dynamically derive class names and instantiate objects. Example:
class someTable extends GeneralObj {
}
$tableName = 'some';
$className = $tableName . 'Table';
$obj = new $className;
No, this is not possible.
The runkit extension allows programmatic manipulation of the PHP runtime environment, but it cannot do this. Even if it could, it would IMHO be a very bad idea, greatly impacting the requirements and complexity of the application in exchange for saving a few keystrokes.
In an unrelated note, your GeneralObj class has functionality that sounds suspiciously like that of a dependency injection container. Perhaps you should consider replacing it with one?
Something like this autoloader:
myAutoloader::Register();
class myAutoloader
{
/**
* Register the Autoloader with SPL
*
*/
public static function Register() {
if (function_exists('__autoload')) {
// Register any existing autoloader function with SPL, so we don't get any clashes
spl_autoload_register('__autoload');
}
// Register ourselves with SPL
return spl_autoload_register(array('myAutoloader', 'Load'));
} // function Register()
/**
* Autoload a class identified by name
*
* #param string $pClassName Name of the object to load
*/
public static function Load($pClassName){
if (class_exists($pClassName,FALSE)) {
// Already loaded
return FALSE;
}
$pClassFilePath = str_replace('_',DIRECTORY_SEPARATOR,$pClassName) . '.php';
if (file_exists($pClassFilePath) === FALSE) {
// Not a class file
return new GeneralObj($pClassName);
}
require($pClassFilePath);
} // function Load()
}
And it's up to GeneralObj to throw an exception if the table class can't be instantiated

Categories