CakePHP Logging to Live DB During Unit Testing - cakephp

I'm using DB logging in Cake 2.1, which works great.
The problem I'm having is when running Unit Tests, all logs are still getting sent to the live db rather than the test database.
All other db interactions go to test, except logging.
I do have a log fixture created and imported into the test case.
Here's my Database logger (/Lib/Log/Engine/DatabaseLogger.php)
App::uses('CakeLogInterface', 'Log');
class DatabaseLogger implements CakeLogInterface
{
public function __construct($options = array() )
{
App::import('Model', 'Log');
$this->Log = new Log;
}
public function write($type, $message)
{
$this->Log->create();
$log['type'] = ucfirst($type);
$log['date'] = date('Y-m-d H:i:s');
$log['message'] = $message;
return $this->Log->save($log);
}
}
I'm sure I'm missing some basic setting here but I can't figure this out for the life of me.

Well, in my case the problem was caused because of a bad initialization of a constructor.
You can check the update solution here:
How to choose the test DB cakePHP testing
And here:
How to override model's constructor correctly in CakePHP

Related

How to use the RefreshDatabase trait with a Sql server database?

I am trying to use a real Sql Server connection to run my php unit test in Laravel because SqLite does not have the function I am trying to test.
The database name is "Test" and the connection details have all been added to database.php.
I have changed my phpunit.xml env variables to refer to the new test database.
<env name="DB_CONNECTION" value="test_sqlserver"/>
<env name="DB_DATABASE" value="Test"/>
Now, when I try to run a simple test with a class that use the RefreshDatabase trait, or even the DatabaseMigrations trait, it result in the following error:
Symfony\Component\Console\Exception\InvalidOptionException: The "--drop-views" option does not exist.
It can be reproduced with the following test:
<?php
namespace Tests\Unit\Actions;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class GetResourcesExceedingHoursByPayPeriodWeeksActionTest extends TestCase
{
use RefreshDatabase;
protected $dropViews = false;
/** #test */
public function it_can_get_resources_exceeding_hours_by_pay_period_weeks()
{
}
}
Edit
The solution bellow proposed by Daniel did not solve the issue. Inspiring from that however, I added the following method:
protected function migrateFreshUsing()
{
$seeder = $this->seeder();
return array_merge([
// '--drop-views' => $this->shouldDropViews(),
// '--drop-types' => $this->shouldDropTypes(),
],
// $seeder ? ['--seeder' => $seeder] : ['--seed' => $this->shouldSeed()]
);
}
It resolved the error, however, now phpunit does not output any result. (I added a simple assertion $this->assertTrue(false);)
Full class code:
<?php
namespace Tests\Unit\Actions;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class GetResourcesExceedingHoursByPayPeriodWeeksActionTest extends TestCase
{
use RefreshDatabase;
protected $dropViews = false;
protected function migrateFreshUsing()
{
$seeder = $this->seeder();
return array_merge([
// '--drop-views' => $this->shouldDropViews(),
// '--drop-types' => $this->shouldDropTypes(),
],
//$seeder ? ['--seeder' => $seeder] : ['--seed' => $this->shouldSeed()]
);
}
/** #test */
public function it_can_get_resources_exceeding_hours_by_pay_period_weeks()
{
$this->assertTrue(false);
}
}
Edit 13:00
Adding the new tag in the phpunit.xml did not help to add output to the console.
However, I found the line causing the issue, as when it is commented, phpunit does output to the console.
To test correctly, I extended from the base TestCase class instead of the one shown above.
Then I added
$this->seed(DatabaseSeeder::class); as the first line of my test, which resulted in no output. Commenting it would allow phpunit to output, but obviously, the test would work with the dummy assertion, but not with anything that requires seeding.
With further investigation it seems that the issue is caused by 3 seeders that uses a third party package to seed from a csv file.
Every other seeder run fine.
I will keep investigating this path and update my question with the solution.
How can I test using a real Sql server database?
I'm not familiar enough with using SqlServer with Laravel, but this answer might be what you're looking for.
It's often the case that there's a default method that doesn't mesh well with a specific use case.
Writing this into your Tests\TestCase class might solve the issue.
/**
* Refresh a conventional test database.
*
* #return void
*/
protected function refreshTestDatabase()
{
if (! RefreshDatabaseState::$migrated) {
$this->artisan('migrate:fresh', [
'--drop-views' => $this->shouldDropViews(),
'--drop-types' => $this->shouldDropTypes(),
]);
$this->app[Kernel::class]->setArtisan(null);
RefreshDatabaseState::$migrated = true;
}
$this->beginDatabaseTransaction();
}
EDIT Jan 13 2023 12:23 pm
For the PhpUnit not outputting anything issue, reference this answer. Let me know in the comments if you don't get an explicit error displayed on your terminal.

