Laravel Redirect does not work in Event handler / listener - laravel

I have a Auth.Attempt event handler class, which I detect user's login attempts to decide to lock user's account.
However, when I tried to redirect user to login page with a flash message, I found the redirection does not work, it's still carry on next step.
I want to interrupt the process in the event and give my custom warning message. Can anyone help me out? Thanks a lot.
My event handler:
namespace MyApp\Handlers\Security;
use DB;
use Session;
use Redirect;
class LoginHandler
{
/**
* Maximum attempts
* If user tries to login but failed more than this number, User account will be locked
*
* #var integer
*/
private $max_attemtps;
/**
* Maximum attempts per IP
* If an IP / Device tries to login but failed more than this number, the IP will be blocked
*
* #var integer
*/
private $ip_max_attempts;
public function __construct()
{
$this->max_attempts = 10;
$this->ip_max_attempts = 5;
}
public function onLoginAttempt($data)
{
//detection process.......
// if login attempts more than max attempts
return Redirect::to('/')->with('message', 'Your account has been locked.');
}
}
Now the way I am doing this is like below:
Session::flash('message', 'Your account has been locked.');
header('Location: '.URL::to('/'));
It works but I am not sure if it's perfect way to do it.

You can still send an HttpException who will work. But obviously instructions after the event handler will not be interpreted
abort(redirect('/'));

Not getting to much into this very interesting discussion:
Should exceptions be used for flow control
You can try setting up your own exception handler and redirect from there on to the login page.
class FancyException extends Exception {}
App::error(function(FancyException $e, $code, $fromConsole)
{
$msg = $e->getMessage();
Log::error($msg);
if ( $fromConsole )
{
return 'Error '.$code.': '.$msg."\n";
}
if (Config::get('app.debug') == false) {
return Redirect::route('your.login.route');
}
else
{
//some debug stuff here
}
});
And in your function:
public function onLoginAttempt($data)
{
//detection process.......
// if login attempts more than max attempts
throw new FancyException("some msg here");
}

Related

How to run a code when a Laravel Job try is killed by timeout (Horizon)

I created a Laravel Job with 3 tries and timeout after 10 minutes. I am using Horizon.
I can handle the failure after 3 tries using the method failed, but how can I handle the timeout event each 3 tries of this job ?
Used for logging and feedback, I want my user to be notified when the first or second try fails and it will be retried later.
class MyJob implements ShouldQueue
{
public $tries = 3;
public $timeout = 600;
// [...]
public function failed(Throwable $exception)
{
// The failure of the 3 tries.
}
// Any method for catching each timeouts ?
}
You may define the $failOnTimeout property on the job class
/**
* Indicate if the job should be marked as failed on timeout.
*
* #var bool
*/
public $failOnTimeout = true;
https://laravel.com/docs/9.x/queues#failing-on-timeout
I dont think there is a method for that,
But you can do something like catch the Error thrown if the job fails and verify that its from timeout exception which I believe would throw the exception handler Symfony\Component\Process\Exception\ProcessTimedOutException.
Something like;
public function handle() {
try {
// run job
} catch (\Throwable $exception) {
// manually fail it if attempt is more than twice
if ($this->attempts() > 2)
$this->fail($exception);
// Check if the error it timeout related
if ( $exception instanceof \Symfony\Component\Process\Exception\ProcessTimedOutException ) {
// Whatever you want to do when it fails due to timeout
}
// release the job back to queue after 5 seconds
$this->release(5);
return;
}
}
Just try running a job and make sure it fails because of timeout, to verify the actual timeout class exception
Ok I found the solution.
TLDR;
Put a pcntl_signal at the beginning of your your job handle() and then you can do something like call a onTimeout() method :
public function handle()
{
pcntl_signal(SIGALRM, function () {
$this->onTimeout();
exit;
});
// [...]
}
public function onTimeout()
{
// This method will be called each
}
The story behind :
The Queue documentation says : The pcntl PHP extension must be installed in order to specify job timeouts.
So, digging into the pcntl PHP documentation I found interesting pcntl_* functions. And a call of pcntl_signal into Illuminate/Queue/Worker.php l221.
It looks that if we register a method using pcntl_signal it replace the previous handler. I tried to load the Laravel one using pcntl_signal_get_handler but I can't manage to call it. So the workaroud is to call exit; so Laravel will consider the process as lost and mark it as timeout (?). There is the 3 tries, the retry_after is respected, and at the last try the job fails ... It may be cleaner to keep the original handler, but as it work well on my case so I will stop investigate.

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'));
}
}

Doctrine EntityManager is Closed using Swoole

