PHP MVC: How to exit from Actions/Controllers early? - php

In a PHP MVC framework, how can I cleanly and elegantly exit from the current controller/action, but continue normal script execution?
For example, let's say my framework normally follows this outline:
Map URL to Controller/Action
Instantiate Controller, call Action (capturing output)
Do stuff
Render View
At end of Action method, continue normal operation
Process output if necessary
Send output to browser
Now, let's say I want to stop "normal" execution somewhere in the "Do Stuff" step to, say, render a different view, or do a header redirect, and I want to stop processing the rest of the body of the Action, but continue onto the "Process output" step
How can I achieve this the best way? My only ideas are:
//in controller
protected function redirect($url) {
header("Location: $url");
exit();
}
but this entirely skips the rest of the framework's execution, and dumps whatever was in the output buffer straight to the user. An alternative:
//in dispatcher
call_user_func_array(array($controller,$action),$params);
afterwards:
...
//in controller
protected function redirect($url) {
header("Location: $url");
goto afterwards;
}
However, this makes me twitch and goes against everything I've learned, especially because the label it's referencing is in another file completely.
So, is there any other way to achieve this?
Note: The redirect example probably should use the exit() way, because we're just redirecting to another page anyway and don't care about output. I'm looking for a general-use solution.

In your Action method, you can collect all of your output in a string rather than printing it out. Print it out only at the end of the method. If you need to redirect or bail out, then you haven't output anything yet and you can either redirect or return from the method.

Perhaps you could write a custom exception to represent a "Stop normal execution in the 'Do Stuff'" step? It's messy... but it would work.

you need to abstract much more things. dont just output inside a action! dont set headers as there can go so much wrong before. have different handlers. response handler. handle respons and act depending what you respond. dont just output inside actions. if you have errors throw exceptions catch them with a exceptionhadler and so on.
basic controller action
public function view(int $user_id): ResponseHandler {
$this->validate($user_id); //throws exception
if (1 == 2) throw new ControllerInvalidArgumentException();
$view = new View('nameof view');
return new ResponseHandler($view);
}
just have a look at existing frameworks they have very good implemntations of design patterns like laravel or symfony. not sure what you try just dont write your own framework. if you want to learn read and learn from others if you understand whyt they do you will know how to do.

Related

Terminate laravel app not in route handler and send response

I need to terminate laravel app in class method, not in route handler.
I use custom Exception and method render() it work fine but I think it is not best way.
Second way I use:
redirect()->back()->with(...)->send();
die();
but by this way dont work with() method and Session flush, work only redirect...
when I dump session it empty...
What the best way to solve this issue?
When you to use die() function, you stop the execution and Laravel does not write in session the data added during the execution, you will need use session()->save() to force write in session, but don't is the better way.
Maybe you can just put a return in your code:
return redirect()->back()->with(...)->send();

Symfony2: force stop code execution after sendiing Response headers

