Skip to content

Commit

Permalink
Add Url helper to preserve BC with legacy Laminas\View\Helper\Url
Browse files Browse the repository at this point in the history
  • Loading branch information
gsteel committed Aug 8, 2022
1 parent 019c5b9 commit 2d9c73c
Show file tree
Hide file tree
Showing 10 changed files with 1,333 additions and 293 deletions.
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@
},
"require": {
"php": "^7.4 || ~8.0.0 || ~8.1.0",
"laminas/laminas-http": "^2.15",
"laminas/laminas-http": "^2.15.1",
"laminas/laminas-mvc": "^3.3.3",
"laminas/laminas-router": "^3.7.0",
"laminas/laminas-servicemanager": "^3.16.0",
"laminas/laminas-stdlib": "^3.11",
"laminas/laminas-view": "^2.22.0",
"psr/container": "^1 || ^2",
"webmozart/assert": "^1.11.0"
Expand Down
1,218 changes: 926 additions & 292 deletions composer.lock

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="v4.25.0@d7cd84c4ebca74ba3419b9601f81d177bcbe2aac">
<file src="src/Helper/Url.php">
<RedundantCastGivenDocblockType occurrences="1">
<code>(string) $this-&gt;routeMatch-&gt;getMatchedRouteName()</code>
</RedundantCastGivenDocblockType>
</file>
</files>
1 change: 1 addition & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorBaseline="psalm-baseline.xml"
>
<projectFiles>
<directory name="src"/>
Expand Down
2 changes: 2 additions & 0 deletions src/ConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ public function getViewHelperConfig(): array
return [
'factories' => [
Helper\ServerUrl::class => Helper\Factory\ServerUrlFactory::class,
Helper\Url::class => Helper\Factory\UrlFactory::class,
/**
* Factories for helpers in Laminas\View
*/
ViewHelper\Doctype::class => Helper\Factory\DoctypeFactory::class,
],
'aliases' => [
'serverUrl' => Helper\ServerUrl::class,
'url' => Helper\Url::class,
],
];
}
Expand Down
15 changes: 15 additions & 0 deletions src/Exception/RouteNotMatchedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Laminas\Mvc\View\Exception;

use RuntimeException;

final class RouteNotMatchedException extends RuntimeException implements ExceptionInterface
{
public static function withEmptyRouteName(): self
{
return new self('A route name was not provided or RouteMatch does not contain a matched route name');
}
}
39 changes: 39 additions & 0 deletions src/Helper/Factory/UrlFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Laminas\Mvc\View\Helper\Factory;

use Laminas\Mvc\Application;
use Laminas\Mvc\View\Assert;
use Laminas\Mvc\View\Helper\Url;
use Laminas\Router\RouteMatch;
use Laminas\Router\RouteStackInterface;
use Psr\Container\ContainerInterface;

use function assert;

final class UrlFactory
{
public function __invoke(ContainerInterface $container): Url
{
/**
* This is traditionally the alias we use in MVC to fetch the router in use
*/
$router = $container->get('HttpRouter');
Assert::isInstanceOf($router, RouteStackInterface::class);

/**
* The RouteMatch instance must be retrieved from the MVC Event
*/
$mvcApplication = $container->get('Application');
assert($mvcApplication instanceof Application);
$routeMatch = $mvcApplication->getMvcEvent()->getRouteMatch();
Assert::isInstanceOf($routeMatch, RouteMatch::class);

return new Url(
$routeMatch,
$router
);
}
}
95 changes: 95 additions & 0 deletions src/Helper/Url.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace Laminas\Mvc\View\Helper;

use Laminas\Mvc\ModuleRouteListener;
use Laminas\Mvc\View\Exception\RouteNotMatchedException;
use Laminas\Router\RouteInterface;
use Laminas\Router\RouteMatch;
use Laminas\Router\RouteStackInterface;
use Laminas\Stdlib\ArrayUtils;

use function array_merge;
use function assert;
use function func_num_args;
use function is_array;
use function is_bool;
use function is_object;
use function is_string;

final class Url
{
private RouteMatch $routeMatch;
private RouteStackInterface $router;

public function __construct(
RouteMatch $routeMatch,
RouteStackInterface $router
) {
$this->routeMatch = $routeMatch;
$this->router = $router;
}

/**
* Generates an url given the name of a route.
*
* @see RouteInterface::assemble()
*
* @param string|null $name Name of the route
* @param iterable<string, mixed> $params Parameters for the link
* @param iterable<mixed>|bool $options Options for the route, or bool $reuseMatchedParams to skip the 4th argument
* @param bool $reuseMatchedParams Whether to reuse matched parameters
* @return string Url For the link href attribute
* @throws RouteNotMatchedException If RouteMatch didn't contain a matched route name.
*/
public function __invoke(
?string $name = null,
iterable $params = [],
$options = [],
bool $reuseMatchedParams = false
): string {
$name = $name ?? (string) $this->routeMatch->getMatchedRouteName();
if ($name === '') {
throw RouteNotMatchedException::withEmptyRouteName();
}

if (is_object($params)) {
$params = ArrayUtils::iteratorToArray($params);
}

if (is_object($options)) {
$options = ArrayUtils::iteratorToArray($options);
}

if (func_num_args() === 3 && is_bool($options)) {
$reuseMatchedParams = $options;
$options = [];
}

assert(is_array($options));

if ($reuseMatchedParams) {
$routeMatchParams = $this->routeMatch->getParams();

/** @var mixed $controller */
$controller = $routeMatchParams[ModuleRouteListener::ORIGINAL_CONTROLLER] ?? null;

if (is_string($controller)) {
$routeMatchParams['controller'] = $controller;
unset($routeMatchParams[ModuleRouteListener::ORIGINAL_CONTROLLER]);
}

if (isset($routeMatchParams[ModuleRouteListener::MODULE_NAMESPACE])) {
unset($routeMatchParams[ModuleRouteListener::MODULE_NAMESPACE]);
}

$params = array_merge($routeMatchParams, $params);
}

$options['name'] = $name;

return (string) $this->router->assemble($params, $options);
}
}
44 changes: 44 additions & 0 deletions test/Helper/Factory/UrlFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace LaminasTest\Mvc\View\Helper\Factory;

use Laminas\Mvc\Application;
use Laminas\Mvc\MvcEvent;
use Laminas\Mvc\View\Helper\Factory\UrlFactory;
use Laminas\Router\Http\RouteMatch;
use Laminas\Router\Http\TreeRouteStack;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;

class UrlFactoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
}

public function testTheFactoryWillComposeTheCorrectDependencies(): void
{
$router = new TreeRouteStack();
$match = new RouteMatch([]);
$event = new MvcEvent();
$event->setRouteMatch($match);

$application = $this->createMock(Application::class);
$application->expects(self::once())
->method('getMvcEvent')
->willReturn($event);

$container = $this->createMock(ContainerInterface::class);
$container->expects(self::exactly(2))
->method('get')
->willReturnMap([
['HttpRouter', $router],
['Application', $application],
]);

(new UrlFactory())->__invoke($container);
}
}
Loading

0 comments on commit 2d9c73c

Please sign in to comment.