Skip to content

Commit

Permalink
FEATURE: Introduce routingArguments and queryParameters to Fusion…
Browse files Browse the repository at this point in the history
… link prototypes to replace `arguments` and `additionalParams`

The Fusion prototype `Neos.Fusion:ActionUri` has two additional properties:

- :routingArguments: (array) That are handled by the router
- :queryParameters: (array) Query parameters that are appended after routing

Those will eventually replace the properties `arguments` and `additionalParams` which are deprecated and will be removed with Neos 9.

The Fusion prototypes `Neos.Fusion:NodeUri` and `Neos.Fusion:NodeLink` have one additional property:

- :queryParameters: (array) Query parameters that are appended after routing

This will eventually replace the property `additionalParams` which is deprecated and will be removed with Neos 9.

Also this pr deprecates the fusion properties `addQueryString` and `argumentsToBeExcludedFromQueryString` from the `Neos.Fusion:ActionUri`, `Neos.Neos:NodeUri`, and `Neos.Neos:NodeLink`.
  • Loading branch information
mficzel committed Feb 24, 2023
1 parent 89d4c84 commit 9f1fec7
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 14 deletions.
57 changes: 53 additions & 4 deletions Neos.Fusion/Classes/FusionObjects/ActionUriImplementation.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
* source code.
*/

use GuzzleHttp\Psr7\Uri;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\Routing\UriBuilder;
use Neos\Fusion\Exception\RuntimeException;
use Neos\Utility\Arrays;

/**
* A Fusion ActionUri object
Expand Down Expand Up @@ -85,10 +88,21 @@ public function getAction(): ?string
return $this->fusionValue('action');
}

/**
* Controller arguments that are to be handled by the router
*
* @return array|null
*/
public function getRoutingArguments(): ?array
{
return $this->fusionValue('routingArguments');
}