What is the correct way to stop code execution after sending Response headers, but without using exit()?
I know the script SHOULD return a Response, but how can I force it to be returned from outside of a Controller, for example from a service?
Lets say my service's method does return a Response this way:
return RedirectResponse($url)->send();
But in another place it can return other things. So what I can do is to check what it does actually return:
$result = $myService->doSomething();
if ($result instanceof RedirectResponse) {
return $result;
}
What I want to achieve is to avoid checking result type in every place where I use my service, BUT I would like to see the returned response in Profiler/logs (if I use exit() I can't).
Is there a way to force kernel terminate?
EDIT:
Actually the service is used in event before any controller, so I want to do redirect before any controller execution. So maybe a way to omit controller execution?
A controller is a PHP callable you create that takes information from the HTTP request and creates and returns an HTTP response (as a Symfony Response object).
The only concern for a Controller is to process a request and return a response. Having a Service handle a Response object is probably a bad design choice.
In any case, you could just die/exit your controller and use Kernel events to hook in the Request/Response flow and inspect the returned Response. Probably the terminate event is the right choice http://symfony.com/doc/current/components/http_kernel/introduction.html
Ok, I found a way to do it. All I had to do is to terminate the kernel before exit - it does dispatch all after-response events (like profiling, logging etc.).
$response = new RedirectResponse($url);
$response->send();
$kernel->terminate($request, $response);
exit();
If anyone would found better way do to this, please answer so I can switch the mark.

Dependency injection php website

The more I read about dependency injection the more I get confused. I know what it is for, that is not the problem. Trying to do some design on paper this is what I came up with and somehow it seems to me I am overlooking something.
First I imagined building an actual server that would accept incoming requests and returns responses to the user.
class Server {
private $responseBuilder;
public function __construct($responseBuilder) {
$this->responseBuilder = $responseBuilder;
}
public function run() {
// create socket, receive request
$response = $this->responsebuilder->build($request);
// send response
}
}
class Response {
private $method;
private $message;
private $url;
// getters & setters
}
class ServerBuilder {
public build() {
// construction logic
return new Server(new ResponseBuilder());
}
}
Since Apache is used to handle server requests we could replace the server with something that just send the response.
$bldr = new ResponseBuilder();
$response = $bldr->build();
// send response some way
Note that ResponseBuilder has direct access to the request ($_SERVER['..'])
and so it has everything it needs to choose the right response.
PHP however allows us to build and send responses inline. So we could have a Controller object for each page or something else that send the response and have a builder for that.
$bldr = new ControllerBuilder();
$controller = $bldr->build();
$controller->run();
class ExampleController implements Controller {
public function run() {
header("HTTP/1.1 404 Not Found");
echo 'sorry, page not found';
}
}
This all makes sense to me. But let's look at the server example again.
It calls $responseBuilder->build() and gets a response back. But this would mean that the builder (or other builders if we split it) is also responsible for anything else that might occur like authenticating a user, writing to the database,... and I can't get my head around the fact that writing to a database would be part of the object graph construction.
It would be like: Send me your request. Oh you want the homepage? I will build you your response and while I'm at it I will also do some things that have nothing to do with building it like logging what I just did and saving some of your data in a cookie and sending a mail to the administrator that you are the first visitor on this page ever, ...
You should decouple them. You have a few assumptions that I think are a bit strange. Let's start with them.
The main purpose of an incoming http request is to give back some html
I have built PHP backends that only return JSON, instead of HTML. I had a really strong border between back and front end. I only used the backend to give me data from the database, or add/edit data in the databse. The front end was just a PHP script that would build the pages any way i wanted.
Since it is the web there is in theory no use for setters since
everything can be injected in the constructor
You could use the constructor, but you don't have too. You can use setters. Dependency injection is actually just turning the flow around.
You are on the right track though. You want some class that is responsible for building your pages. So, make it only responsible for your building your pages, and take out the other responsibilities. Things like logging, authentication etc should be outside of that.
For instance if you want logging, you could have your builder create your page, and your logger could then listen to all the things your builder is doing (with the observer pattern for instance). So if your builder says: "i created the home page", you can log it with your logger, who is actually listening to your builder.
Authentication for instance should happen even before your builder starts. You don't want your builder to go to work if you can already figure out that a user is not supposed to be on a page. You could use a database for that, and whitelist any usertype/pagerequest combination.
Then for data handling, i would create a backend, that only handles requests that are supposed to give back data, or save it. The front end could then communicate to get it's content by pulling it.
I hope this clears up a few things, but I'll be happy to answer more indept questions.

Correct ZF2 Redirect behaviour

I have been using ZF2 for a few months now and I am confused about how the controller redirect is supposed to work. Is it supposed to immediately stop processing the current action, and send the redirect request? Or is it supposed to complete processing of the current action, then perform the redirect?
Pretty fundamental question, huh?
Back in ZF1, I am pretty sure the redirection took place immediately (unlike forward(), which was stored up until the current action was completed). I assumed it was the same case in ZF2 and so far that has been my experience, however today suddenly I find that controllers are storing the redirect up, and sending it at the end of the current action.
Here's an example:
public function testAction()
{
$this->redirect()->toUrl('/info');
echo 'Hello';
die();
}
In this case, the action will echo 'Hello' and then die.
I think that this is probably the normal course of events and that I have just (by sheer fluke) not noticed it before today. I just want to be sure though, before I go back and alter all my controllers. (The alternative explanation is that somewhere in my config I am destroying/overriding the redirect plugin).
In Zend Framework 2.*, execution is never halted (except for a particular upload progress handler and some locations in the console component).
Therefore, you have to stop your controller from dispatching manually:
public function testAction()
{
return $this->redirect()->toUrl('/info');
echo 'Hello'; // will never be executed
}
To be more precise, as of this callback (used when triggering a Zend\Mvc\MvcEvent::EVENT_DISPATCH), any listener to the dispatch event of an application that returns a ResponseInterface causes a "short-circuit" to the application finish event.
Short-circuiting (in the Zend\Mvc\Application) basically causes subsequent events to be skipped and forces the application to directly trigger the Zend\Mvc\MvcEvent::EVENT_FINISH event, therefore echoing the response (happens in a listener of the finish event).
In this particular controller action, the call to the $this->redirect()->toUrl('...') helper produces a Zend\Http\Response, and since we directly return it, the short-circuit is triggered.

Zend: Is it possible to send view variables after a redirect?

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 } ?>

Categories