I'm trying to get all the errors from all the queries I do in my project and redirect this errors to a controller called "error" that will treat theses errors as I want. The problem looks like when I redirect all the information goes in the url generated by the function via GET.
I suppose that if this information is sent via POST will disappear this problem but I'm not using obviously any form inside the controller. So, how can I say to the redirect function that these information shouldn't go with the url and instead should go via POST?
Is possible what I'm trying to do?
Inside Controllers:
try {
$results = $queries->aQuery();
} catch (ErrorException $errorException) {
return $this->redirect($errorException->redirectResponse);
}
Inside the service query:
public function aQuery(){
$query="SELECT * FROM blabla ...";
try {
$stmt = $this->DB->->prepararQuery($query);
$stmt->execute();
$results = $stmt->fetchAll();
} catch (DBALException $DBALException) {
$errorException = new ErrorException($this->router->generate('error',
[
'errorQuery' => $query,
'errorData' => "0 => '".$data1."', 1 ....",
'errorOrigin' => 'a place',
'errorResponseText' => $DBALException->getMessage()
]
));
throw $errorException;
}
}
The ErrorException:
class ErrorException extends \Exception
{
/**
* #var \Symfony\Component\HttpFoundation\RedirectResponse
*/
public $redirectResponse;
/**
* ErrorException constructor.
* #param \Symfony\Component\HttpFoundation\RedirectResponse $redirectResponse
*/
public function __construct(string $redirectResponse)
{
$this->redirectResponse = $redirectResponse;
}
}
If what you are trying to achieve is a centralized way to handle exceptions have a look at https://symfony.com/doc/4.0/event_dispatcher.html#creating-an-event-listener and use kernel.exception event
public function onKernelException(GetResponseForExceptionEvent $event)
{
if (! $event->getException() instanceof ErrorException) {
return;
}
// handle your custom ErrorException
$response = new Response();
$response->setContent($event->getException()->getMessage());
// sends the modified response object to the event
$event->setResponse($response);
}
Related
I want to implement Twilio browser to browser call with Symfony5 and ApiPlatform
I'm following this tuto:
https://www.twilio.com/docs/voice/client/tutorials/calls-between-devices?code-sample=code-generate-twiml-from-client-parameters-3&code-language=PHP&code-sdk-version=5.x
I have this function, that's the one I want my TwiML app to be configured on
/**
* #Route("/twilio/handle/twiml/{clientId}", name="twilio_handl_twiml")
* #param $clientId
* #return VoiceResponse
*/
public function handleTwiml($clientId): VoiceResponse
{
/** #var Client $client */
$client = $this->clientRepository->findOneBy(['id' => 11]);
$to = $client->getUser()->getLastName().$client->getUser()->getId();
$voiceResponse = new VoiceResponse();
$number = htmlspecialchars($to);
$dial = $voiceResponse->dial(null, array('callerId' => '+15017122661'));
if (isset($to)) {
if (preg_match("/^[\d\+\-\(\) ]+$/", $number)) {
$dial->number($number);
} else {
$dial->client($number);
}
} else {
$voiceResponse->say('There has been an issue. Thanks for calling!');
}
return $voiceResponse;
}
And I've declared it as a custom route on one of my entities in the "get" section:
* "twilio_handl_twiml"={
* "path"="/twilio/handle/twiml/{clientId}",
* "controller"="TwilioController:class"
* },
Now the function creates a proper VoiceResponse object
But when I call this route I get the following error message:
The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned an object of type Twilio\TwiML\VoiceResponse.
Now does anyone know why I couldn't return whatever kind of Response I want from a custom route ?
I don't really see why the framework would declare this as an error
If anyone can help me understand better this error I'd appreciate it
Thanks!
Twilio developer evangelist here.
As #Cerad has said in the comments, you need to respond with an object derived from the Symfony Response object.
I haven't used Symfony, so please excuse me if this is wrong, but I think you can update your handler to the following, it might work:
use Symfony\Component\HttpFoundation\Response;
/**
* #Route("/twilio/handle/twiml/{clientId}", name="twilio_handl_twiml")
* #param $clientId
* #return Response
*/
public function handleTwiml($clientId): VoiceResponse
{
/** #var Client $client */
$client = $this->clientRepository->findOneBy(['id' => 11]);
$to = $client->getUser()->getLastName().$client->getUser()->getId();
$voiceResponse = new VoiceResponse();
$number = htmlspecialchars($to);
$dial = $voiceResponse->dial(null, array('callerId' => '+15017122661'));
if (isset($to)) {
if (preg_match("/^[\d\+\-\(\) ]+$/", $number)) {
$dial->number($number);
} else {
$dial->client($number);
}
} else {
$voiceResponse->say('There has been an issue. Thanks for calling!');
}
$response = new Response(
$voiceResponse->asXML(),
Response::HTTP_OK,
['content-type' => 'application/xml']
);
return $response;
}
The key here is to build up the Symfony response with the content of the voice response ($voiceResponse->asXML()) and also set the content type to application/xml.
I have a CrudController for my entity, Participant. I want to add a custom action, sendAcknowledgementEmail. The EasyAdmin docs doesn't mention anything about the custom function parameters or return values.
I have the following code
public function configureActions(Actions $actions): Actions
{
$send_acknowledgement_email = Action::new('sendAcknowledgementEmail', 'Send Acknowledgement Email', 'fa fa-send')
->linkToCrudAction('sendAcknowledgementEmail');
return $actions
->add(Crud::PAGE_INDEX, $send_acknowledgement_email)
->add(Crud::PAGE_EDIT, $send_acknowledgement_email)
;
}
public function sendAcknowledgementEmail() //Do I need parameters?
{
//How do I get the Entity?
//What should I return?
}
So far, EasyAdmin detects the custom function but I get an error "The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned null. Did you forget to add a return statement somewhere in your controller?"
How do I continue from here?
The v3.x of the bundle is quite new and the documentation is not perfect yet.
Based on Ceochronos answer, here is my implementation for a clone action.
public function configureActions(Actions $actions): Actions
{
$cloneAction = Action::new('Clone', '')
->setIcon('fas fa-clone')
->linkToCrudAction('cloneAction');
return $actions
->add(Crud::PAGE_INDEX, $cloneAction);
}
public function cloneAction(AdminContext $context)
{
$id = $context->getRequest()->query->get('entityId');
$entity = $this->getDoctrine()->getRepository(Product::class)->find($id);
$clone = clone $entity;
// custom logic
$clone->setEnabled(false);
// ...
$now = new DateTime();
$clone->setCreatedAt($now);
$clone->setUpdatedAt($now);
$this->persistEntity($this->get('doctrine')->getManagerForClass($context->getEntity()->getFqcn()), $clone);
$this->addFlash('success', 'Product duplicated');
return $this->redirect($this->get(CrudUrlGenerator::class)->build()->setAction(Action::INDEX)->generateUrl());
}
After browsing through the EasyAdmin AbstractCrudController I came up with the following working code.
In order to get the current object you need the parameter AdminContext
For my use case I want to return to the CrudController index action, so for that I can do a redirect.
Note: you need to inject the CrudUrlGenerator service in your constructor controller.
public function sendAcknowledgementEmail(AdminContext $context)
{
$participant = $context->getEntity()->getInstance();
// Your logic
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl();
return $this->redirect($url);
}
My current function looks like this:
public function sendAcknowledgementEmail(AdminContext $context)
{
$participant = $context->getEntity()->getInstance();
$participant->sendAcknowledgementEmail();
$this->addFlash('notice','<span style="color: green"><i class="fa fa-check"></i> Email sent</span>');
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl();
return $this->redirect($url);
}
My current working code
<?php
namespace App\Controller\Admin;
use App\Service\WebinarService;
use EasyCorp\Bundle\EasyAdminBundle\Router\CrudUrlGenerator;
use Symfony\Contracts\Translation\TranslatorInterface;
// ...
class ParticipantCrudController extends AbstractCrudController
{
private CrudUrlGenerator $crudUrlGenerator;
private WebinarService $webinar_service;
private TranslatorInterface $translator;
public function __construct(CrudUrlGenerator $crudUrlGenerator, WebinarService $webinar_service, TranslatorInterface $translator)
{
$this->crudUrlGenerator = $crudUrlGenerator;
$this->webinar_service = $webinar_service;
$this->translator = $translator;
}
// ...
public function sendAcknowledgementEmail(AdminContext $context): Response
{
$participant = $context->getEntity()->getInstance();
try {
$this->webinar_service->sendAcknowledgementEmail($participant);
$this->addFlash('notice', 'flash.email.sent');
} catch (Exception $e) {
$this->addFlash('error', $this->translator->trans('flash.error', ['message' => $e->getMessage()]));
}
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl()
;
return $this->redirect($url);
}
}
I would like to add one action to a page (let's say the route entity.node.canonical) but this action would appear and disappear from the page time to time.
So what I'm trying to do is to create the action using a deriver (I put my condition to show the action in the method getDerivativeDefinitions) and then, on a kernel event, I refresh the local actions using \Drupal::service('plugin.manager.menu.local_action')->clearCachedDefinitions();
But it still doesn't work !
So is anybody capable of showing me a method to show and hide an action ?
Here is my derivative:
class ConditionalAction extends DeriverBase implements ContainerDeriverInterface {
/**
* {#inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition)
{
// If the seconds is more than 30, hide the action.
if (date('s') > 30) {
return $this->derivatives;
}
// Else, show the action.
$menu_entry = $base_plugin_definition;
$menu_entry['route_name'] = 'my.route.name';
$menu_entry['title'] = 'Test action';
$menu_entry['appears_on'][] = \Drupal::routeMatch()->getRouteName();
$menu_entry['route_parameters'] = ['time' => date('s')];
$this->derivatives['my.action.name'] = $menu_entry;
return $this->derivatives;
}
}
And here is the event subscriber:
class MyModuleKernelSubscriber implements EventSubscriberInterface {
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents() {
return [
KernelEvents::REQUEST => 'onKernelRequest'
];
}
/**
* Event called when a request is sent.
*
* #param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event) {
// Do not consider the ajax requests.
$request = $event->getRequest();
if ($request->isXmlHttpRequest() === TRUE) {
return;
}
// Flush local action cache.
\Drupal::service('plugin.manager.menu.local_action')->clearCachedDefinitions();
}
}
So, here, on each request, the local action should be cleared and the code should go through my derivative. But it's not working... Any thought ?
I really need it to be an action ! And I really need the URL argument to be dynamic as well (here, $menu_entry['route_parameters'] = ['time' => date('s')];).
Thank you
By looking inside the console command drupal cache:reset, I found that the lines
$bins = Cache::getBins();
$bins['render']->deleteAll();
$bins['discovery']->deleteAll();
reset the local actions on each requests.
So I have my answer now.
Hope this will help !
I have an Event Subscriber with function setAcademicCalendar. I want to catch exceptions, display an error message in the flash bag and terminate form submit. Basically, I want to stay in the form (no redirects), give the user an error message and don't save the form.
I have two problems. 1. I don't know to terminate the process 2. The flash message is only displayed after a page refresh.
private function setAcademicCalendar(FormEvent $event) {
/** #var CalendarEvent $calendar_event */
$calendar_event = $event->getData();
if ($calendar_event->getCalendar() instanceof Calendar) {
try {
$sem = $this
->container
->get('academic_calendar')
->getSemester($calendar_event->getStart());
$calendar_event->setSemester($sem);
} catch (\Exception $e) {
$this->container->get('session')->getFlashBag()->add('error', $e->getMessage());
}
}
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
FormEvents::SUBMIT => 'submitData',
FormEvents::PRE_SET_DATA => 'preSetData',
];
}
/**
* #param FormEvent $event
*/
public function submitData(FormEvent $event)
{
$this->setAcademicCalendar($event);
}
If you want to do this without redirection / refresh forget about validation in strict php. Only some strict frontend techs - like angular js , vue.js with some api calls
I have a problem regarding a Symfony application, I want to take as input the "username" or "id" for my controller , and receive information that is in my table "user" and also 2 other table for example : A user has one or more levels , and also it has points must earn points to unlock a level , I want my dan Home page display the username and the level and extent that it has , I jn am beginner and not come to understand the books symfony that I use, I work with PARALLEL " symfony_book " and " symfony_cook_book " and also tutorial youtube May I blocks , here is the code for my cotroler
"
/**
* #Route("/{id}")
* #Template()
* #param $id=0
* #return array
*/
public function getUserAction($id)
{
$username = $this->getDoctrine()
->getRepository('voltaireGeneralBundle:FosUser')
->find($id);
if (!$username) {
throw $this->createNotFoundException('No user found for id '.$id);
}
//return ['id' => $id,'username' => $username];
return array('username' => $username);
}
and I have to use the relationship among classes
use Doctrine\Common\Collections\ArrayCollection;
class Experience {
/**
* #ORM\OneToMany(targetEntity="FosUser", mappedBy="experience")
*/
protected $fosUsers;
public function __construct()
{
$this->fosUsers = new ArrayCollection();
}
}
and
class FosUser {
/**
* #ORM\ManyToOne(targetEntity="Experience", inversedBy="fosUsers")
* #ORM\JoinColumn(name="experience_id", referencedColumnName="id")
*/
protected $fosUsers;
}
and i have always an error
In Symfony you cannot return an array in Action function!, Action function must always return a Response object...So if you want to return data to browser in Symfony, Action function have to return a string wrapped up in Response object.
In your controller code, to return the array to browser, You can serialize an array to JSON and send it back to browser:
public function getUserAction($id)
{
$username = $this->getDoctrine()
->getRepository('voltaireGeneralBundle:FosUser')
->find($id);
if (!$username) {
throw $this->createNotFoundException('No user found for id '.$id);
}
return new Response(json_encode(array('username' => $username)));
}
I suggest you to read more about HTTP protocol , PHP, and Symfony.