Skip to content

Commit ae88160

Browse files
Introduce Controls trait
Add a `Controls` trait that provides the factory function `createViewmodeSwitcher()` and a `handleControls()` function to handle the requests of all tracked controls.
1 parent 6a898c6 commit ae88160

2 files changed

Lines changed: 211 additions & 0 deletions

File tree

src/Common/Controls.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace ipl\Web\Common;
4+
5+
use Icinga\Web\UrlParams;
6+
use ipl\Html\Form;
7+
use ipl\Web\Control\ViewModeSwitcher;
8+
use Psr\Http\Message\ServerRequestInterface;
9+
10+
/**
11+
* Provides factory methods to prepare reusable web controls and allows to handle their requests
12+
*/
13+
trait Controls
14+
{
15+
/** @var Form[] Controls registered for handling by {@see self::handleControls()} */
16+
private array $trackedControls = [];
17+
18+
/**
19+
* Create a {@see ViewModeSwitcher} control
20+
*
21+
* @param UrlParams $params The url params; the view mode param is shifted out of them
22+
* @param ?string $viewModeParam Custom view mode param, null uses the default of the given class
23+
* @param class-string<ViewModeSwitcher> $viewModeSwitcherClass
24+
*
25+
* @return ViewModeSwitcher
26+
*/
27+
public function createViewModeSwitcher(
28+
UrlParams $params,
29+
?string $viewModeParam = null,
30+
string $viewModeSwitcherClass = ViewModeSwitcher::class
31+
): ViewModeSwitcher {
32+
$viewModeSwitcher = new $viewModeSwitcherClass();
33+
if ($viewModeParam !== null) {
34+
$viewModeSwitcher->setViewModeParam($viewModeParam);
35+
}
36+
37+
$viewModeSwitcher->populate([
38+
$viewModeSwitcher->getViewModeParam() => $params->shift($viewModeSwitcher->getViewModeParam())
39+
]);
40+
41+
$this->trackControl($viewModeSwitcher);
42+
43+
return $viewModeSwitcher;
44+
}
45+
46+
/**
47+
* Register the given control to be handled by {@see self::handleControls()}
48+
*
49+
* @param Form $control
50+
*
51+
* @return $this
52+
*/
53+
protected function trackControl(Form $control): static
54+
{
55+
$this->trackedControls[] = $control;
56+
57+
return $this;
58+
}
59+
60+
/**
61+
* Call {@see Form::handleRequest()} on every control in {@see self::$trackedControls}
62+
*
63+
* @param ServerRequestInterface $request
64+
*
65+
* @return $this
66+
*/
67+
protected function handleControls(ServerRequestInterface $request): static
68+
{
69+
foreach ($this->trackedControls as $control) {
70+
$control->handleRequest($request);
71+
}
72+
73+
return $this;
74+
}
75+
}

tests/Common/ControlsTest.php

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
namespace ipl\Tests\Web\Common;
4+
5+
use Icinga\Web\UrlParams;
6+
use ipl\Html\Form;
7+
use ipl\I18n\NoopTranslator;
8+
use ipl\I18n\StaticTranslator;
9+
use ipl\Web\Common\Controls;
10+
use ipl\Web\Control\ViewModeSwitcher;
11+
use ipl\Tests\Web\TestCase;
12+
use Psr\Http\Message\ServerRequestInterface;
13+
14+
class ControlsTest extends TestCase
15+
{
16+
protected function setUp(): void
17+
{
18+
StaticTranslator::$instance = new NoopTranslator();
19+
}
20+
21+
public function testCreateViewModeSwitcher(): void
22+
{
23+
$controller = $this->controls();
24+
25+
$params = UrlParams::fromQueryString('view=minimal&foo=bar');
26+
$switcher = $controller->createViewModeSwitcher($params);
27+
28+
$this->assertSame('minimal', $switcher->getViewMode(), 'The view mode should be populated from the param');
29+
$this->assertFalse($params->has('view'), 'The view mode param should be shifted out of the params');
30+
$this->assertSame('bar', $params->get('foo'), 'Other params should be left untouched');
31+
$this->assertContains($switcher, $controller->trackedControls(), 'The created switcher should be tracked');
32+
33+
$customParams = UrlParams::fromQueryString('layout=detailed');
34+
$customSwitcher = $controller->createViewModeSwitcher($customParams, 'layout');
35+
36+
$this->assertSame(
37+
'detailed',
38+
$customSwitcher->getViewMode(),
39+
'The view mode should be read from the custom param'
40+
);
41+
$this->assertFalse(
42+
$customParams->has('layout'),
43+
'The custom view mode param should be shifted out of the params'
44+
);
45+
}
46+
47+
public function testCreateViewModeSwitcherResolvesTheDefaultViewMode(): void
48+
{
49+
$default = $this->controls()->createViewModeSwitcher(UrlParams::fromQueryString('foo=bar'));
50+
51+
$this->assertSame(
52+
ViewModeSwitcher::DEFAULT_VIEW_MODE,
53+
$default->getViewMode(),
54+
'Without a param the default view mode should apply'
55+
);
56+
57+
$default->setDefaultViewMode('detailed');
58+
59+
$this->assertSame(
60+
'detailed',
61+
$default->getViewMode(),
62+
'A default set after creation should be honored, as it is read lazily'
63+
);
64+
65+
$withParam = $this->controls()->createViewModeSwitcher(UrlParams::fromQueryString('view=minimal'));
66+
$withParam->setDefaultViewMode('detailed');
67+
68+
$this->assertSame(
69+
'minimal',
70+
$withParam->getViewMode(),
71+
'A present param should take precedence over the default'
72+
);
73+
}
74+
75+
public function testCreateViewModeSwitcherWithCustomClass(): void
76+
{
77+
$customClass = get_class(new class extends ViewModeSwitcher {
78+
});
79+
80+
$switcher = $this->controls()->createViewModeSwitcher(
81+
UrlParams::fromQueryString('view=minimal'),
82+
viewModeSwitcherClass: $customClass
83+
);
84+
85+
$this->assertInstanceOf($customClass, $switcher);
86+
}
87+
88+
public function testHandleControls(): void
89+
{
90+
$request = $this->createMock(ServerRequestInterface::class);
91+
92+
$first = $this->createMock(Form::class);
93+
$first->expects($this->once())->method('handleRequest')->with($request);
94+
95+
$second = $this->createMock(Form::class);
96+
$second->expects($this->once())->method('handleRequest')->with($request);
97+
98+
$controller = $this->controls();
99+
$controller->track($first);
100+
$controller->track($second);
101+
102+
$this->assertSame(
103+
[$first, $second],
104+
$controller->trackedControls(),
105+
'Controls should be tracked in registration order'
106+
);
107+
108+
$controller->handle($request);
109+
}
110+
111+
/**
112+
* Get a controller using the {@see Controls} trait, with public seams onto its protected orchestration
113+
*/
114+
private function controls(): object
115+
{
116+
return new class {
117+
use Controls;
118+
119+
/** @return Form[] */
120+
public function trackedControls(): array
121+
{
122+
return $this->trackedControls;
123+
}
124+
125+
public function track(Form $control): void
126+
{
127+
$this->trackControl($control);
128+
}
129+
130+
public function handle(ServerRequestInterface $request): void
131+
{
132+
$this->handleControls($request);
133+
}
134+
};
135+
}
136+
}

0 commit comments

Comments
 (0)