I often find that my controllers in Symfony resemble the following:
try {
$myService->doFoo();
$success = 'Congratulations! It worked!';
} catch (ExceptionOne $e) {
$error = 'Exception one occurred. Please try this instead.'
} catch (ExceptionTwo $e) {
$error = 'Exception two occurred. Please try this instead.'
} catch (ExceptionThree $e) {
$error = 'Exception three occurred. Please try this instead.'
} catch (ExceptionFour $e) {
$error = 'Exception four occurred. Please try this instead.'
} catch (ExceptionFive $e) {
$error = 'Exception five occurred. Please try this instead.'
} catch (ExceptionSix $e) {
$error = 'Exception six occurred. Please try this instead.'
} catch (ExceptionSeven $e) {
$error = 'Exception seven occurred. Please try this instead.'
} catch (ExceptionEight $e) {
$error = 'Exception eight occurred. Please try this instead.'
} catch (ExceptionNine $e) {
$error = 'Exception nine occurred. Please try this instead.'
}
return $this->render('Bundle:Foo:bar.html.twig', [
'success' => $success,
'error' => $error,
]);
Obviously this is an exaggerated scenario, but essentially I'm finding that my controllers are becoming convoluted with long error/success message logic. Does Symfony have a better approach to this pattern, in keeping with the skinny-controller paradigm?
Maybe an ExceptionListener can help you here.
From symfony docs :
class ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
// You get the exception object from the received event
$exception = $event->getException();
$message = sprintf(
'My Error says: %s with code: %s',
$exception->getMessage(),
$exception->getCode()
);
// Customize your response object to display the exception details
$response = new Response();
$response->setContent($message);
// HttpExceptionInterface is a special type of exception that
// holds status code and header details
if ($exception instanceof HttpExceptionInterface) {
$response->setStatusCode($exception->getStatusCode());
$response->headers->replace($exception->getHeaders());
} else {
$response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
}
// Send the modified response object to the event
$event->setResponse($response);
}
}
Related
I always use Laravel project since Laravel 4 up to 7. But this is my first time encountering and error when I migrate my database in freshly download Laravel. This is the error when I migrate:
$e = $event->getThrowable();
if (!$event->hasResponse()) {
$this->finishRequest($request, $type);
throw $e;
}
$response = $event->getResponse();
// the developer asked for a specific status code
if (!$event->isAllowingCustomResponseCode() && !$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) {
// ensure that we actually have an error response
if ($e instanceof HttpExceptionInterface) {
// keep the HTTP status code and headers
$response->setStatusCode($e->getStatusCode());
$response->headers->add($e->getHeaders());
} else {
$response->setStatusCode(500);
}
}
try {
return $this->filterResponse($response, $request, $type);
} catch (\Exception $e) {
return $response;
}
}
/**
* Returns a human-readable string for the specified variable.
*/
private function varToString($var): string
{
if (\is_object($var)) {
return sprintf('an object of type %s', \get_class($var));
}
if (\is_array($var)) {
$a = [];
foreach ($var as $k => $v) {
$a[] = sprintf('%s => ...', $k);
}
return sprintf('an array ([%s])', mb_substr(implode(', ', $a), 0, 255));
}
if (\is_resource($var)) {
return sprintf('a resource (%s)', get_resource_type($var));
}
if (null === $var) {
return 'null';
}
if (false === $var) {
return 'a boolean value (false)';
}
if (true === $var) {
return 'a boolean value (true)';
}
if (\is_string($var)) {
return sprintf('a string ("%s%s")', mb_substr($var, 0, 255), mb_strlen($var) > 255 ? '...' : '');
}
if (is_numeric($var)) {
return sprintf('a number (%s)', (string) $var);
}
return (string) $var;
}
}
Error
Stack trace:
#0 D:\Supporting Enterprises\Projects\Laravel Projects\SuperpagesAPI\artisan(18): require()
#1 {main}
thrown in D:\Supporting Enterprises\Projects\Laravel Projects\SuperpagesAPI\vendor\autoload.php on line 7
Fatal error: Uncaught Error: Class 'ComposerAutoloaderInitbe984857c53a573c5f216d0eb36fe0e7' not found in D:\Supporting Enterprises\Projects\Laravel Projects\SuperpagesAPI\vendor\autoload.php:7
Stack trace:
#0 D:\Supporting Enterprises\Projects\Laravel Projects\SuperpagesAPI\artisan(18): require()
#1 {main}
thrown in D:\Supporting Enterprises\Projects\Laravel Projects\SuperpagesAPI\vendor\autoload.php on line 7
Before this happen I start IIS from the IIS Manager. I don't know if this will effect. I turn it on because I run a C# web application on my end.
It looks like this is composer problem. Just try to run this bash script:
composer dump-autoload
I want to run a code to send email to all users. At first i used this code to run a test.
->setTo([
'john.doe#gmail.com' => 'John Doe',
'jane.doe#gmail.com' => 'Jane Doe',
])
I found out that the mail is 1 mail sent to multiple recipents, while i need to 2 emails to 2 recipients. Because in reality i need to send to over hundred people at once. SO i try foreach loop.
public function contact($email)
{
$users = Users::find()->all();
$content = $this->body;
foreach($users as $user){
if ($this->validate()) {
Yii::$app->mailer->compose("#app/mail/layouts/html", ["content" => $content])
->setTo($user->email)
->setFrom($email)
->setSubject($user->fullname . ' - ' . $user->employee_id . ': ' . $this->subject)
->setTextBody($this->body)
->send();
return true;
}
}
return false;
}
But it only run 1 loop and end.
Please tell me where i'm wrong.
Thank you
the reason just one mail is sent is the
return true
it returns after the first email is sent, you should use try{}catch(){} like below
public function contact($email) {
$users = Users::find()->all();
$content = $this->body;
try {
foreach ($users as $user) {
if ($this->validate()) {
$r = Yii::$app->mailer->compose("#app/mail/layouts/html", ["content" => $content])
->setTo($user->email)
->setFrom($email)
->setSubject($user->fullname . ' - ' . $user->employee_id . ': ' . $this->subject)
->setTextBody($this->body)
->send();
if (!$r) {
throw new \Exception('Error sending the email to '.$user->email);
}
}
}
return true;
} catch (\Exception $ex) {
//display messgae
echo $ex->getMessage();
//or display error in flash message
//Yii::$app->session->setFlash('error',$ex->getMessage());
return false;
}
}
You can either return false in the catch part or return the error message rather than returning false and where ever you are calling the contact function check it the following way.
if(($r=$this->contact($email))!==true){
//this will display the error message
echo $r;
}
I have this code in my controller:
...
if (Model::validateMultiple($ttepk)) {
$transaction = \Yii::$app->db->beginTransaction();
try {
foreach ($ttepk as $ttep) {
$ttep->save(false);
if (!$ttep->assignPs()) {
throw new UserException('assignPs failed');
}
}
$transaction->commit();
return $this->redirect(['index']);
} catch (Exception $ex) {
$transaction->rollBack();
throw $ex;
}
}
...
in model:
...
public function assignPs() {
foreach (...) {
$ttepetk[...] = new Ttepet;
$ttepetk[...]->ttepId = $this->id;
... // set other attributes
}
}
if (Model::validateMultiple($ttepetk)) {
foreach ($ttepetk as $ttepet) {
$ttepet->save(false);
}
return true;
} else {
return false;
}
}
...
Everything is working fine (no inserts are happening if any of the models fail validation), except that I would like to see the exact error, exactly by which Ttep (each Ttep is a model) and by which Ttepet (Ttep:Ttepet = 1:N) has the error happened, and what was that. Now I see the Exeption page only, and I don't know how to make the errors visible. Please point me to the right direction. Thanks!
You could iterate on each single model validating one by one and getting the errors when occurs ...
if (Model::validateMultiple($ttepetk)) {
foreach ($ttepetk as $ttepet) {
$ttepet->save(false);
}
return true;
} else {
foreach($ttepetk as $model){
if ($model->validate()) {
// all inputs are valid
} else {
// validation failed: $errors is an array containing error messages
$errors = $model->errors;
}
}
return $errors;
}
you can get the errors this way
$myErrorResult = $ttep->assignPs();
if (!$myErrorResult) {
......
this is my code:
$transaction = Yii::app()->db->beginTransaction();
try {
$tModel->save();
$activationLink = new ActivationLink;
$activationLink->User_id = $tModel->id;
$activationLink->hash1 = User::generateHashCode(100);
$activationLink->hash2 = User::generateHashCode();
$activationLink->hash3 = User::generateHashCode();
$activationLink->time = time();
$activationLink->save();
User::sendActivatonLink($tModel->mail,$activationLink->id, $activationLink->hash1, $activationLink->hash2, $activationLink->hash3);
$transaction->commit();
$this->redirect(array('view', 'id' => $tModel->id));
} catch (Exception $e) {
$transaction->rollback();
Yii::app()->user->setFlash('error', "{$e->getMessage()}");
$this->refresh();
}
$tModel saved but $activationLink doesn't so it should rolled back. but it didn't ,why?
Yii save() does not throw an exception, when just the validation fails. Thus you have to check the result of save() yourself:
if (!$model->save())
$transaction->rollback();
//or:
if (!$model->save())
throw new Exception("This will trigger my catch statement block");
Please check your mysql engine I think you are not using innodb. To execute transaction we must use innodb. Let me know your table type/engine.
OR
You also need to add in your code to understand error in log.
throw new Exception($e);
It it possible in Silex to use an error handler based on what exception is thrown?
I know this is possible with a single exception handler and a switch statement on the classname of the thrown exception but to me it seems the "Silex way" is cleaner, yet doesn't work.
This is how I would expect it to work
<?php
// Handle access denied errors
$app->error(function (\App\Rest\Exception\AccessDenied $e) {
$message = $e->getMessage() ?: 'Access denied!';
return new Response($message, 403);
});
// Handle Resource not found errors
$app->error(function (\App\Rest\Exception\ResourceNotFound $e) {
$message = $e->getMessage() ?: 'Resource not found!';
return new Response($message, 404);
});
// Handle other exception as 500 errors
$app->error(function (\Exception $e, $code) {
return new Response($e->getMessage(), $code);
});
Problem is that when I throw a ResourceNotFound exception in my controller, the errorhandler tied to AccessDenied is executed
Catchable fatal error: Argument 1 passed to {closure}() must be an instance of App\Rest\Exception\AccessDenied, instance of App\Rest\Exception\ResourceNotFound given
Is this achievable in another way or should I just stuff everything in the handler that works with generic Exceptions and switch on the type of exception thrown?
PS: i'm aware of the $app->abort() method but prefer working with exceptions
EDIT: This feature has now made it into Silex core!
This is currently not possible. Right now you'd have to either have a single handler with a switch statement, or many handlers with an if ($e instanceof MyException) each.
I do like the idea though, and it should be possible to implement it by using reflection. It would be awesome if you could create a new ticket on the tracker, or even work on a patch, if you're interested.
Cheers!
Another solution that I use in my projects:
class ProcessCallbackException extends Exception
{
public function __construct(\Closure $callback, $message = "", Exception $previous = null)
{
parent::__construct($message, 0, $previous);
$this->callback = $callback;
}
public $callback;
}
class AccessDeniedException extends ProcessCallbackException
{
public function __construct($message = null)
{
$f = function() {
return app()->redirect('/login');
};
parent::__construct($f, $message);
}
}
# Handle your special errors
$app->error(function (\Exception $e, $code) {
if ($e instanceof ProcessCallbackException)
{
/** #var ProcessCallbackException $callbackException */
$callbackException = $e;
return call_user_func($callbackException->callback);
}
else
return null;
});