Laravel catch string output when CommandFinished event fires - laravel

command.php example, something like this:
public function handle()
{
$this->info('just an ordinary message');
}
How can i catch this message "just an ordinary message" outside command (for my example in service provider) where command finished event is fired.
my service provider file:
$this->events->listen(CommandFinished::class, function ($event) {
dump($event->command);
});

Related

Handle Exception From Within Method

I am implementing payments for my website using the API of an external service (ie. the service of the payment provider).
Let's say the user clicks 'BUY', and then we go to my controller which says something along the lines of:
public function buyFunction() {
$result = $this->ExternalService->pay();
if ($result->success == true) {
return 'We are happy';
}
}
I have also created the aforementioned externalService which has the pay() method:
class ExternalService {
public function pay() {
response = //Do stuff with Guzzle to call the API to make the payment
return response;
}
}
Now, sometimes things go wrong.
Let's say the API returns an error - which means that it throws a GuzzleException - how do I handle that?
Ideally, if there is an error, I would like to log it and redirect the user to a page and tell him that something went wrong.
What I've tried
I have tried using a try/catch statement within the pay() function and using abort(500) but this doesn't allow me to redirect to the page I want to.
I have tried using a try/catch statement within the pay() function and using return redirect('/mypage') but this just returns a Redirect object to the controller, which then fails when it tries to call result->success
I have tried using number 2 but also adding a try/catch block to the controller method, but nothing changed.
In the end, I have found two solutions. In both, I use a try/catch block inside the pay() method. Then I either return 0; and check in the controller if (result == 0) or I use abort( redirect('/mypage') ); inside the try/catch block of the pay() method.
What is the right way to handle this?
How to use the try/catch blocks?
In my experience, avoid handling exceptions let them pass through and handle them accordingly with try catches. This is the most pragmatic approach. Alternatively you will end up checking result is correct in weird places, eg. if ($result) {...}. Just assume it went good, except if the exception is thrown. Bonus: never do Pokemon catches with Exception $e unless you specifically needs it!
class ExternalService {
public function pay() {
try {
response = $client->get(...);
} catch (BadResponseException $exception) {
Log::warning('This should not happen check payment api: ' . $exception->getMessage());
throw new PaymentException('Payment did not go through');
}
return response;
}
}
Assuming you have your own Exception.
class PaymentException extends HttpException
{
public function __construct(?\Exception $previous = null)
{
parent::__construct(Response::HTTP_BAD_REQUEST, 'Unexpected error processing the payment', $previous);
}
}
This enables you to handle the flow in a controller, where it would make sense to handle the redirect. Sometimes if the exception is very integral or common to the web app, it can also be handled by the exception handler instead.
class PaymentController {
public function pay(PaymentService $service) {
try {
$payment = $service->buyFunction();
} catch (PaymentException $exception) {
return redirect()->route('app.payment.error');
}
return view('app.payment.success', compact('payment'));
}
}

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 - Event not working - __construct() must be an instance

I want to send mail to admin, when users send message to him in website. I create my events and my mailable class..
At the first, I tried without using event listener and it worked but When I tried with event listener, it didn't work..
it gives this error;
Type error: Argument 1 passed to App\Events\SendMessage::__construct() must be an instance of App\Contact, instance of App\Models\Contact given, called in /var/www/parti/app/Http/Controllers/ContactController.php on line 32
Event
public $mesaj;
public function __construct(Contact $mesaj)
{
$this->mesaj = $mesaj;
}
Listener
public function handle(SendMessage $event)
{
Mail::to(User::first()->email)->send(new SendMessageToAdminAsMail($event->mesaj));
}
Mail Class
public $mesaj;
public function __construct(Contact $mesaj)
{
$this->mesaj = $mesaj;
}
public function build()
{
return $this
->from($this->mesaj->email)
->subject($this->mesaj->name." send a message")
->view('backend.mail.message');
}
What is my mistake ? Thank's for your help...

Laravel Broadcasting and Queues

I am developing a app to process videos. I try to push 10 videos into a queue. After a video was processed I fired a event like this:
...
$production->render();
event(new VideoHasProcessed(basename($this->video->path)));
...
And I do following this video to push a notification to users (https://laracasts.com/series/whats-new-in-laravel-5-1/episodes/12)
The problem is: I want to push a notification when each video was processed but in fact, after 10 videos was processed the notification was displayed.
This is my script:
<script>
(function () {
var pusher = new Pusher(PUSHER_ID, {
encrypted: true
});
var channel = pusher.subscribe('test');
channel.bind('App\\Events\\VideoHasProcessed', function(data) {
console.log(data);
});
})();
</script>
Update:
Dispatch a job
foreach ($request->input('files') as $file)
{
...
// Dispatch a job.
$this->dispatch(new ProduceVideo($video));
....
}
And my job class:
public function handle(ProductionRepository $production)
{
...
$production->render();
event(new VideoHasProcessed(basename($this->video->path)));
...
}
After digging into laravel 5.3 core I found out the solution. You event class needs to extend ShouldBrodcastNow interface:
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
class MyEvent implements ShouldBrodcastNow
{
...
}
In that way if you need the event to be broadcasted right away, this should do the trick.
NOTE: there is not need for MyEvent to implement both interfaces (ShouldBroadcastNow, ShouldBroadcast) because the interface ShouldBroadcastNow already extends ShouldBroadcast
If each of your 10 videos are processed as separate queued jobs you'll just need to fire your event in each queued job. Here's an example handle function for a job class (see https://laravel.com/docs/5.2/queues#job-class-structure):
/**
* Execute the job.
*/
public function handle()
{
// Your video processing script
// Fire your event
event(new VideoHasProcessed(basename($this->video->path)));
}
If your videos are processed as 1 job you'll need to add your event inside any loop you may be using to process each video:
foreach($videos as $video):
// Your video processing script
// Fire your event
event(new VideoHasProcessed(basename($this->video->path)));
endforeach;

How to register events from within a module in Yii?

I'm trying to register events from within a submodule in Yii.
It just doesn't seem to work.
The init method is definitely called.
class TestModule extends CWebModule
{
public function init()
{
$this->setImport(array(
'test.models.*',
'test.components.*',
));
Yii::app()->onBeginRequest = array($this, 'onBeginRequest');
}
public function onBeginRequest($event) {
die('Request!');
}
public function beforeControllerAction($controller, $action)
{
if (parent::beforeControllerAction($controller, $action))
{
return true;
}
else
return false;
}
}
To register an event you can do:
$this->getEventHandlers($eventName)->add($eventHandler);
Where $eventHandler is the name of the callback you want to define for the $eventName event.
You can also do it with the following way:
$this->attachEventHandler($eventName, $eventHandler);
I solved the problem myself.
The problem was, that i was actually too late for onBeginRequest (Request was alrdy processed).
So what i did was writing a component with Event Handlers for onBeginRequest and onEndRequest, registering the event handlers in config/main.php and call my Module from this Component.
I basically had to proxy all these events.

Resources