I have a few modules, one is an API. I want to set a different ErrorHandler for this API module.
Because when the default ErrorHandler is fired it uses the default module with the default layout, which contains HTML. Which is something I do not want my API to be returning.
I have looked on here and at the Zend Docs and have not come up with anything that allows me to do this.
Thanks for any help.
Here is the one I use:
<?php
class My_Controller_Plugin_Modular_ErrorController extends Zend_Controller_Plugin_Abstract
{
public function routeShutdown (Zend_Controller_Request_Abstract $request)
{
$front = Zend_Controller_Front::getInstance();
// Front controller has error handler plugin
// if the request is an error request.
// If the error handler plugin is not registered,
// we will be unable which MCA to run, so do not continue.
$errorHandlerClass = 'Zend_Controller_Plugin_ErrorHandler';
if (!$front->hasPlugin($errorHandlerClass)) {
return false;
}
// Determine new error controller module_ErrorController_ErrorAction
$plugin = $front->getPlugin($errorHandlerClass);
$errorController = $plugin->getErrorHandlerController();
$errorAaction = $plugin->getErrorHandlerAction();
$module = $request->getModuleName();
// Create test request module_ErrorController_ErrorAction...
$testRequest = new Zend_Controller_Request_Http();
$testRequest->setModuleName($module)
->setControllerName($errorController)
->setActionName($errorAaction);
// Set new error controller if available
if ($front->getDispatcher()->isDispatchable($testRequest)) {
$plugin->setErrorHandlerModule($module);
} else {
return false;
}
return true;
}
}
(BTW, This issue already was discussed here, look carefully.)
#takeshin: Thank you for sharing your plugin, this is great! And just the thing I was looking for on the google machine.
I made some changes, respectfully, to the logic that determines the request to be an "error request," since I found that the full plugin callback was running on every request, regardless of whether or not an error had occured.
I just changed the plugin hook to "postDispatch" and tested that an exception had actually occurred during dispatch. The rest of the code functions exactly the same as yours.
Now, you can put a die statement in the middle of the plugin, and you will only see it after an exception has occurred during the request.
<?php
class Rm_Controller_Plugin_Modular_ErrorController
extends Zend_Controller_Plugin_Abstract
{
public function postDispatch(Zend_Controller_Request_Abstract $request)
{
$front = Zend_Controller_Front::getInstance();
// true if response has any exception
$isError = $front->getResponse()->hasExceptionOfType('Exception');
// if there was no error during dispatch
if (!$isError) {
return false;
}
// standard error handler plugin class name
$errorHandlerClass = 'Zend_Controller_Plugin_ErrorHandler';
// if the error handler plugin is not registered, do not continue.
if (!$front->hasPlugin($errorHandlerClass)) {
return false;
}
$plugin = $front->getPlugin($errorHandlerClass);
// the module that was requested and threw error
$module = $request->getModuleName();
// the controller & action name that error handler will dispatch
$errorController = $plugin->getErrorHandlerController();
$errorAction = $plugin->getErrorHandlerAction();
// create a dummy request to test for dispatchablility
$testRequest = new Zend_Controller_Request_Http();
$testRequest->setModuleName($module)
->setControllerName($errorController)
->setActionName($errorAction);
// verify that the current module has defined an ErrorController::errorAction
if ($front->getDispatcher()->isDispatchable($testRequest)) {
// tell error controller plugin to use the module's error controller
$plugin->setErrorHandlerModule($module);
} else {
return false;
}
return true;
}
}
Just off the top of my head, you could store a copy of the original request object in the registry from a controller plugin. In the preDispatch() of your controller you could then forward to another error controller based on the requested module.
// Plugin
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$clonedRequest = clone $request;
Zend_Registry::set('originalRequest', $clonedRequest);
}
// ErrorController
public function preDispatch()
{
$request = Zend_Registry::get('originalRequest');
$this->_forward('error', 'error', $request->getModuleName());
}
Related
I have axios request in my vue application:
async get_banner(id:number) : Promise<any> {
return global.axios.get(`${process.env.VUE_APP_DOMAIN}/banners/${id}`)
}
it works while banner/${id} response exits, but I have situation when I should disable banner in my admin panel so api endpoint becomes empty. (not exits) so I am getting Request failed with status code 404 in my vue app console.
question is how to prevent error and know if url exits or not? what is best practice to do this?
You can't tell whether an API exists or not without trying it (or relying on another API to get status of the former API)
It's usually just a manner of handling the response properly. Usually this would look something like this...
getTheBanner(id){
this.errorMessage = null; // reset message on request init
get_banner(id)
.then(r => {
// handle success
this.results = r;
})
.catch(e => {
// handle error
if (e.status === 404){
// set error message
this.errorMessage = "Invalid banner Id";
}
})
}
then in your template you could have something like this
<div v-if="errorMessage" class="alert danger">{errorMessage}</div>
Explaination:
Yes, you're absolutely right. This is the default behavior of strapi. Whenever the response is empty it throws a 404 error. This is basically because the findOne method in service returns null to the controller and when the controller sends this to the boom module it returns a 404 Not Found error to the front end.
Solution:
Just override the find one method in the controller to return an empty object {} when the response is null.
Implementation
// Path - yourproject/api/banner/controllers/banner.js
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
/**
* Retrieve a record.
*
* #return {Object}
*/
async findOne(ctx) {
const { id } = ctx.params;
const entity = await strapi.services.restaurant.findOne({ id });
// in case no entity is found, just return emtpy object here.
if(!entity) return {};
return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
Side Note:
There's no need to make any changes to the browser side axios implementation. You should always handle such cases in controller rather the client side.
Reference:
Backend Customizations
I am writing tests for a ZF1 application, here is the init of my controller:
public function init()
{
$this->_redirector = $this->_helper->getHelper('Redirector');
$action = $this->getRequest()->getActionName();
if(!in_array($action,array('login','logout'))) {
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
// Identity exists; get it
$identity = $auth->getIdentity();
} else {
$this->_redirector->gotoUrl('/hr/index/login?redirect='.urlencode($this->getRequest()->getRequestUri()));
}
}
$this->_user = AdminUserQuery::create()->findOneByUsername($identity);
$this->view->user = $this->_user;
...
}
and the test in question:
public function testNoAuthGivesLogin() {
$this->dispatch('/hr');
$this->assertRedirectTo( '/hr/index/login?redirect=%2Fhr' );
}
Simple... basically if there is no auth, redirect to the login. this works in the browser, but in phpUnit it continues to execute the code after the redirect, meaning that the properties that get set below it are not there, specifically $this->_user, so in my index action, when it calls getId() on $this->_user, it throws an error
How do I tell phpUnit to stop executing code after a redirect is detected, if I add a die() or exit() or even $this->_redirector->setExit(true); it halts the phpUnit session entirely and no tests get run.
You'll probably find the code execution continues in the browser as well, you just aren't seeing the errors. I believe there's a gotoUrlAndExit() which you want to use instead.
I've got an app with custom routes - in case a URL that's not available is called, an exception is thrown. In the catch block, I'm trying to
send an 404 error
show a custom not found page (available via /notfound)
How can I achieve that? If I redirect to that page, it always does a 302 redirect ... - my idea then was to kind of render the notfound view from within bootstrap
Thanks!
My way of doing:
1- In public, i add a file redirect404.html with a specific message about 4040 error.
2- In my ErrorController in the errorAction() I do something like:
$errors = $this->_getParam('error_handler');
if (!$errors || !$errors instanceof ArrayObject) {
$this->view->message = 'You have reached the error page';
return;
}
$redirect404 = false;
switch ($errors->type) {
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
// 404 error -- controller or action not found
$httpCode = 404;
$this->getResponse()->setHttpResponseCode(404);
$redirect404 = true;
break;
....
}
$this->getResponse()->setHttpResponseCode($httpCode);
$this->getResponse()->clearBody();
...
if (APPLICATION_ENV != 'development') {
if ($redirect404)
$this->_redirect('./../redirect404.html'); // with Static page
....
}
// If you want a dynamic page use your error.phtml
// and use $this->view like any other controller
3 - In your code throw the good exception.
I hope it will help you
I had a small test done in PHP for a Controller I had written in Symfony2:
class DepositControllerTest extends WebTestCase {
public function testDepositSucceeds() {
$this->crawler = self::$client->request(
'POST',
'/deposit',
array( "amount" => 23),
array(),
array()
);
$this->assertEquals(
"Deposit Confirmation",
$this->crawler->filter("title")->text());
}
}
Up to here, everything was great. Problem started when I realized I wanted to disable possible re-submissions while refreshing the page. So I added a small mechanism to send nonce on every submission.
It works something like this:
class ReplayManager {
public function getNonce() {
$uid = $this->getRandomUID();
$this->session->set("nonce", $uid);
return $uid;
}
public function checkNonce($cnonce) {
$nonce = $this->session->get("nonce");
if ($cnonce !== $nonce)
return false;
$this->session->set("nonce", null);
return true;
}
}
So I had to mofidy the controller to get the nonce when displaying the form, and consume it when submitting.
But now this introduces a problem. I cant make a request to POST /deposit because I dont know what nonce to send. I thought to requesting first GET /deposit to render the form, and setting one, to use it in the POST, but I suspect Symfony2 sessions are not working in PHPUnit.
How could I solve this issue? I would not want to go to Selenium tests, since they are significant slower, not to mention that I would have to rewrite A LOT of tests.
UPDATE: I add a very simplified version of the controller code by request.
class DepositController extends Controller{
public function formAction(Request $request){
$this->replayManager = $this->getReplayManager();
$context["nonce"] = $this->replayManager->getNonce();
return $this->renderTemplate("form.twig", $context);
}
protected function depositAction(){
$this->replayManager = $this->getReplayManager();
$nonce = $_POST["nonce"];
if (!$this->replayManager->checkNonce($nonce))
return $this->renderErrorTemplate("Nonce expired!");
deposit($_POST["amount"]);
return $this->renderTemplate('confirmation.twig');
}
protected function getSession() {
$session = $this->get('session');
$session->start();
return $session;
}
protected function getReplayManager() {
return new ReplayManager($this->getSession());
}
}
I'm not sure what ReplayManager does, but it looks to me as if it is not the right class to handle the 'nonce'. As the 'nonce' is ultimately stored in and retrieved from the session it should either be handled by the controller or abstracted out into its own class which is then passed in as a dependency. This will allow you to mock the nonce (sounds like a sitcom!) for testing.
In my experience problems in testing are actually problems with code design and should be considered a smell. In this case your problem stems from handling the nonce in the wrong place. A quick refactoring session should solve your testing problems.
It is possible to access the Symfony2 session from PHPUnit via the WebTestCase client. I think something like this should work:
public function testDepositSucceeds() {
$this->crawler = self::$client->request(
'GET',
'/deposit',
);
$session = $this->client->getContainer()->get('session');
$nonce = $session->get('nonce');
$this->crawler = self::$client->request(
'POST',
'/deposit',
array("amount" => 23, "nonce" => $nonce),
array(),
array()
);
$this->assertEquals(
"Deposit Confirmation",
$this->crawler->filter("title")->text());
}
EDIT:
Alternatively, if there is a problem getting the nonce value from the session, you could try replacing the two lines between the GET and POST requests above with:
$form = $crawler->selectButton('submit');
$nonce = $form->get('nonce')->getValue(); // replace 'nonce' with the actual name of the element
I have following calss to validate my input elements.
class Validator_FormValidator {
public function genValid($value){
$chainValid = new Zend_Validate();
$strLenV = new Zend_Validate_StringLength(array('min' => 4, 'max' => 10));
$strLenV->setMessage("You have entered a Invalid value","stringLengthInvalid");
$chainValid->addValidator($strLenV);
return $result;
}
}
and then in the index controller
public function indexAction()
{
$this->view->form = new Form_LoginForm();
$this->view->registrationForm = new Form_RegistrationForm();
if($this->getRequest()->isPost()) {
$validatorObj = new Validator_FormValidator();
$valid = new Zend_Validate_Callback(array($validatorObj,'genValid'));
$username = $this->_getParam('username');
if ($valid->isValid($username)) { echo 'success'; }
else {
echo 'failure';
foreach ($valid->getMessages() as $message) {
echo "$message\n";
}
}
}
}
Now I am trying to add customized error messages in my Validator_FormValidator class which are not display in my controller failure condition.
Could any of you plz guide how to do this? Is this what I am trying to do(createing a separate validator class and put all validation function in it) is a good practice?
Usual way of creating your own, custom validates is by extending Zend_Validate_Abstract or any class of already existing validator, e.g. Zend_Validate_StringLength. This way you could customize validation process as well as its all properties, e.g. messages.