Zend 2: Route constraints log specific error - php

I try to log if user type wrong url parameter for a route with
'constraints' => array('personalnumber' => '[0-9]*')
$error = $e->getError();
if ($error == Application::ERROR_ROUTER_NO_MATCH) {
$url = $e->getRequest()->getUriString();
$sm->get('Zend\Log\RouteLogger')->warn('Url could not match to routing: ' . $url);
}
Can I get a specific error like: Value for Parameter "id" must type integer?

That won't be so easy. You would have to build your own functionality to find out the exact details on why the route didn't match.
Route matching is checked using the RouteInterface::match method from the corresponding class. For example for segment routes this method can be found in the Zend\Router\Http\Segment class on line 359-404.
If there is no match, the class returns null/void. Details on why the route didn't match is not part of the response, so you would have to do such in depth analysis yourself and write your own custom error response.
Such a solution could be to do manually validate the person number (for example by isolating it from the request url) when the dispatch error event is triggered and return your own custom response before the default 404 response.
<?php
namespace Application;
use Zend\Http\Response;
use Zend\Mvc\MvcEvent;
use Zend\Router\Http\RouteMatch;
class Module{
public function onBootstrap(MvcEvent $event)
{
$eventManager = $event->getApplication()->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH_ERROR, array($this, 'validatePersonNumber'), 1000);
}
public function validatePersonNumber(MvcEvent $event)
{
if ($event->getError() !== Application::ERROR_ROUTER_NO_MATCH) {
// Not a 404 error
return;
}
$request = $event->getRequest();
$controller = $event->getController();
if($controller !== 'Application\Expected\ControllerName'){
// not a controller for person number route
return;
}
$url = $request->getRequestUri();
$personNumber = ''; //...manually isolate the person number from the request...
/** #var Response $response */
$response = $event->getResponse();
$response->setStatusCode(404);
$viewModel = $event->getViewModel();
$viewModel->setTemplate('error/404');
$event->setViewModel($viewModel);
$event->stopPropagation(true);
if (strlen($personNumber) !== 12) {
$viewModel->setVariable('message', 'A person number should have 12 characters');
}
if(...additional check...){
$viewModel->setVariable('message', 'Some other message');
}
}
}
To make things prettier you could consider moving all this into a Listener class (instead of polluting your module.php file) and you could also consider the 404 code here. Most likely there is a more suitable status code for such validation response.
Note: This is not a completely finished example, it needs more work!

Related

Dynamically-created Twilio Enqueue waitUrl results in 500 server error

I have a function in my Laravel application that generates TwiML for a holding queue. It seems that when I try to dynamically generate the value for the waitUrl attribute, I end up getting a 500 server error during runtime. Routes are properly established and I'm able to view the correct XML at the waitURL in the browser. However, the error persists.
If I create a static XML file with the same exact content, or use a TwiML Bin, it works like a charm.
Here are the relevant functions:
public function wait() {
return $this->generateWaitTwiml();
}
public function onHold($agentId) {
return $this->generateHoldQueueTwiml($agentId, '/phone/wait');
}
private function generateHoldQueueTwiml($agentId, $waitUrl = null) {
$queue = $agentId . '_hold';
if ($waitUrl === null){
$waitUrl = 'path_to_static.xml';
}
$queue = $agentId . '_hold';
$response = new Twiml();
$response->enqueue(
$queue,
['waitUrl' => $waitUrl]
);
return response($response)->header('Content-Type', 'application/xml');
}
private function generateWaitTwiml() {
$response = new Twiml();
$response
->play('http://path_to_my.mp3');
return response($response)->header('Content-Type', 'application/xml');
}
This was resolved by excluding the URIs from the CSRF verification (in VerifyCsrfToken.php):
class VerifyCsrfToken extends Middleware {
protected $except = [
'uri/',
'uri2/*',
];
}

How should one redirect locale-less URIs to locale-full ones in Symfony-CMF?

