Laravel DatabaseTransactions for PHPunit have no effect - laravel

Writing tests for an existing API, there are many cases where the database has been modified. What I have been doing is something as follows:
public function testPut()
{
//setup
/*Copy an existing record and take its data as an array.
* the function being tested will take an array of data
* and create a new record. Using existing data guarantees
* the data is valid.
*/
$customerObj = Customer::getInstance(); //regular instantiation disabled in this api
$cust = ArCust::first()->toArray();
$oldNum = $cust['CUST_NO'];
unset($cust['CUST_NO']);
$custNo = rand(1, 9999999999999);
//test
/*put() creates a new customer record in the database
and returns the object.
*/
$this->assertInternalType('object', $customerObj->put($custNo, $cust));
//cleanup
/*manually remove the newly created record.
*/
ArCust::whereNam($cust['NAM'])->whereNotIn('CUST_NO', [$oldNum])->delete();
}
I am now running into instances where the API creates or updates many tables based on foreign keys. It would take far too much time to go through and manually reset each table.
The DatabaseTransaction trait provided by Laravel is supposed to take care of resetting everything for you. However, when I use it, I still find the test-created records in the database.
Here is how I have used it:
class CustomerTest extends TestCase
{
use DatabaseTransactions;
public function testPut()
{
//setup
$customerObj = Customer::getInstance();
$cust = ArCust::first()->toArray();
$oldNum = $cust['CUST_NO'];
unset($cust['CUST_NO']);
$custNo = rand(1, 9999999999999);
//test
$this->assertInternalType('object', $customerObj->put($custNo, $cust));
}
}
Am I using this incorrectly? Getting DatabaseTransactions to work correctly will save an incredible amount of time, as well as make the testes more readable to other people.

The issue was that we had multiple database connections defined in config > database.
In the database.php conf file, I changed the default connection to the correct database using its name as defined in the setup:
$connection = 'counterpoint';
and DatabaseTransactions now works.
This next step to this solution is to direct the connection of each test to the appropriate database rather than change the global connection.

Related

laravel-medialibary - addMedia() saves to wrong database connection when model connection set dynamically

I have an issue with laravel-medialibrary saving the database record to the incorrect database connection, it seems calling the addMedia() method always connects to the default mysql connection?
I've searched and can't see any refernce in the documentation and the only search relating I could find is Laravel-medialibrary working with multiple databases, however I am dynamically setting the database connection with a static method within my modal:
public static function connect($connection)
{
DB::purge($connection);
return (new static)->setConnection($connection);
}
When dumping $this->student->getConnectionName() set on my model instance, it is correctly set to the correct connection. However adding media like below saves to default connection.
this->student
->addMedia($this->avatar->getRealPath())
->toMediaCollection('avatar');
where $this->student is set:
$this->student = Student::connect('school_' . $host->id)
->where('student_reference_code', $this->student_reference_code)
->first();
I am assuming that the addMedia must return a new static instance of the model, where the connection wouldn't be set? I have the same issue if I was to query some eloquent methods such as:
$this->student = Student::connect('school_' . $host->id)->all()
since the all() returns the below:
return static::query()->get(
is_array($columns) ? $columns : func_get_args()
);
Any help on how I can store media within the correct database connection would be appreciated.

Why is Laravel's database cache driver not remembering the items I tell it to?

I am implementing relatively simple caching (database driver) on Laravel 8 and have created the cache table in the database using the suggested migration :
$table->string('key')->unique();
$table->text('value');
$table->integer('expiration');
When I use a controller to store a simple entry in the cache it works as expected :
Cache::put('giles', "SOMETHING", 1000);
I can see the entry in the cache table.
But storing more complicated things isn't having the expected result. My original code is :
$statistics = Statistics::all();
$emails = OutgoingEmail::orderBy('created_at', 'DESC')->take(10)->get();
return view('admin.home')->with(compact('emails', 'statistics'));
Whether I try to use the remember method :
$expire = 1000;
// also tried $expire = Carbon::now()->addMinutes(10);
$statistics = Cache::remember('statistics', $expire, function() {
return Statistics::all();
});
or try a more inelegant method of seeing whether the caching is set first, retrieving it if so, or retrieving the collection then using Cache::set() (code not show)...it's not storing it in the cache table.

beforeFind() in Yii ActiveRecord and cache

In some model classes I want to implement cache. And I wanted to do that like:
UsersModel::model()->findByAttributes([...])
In that class I wanted to override method beforeFind() to send request first to cache server, but it seems that method does not take any additional parameters, nor does have object with attributes.
Putting additional conditions/checks in top level code something like :
$response = Yii::app()->cache->get('userUserLogin');
if(empty($response) == true) {
//fetch data from db and set to cache
$userModel = UsersModel::model->findByAttributes([...])
Yii::app()->cache->set('user' . $userModel->username, $userModel->getAttributes());
}
is not nice and trivial, leading to many branches.
You should not use beforeFind() for that. Besides technical problems in implementation, you may get many side effects and hard to debug bugs because of that. That is because cache may be out of date and many internal Yii logic may rely on assumption, that findByAttributes() (and other methods) always fetches fresh data from database. You will also not be able to ignore cache and get model directly from database.
In general you have 2 options:
1. Use CActiveRecord::cache()
$model = UsersModel::model()->cache(60)->findByAttributes([...])
This will query cache results for 60 seconds.
2. Custom helpers
You may add custom methods, which will simplify using cached active records:
public static function findByAttributesFromCache($attributes = []) {
$result = Yii::app()->cache->get(json_encode($attributes));
if ($result === false) {
//fetch data from db and set to cache
$result = static::model()->findByAttributes($attributes);
Yii::app()->cache->set(json_encode($attributes), $result, 60);
}
return $result;
}
You can add such method to trait and reuse it in multiple models. Then all you need is:
$userModel = UsersModel::findByAttributesFromCache([...]);

