Laravel mongodb transactions does not rollback - laravel

I need to use transactions on mongodb in laravel-5.8. I am using jenssegers-laravel-mongodb and use like below snippet in my code but it does not rollback when one of queries failed.
$session = MongoDB::startSession();
$session->startTransaction();
try {
Player::document()->update($updates, ['session' => $session]);
$session->commitTransaction();
return true;
} catch (\Exception $e) {
$session->abortTransaction();
return false;
}
As I found out, this package does not support transactions. I desperately need to support transactions in my code. Could you advise me what to do?

Instead of using MongoDb class try using the Laravel's DB class, manually change the database driver your are connecting to and run the DB transactional commands as show below
\DB::connection(config('database.connections.mongodb'));
\DB::beginTransaction();
try {
Player::document()->update($updates, ['session' => $session]);
\DB::commit();
return true;
} catch (\Exception $e) {
\DB::rollback();
return false;
}

Related

Laravel 8 - Conditionally remember a value in cache [duplicate]

I'm developing one of my first applications with the Laravel 4 framework (which, by the way, is a joy to design with). For one component, there is an AJAX request to query an external server. The issue is, I want to cache these responses for a certain period of time only if they are successful.
Laravel has the Cache::remember() function, but the issue is there seems to be no "failed" mode (at least, none described in their documentation) where a cache would not be stored.
For example, take this simplified function:
try {
$server->query();
} catch (Exception $e) {
return Response::json('error', 400);
}
I would like to use Cache::remember on the output of this, but only if no Exception was thrown. I can think of some less-than-elegant ways to do this, but I would think that Laravel, being such an... eloquent... framework, would have a better way. Any help? Thanks!
This is what worked for me:
if (Cache::has($key)) {
$data = Cache::get($key);
} else {
try {
$data = longQueryOrProcess($key);
Cache::forever($key, $data); // only stored when no error
} catch (Exception $e) {
// deal with error, nothing cached
}
}
And of course you could use Cache::put($key, $data, $minutes); instead of forever
I found this question, because I was looking for an answer about this topic.
In the meanwhile I found a solution and like to share it here:
(also check out example 2 further on in the code)
<?php
/**
* Caching the query - Example 1
*/
function cacheQuery_v1($server)
{
// Set the time in minutes for the cache
$minutes = 10;
// Check if the query is cached
if (Cache::has('db_query'))
{
return Cache::get('db_query');
}
// Else run the query and cache the data or return the
// error response if an exception was catched
try
{
$query = $server->query(...);
}
catch (Exception $e)
{
return Response::json('error', 400);
}
// Cache the query output
Cache::put('db_query', $query, $minutes);
return $query;
}
/**
* Caching the query - Example 2
*/
function cacheQuery_v2($server)
{
// Set the time in minutes for the cache
$minutes = 10;
// Try to get the cached data. Else run the query and cache the output.
$query = Cache::remember('db_query', $minutes, function() use ($server) {
return $server->query(...);
});
// Check if the $query is NULL or returned output
if (empty($query))
{
return Response::json('error', 400);
}
return $query;
}
I recommend you to use Laravel's Eloquent ORM and/or the Query Builder to operate with the Database.
Happy coding!
We're working around this by storing the last good value in Cache::forever(). If there's an error in the cache update callback, we just pull the last value out of the forever key. If it's successful, we update the forever key.

How to mock a Job object in Laravel?