Background
We have a (fairly typical?) arrangement for a multilingual Symfony CMF website, where resource paths are prefixed by the desired locale—for example:
http://www.example.com/en/path/to/english-resource.html; and
http://www.example.com/fr/voie/à/ressource-française.html.
We are using RoutingAutoBundle to store such routes in the content repository, and DynamicRouter to utilise them: simple and easy.
If a GET request arrives without a locale prefix, we would like to:
determine the most appropriate locale for the user; and then
redirect1 the user to the same path but with locale prefix added.
Current Approach
The first part is an obvious candidate for LuneticsLocaleBundle, with router higher in its guessing order than our desired fallback methods: again, simple and easy.
However, how best to implement the second part is a little less obvious. Currently we have configured Symfony's default/static router to have a lower priority in the routing chain than DynamicRouter, and have therein configured a controller as follows:
/**
* #Route("/{path}", requirements={"path" = "^(?!(en|fr)(/.*)?$)"})
* #Method({"GET"})
*/
public function localeNotInUriAction()
{
$request = this->getRequest();
$this->redirect(
'/'
. $request->getLocale() // set by Lunetics
. $request->getRequestUri()
);
}
But this feels rather hacky and I'm on the search for something "cleaner".
A better way?
Initially I thought to modify LuneticsLocaleBundle so that it would fire an event whenever a guesser determines the locale, thinking that if it was not the RouterLocaleGuesser then we could infer that the requested URI did not contain a locale. However this clearly isn't the case, since the RouterLocaleGuesser will only determine the locale if there was a route in the first place—so I'd not have made any progress.
I'm now a bit stuck for any other ideas. Perhaps I'm already doing the right thing after all? If so, then all I need to do is find some way to inject the permitted locales (from the config) into the requirement regex…
External redirection, i.e. via a response with HTTP 302 status.
we use a custom 404 handler and lunetics:
exception_listener:
class: AppBundle\EventListener\ExceptionListener
arguments:
container: "#service_container"
tags:
- { name:"kernel.event_listener", event:kernel.exception, handler:onKernelException }
and the php class
class ExceptionListener
{
/**
* #var ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($this->container->getParameter('kernel.debug')) {
// do not interfere with error handling while debugging
return;
}
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
$this->handle404($event);
return;
}
// ...
}
public function handle404(GetResponseForExceptionEvent $event)
{
$request = $event->getRequest();
if (preg_match('#^\/(de|fr|en)\/#', $request->getPathInfo())) {
// a real 404, these are nicely handled by Twig
return;
}
// i *think* that the locale is not set on the request, as lunetics comes after routing, and the routing will raise the 404
$bestLang = $this->container->get('lunetics_locale.guesser_manager')->runLocaleGuessing($request);
if (! $bestLang) {
$bestLang = 'de';
}
$qs = $request->getQueryString();
if (null !== $qs) {
$qs = '?'.$qs;
}
$url = $request->getSchemeAndHttpHost() . $request->getBaseUrl() . '/' . $bestLang . $request->getPathInfo() . $qs;
$this->redirect($event, $url);
}
it would be nicer to also check if the target path actually exists - as is, we will redirect /foobar to /de/foobar and display a 404 for that one, which is not that elegant.

Zend Framework 2: How to place a redirect into a module, before the application reaches a controller

Let's say we have a module named Cart and want to redirect users if some condition is met.
I want to place a redirect at the module bootstrapping stage, before the application reaches any controller.
So here is the module code:
<?php
namespace Cart;
class Module
{
function onBootstrap() {
if (somethingIsTrue()) {
// redirect
}
}
}
?>
I wanted to use the Url controller plugin, but it seems the controller instance is not available at this stage, at least I don't know how to get it.
Thanks in advance
This should do the necessary work:
<?php
namespace Cart;
use Zend\Mvc\MvcEvent;
class Module
{
function onBootstrap(MvcEvent $e) {
if (somethingIsTrue()) {
// Assuming your login route has a name 'login', this will do the assembly
// (you can also use directly $url=/path/to/login)
$url = $e->getRouter()->assemble(array(), array('name' => 'login'));
$response=$e->getResponse();
$response->getHeaders()->addHeaderLine('Location', $url);
$response->setStatusCode(302);
$response->sendHeaders();
// When an MvcEvent Listener returns a Response object,
// It automatically short-circuit the Application running
// -> true only for Route Event propagation see Zend\Mvc\Application::run
// To avoid additional processing
// we can attach a listener for Event Route with a high priority
$stopCallBack = function($event) use ($response){
$event->stopPropagation();
return $response;
};
//Attach the "break" as a listener with a high priority
$e->getApplication()->getEventManager()->attach(MvcEvent::EVENT_ROUTE, $stopCallBack,-10000);
return $response;
}
}
}
?>
Of course it gives you an error because you must attach your listener to an event. In the folllowing example i use SharedManager and i attach the listener to AbstractActionController.
Of course you can attach your listener to another event. Below is just a working example to show you how it works. For mor info visit http://framework.zend.com/manual/2.1/en/modules/zend.event-manager.event-manager.html.
public function onBootstrap($e)
{
$e->getApplication()->getEventManager()->getSharedManager()->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function($e) {
$controller = $e->getTarget();
if (something.....) {
$controller->plugin('redirect')->toRoute('yourroute');
}
}, 100);
}
The page isn't redirecting properly on error
public function onBootstrap($e) {
$e->getApplication()->getEventManager()->getSharedManager()->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function($e) {
if(someCondition==true) {
$controller->plugin('redirect')->toRoute('myroute');
}
}
Can you try this.
$front = Zend_Controller_Front::getInstance();
$response = new Zend_Controller_Response_Http();
$response->setRedirect('/profile');
$front->setResponse($response);

Laravel - using controllers instead of routes for actions

I've literally downloaded Laravel today and like the looks of things but i'm struggeling on 2 things.
1) I like the controllers' actions method of analysing urls instead of using routes, it seems to keep everything together more cleanly, but lets say I want to go to
/account/account-year/
how can I write an action function for this? i.e.
function action_account-year()...
is obviously not valid syntax.
2) If i had
function action_account_year( $year, $month ) { ...
and visited
/account/account_year/
An error would be displayed about missing arguments, how do you go about making this user friendly/load diff page/display an error??
You would have to manually route the hyphenated version, e.g.
Route::get('account/account-year', 'account#account_year');
Regarding the parameters, it depends on how you are routing. You must accept the parameters in the route. If you are using full controller routing (e.g. Route::controller('account')) then the method will be passed parameters automatically.
If you are manually routing, you have to capture the params,
Route::get('account/account-year/(:num)/(:num)', 'account#account_year');
So visiting /account/account-year/1/2 would do ->account_year(1, 2)
Hope this helps.
You can think of the following possibility as well
class AccountController extends BaseController {
public function getIndex()
{
//
}
public function getAccountYear()
{
//
}
}
Now simply define a RESTful controller in your routes file in the following manner
Route::controller('account', 'AccountController');
Visiting 'account/account-year' will automatically route to the action getAccountYear
I thought I'd add this as an answer in case anyone else is looking for it:
1)
public function action_account_year($name = false, $place = false ) {
if( ... ) {
return View::make('page.error' );
}
}
2)
not a solid solutions yet:
laravel/routing/controller.php, method "response"
public function response($method, $parameters = array())
{
// The developer may mark the controller as being "RESTful" which
// indicates that the controller actions are prefixed with the
// HTTP verb they respond to rather than the word "action".
$method = preg_replace( "#\-+#", "_", $method );
if ($this->restful)
{
$action = strtolower(Request::method()).'_'.$method;
}
else
{
$action = "action_{$method}";
}
$response = call_user_func_array(array($this, $action), $parameters);
// If the controller has specified a layout view the response
// returned by the controller method will be bound to that
// view and the layout will be considered the response.
if (is_null($response) and ! is_null($this->layout))
{
$response = $this->layout;
}
return $response;
}

Symfony2: Redirecting to last route and flash a message?

I'm having a small problem when trying to flash a message and redirect the user back to the previous page in Symfony 2.
I have a very simple CRUD. When new, or edit, i want to flash a message if something goes wrong in the respective create/update methods:
User --GET--> new
new --POST--> create (fails)
--REDIRECT--> new (with flash message)
I'm doing the following:
$this->container->get('session')->setFlash('error', 'myerror');
$referer = $this->getRequest()->headers->get('referer');
return new RedirectResponse($referer);
However, it's not redirecting to the correct referrer! Even though the value of referrer is correct (eg.: http://localhost/demo/2/edit/) It redirects to the index. Why?
This is an alternative version of Naitsirch and Santi their code. I realized a trait would be perfect for this functionality. Also optimized the code somewhat. I preferred to give back all the parameters including the slugs, because you might need those when generating the route.
This code runs on PHP 5.4.0 and up. You can use the trait for multiple controllers of course. If you put the trait in a seperate file make sure you name it the same as the trait, following PSR-0.
<?php
trait Referer {
private function getRefererParams() {
$request = $this->getRequest();
$referer = $request->headers->get('referer');
$baseUrl = $request->getBaseUrl();
$lastPath = substr($referer, strpos($referer, $baseUrl) + strlen($baseUrl));
return $this->get('router')->getMatcher()->match($lastPath);
}
}
class MyController extends Controller {
use Referer;
public function MyAction() {
$params = $this->getRefererParams();
return $this->redirect($this->generateUrl(
$params['_route'],
[
'slug' => $params['slug']
]
));
}
}
For symfony 3.0,flash message with redirection back to previous page,this can be done in controller.
$request->getSession()
->getFlashBag()
->add('notice', 'success');
$referer = $request->headers->get('referer');
return $this->redirect($referer);
The message from Naitsirch presented in the next url:
https://github.com/symfony/symfony/issues/2951
Seems a good solution for that you need:
public function getRefererRoute()
{
$request = $this->getRequest();
//look for the referer route
$referer = $request->headers->get('referer');
$lastPath = substr($referer, strpos($referer, $request->getBaseUrl()));
$lastPath = str_replace($request->getBaseUrl(), '', $lastPath);
$matcher = $this->get('router')->getMatcher();
$parameters = $matcher->match($lastPath);
$route = $parameters['_route'];
return $route;
}
Then with a redirect:
public function yourFunctionAction()
{
$ruta = $this->getRefererRoute();
$locale = $request->get('_locale');
$url = $this->get('router')->generate($ruta, array('_locale' => $locale));
$this->getRequest()->getSession()->setFlash('notice', "your_message");
return $this->redirect($url);
}
This works for me:
$this->redirect($request->server->get('HTTP_REFERER'));
I have similar functionality on my site. It is multilingual. Articles exists only in a single locale. When the user will try to switch to other locale, it should redirect back to previous page and flash message that that page/article doesn't exist on the requested locale.
/en/article/3 -> /fr/article/3 (404) -> Redirect(/en/article/3)
Here is my version of the script that works well on dev and prod environments:
$referer = $request->headers->get('referer')
// 'https://your-domain.com' or 'https://your-domain.com/app_dev.php'
$base = $request->getSchemeAndHttpHost() . $request->getBaseUrl();
// '/en/article/3'
$path = preg_replace('/^'. preg_quote($base, '/') .'/', '', $referer);
if ($path === $referer) {
// nothing was replaced. referer is an external site
} elseif ($path === $request->getPathInfo()) {
// current page and referer are the same (prevent redirect loop)
} else {
try {
// if this will throw an exception then the route doesn't exist
$this->container->get('router')->match(
// '/en/hello?foo=bar' -> '/en/hello'
preg_replace('/\?.*$/', '', $path)
);
// '/app_dev.php' . '/en/article/3'
$redirectUrl = $request->getBaseUrl() . $path;
return new RedirectResponse($redirectUrl);
} catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {}
}
I just set up a simple app, and it seems to work fine. My createAction() looks like this:
public function createAction()
{
$entity = new Pokemon();
$request = $this->getRequest();
$form = $this->createForm(new PokemonType(), $entity);
$form->bindRequest($request);
if ($entity->getName() == "pikachu")
{
$this->container->get("session")->setFlash("error", "Pikachu is not allowed");
$url = $this->getRequest()->headers->get("referer");
return new RedirectResponse($url);
}
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('pokemon_show', array('id' => $entity->getId())));
}
return $this->render('BulbasaurBundle:Pokemon:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView()
));
}
The flow goes:
User navigates to /new
User enters invalid option of "pikachu"
User clicks submit (POSTs to /create)
Application rejects the entry, adds flash message, and redirects back to /new
User sees /new with the flash message
A few things to check:
Is your route for /demo/{entityId}/edit actually working? (i.e. if you enter it in the browser, does it actually go to where you expect it to?)
Are you chaining together different redirects/forwards? I've noticed that I get unexpected (but correct) behavior when I have a controller that redirects to a URL, and the controller responsible for that URL also redirects somewhere else. I've fixed this issue by using forwards instead.
That said, if all else fails, you could just use the controller's redirect() method to manage the redirect:
public function createAction()
{
...
return $this->redirect($this->generateUrl("pokemon_new"));
...
}
Here you go, declare this as a service and it will return referer to you wherever and whenever you need it. No traits, no weird dependencies.
class Referer
{
/** #var RequestStack */
private $requestStack;
/** #var RouterInterface */
private $router;
public function __construct(RequestStack $requestStack, RouterInterface $router)
{
$this->requestStack = $requestStack;
$this->router = $router;
}
public function getReferer() : string
{
$request = $this->requestStack->getMasterRequest();
if (null === $request)
{
return '';
}
//if you're happy with URI (and most times you are), just return it
$uri = (string)$request->headers->get('referer');
//but if you want to return route, here you go
try
{
$routeMatch = $this->router->match($uri);
}
catch (ResourceNotFoundException $e)
{
return '';
}
$route = $routeMatch['_route'];
return $route;
}
}
seems like you need to have a payload for your redirect to point to. it seems like obscure concept code to me. I would also advise you to make sure your configuration files point to the correct redirect code snippet. Check your server access file to make sure it has redirects enabled also.

Categories