I am using Zend Expressive 2 due to PHP version constraints. If I return variables in step one of pipeline (IndexAction) the variables appear just fine.
If I delegate to the next step (VerifyInputAction) and determine there is an error in the input, I need to return an error to view script. For some reason, it will not take the variables with it that I pass with the template renderer. It will still load the template, just not with the $data array variables.
I'm using Zend View as the template renderer.
My pipeline looks as follows.
IndexAction()
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
if ($request->getMethod() !== "POST") {
return new HtmlResponse($this->template->render('app::home-page', ['error' => 'hello']));
} else {
$delegate->process($request);
//return new HtmlResponse($this->template->render('app::home-page'));
}
}
VerifyInputaction()
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
$data = [];
$file = $request->getUploadedFiles()['recordsFile'];
$fileType = substr($file->getClientFilename(), strpos($file->getClientFilename(), '.'));
// If file type does not match appropriate content-type or does not have .csv extension return error
if (! in_array($file->getClientMediaType(), $this->contentTypes) || ! in_array($fileType, $this->extensions)) {
$data['error']['fileType'] = 'Error: Please provide a valid file type.';
return new HtmlResponse($this->template->render('app::home-page', $data));
}
$delegate->process($request);
}
Another problem that might be beyond the scope of this question includes, when I make it to the next Action in the pipeline, if I go to render a view script there I get this error...
Last middleware executed did not return a response. Method: POST Path: /<--path-->/ .Handler: Zend\Expressive\Middleware\LazyLoadingMiddleware
I will do my best to provide more code examples, but due to this being an issue at work I might have some problems with that.
Thanks!
Last middleware executed did not return a response. Method: POST Path: /<--path-->/ .Handler: Zend\Expressive\Middleware\LazyLoadingMiddleware
An action needs to return a response. In your VerifyInputaction you don't return a response if there is no valid csv file. I'm guessing this happens in your case and the $delegate->process($request); is triggered, which probably doesn't call another action which returns a middleware.
Looking at your code, it makes more sense to call VerifyInputaction first, check if it is a post and verify. If any of those fails, go to the next action which would be IndexAction. This could display the form with an error message. You can pass error message within the request as explained here: https://docs.zendframework.com/zend-expressive/v2/cookbook/passing-data-between-middleware/
Pipeline:
VerifyInputaction -> Check POST, verify input -> redirect if success
IndexAction -> render template and return response
I don't see any reason in your code why $data is not passed. My guess is that somehow the template is rendered in IndexAction which doesn't have the $data but has error set. You might check for this. The confusion is here that you render the same template in 2 different actions. Using the solution I mentioned, you only need to render it in IndexAction.
Related
In my Laravel 5.2 application, I'm using CloudConvert to convert my files. I've implemented asynchronous conversion, which requires a public callback URL to my site. Like this:
public function upload(Request $request) {
// Store uploaded file...
CloudConvert::file(/* path to the file */)
->callback(action('UploadController#saveFileFromProcess'))
->convert('pdf');
}
And the callback:
public function saveFileFromProcess() {
try {
CloudConvert::useProcess($request->input('url'))
->save(/* path to file storage */);
} catch (\Exception $e) {
Log::error($e->getMessage());
return false;
}
return true;
}
Now, the conversion works just fine. But I can see in the logs that Laravel throws an error after the conversion is done:
The Response content must be a string or object implementing
__toString(), "boolean" given.
I understand that this is because a route is called and it returns true or false, instead of e.g. rendering a view.
What should I then return to avoid the error? Is a string enough? Is there anything specific I can return for this kind of call?
And what if I still want to stop the script when e.g. specific Request input is missing?
You could return an array with the response, for example return ['status' => true];, which automatically will be converted to JSON and you can use it if you access this route with AJAX.
I am encountering a very strange thing while doing testing with Laravel 4. It looks like a bug but there's probably a logical explanation.
I have replicated the "bug" in a clean Laravel install and here's my code:
My resource controller /app/controllers/TestController.php:
(Created with php artisan controller:make TestController)
class TestController extends \BaseController {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
return Response::json(array());
}
// The end of the file is unchanged
In my app/routes.php:
Route::get('/', function()
{
return View::make('hello');
});
// Note the composed part of the URL.
// My problem isn't present if I just use `myapi` or `path` as a url
Route::resource('myapi/path', 'TestController');
Added in /app/test/ExampleTest.php:
public function testTest()
{
$res = $this->call('GET', 'myapi/path');
// Until here everything works right
$this->assertEquals(json_decode($res->getContent()), array());
// Now I call the same URL a second time
$res = $this->call('GET', 'myapi/path');
$this->assertEquals(json_decode($res->getContent()), array());
}
Now I run phpunit and here's what I get:
There was 1 error:
1) ExampleTest::testTest
Symfony\Component\HttpKernel\Exception\NotFoundHttpException:
/home/me/Web/laraveltest/bootstrap/compiled.php:5531
/home/me/Web/laraveltest/bootstrap/compiled.php:4848
/home/me/Web/laraveltest/bootstrap/compiled.php:4836
/home/me/Web/laraveltest/bootstrap/compiled.php:4828
/home/me/Web/laraveltest/bootstrap/compiled.php:721
/home/me/Web/laraveltest/bootstrap/compiled.php:702
/home/me/Web/laraveltest/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php:81
/home/me/Web/laraveltest/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php:332
/home/me/Web/laraveltest/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php:51
/home/me/Web/laraveltest/app/tests/ExampleTest.php:25
In my other project I get a slightly different backtrace, but I have the impression that's the same problem: (but I have no idea of why the other is compiled and this one not)
2) UnitModelTest::testOther
Symfony\Component\HttpKernel\Exception\NotFoundHttpException:
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php:148
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:1049
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:1017
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:996
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:775
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:745
/home/me/Web/my-project/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php:81
/home/me/Web/my-project/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php:327
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php:51
/home/me/Web/my-project/app/tests/UnitModelTest.php:32
In both case the line given in the trace for the test file corresponds to the second call of the test.
As I noted in the comments of the routes.php file, if I use a simple url with no slash, the test passes without problem.
I have no problem when I use the api from the browser.
I found many topics related to the NotFoundHttpException on StackOverflow, but none looks like mine. This one is specifically present when testing and only trigger an error at the second call.
So what am I doing wrong here ? Or is it really a bug ?
The problem is that calls made with the same client will use the provided URI relatively. That means what you actually call is:
myapi/path
myapi/myapi/path
You can fix this if you add a preface the urls with a / to make them absolute to the root of the application.
public function testTest()
{
$res = $this->call('GET', '/myapi/path');
// Until here everything works right
$this->assertEquals(json_decode($res->getContent()), array());
// Now I call the same URL a second time
$res = $this->call('GET', '/myapi/path');
$this->assertEquals(json_decode($res->getContent()), array());
}
If you experience other issues like that it often helps to call
$this->refreshApplication();
(This would also create a new client and therefore solve this issue as well)
In my case this error happened because i change public directory name to public_html my solution was put this in the \App\Providers\AppServiceProvider register method.
public function register()
{
// ...
$this->app->bind('path.public', function() {
return base_path('public_html');
});
}
I'm working on a RESTful and am stuck on message gathering for returning to the user. Basically, depending on the options selected, a few classes will be included dynamically. I'll try to provide a real-world break down. We have a HTML-email-tempalte maker - depending on the template chosen a php script will be included. This script may have warnings and I need to pass them "upstream" so that the API can report them. So we have something like this ( -> = includes )
API -> HTMLGenerator -> (dynamically) template-script.php
I need the template-script to be able to report errors to the API controller so the API can report them to the API user. Not sure the best way/practice to accomplish this.
So far , my thoughts are maybe a singleton or session variable that the template-script can add messages to, then the API Controller can report them. Any thoughts?
Main API
REST create by POST to /v1/html basically just:
class API {
require($dynamic_script);
$errors = array('warnings'=>array('warning1',waring2'));
//set http header and return JSON
}
HTMLGenerator
class HTMLGenerator {
//basically some wrappers for junior / non-programmers
function addHeading($text) {
//Add a header and do some checks.
if(strlen($text) > $warnTooLong )
HTMLErrors::addWarning("Message");
}
}
Dynamic Script
$h = new HTMLGenerator();
$h->addHeader($text);
$h->addImage($imageUrl);
You need to use a custom error handler.
See this link - http://php.net/manual/en/function.set-error-handler.php
It allows us to handle a error that might be thrown to capture it and process it. So, when you capture it, you can pass this to the parent class and furthur upstream for further processing.
Global object would work, set_error_handler too, but these are just hacks. The cleanest option is to modify your app elements to do what they are suppose to do - return those messages.
These shouldn't be too hard to do:
function myOldFunction($param1, $param2)
{
// do something
}
modify this way:
function myOldFunction($param1, $param2, array &$messages = array())
{
// do something
$messages[] = 'hey mama, i\'m on stack overflow!';
}
usage:
$messages = array();
myOldFunction(1, 2, $messages);
print_r($messages);
We're currently running an app that caches pages to static html files using Zend_Cache_Backend_Static. This works really well, except that our cache is getting filled with hundreds of empty files and folders when incorrect urls are requested. Is there any way to prevent a page being cached if an Exception is being thrown? I was surprised to discover that this wasn't standard behaviour.
I've done a little digging and the ZF code that actually deals with saving out the static html pages is as follows in Zend_Cache_Frontend_Capture:
public function _flush($data) {
$id = array_pop($this->_idStack);
if ($id === null) {
Zend_Cache::throwException('use of _flush() without a start()');
}
if ($this->_extension) {
$this->save(serialize(array($data, $this->_extension)), $id, $this->_tags);
} else {
$this->save($data, $id, $this->_tags);
}
return $data;
}
This function is the output_callback for ob_start. I've tried getting hold of the response object to test for status but it doesn't seem to work inside _flush.
$response = Zend_Controller_Front::getInstance()->getResponse();
if($response->getStatus() == '200') {
// do the save as normal
}
else {
// do nothing
return false;
}
My only other thought was to test the length of $data, only caching if strlen($data) > 0 seems to work but it doesn't feel robust enough.
Update:
Unfortunately by the time we hit the ErrorController the static page has already been written to the cache, so disabling the cache at that point won't work. However it is possible to remove the page based on $_SERVER['REQUEST_URI'], which is what is used as an id when the page is first written. This line can be added to the start of errorAction in the ErrorController:
$this->_helper->cache->removePage($_SERVER['REQUEST_URI'], true);
It works nicely, but I'd prefer not to write the page in the first place!
From further experimentation the problem is not down to standard Zend Framework exceptions that cause 404s (ie. Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE, Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER, Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION) but to my custom exceptions. This is now really obvious now that I think about it, as Zend_Cache_Backend_Static needs to be initialised in the init method of an action controller. Any situation where there is no route, controller or action it won't ever be initialised anyway.
I'm throwing exceptions in existing actions where a user may be querying for a non-existent article. Therefore caching has been enabled in init and the page has been written by the time we hit postDispatch in a Front Controller Plugin (still not sure why this is the case it just is) so I can't cancel at that point. One solution then is to cancel the cache at the point of throwing the exception. The standard method of managing static page caching is using the Zend_Controller_Action_Helper_Cache action helper. I've extended this to add a cancel method like so:
<?php
class Zend_Controller_Action_Helper_PageCache extends Zend_Controller_Action_Helper_Cache {
public function cancel() {
$cache = $this->getCache(Zend_Cache_Manager::PAGECACHE);
$cache->setOption('caching', false);
$cache->getBackend('disable_caching', true);
}
}
My action controller now looks like this:
<?php
class IndexController extends Zend_Controller_Action {
private $_model;
public function init() {
$this->_model = new Model();
// using extended pageCache rather than $this->_helper->cache:
$this->_helper->pageCache(array('index'), array('indexaction'));
}
public function indexAction() {
$alias = $this->_request->getParam('article');
$article = $this->_model->getArticleByAlias($alias);
if(!$article) {
// new cancel method will disable caching
$this->_helper->pageCache->cancel();
throw new Zend_Controller_Action_Exception('Invalid article alias', 404);
}
$this->view->article = $article;
}
}
You should alter your .htaccess file RewriteRules to check for filesizes with option -s
This way if an error should occur when a page is being cached (thus producing a 0 byte file) it won't permanently be stored in the cache.
If you are using the standard ErrorController to handle 404, 500, and unhandled exceptions, and you can get a reference to your cache object from there, you could disable caching from the error handler.
In your error controller (or wherever you would like to cancel caching from), try:
$cache->setOption('caching', false);
When the save() metod of Zend_Cache_Core is called by Zend_Cache_Frontend_Capture::_flush(), it will see the caching option is set to false and it will not actually save the data to the cache and return true.
How could I send additional view parameters after I have done a redirect (e.g. $this->_redirect->gotoSimple();)?
For example, let's say I have an Edit action which will redirect the user to an Error action handler and I would like to be able to send custom, detailed error messages to its view. To illustrate it clearer, the flow would be:
At the Edit view (say, http://localhost/product/edit), the user submits something nasty
At editAction(), a fail check triggers a redirect to my Error view/action handler (so that my URL would read like http://localhost/error/index)
The Error/index.phtml takes a "errorMessage" view variable to display the custom error message, and editAction() needs a means to pass in some value to that "errorMessage" view variable
A quick code snippet would probably look like:
public function editAction() {
//DO THINGS...
// Upon failure
if($fail) {
$this->_redirector->gotoUrl('/error/index');
//TODO: I need to be able to do something like
// $errorView->errorMessage = "Generic error";
}
}
Any solutions, or even other better ways of achieving this, is greatly appreciated.
Don't use gotoURL() for internal redirects. Use gotoSimple(). I takes up to 4 parameters:
gotoSimple($action,
$controller = null,
$module = null,
array $params = array())
In your case it's going to be:
$this->_redirector->gotoSimple('index',
'error',
null,
array('errorMessage'=>$errMsg));
See Redirector Zend_Controller_Action_Helper for details.
I have not seen anywhere that an action (editAction) accesses another action's view (errorView). for the special case of error handling, my idea is using Exceptions. you throw different exceptions for different bad situations, and in your error handler action, you can decide what to show to user based on the exception type:
// file: ProductContorller.php
public function editAction() {
// some code
if ($badThing) {
throw new Exception('describe the bad thing',$errorCode);
}
if ($badThing2) {
throw new Exception('describe the other bad thing',$errorCode2);
}
}
// file: ErrorController.php
public function errorAction() {
$error = $this->_getParam('error_handler');
$exception = $error->exception; // the original Exception object thrown by some code
$code = $exception->getCode();
switch ($code ) {
// decide different things for different errors
}
}
for more information about error handling, the Zend Framework quick start is a great tutorial.
for other situations, you can use some messaging mechanism to communicate between these 2 actions. using flashMessenger action helper is the first thing comes into my mind:
// file: ProductContorller.php
public function editAction() {
// some code
if ($badThing) {
$this->_helper->flashMessenger->addMessage('error1');
$this->_redirect('error');
}
if ($badThing2) {
$this->_helper->flashMessenger->addMessage('error2');
$this->_redirect('error');
}
}
// file: ErrorController.php
public function errorAction() {
$errors = $this->_helper->flashmessenger->getMessages();
if ( in_array('error1',$errors) ) {
// do something
} // elseif ( ...
}
although remember that flashMessenger uses sessions, so sessions and most likely cookies are going to be involved in this messaging process.
The standard way of doing this is with a session-based store of a message you wish to display. It's common enough that there is a view-based helper, FlashMessenger.
The FlashMessenger helper allows you
to pass messages that the user may
need to see on the next request. To
accomplish this, FlashMessenger uses
Zend_Session_Namespace to store
messages for future or next request
retrieval. It is generally a good idea
that if you plan on using Zend_Session
or Zend_Session_Namespace, that you
initialize with Zend_Session::start()
in your bootstrap file. (See the
Zend_Session documentation for more
details on its usage.)
go through this link.. it explains how can we set view variables before _redirect
http://www.rmauger.co.uk/2009/06/creating-simple-extendible-crud-using-zend-framework/
I'll add this to give some more info on how the FlashMessenger class works ( I had some issues figuring it out).
I read somewhere that a session should be started in Bootstrap.php using
Zend_Session::start();
..but my code worked without that, so I suspect sessions are already started.
We're in a controller-object and an action-method is being called. Then something happens, like an insert or an edit into the database, anything really.
We now set one or more messages. I use the following syntax.
$this->_helper->FlashMessenger("Message in a bottle.");
Which is exactly the same as using
$this->_helper->FlashMessenger->addMessage("Message in a bottle.");
This sets a message in the session, you can check that directly by calling
print_r($this->_helper->FlashMessenger->getMessages());
die();
Now there's a redirect to a new url (so a new request basically), inside the controller+action that is handling the request we'll add the messages to the view like so:
$this->view->flashMessages = $this->_helper->FlashMessenger->getMessages();
We now have a choice of where to output these messages. We can do this inside a view that "belongs to" a certain controller, so that could be
views/scripts/index/index.phtml
The drawback here is that you'd have to add the code outputting the messages to every viewscript that uses it. That's not very DRY.
In my eyes a superior solution is the following. Output these messages at in the file where you define the basic layout of your application. That's probably
layouts/scripts/index.phtml
I wrote the following code there.
<?php if( isset($this->flashMessages) && !empty($this->flashMessages) ){ ?>
<ul id="messages">
<?php foreach( $this->flashMessages as $message ){?>
<li>
<?php echo $message;?>
</li>
<?php } ?>
</ul>
<?php } ?>