Symfony 1.4 functional test not displaying new data

I'm running a functional test against my Symfony 1.4 project and it keeps failing because it doesn't fetch the latest data.
The test makes up a new site entry, then a survey at the site, then adds data to the survey. Each of these are on separate pages and each work. The data is definitely present in the database. After saving the last form, the survey_data, it returns to the survey page where it should get the survey details and a list of all the data items added to it. This works in dev and prod environments but in my functional tests the survey_data list is empty. Looking through the logs it doesn't try to fetch the data from the database (Doctrine & Postgres). But if I manually load the page in a browser just seconds later the list is there, and if I run a test that goes directly to the page (without clearing the database) the list is there, so the test login has the rights to see that page and the contents, but won't show it at first, as if it has cached the page survey page before the survey_data was added.
So my question is, how can I ensure my functional tests get the latest data from the DB or how can I refresh the local data object cache after saving a new item to the database?
Additional:
My functional tests extend sfPHPUnitBaseFunctionalTestCase and I use the sfBrowser class to check the contents of the pages and navigate through them
The issue appears to be with Doctrine caching any objects it updates for the duration of the execution. This gets cleared out after each web request in a production environment - one request per execution - but in the test environment it appears to persist between requests as they're all happening in the same execution.
It becomes troublesome when testing the process of, say, adding a new Item to a List - the List is in memory (test browser has been to its page before), the new Item gets created, saved but the link between them isn't formed in-memory.
Targeted refreshing of related objects
If you know what object you're looking to forcibly refresh you can:
$SomeDoctrineRecordObject->refreshRelated(); // for all relationships
$SomeDoctrineRecordObject->refreshRelated($relation); // for a specific one
( for the above List and Item example, you'd $list->refreshRelated('item') )
This is only useful if you're using the Doctrine object already, else you have to go pull it out of the route or database each time you need to refresh it. A more general approach is to extend the sfTestFunctional class and override the methods which might result in a relationship change and thus need to trigger a refresh.
General refreshing of all objects
The code to clear out Doctrine's object cache:
$manager = Doctrine_Manager::getInstance();
$connection = $manager->getCurrentConnection();
$tables = $connection->getTables();
foreach ( $tables as $table ) {
$table->clear();
}
And an example of how to hook it into a custom functional test object:
class myTestFunctional extends sfTestFunctional
{
public function get( $uri, $parameters = array(), $changeStack = true )
{
$this->clearEntityCache();
return parent::get( $uri, $parameters, $changeStack );
}
public function click( $name, $arguments = array(), $options = array() )
{
$this->clearEntityCache();
return parent::click( $name, $arguments, $options );
}
protected function clearEntityCache()
{
$manager = Doctrine_Manager::getInstance();
$connection = $manager->getCurrentConnection();
$tables = $connection->getTables();
foreach ( $tables as $table ) {
$table->clear();
}
}
}
Thus every time myTestFunctional gets a url or clicks to change page it clears out any Doctrine objects stored in memory. Not subtle, but effective and doesn't make the tests themselves more labourious to write!

Doctrine 2 result cache invalidation

I'm using Doctrine 2's result cache on a query retrieving the number of new messages of a user (messaging app):
$query->useResultCache(true, 500, 'messaging.nb_new_messages.'.$userId);
I tried to invalidate this cache like this (in my entity repository):
public function clearNbNewMessagesOfUserCache($userId) {
$cacheDriver = $this->getEntityManager()->getConfiguration()->getResultCacheImpl();
$result = $cacheDriver->delete('skepin_messaging.nbNewMessages.'.$userId);
if (!$result) {
return false;
}
return $cacheDriver->flushAll();
}
So that I don't need to make a useless query on each page of my website.
My questions: is that a recommended practice? Will I eventually run into problems?
I had the idea to build an onFlush hook.
There you have all entities queued for inserts, updates and deletes hence you can invalidate the caches depending on entity name and identifier etc.
Unfortunately, I have not yet build any event listeners but I definitely plan to build such a thing for my project.
Here is a link to the doctrine documentation for the onFlush event
Edit:
There is even an easier way to implement events.
In an entity class you can add #HasLifecycleCallbacks to the annotations and than you can define a function with a #PreUpdate or #PrePersist annotation.
Than every time this model is updated or persisted this function will be called.
/**
* #Entity
* #Table(name="SomeEntity")
* #HasLifecycleCallbacks
*/
class SomeEntity
{
...
/**
* #PreUpdate
* #PrePersist
*/
public function preUpdate()
{
// This function is called every time this model gets updated
// or a new instance of this model gets persisted
// Somethink like this maybe...
// I have not yet completely thought through all this.
$cache->save(get_class($this) . '#' . $this->getId(), $this);
}
}
So maybe this can be used to invalidate every single instance of an entity?
This is an old question I stumbled upon. It's really simple using Doctrine 2.8 nowadays:
/** #var \Psr\Cache\CacheItemPoolInterface|null $cache */
$cache = $em->getConfiguration()->getResultCache();
$cache->deleteItem('skepin_messaging.nbNewMessages.'.$userId);
$cache->clear(); // clear all items
Please be aware that Doctrine internally generates a "real cache key" which won't look like yours. I don't know how to generate that cache key, without re-creating the used query.

Resources