CakePHP: Use AWS's DynamoDB Session Handler

I have a CakePHP application hosted on AWS Elastic Beanstalk. Because of the multiple EC2 instances I will use in the future I want to store my PHP sessions in a database. AWS provides a very nice library for storing PHP sessions in their DynamoDB database. See http://goo.gl/URoi3s
Now I putted the AWS SDK in my vendors folder and created an access wrapper for it (a plugin):
<?php
Configure::load('aws');
require_once VENDORS . 'autoload.php';
use Aws\Common\Aws;
class AwsComponent extends Component
{
private $_aws;
public function __construct()
{
$this->_aws = Aws::factory(array(
'key' => Configure::read('Aws.key'),
'secret' => Configure::read('Aws.secret'),
'region' => Configure::read('Aws.region')
));
}
public function getClient($service)
{
return $this->_aws->get($service);
}
}
The wrapper is working well, I already implemented some S3 stuff. Now for the session handler i added the following code to my AppController.php:
public $components = array('Aws.Aws');
public function beforeFilter()
{
$this->_setSessionStorage();
}
private function _setSessionStorage()
{
$client = $this->Aws->getClient('dynamodb');
$client->registerSessionHandler(array(
'table_name' => 'sessions'
));
}
The AWS's internal registerSessionHandler() is executed (tested it) but the session is not beeing stored into the DynamoDB table. Of course I created the table before and if I add the call to the AWS library directly to my webroot/index.php before dispatcher is loaded everything works fine.
I think the problem is that my code is executed after CakePHP calls session_start(). So what is the best way to implement that? http://goo.gl/kUFUIR doesn't help me, I don't want to rewrite the AWS library for beeing compatible with the CakePHP interface.
So what is the best way to implement that? http://goo.gl/kUFUIR
doesn't help me, I don't want to rewrite the AWS library for beeing
compatible with the CakePHP interface.
This is in fact the best way. And this does not mean to reinvent the wheel, abstraction in OOP means that you make things available in a generic interface that can be replaced with something else. You wrap a foreign API or code in an API compatible to your system, in this case a CakePHP application.
Wrap the vendor lib in a AwsSession adapter that implements the CakeSessionHandlerInterface. This way it's API compatible with other session adapters in the case you change it and it might be even solve your core problem, because CakeSession will take care of the initialization.
Your component is initialized after the session in CakePHP, when the controller is already instantiated and then is initializing all its components. So this happens at a pretty late time. Your alternative is to stop CakePHP from initializing the session, I never had a need to do so, so no idea without looking it up myself. Dig in CakeSession. Even if you manage to do so, other components like the default Auth adapter depends on being able to work with Sessions, so you have to take care of the issue that your component has to be loaded before Auth as well. Pretty fragile system with lots of possbile points of failure. Seriously, go for the Session adapter, guess its a lot less painful to get it working this way.
By a quick look at the DynamoDB Session documentation this seems to be pretty easy. Extend the regular session handler and overload only the init and garbage collection of it to add the Aws API calls there, no guarantee this is right but seems to be easy.
What I end up with in CakePHP 3.
src/Network/Session/DynamoDbSession.php
&lt?php
namespace App\Network\Session;
use Aws\DynamoDb\DynamoDbClient;
use Cake\Core\Configure;
class DynamoDbSession implements \SessionHandlerInterface
{
private $handler;
/**
* DynamoDbSession constructor.
*/
public function __construct()
{
$client = new DynamoDbClient(Configure::read('DynamoDbCredentials'));
$this->handler = $client->registerSessionHandler(array(
'table_name' => Configure::read('DynamoDbCredentials.session_table')
));
}
public function close()
{
return $this->handler->close();
}
public function destroy($session_id)
{
return $this->handler->destroy($session_id);
}
public function gc($maxlifetime)
{
return $this->handler->gc($maxlifetime);
}
public function open($save_path, $session_id)
{
return $this->handler->open($save_path, $session_id);
}
public function read($session_id)
{
return $this->handler->read($session_id);
}
public function write($session_id, $session_data)
{
return $this->handler->write($session_id, $session_data);
}
}
Activate it in config/app.php file:
'Session' => [
'defaults' => 'php',
'handler' => [
'engine' => 'DynamoDbSession'
],
'timeout' => (30 * 24 * 60)
]

How to use cakephp 2 with custom database connection and raw queries