When it comes to Queue testing in Laravel, I use the provided Queue Fake functionality. However, there is a case where I need to create a Mock for a Job class.
I have the following code that pushes a job to a Redis powered queue:
$apiRequest->storeRequestedData($requestedData); // a db model
// try-catch block in case the Redis server is down
try {
App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run');
$apiRequest->markJobQueued();
} catch (\Exception $e) {
//handle the case when the job is not pushed to the queue
}
I need to be able to test the code in the catch block. Because of that, I'm trying to mock the Job object in order to be able to create a faker that will throw an exception.
I tried this in my Unit test:
ProcessRunEndpoint::shouldReceive('dispatch');
That code returns: Error: Call to undefined method App\Jobs\ProcessRunEndpoint::shouldReceive().
I also tried to swap the job instance with a mock object using $this->instance() but it didn't work as well.
That said, how can I test the code in the catch block?
According to the docs, you should be able to test jobs through the mocks provided for the Queue.
Queue::assertNothingPushed();
// $apiRequest->storeRequestedData($requestedData);
// Use assertPushedOn($queue, $job, $callback = null) to test your try catch
Queue::assertPushedOn('run', App\Jobs\ProcessRunEndpoint::class, function ($job) {
// return true or false depending on $job to pass or fail your assertion
});
Making the line App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run'); throw an exception is a bit complicated. dispatch() just returns an object and onQueue() is just a setter. No other logic is done there. Instead, we can make everything fail by messing with the configuration.
Instead of Queue::fake();, override default queue driver with one that just won't work: Queue::setDefaultDriver('this-driver-does-not-exist'); This will make every job dispatch fail and throw an ErrorException.
Minimalist example:
Route::get('/', function () {
try {
// Job does nothing, the handle method is just sleep(5);
AJob::dispatch();
return view('noError');
} catch (\Exception $e) {
return view('jobError');
}
});
namespace Tests\Feature;
use App\Jobs\AJob;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
class AJobTest extends TestCase
{
/**
* #test
*/
public function AJobIsDispatched()
{
Queue::fake();
$response = $this->get('/');
Queue::assertPushed(AJob::class);
$response->assertViewIs('noError');
}
/**
* #test
*/
public function AJobIsNotDispatched()
{
Queue::setDefaultDriver('this-driver-does-not-exist');
$response = $this->get('/');
$response->assertViewIs('jobError');
}
}
I found a solution. Instead of using a facade for adding a job to the queue (App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run');), I injected it into the action of the controller:
public function store(ProcessRunEndpoint $processRunEndpoint)
{
// try-catch block in case the Redis server is down
try {
$processRunEndpoint::dispatch($apiRequest)->onQueue('run');
} catch (\Exception $e) {
//handle the case when the job is not pushed to the queue
}
}
With this, the job object is resolved from the container, so it can be mocked:
$this->mock(ProcessRunEndpoint::class, function ($mock) {
$mock->shouldReceive('dispatch')
->once()
->andThrow(new \Exception());
});
Although still not sure why the shouldReceive approach doesn't work for the facade: https://laravel.com/docs/8.x/mocking#mocking-facades

Laravel dusk if browser->assertSee then do this

Is there a way to proceed with a test even if assertSee returns with an error.
This is my current code:
$browser->visit('https://urltotest.co.uk/find-an-engineer/')
->type("EngineerId", $engineerId)
->click('#checkEngineer');
$test = $browser->assertSee("Engineer cannot be found");
What I would like to be able to do is go:
if ($test == false) {
return false;
} else {
return $browser->text('.engineer-search-results-container .search-result .col-md-8 .row .col-xs-10 h3');
}
But when the assertSee fails all I get back is:
Did not see expected text [Engineer cannot be found] within element [body].
As an exception. Any way I can proceed if it can't find that element?
You should be able to achieve this using a try catch to catch the exception:
try {
$browser->assertSee("Engineer cannot be found");
} catch(\Exception $e) {
return false;
}
To note, I do not know if there is a method such as $browser->fail() (like phpunit's $this->fail()) which will fail the test for you.

How to handle 'throw new DecryptException('The payload is invalid.')' on Laravel

I have small Laravel project working on Crypt class. It work fine for both Crypt::encrypt(..) and Crypt::decrypt(..). But I have problem if I directly change on crypted value then try to capture exception. For example, my crypted value is
zczc1234j5j3jh38234wsdfsdf214
Then I directly add some words as below.
zczc1234j5j3jh38234wsdfsdf214_addsometext
I try to decrypt and get error as below
throw new DecryptException('The payload is invalid.')
So, I try to capture exception with render method.
public function render($request, Exception $exception)
{
if ($exception instanceof \Illuminate\Contracts\Encryption\DecryptException) {
dd("error");
return route('login')->withError('Your DB may be hacked');
}
return parent::render($request, $exception);
}
I do not known why method not fire, Appreciated&thanks for all comment.
You should handle this with
use Illuminate\Contracts\Encryption\DecryptException;
try {
$decrypted = decrypt($encryptedValue);
} catch (DecryptException $e) {
//
}
check https://laravel.com/docs/5.8/encryption

unassign order status from status

I need to unassign status from state in migration.
$status->setStatus('approved')
->unassignState(Mage_Sales_Model_Order::STATE_NEW)
->assignState(Mage_Sales_Model_Order::STATE_PROCESSING, false)
->save();
If status is assigned to migration, it works. But if state is not assigned, there is exception and
migrations fails.
What is the best way to solve this problem?
The only solution that occured to me, is to create function trapper that catches exception. I this case
it seems that migration works even if status is not assigned to state.
function unAssignStatusFromState($status, $state)
{
try {
$status->unassignState($state);
return true;
} catch(Exception $e) {
return false;
}
}

Resources