7

In Magento 2 is it possible, from a controller execute method, to manipulate the request to send a custom header and error page.

In Context: We're implementing a small, limited REST API endpoint that returns specific data. We don't want to use the new Magento 2 API endpoints because 1. Unproven; 2. User experience in setting up authentication. 3. Full control over data formats to support the specific application. Suggestions to do so will be smiled at, and politely ignored.

I'd like to send a proper 403 Forbidden response when the API receives unauthorized requests. I realize I could do something like

function execute()
{
    if(not authorized)
    {
        header('HTTP/1.0 403 Forbidden');
        echo "What are you doing here?";
        exit;
    }
}

But that's less than ideal -- especially since halting execution will stop other events and Magento processes from firing.

Most PHP frameworks have a way to directly Manipulate the Response to set a custom header and response string.

Does Magento 2 have something similar?

If so, what's the syntax/procedure/best-practice for using it in a controller's execute method? Can I just get a reference to the response object and set things? Do I need to return something specific from execute (instead of the standard page factory). Is there a higher level abstraction for doing this that doesn't require manipulating the response? Throwing a specific exception? etc.

3 Answers 3

16

First of all, to comply with action controller interface \Magento\Framework\App\ActionInterface::execute(), your action must return an instance of \Magento\Framework\Controller\ResultInterface (\Magento\Framework\App\ResponseInterface is also supported, but is legacy and will be removed in future releases of M2, when all core usages are refactored).

So choose from available implementations of \Magento\Framework\Controller\ResultInterface. The most suitable for custom REST API (assuming it operates with JSON) seems to be \Magento\Framework\Controller\Result\Json. However, if you need something even more custom, consider \Magento\Framework\Controller\Result\Raw.

Working sample:

<?php
namespace VendorName\ModuleName\Controller;

/**
 * Demo of authorization error for custom REST API
 */
class RestAuthorizationDemo extends \Magento\Framework\App\Action\Action
{
    /** @var \Magento\Framework\Controller\Result\JsonFactory */
    protected $jsonResultFactory;

    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Magento\Framework\Controller\Result\JsonFactory $jsonResultFactory
    ) {
        parent::__construct($context);
        $this->jsonResultFactory = $jsonResultFactory;
    }

    public function execute()
    {
        /** @var \Magento\Framework\Controller\Result\Json $result */
        $result = $this->jsonResultFactory->create();
        /** You may introduce your own constants for this custom REST API */
        $result->setHttpResponseCode(\Magento\Framework\Webapi\Exception::HTTP_FORBIDDEN);
        $result->setData(['error_message' => __('What are you doing here?')]);
        return $result;
    }
}

The code above will result in response with HTTP status code 403 and body {"error_message":"What are you doing here?"}

8

Magento 2 provides a Magento\Framework\App\ResponseInterface abstract type for you to instantiate via the dependency injection. Object manager used below for simplicity's sake.

/** @var \Magento\Framework\App\ObjectManager $om */
$om = \Magento\Framework\App\ObjectManager::getInstance();
/** @var     \Magento\Framework\App\ResponseInterface|\Magento\Framework\App\Response\Http $response */
$response = $om->get('Magento\Framework\App\ResponseInterface');
$response->setHeader('<header name>', '<header value>', $overwriteExisting = true);

For example

$response->setHeader('Content-Transfer-Encoding', 'binary', true);

Status headers are considered a special case -- you'll want to use the setStatusCode message here. Also, controller's have access to a getResponse message, so you can avoid the messy business of injecting on a controller

public function execute()
{      
    $this->getResponse()
        ->setStatusCode(\Magento\Framework\App\Response\Http::STATUS_CODE_403)
        ->setContent('Error');
    return ;
}
6

In Magento 2.1 and above, easiest way is to use the result factory:

public function execute()
{
    $resultPage = $this->resultFactory
        ->create(ResultFactory::TYPE_JSON)
        ->setHeader("myheaderkey", "value")
        ->setHttpResponseCode(403);

    return $resultPage;
}

The ResultFactory is already injected into your controller because of inheritance of \Magento\Framework\App\Action\Action.

Moreover, you're programming against Magento\Framework\Controller\ResultInterface, which is the expected (or recommended) return type of a controller.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.