I have to work with an Oracle database using the old database driver (ora_logon ) which is not supported by cakephp. I cant use the oci driver instead.
Right now I do the follow:
Every method of every model connects to the database and retrieve data
class SomeClass extends Model {
public function getA(){
if ($conn=ora_logon("username","password"){
//make the query
// retrieve data
//put data in array and return the array
}
}
public function getB(){
if ($conn=ora_logon("username","password"){
//make the query
// retrieve data
//put data in array and return the array
}
}
}
I know that it is not the best way go.
How could I leave cakephp manage opening and closing of the connection to the database and have models only retrieve data? I'm not interested in any database abstraction layer.
I would think you could just make your own OracleBehavior. Each model could use this behavior, and in it, you can overwrite or extend the Model's find() behavior to build a traditional oracle query and run it (I don't know much about Oracle).
Then, in your Behavior's beforeFind() you can open your connection, and in your Behavior's afterFind(), you can close your database connection.
That way, every time before a query is run, it automatically opens the connection, and every time after a find it closes it. You can do the same with beforeSave() and afterSave() and beforeDelete() and afterDelete(). (You'll likely want to create a single connect() method and disconnect() method in the Behavior, so you don't have duplicate code in each beforeX() method.
Do you really need to extend a Cake Model class?
class SomeClass extends Model {
private $conn;
public function constructor() {
parent::constructor();
$conn = ora_logon("username","password");
if(!$conn)
throw new Exception();
}
public function getA() {
//Some code
}
}
SomeController:
App::uses('SomeClass','Model');
public function action() {
$data = array();
$error = null;
try{
$myDb = new SomeClass();
$data = $myDb->getA();
} catch($e) {
$error = 'Cannot connect to database';
}
$this->set(compact('data', 'error'));
}

Custom datasource in CakePHP not working

I’m trying to create a custom datasource for Amazon Web Services in CakePHP. My approach is as follows:
Base AwsDataSource that creates signatures, makes the actual HTTP requests etc
Various datasources for each AWS product (i.e. S3, SQS etc) that extend this class and specifies the endpoint to use
Models for things like S3Bucket, SqsQueue, SqsMessage and so on
My base datasource class looks like this (simplified):
<?php
class AwsDataSource extends DataSource {
public $config = array(
'key' => '',
'secret' => '',
'region' => ''
);
public $endpoint;
public function signRequest($parameters) {
// generates signature
}
public function makeRequest($parameters = array(), $method = 'get') {
// generates signature and makes HTTP request to AWS servers
}
}
And a sample model looks like this:
<?php
class SqsQueue extends AwsAppModel {
public $name = 'SqsQueue';
public $useTable = false;
}
My problem comes trying to then use these models/datasources in my CakePHP app.
I’ve implemented methods named create(), read(), update() and delete() in my AWS datasource as per the CakePHP cookbook, but they don‘t seem to be getting called. I know this because I’ve put die() statements in my datasource with a message, and execution is never stopped.
I’ve exhausted the cookbook, so if any one could show me how to get my models to call the CRUD methods in my datasource classes then I’d be most grateful.
My bad. Turns out my approach was flawed.
The datasource is specified in database config and is specified as AwsDataSource. Therefore, S3DataSource or SqsDataSource is never used, even though that’s where I’ve defined my CRUD methods, hence my application never exiting (because the CRUD methods aren’t defined in AwsDataSource, the actual datasource being called).
Looks like it’s back to the drawing board.

zend framework: models cannot interact with database on the server

I have just finished my first site built with zend framework and all works great on my local machine.
Then I uploaded it to the server (godaddy) and all works except any connection my models do with the database. I have made a connetion to the database with regular PDO with the credentials in my application.ini and it worked, and I can interact with the model if it's not returning anything from the database (and again all the models work great on my local machine).
My models looks like this:
class Default_Model_picture extends Zend_Db_Table_Abstract
{
protected $_name = 'pictures';
protected $_primary = 'id';
public function getPicturesByCategory($category)
{
$query = $this->select()->from(array('pictures'), array(
'pictures.id', 'pictures.pic_name', 'pictures.pic_desc',
'pictures.pic_category', 'pictures.pic_date_added',
'pictures.pic_larger', 'pictures.pic_url'));
$query->where('pic_category = ?', $category);
$query->order('pic_date_added ASC');
$result = $this->fetchAll($query);
return $result;
}
}
this is an example for a model, obviously i did not added lots of methods.
i have no idea what to do next.
I am assuming you set up the db connection correctly into $db. Afterwards you must set it as the default adapter for Zend_Db_Table.
Zend_Db_Table::setDefaultAdapter($db);
I am just assuming this is what went wrong. But it is a common problem, so I decided to go ahead and answer anyway.
Since your script works fine on your local machine, the first thing I check is if you have got the database connection params setup correctly in your application.ini
Try to write a test script that uses the pdo functions on itself (without zend framework). see if you get any errors at all
try {
$dbh = new PDO('mysql:host=YOURHOST;dbname=YOURDBNAME', $YOURUSERNAME, $YOURPASSWORD);
foreach($dbh->query('SELECT * from FOO') as $row) {
print_r($row);
}
$dbh = null;
} catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "<br/>";
die();
}

Resources