calling "return" in Controller method send response immediately

Hi,

Intresting issue. I'm mounting controllers to a Micro app, setup is a bit more complicated than that but for intents this should explain the issue. When I call $app->after() and if I call send() I get the following exception:

PHP Fatal error: Uncaught Phalcon\Http\Response\Exception: Response was already sent in phalcon/http/response.zep:611

Now this is the strange part, if the method in the controller returns a nonscalar value such as an array I have no exception and when I call getReturnedValue() I get the expected value. If the method returns a string the string is immediately output to the client and I get the exception above.

class IndexController extends \Phalcon\Mvc\Controller
{
    public function index()
    {
        // this will fail if I call send(), sent directly to browser; getReturnedValue() captures this value
        return 'test';

        // this will succeed only if I call send(), response is not sent and getReturnedValue() captures this value
        return ['test' => 'test'];
    }
}


// example from index.php
$app->after(function () use ($app) {
    echo json_encode($app->getReturnedValue()); // duplicate output if Controller returns a string, string is always output even if I don't call this
});

If I call $app->response->send() while returning a string I get duplicate output and I get an exception. If I don't call send() when returning an array I get no output.



61.1k

this was added in 3.0.

i think you need dispatcher https://docs.phalconphp.com/en/3.0.0/reference/dispatching.html

Thanks for the repsonse Izo, as I understand it Micro doesm't support dispatcher.

edited Dec '16

It doesn't but you don't need it anyway for this.

Root cause with output here is the handle() method (of both Micro and MVC apps) which will output by default any string returned by controller/handler. Thus, we need to override such behaviour a little bit.

Take a look at my working example, simple Micro after() middleware is doing all the job here:

//Executed after the handler is executed. It is used to prepare the response.
$app->after(function () use ($app, $config){

//prepare response headers
    $app->response->setCache(0); //disable cache

// add headers just for fun
    $app->response->setHeader('X-SAPI', php_sapi_name());  //Add SAPI HTTP header
    $app->response->setHeader('X-Runtime', zend_version()); //Add Zend Engine version HTTP field
    $app->response->setHeader('X-Version', \Phalcon\Version::get()); //Add framework version string
    $app->response->setHeader('X-Revision', PHP_VERSION_ID);  //Add PHP signature

    $isRedirect = $app->getReturnedValue() instanceof \Phalcon\Http\Response ? true : false;

    if ($isRedirect && !$app->response->isSent()) return $app->response->send();

    !$app->response->getContent() ? $app->response->setContent($app->getReturnedValue()) : null;

    //THIS IS MANDATORY IN ORDER TO TELL NGINX WHENEVER TO TAKE INTO ACCOUNT GZIP MIN LIMIT!
    $app->response->setContentLength(strlen($app->response->getContent())); //Calculate Content-Length header (Phalcon 3.0.x)

    //Finally, send the prepared response, flush output buffers (HTTP header and body)
    !$app->response->isSent() && $app->response->send();

    //forcing exit is mandatory here since we heavily override app and web server
    exit;
});
edited Dec '16

Ah, I see it now! Thanks Jonathan!

cphalcon/phalcon/mvc/application.zep.. I don't like this, why is the framework assuming how to handle a returned value?

/**
 * If the returned value is a string return it as body
 */
if typeof possibleResponse == "string" {
    let response = <ResponseInterface> dependencyInjector->getShared("response");
    response->setContent(possibleResponse);
    return response;
}
edited Dec '16

Well, in order to make it easy for 90% of use cases out there where handlers will return just rendered view (string) or whatever as a string.