I have a Laravel application which is running 'swooletw/laravel-swoole' to handle requests. Doctrine is being used as ORM using laravel-doctrine/orm.
Normally if some ORMException is thrown by doctrine it closes the EntityManager which is supposed to be opened/reset automatically on next request. But while I am using swoole it does not simply happen and EntityManager remains closed unless the swoole worker is restarted.
I tried to fix this by checking if entitmanager is closed and resetting it in a Middleware. So on next request it should supply a new entity manager.
protected function handleClosedManagers()
{
foreach (Registry::getManagerNames() as $managerName) {
/** #var EntityManager $manager */
$manager = Registry::getManager($managerName);
if (!$manager->isOpen()) {
Registry::resetManager($managerName);
$manager = Registry::getManager($managerName);
}
}
}
Ideally it should fix it but I am getting "EntityManager is Closed." exception.
I assumed that I need to refresh it from laravel container as well.
So i changed the code to this:
protected function handleClosedManagers()
{
foreach (Registry::getManagerNames() as $managerName) {
/** #var EntityManager $manager */
$manager = Registry::getManager($managerName);
if (!$manager->isOpen()) {
Registry::resetManager($managerName);
$manager = Registry::getManager($managerName);
app()->forgetInstance('doctrine.managers.'.$managerName);
app()->forgetInstance('mem');
app()->forgetInstance(MagentoEntityManager::class);
app()->singleton('doctrine.managers.'.$managerName, function () use ($manager) {
return $manager;
});
// we are using separate entitymanager than default one
app()->singleton('mem', function () use ($manager) {
return $manager;
});
}
}
}
Now I am getting this on persist :
ErrorException: Undefined index: 000000005ba55063000000001b2f101d in file /***/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 3003
Anyone know the proper way to handle this situation?
I faced same problem in a Zend Expressive app.
Instead of trying to replace closed Entity Manager in a container and all services where it could be already injected, I found it easier to make the Entity Manager resettable by wrapping it into a decorator, that allows to replace actual Entity Manager with a fresh instance when needed:
final class ResettableEntityManager extends \Doctrine\ORM\Decorator\EntityManagerDecorator
{
/**
* #var callable
*/
private $entityManagerFactory;
public function __construct(callable $entityManagerFactory)
{
$this->entityManagerFactory = $entityManagerFactory;
parent::__construct($entityManagerFactory());
}
public function recreate(): void
{
$this->wrapped = ($this->entityManagerFactory)();
}
}
At the end of each request I then recreate the Entity Manager if it's closed, otherwise I just clear it to prevent memory leaks:
if ($this->entityManager->isOpen()) {
$this->entityManager->clear();
} else {
$this->entityManager->recreate();
}

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...

How to make Behat wait for Angular ajax calls?

I have a reporting page that is basically a table you can add and remove columns from. When you add a column, the data for that column is fetched and loaded with ajax, using angular.
Consider this Behat scenario:
Given I have a user named "Dillinger Four"
And I am on "/reports"
When I add the "User's Name" column
Then I should see "Dillinger Four"
How can I make Behat wait until angular's ajax call completes? I would like to avoid using a sleep, since sleeps add unnecessary delay and will fail if the call takes too long.
I used the following to wait for jquery code:
$this->getSession()->wait($duration, '(0 === jQuery.active)');
I haven't found a similar value to check with angular.
Your link above was helpful, just to expand on it and save someone else a little time.
/**
* #Then /^I should see "([^"]*)" if I wait "([^"]*)"$/
*/
public function iShouldSeeIfIWait($text, $time)
{
$this->spin(function($context) use ($text) {
$this->assertPageContainsText($text);
return true;
}, intval($time) );
}
/**
* Special function to wait until angular has rendered the page fully, it will keep trying until either
* the condition is meet or the time runs out.
*
* #param function $lambda A anonymous function
* #param integer $wait Wait this length of time
*/
public function spin ($lambda, $wait = 60)
{
for ($i = 0; $i < $wait; $i++)
{
try {
if ($lambda($this)) {
return true;
}
} catch (Exception $e) {
// do nothing
}
sleep(1);
}
$backtrace = debug_backtrace();
throw new Exception(
"Timeout thrown by " . $backtrace[1]['class'] . "::" . $backtrace[1]['function'] . "()\n" .
$backtrace[1]['file'] . ", line " . $backtrace[1]['line']
);
}
Then in your Scenario use:
Then I should see "Something on the page." if I wait "5"
You can use code from Angular's Protractor library to wait for loading. Here you can find a function waitForAngular(). It simply waits for a client-side function with the same name
Here's working PHP code.
class WebContext implements Context
{
/**
* #Then the list of products should be:
*/
public function theListOfProductsShouldBe(TableNode $table)
{
$this->waitForAngular();
// ...
}
private function waitForAngular()
{
// Wait for angular to load
$this->getSession()->wait(1000, "typeof angular != 'undefined'");
// Wait for angular to be testable
$this->getPage()->evaluateScript(
'angular.getTestability(document.body).whenStable(function() {
window.__testable = true;
})'
);
$this->getSession()->wait(1000, 'window.__testable == true');
}
}

Resources