/**
* Controller arguments
*
* @return array|null
* @deprecated to be removed with Neos 9
*/
public function getArguments(): ?array
{
Expand Down Expand Up @@ -120,16 +134,28 @@ public function getSection(): ?string
* Additional query parameters that won't be prefixed like $arguments (overrule $arguments)
*
* @return array|null
* @deprecated to be removed with Neos 9
*/
public function getAdditionalParams(): ?array
{
return $this->fusionValue('additionalParams');
}

/**
* Query parameters that are appended to the url
*
* @return array|null
*/
public function getQueryParameters(): ?array
{
return $this->fusionValue('queryParameters');
}

/**
* Arguments to be removed from the URI. Only active if addQueryString = true
*
* @return array|null
* @deprecated to be removed with Neos 9
*/
public function getArgumentsToBeExcludedFromQueryString(): ?array
{
Expand All @@ -140,6 +166,7 @@ public function getArgumentsToBeExcludedFromQueryString(): ?array
* If true, the current query parameters will be kept in the URI
*
* @return boolean
* @deprecated to be removed with Neos 9
*/
public function isAddQueryString(): bool
{
Expand All @@ -157,12 +184,21 @@ public function isAbsolute(): bool
}

/**
* @return string
* @return UriBuilder
*/
public function evaluate()
public function createUriBuilder(): UriBuilder
{
$uriBuilder = new UriBuilder();
$uriBuilder->setRequest($this->getRequest());
return $uriBuilder;
}

/**
* @return string
*/
public function evaluate()
{
$uriBuilder = $this->createUriBuilder();

$format = $this->getFormat();
if ($format !== null) {
Expand Down Expand Up @@ -195,13 +231,26 @@ public function evaluate()
}

try {
return $uriBuilder->uriFor(
$arguments = $this->getArguments();
$routingArguments = $this->getRoutingArguments();
if ($arguments && $routingArguments) {
throw new RuntimeException('Neos.Fusion:ActionUri does not allow to combine "arguments" and "routingArguments"', 1665431866);
}
$uriString = $uriBuilder->uriFor(
$this->getAction(),
$this->getArguments(),
$routingArguments ?: $arguments,
$this->getController(),
$this->getPackage(),
$this->getSubpackage()
);
$queryParameters = $this->getQueryParameters();
if (empty($queryParameters)) {
return $uriString;
}
$uri = new Uri($uriString);
parse_str($uri->getQuery(), $queryParametersFromRouting);
$mergedQueryParameters = Arrays::arrayMergeRecursiveOverrule($queryParametersFromRouting, $queryParameters);
return (string)$uri->withQuery(http_build_query($mergedQueryParameters, '', '&'));
} catch (\Exception $exception) {
return $this->runtime->handleRenderingException($this->path, $exception);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ prototype(Neos.Fusion:ActionUri) {
action = null
format = null
section = null
routingArguments = Neos.Fusion:DataStructure
queryParameters = Neos.Fusion:DataStructure
additionalParams = Neos.Fusion:DataStructure
arguments = Neos.Fusion:DataStructure
argumentsToBeExcludedFromQueryString = Neos.Fusion:DataStructure
Expand Down
248 changes: 248 additions & 0 deletions Neos.Fusion/Tests/Unit/FusionObjects/ActionUriImplementationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
<?php
namespace Neos\Fusion\Tests\Unit\FusionObjects;

/*
* This file is part of the Neos.Fusion 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\Routing\UriBuilder;
use Neos\Flow\Tests\UnitTestCase;
use Neos\Fusion\Core\Runtime;
use Neos\Fusion\FusionObjects\ActionUriImplementation;

/**
* Testcase for the Fusion ActionUri object
*/
class ActionUriImplementationTest extends UnitTestCase
{
/**
* @var ActionUriImplementation
*/
protected $mockActionUriImplementation;

/**
* @var UriBuilder
*/
protected $mockUriBuilder;

/**
* @var Runtime
*/
protected $mockRuntime;

public function setUp(): void
{
$this->mockRuntime = $this->getMockBuilder(Runtime::class)->disableOriginalConstructor()->getMock();
$this->mockUriBuilder = $this->getMockBuilder(UriBuilder::class)->disableOriginalConstructor()->getMock();

$methodsToMock = [
'getRequest',
'getPackage',
'getSubpackage',
'getController',
'getAction',
'getRoutingArguments',
'getArguments',
'getFormat',
'getSection',
'getAdditionalParams',
'getQueryParameters',
'isAbsolute',
'getArgumentsToBeExcludedFromQueryString',
'isAddQueryString',
'createUriBuilder'
];

$this->mockActionUriImplementation = $this->getMockBuilder(ActionUriImplementation::class)->disableOriginalConstructor()->onlyMethods($methodsToMock)->getMock();
$this->mockActionUriImplementation->expects($this->once())->method('createUriBuilder')->willReturn($this->mockUriBuilder);
$this->inject($this->mockActionUriImplementation, 'runtime', $this->mockRuntime);
}

/**
* @return void
* @test
*/
public function actionIsPassedToTheUriBuilder()
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockUriBuilder->expects($this->once())->method('uriFor')->with('hello', [], null, null, null)->willReturn("http://example.com");
$this->mockActionUriImplementation->evaluate();
}

/**
* @return void
* @test
*/
public function formatIsPassedToTheUriBuilder()
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockActionUriImplementation->expects($this->once())->method('getFormat')->willReturn('square');
$this->mockUriBuilder->expects($this->once())->method('setFormat')->with('square');
$this->mockActionUriImplementation->evaluate();
}

/**
* @return void
* @test
*/
public function additionalParamsArePassedToTheUriBuilder()
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockActionUriImplementation->expects($this->once())->method('getAdditionalParams')->willReturn(['nudel' => 'suppe']);
$this->mockUriBuilder->expects($this->once())->method('setArguments')->with(['nudel' => 'suppe']);
$this->mockActionUriImplementation->evaluate();
}

/**
* @return void
* @test
*/
public function argumentsToBeExcludedFromQueryStringArePassedToTheUriBuilder()
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockActionUriImplementation->expects($this->once())->method('getArgumentsToBeExcludedFromQueryString')->willReturn(['nudel', 'suppe']);
$this->mockUriBuilder->expects($this->once())->method('setArgumentsToBeExcludedFromQueryString')->with(['nudel', 'suppe']);
$this->mockActionUriImplementation->evaluate();
}

/**
* @return void
* @test
*/
public function absoluteIsPassedToTheUriBuilder()
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockActionUriImplementation->expects($this->once())->method('isAbsolute')->willReturn(true);
$this->mockUriBuilder->expects($this->once())->method('setCreateAbsoluteUri')->with(true);
$this->mockActionUriImplementation->evaluate();
}

/**
* @return void
* @test
*/
public function sectionIsPassedToTheUriBuilder()
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockActionUriImplementation->expects($this->once())->method('getSection')->willReturn('something');
$this->mockUriBuilder->expects($this->once())->method('setSection')->with('something');
$this->mockActionUriImplementation->evaluate();
}

/**
* @return void
* @test
*/
public function addQueryStringIsPassedToTheUriBuilder()
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockActionUriImplementation->expects($this->once())->method('isAddQueryString')->willReturn(true);
$this->mockUriBuilder->expects($this->once())->method('setAddQueryString')->with(true);
$this->mockActionUriImplementation->evaluate();
}

