Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EXPERIMENT: FEATURE: Introduce trait ActionToMethodDelegation to create own simple controller. #3297

Draft
wants to merge 2 commits into
base: temporary-mvc-refactoring-target-branch
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions Neos.Flow/Classes/Mvc/Controller/ActionToMethodDelegation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace Neos\Flow\Mvc\Controller;

use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\ActionResponse;
use Neos\Flow\Mvc\Exception\NoSuchActionException;

/**
* Helps to implement a controller with direct access to flows request/response abstraction.
*
* ```
* ┌─────────────────────────────────────────────────────────────────────────────┐
* │ class MyController implements ControllerInterface │
* │ { │
* │ use ActionToMethodDelegation; │
* │ public function myAction(ActionRequest $actionRequest): ActionResponse; │
* │ } │
* └─────────────────────────────────────────────────────────────────────────────┘
* ```
*
* The request comes directly from the dispatcher and goes directly back to it.
*
* For helpers to facilitate throws, forwards, redirects: {@see SpecialResponsesSupport}
*
* Views or other processing needs to be added to your controller as needed,
* helpers will be suggested here as they become available.
* @api
*/
trait ActionToMethodDelegation
{
/**
* @internal you don't need to use this trait if you need to override this functionality.
*/
final public function processRequest(ActionRequest $request): ActionResponse
{
$request->setDispatched(true);
$actionMethodName = $this->resolveActionMethodName($request);
return $this->$actionMethodName($request);
}

/**
* Resolves and checks the current action method name
*
* @return string Method name of the current action
* @throws NoSuchActionException
*/
private function resolveActionMethodName(ActionRequest $request): string
{
$actionMethodName = $request->getControllerActionName() . 'Action';
if (!is_callable([$this, $actionMethodName])) {
throw new NoSuchActionException(sprintf('An action "%s" does not exist in controller "%s".', $actionMethodName, get_class($this)), 1186669086);
}

return $actionMethodName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
namespace Neos\Flow\Tests\Functional\Mvc\Fixtures\Controller;

use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\ActionResponse;
use Neos\Flow\Mvc\Controller\ActionToMethodDelegation;
use Neos\Flow\Mvc\Controller\ControllerInterface;

class SimpleActionControllerTestController implements ControllerInterface
{
use ActionToMethodDelegation;

public function indexAction(ActionRequest $actionRequest): ActionResponse
{
$response = new ActionResponse();
$response->setContent('index');

return $response;
}

public function simpleReponseAction(ActionRequest $actionRequest): ActionResponse
{
$response = new ActionResponse();
$response->setContent('Simple');

return $response;
}

public function jsonResponseAction(ActionRequest $actionRequest): ActionResponse
{
$response = new ActionResponse();
$response->setContent(json_encode(['foo' => 'bar', 'baz' => 123]));
$response->setContentType('application/json');

return $response;
}

public function argumentsAction(ActionRequest $actionRequest): ActionResponse
{
$response = new ActionResponse();
$response->setContent(strtolower($actionRequest->getArgument('testArgument')));
if ($actionRequest->getFormat() === 'html') {
$response->setContentType('text/html');
}

return $response;
}
}
88 changes: 88 additions & 0 deletions Neos.Flow/Tests/Functional/Mvc/SimpleActionControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
namespace Neos\Flow\Tests\Functional\Mvc;

use Neos\Flow\Tests\FunctionalTestCase;
use Psr\Http\Message\ServerRequestFactoryInterface;

/**
*
*/
class SimpleActionControllerTest extends FunctionalTestCase
{
/**
* @var ServerRequestFactoryInterface
*/
protected $serverRequestFactory;

/**
* Additional setup: Routes
*/
protected function setUp(): void
{
parent::setUp();

$this->registerRoute('test', 'test/mvc/simplecontrollertest(/{@action})', [
'@package' => 'Neos.Flow',
'@subpackage' => 'Tests\Functional\Mvc\Fixtures',
'@controller' => 'SimpleActionControllerTest',
'@action' => 'index',
'@format' => 'html'
]);

$this->serverRequestFactory = $this->objectManager->get(ServerRequestFactoryInterface::class);
}

/**
* Checks if a simple request is handled correctly. The route matching the
* specified URI defines a default action "index" which results in indexAction
* being called.
*
* @test
*/
public function defaultActionSpecifiedInRouteIsCalledAndResponseIsReturned()
{
$response = $this->browser->request('http://localhost/test/mvc/simplecontrollertest');
self::assertEquals(200, $response->getStatusCode());
self::assertEquals('index', $response->getBody()->getContents());
}

/**
* Checks if a simple request is handled correctly if another than the default
* action is specified.
*
* @test
*/
public function actionSpecifiedInActionRequestIsCalledAndResponseIsReturned()
{
$response = $this->browser->request('http://localhost/test/mvc/simplecontrollertest/simplereponse');
self::assertEquals(200, $response->getStatusCode());
self::assertEquals('Simple', $response->getBody()->getContents());
}

/**
* Checks if the content type and json content is correctly passed through
*
* @test
*/
public function responseContainsContentTypeAndContent()
{
$response = $this->browser->request('http://localhost/test/mvc/simplecontrollertest/jsonresponse');
self::assertEquals(200, $response->getStatusCode());
self::assertEquals(['application/json'], $response->getHeader('Content-Type'));
self::assertEquals(json_encode(['foo' => 'bar', 'baz' => 123]), $response->getBody()->getContents());
}

/**
* Checks if arguments and format are passed
*
* @test
*/
public function responseContainsArgumentContent()
{
$argument = '<DIV><h1>Some markup</h1></DIV>';
$response = $this->browser->request('http://localhost/test/mvc/simplecontrollertest/arguments?testArgument=' . urlencode($argument));
self::assertEquals(200, $response->getStatusCode());
self::assertEquals(['text/html'], $response->getHeader('Content-Type'));
self::assertEquals(strtolower($argument), $response->getBody()->getContents());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php
namespace Neos\Flow\Tests\Unit\Mvc\Controller;

/*
* This file is part of the Neos.Flow package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\ActionResponse;
use Neos\Flow\Mvc\Controller\ActionToMethodDelegation;
use Neos\Flow\Mvc\Exception\NoSuchActionException;
use Neos\Flow\Tests\UnitTestCase;

/**
* Tests for @see ActionToMethodDelegation
*/
class ActionToMethodDelegationTest extends UnitTestCase
{
/**
* Note: additional checks like "123" or "Foo" might seem sensible but those cases are prevented in the ActionRequest already.
*
* @return array[]
*/
public function wrongActionNameDataProvider()
{
return [
['foo'],
['fooAction'],
['addTestContentAction']
];
}

/**
* @test
* @dataProvider wrongActionNameDataProvider
*/
public function exceptionIfActionDoesNotExist(string $actionName)
{
$request = $this->createMock(ActionRequest::class);
$request->method('getControllerActionName')->willReturn($actionName);

$this->expectException(NoSuchActionException::class);

$testObject = new Fixtures\SimpleActionTestController();
$testObject->processRequest($request);
}

/**
* @return void
* @throws NoSuchActionException
* @throws \Neos\Flow\SignalSlot\Exception\InvalidSlotException
* @test
*/
public function responseOnlyContainsWhatActionSets()
{
$request = $this->createMock(ActionRequest::class);
$request->method('getControllerActionName')->willReturn('addTestContent');

$testObject = new Fixtures\SimpleActionTestController();
$response = $testObject->processRequest($request);
self::assertInstanceOf(ActionResponse::class, $response);
self::assertEquals('Simple', $response->getContent());
self::assertFalse($response->hasContentType());
// default
self::assertEquals(200, $response->getStatusCode());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
namespace Neos\Flow\Tests\Unit\Mvc\Controller\Fixtures;

use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\ActionResponse;
use Neos\Flow\Mvc\Controller\ActionToMethodDelegation;
use Neos\Flow\Mvc\Controller\ControllerInterface;

class SimpleActionTestController implements ControllerInterface
{
use ActionToMethodDelegation;

public function addTestContentAction(ActionRequest $actionRequest): ActionResponse
{
$response = new ActionResponse();
$response->setContent('Simple');
return $response;
}
}
Loading