/**
* @return void
* @test
*/
public function actionPackageAndArgumentsArePassedToUriBuilder()
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockActionUriImplementation->expects($this->once())->method('getArguments')->willReturn(['test' => 123]);
$this->mockActionUriImplementation->expects($this->once())->method('getRoutingArguments')->willReturn([]);
$this->mockActionUriImplementation->expects($this->once())->method('getController')->willReturn('Special');
$this->mockActionUriImplementation->expects($this->once())->method('getPackage')->willReturn('Vendor.Package');
$this->mockActionUriImplementation->expects($this->once())->method('getSubpackage')->willReturn('Stuff');

$this->mockUriBuilder->expects($this->once())->method('uriFor')->with('hello', ['test' => 123], 'Special', 'Vendor.Package', 'Stuff')->willReturn("http://example.com");
$this->mockActionUriImplementation->evaluate();
}

/**
* @return void
* @test
*/
public function actionPackageAndRoutingArgumentsArePassedToUriBuilder()
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockActionUriImplementation->expects($this->once())->method('getArguments')->willReturn([]);
$this->mockActionUriImplementation->expects($this->once())->method('getRoutingArguments')->willReturn(['test' => 123]);
$this->mockActionUriImplementation->expects($this->once())->method('getController')->willReturn('Special');
$this->mockActionUriImplementation->expects($this->once())->method('getPackage')->willReturn('Vendor.Package');
$this->mockActionUriImplementation->expects($this->once())->method('getSubpackage')->willReturn('Stuff');

$this->mockUriBuilder->expects($this->once())->method('uriFor')->with('hello', ['test' => 123], 'Special', 'Vendor.Package', 'Stuff')->willReturn("http://example.com");
$this->mockActionUriImplementation->evaluate();
}

/**
* @return void
* @test
*/
public function actionUriUsesArguments(): void
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockActionUriImplementation->expects($this->once())->method('getArguments')->willReturn(['foo' => 'bar']);
$this->mockActionUriImplementation->expects($this->once())->method('getRoutingArguments')->willReturn([]) ;
$this->mockUriBuilder->expects($this->once())->method('uriFor')->with('hello', ['foo' => 'bar'], null, null, null)->willReturn("http://hostname");
$this->mockActionUriImplementation->evaluate();
}

/**
* @return void
* @test
*/
public function actionUriUsesRoutingArguments(): void
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockActionUriImplementation->expects($this->once())->method('getArguments')->willReturn([]);
$this->mockActionUriImplementation->expects($this->once())->method('getRoutingArguments')->willReturn(['foo' => 'bar']) ;
$this->mockUriBuilder->expects($this->once())->method('uriFor')->with('hello', ['foo' => 'bar'], null, null, null)->willReturn("http://hostname");
$this->mockActionUriImplementation->evaluate();
}

/**
* @return void
* @test
*/
public function actionUriThrowsExceptionIfRoutingArgumentAreUsedTogetherWithArguments(): void
{
$this->mockActionUriImplementation->expects($this->once())->method('getArguments')->willReturn(['bar' => 'baz']);
$this->mockActionUriImplementation->expects($this->once())->method('getRoutingArguments')->willReturn(['foo' => 'bar']) ;
$this->mockUriBuilder->expects($this->never())->method('uriFor');
$this->mockRuntime->expects($this->once())->method('handleRenderingException');
$this->mockActionUriImplementation->evaluate();
}

public function queryParameterAppendingDataProvider(): array
{
return [
['https://example.com', ['foo' => 'bar'], 'https://example.com?foo=bar'],
['https://example.com?foo=bar', ['bar' => 'baz'], 'https://example.com?foo=bar&bar=baz'],
['https://example.com?foo=bar', ['foo' => 'bam'], 'https://example.com?foo=bam'],
['https://example.com', ['foo' => ['bar' => 'baz']], 'https://example.com?foo%5Bbar%5D=baz'],
['https://example.com?foo=bar', ['foo' => ['bar' => 'baz']], 'https://example.com?foo%5Bbar%5D=baz'],
['https://example.com?foo[bar]=baz', ['foo' => ['blah' => 'blubb']], 'https://example.com?foo%5Bbar%5D=baz&foo%5Bblah%5D=blubb']
];
}

/**
* @return void
* @dataProvider queryParameterAppendingDataProvider
* @test
*/
public function actionUriAppendsQueryParametersToUri($uriFromLinking, $queryParameters, $expectedFinalUri): void
{
$this->mockActionUriImplementation->expects($this->once())->method('getAction')->willReturn('hello');
$this->mockActionUriImplementation->expects($this->once())->method('getQueryParameters')->willReturn($queryParameters);
$this->mockUriBuilder->expects($this->once())->method('uriFor')->with('hello', [], null, null, null)->willReturn($uriFromLinking);
$actualResult = $this->mockActionUriImplementation->evaluate();
$this->assertEquals($expectedFinalUri, $actualResult);
}
}

0 comments on commit 9f1fec7

Please sign in to comment.