Skip to content

Commit 3b11f51

Browse files
committed
revise and pass T to FieldItemListInterface
1 parent b13060e commit 3b11f51

File tree

4 files changed

+286
-23
lines changed

4 files changed

+286
-23
lines changed

stubs/Drupal/Core/Field/FormatterBase.stub

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
1-
21
<?php
32

43
namespace Drupal\Core\Field;
54

5+
use Drupal\Core\Form\FormStateInterface;
6+
67
/**
7-
* @template TFieldItemList of \Drupal\Core\Field\FieldItemListInterface
8+
* @template T of FieldItemInterface
89
*/
9-
interface FormatterInterface {
10+
interface FormatterInterface extends PluginSettingsInterface {
1011

1112
/**
12-
* @param TFieldItemList $items
13-
* @param string $langcode
14-
*
15-
* @return array<int, array<int|string, mixed>>
13+
* @param array<\Drupal\Core\Field\FieldItemListInterface<T>> $entities_items
1614
*/
17-
public function viewElements(FieldItemListInterface $items, $langcode): array;
15+
public function prepareView(array $entities_items): void;
1816

1917
/**
20-
* @param TFieldItemList $items
21-
* @param string $langcode
18+
* @param \Drupal\Core\Field\FieldItemListInterface<T> $items
19+
* @param string|null $langcode
2220
*
2321
* @return array<int|string, mixed>
2422
*/
2523
public function view(FieldItemListInterface $items, $langcode = NULL);
2624

25+
/**
26+
* @param \Drupal\Core\Field\FieldItemListInterface<T> $items
27+
* @param string $langcode
28+
*
29+
* @return array<int, array<int|string, mixed>>
30+
*/
31+
public function viewElements(FieldItemListInterface $items, $langcode);
32+
2733
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Tests\Type;
6+
7+
use mglaman\PHPStanDrupal\Tests\AdditionalConfigFilesTrait;
8+
use PHPStan\Testing\TypeInferenceTestCase;
9+
10+
final class FormatterInterfaceStubTest extends TypeInferenceTestCase
11+
{
12+
use AdditionalConfigFilesTrait;
13+
14+
public static function dataFileAsserts(): iterable
15+
{
16+
yield from self::gatherAssertTypes(__DIR__ . '/data/formatter-interface.php');
17+
}
18+
19+
/**
20+
* @dataProvider dataFileAsserts
21+
* @param string $assertType
22+
* @param string $file
23+
* @param mixed ...$args
24+
*/
25+
public function testFileAsserts(
26+
string $assertType,
27+
string $file,
28+
...$args
29+
): void
30+
{
31+
$this->assertFileAsserts($assertType, $file, ...$args);
32+
}
33+
}
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
<?php
2+
3+
namespace DrupalFormatterInterface;
4+
5+
use Drupal\Core\Field\FieldDefinitionInterface;
6+
use Drupal\Core\Field\FieldItemListInterface;
7+
use Drupal\Core\Field\FormatterBase;
8+
use Drupal\Core\Field\FormatterInterface;
9+
use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
10+
use Drupal\Core\Form\FormStateInterface;
11+
use function PHPStan\Testing\assertType;
12+
13+
/**
14+
* @implements \Drupal\Core\Field\FormatterInterface<\Drupal\Core\Field\Plugin\Field\FieldType\StringItem>
15+
*/
16+
class StringFormatter implements FormatterInterface {
17+
18+
public function settingsForm(array $form, FormStateInterface $form_state) {
19+
return [];
20+
}
21+
22+
public function settingsSummary() {
23+
return [];
24+
}
25+
26+
public function prepareView(array $entities_items) {
27+
$first_item = $entities_items[0]->first();
28+
// Assert that the first item in the list is a StringItem
29+
assertType('Drupal\Core\Field\Plugin\Field\FieldType\StringItem|null', $first_item);
30+
}
31+
32+
public function view(FieldItemListInterface $items, $langcode = NULL) {
33+
assertType('Drupal\Core\Field\FieldItemListInterface<Drupal\Core\Field\Plugin\Field\FieldType\StringItem>', $items);
34+
$first_item = $items->first();
35+
// Assert that the first item in the list is a StringItem
36+
assertType('Drupal\Core\Field\Plugin\Field\FieldType\StringItem|null', $first_item);
37+
return [];
38+
}
39+
40+
public function viewElements(FieldItemListInterface $items, $langcode) {
41+
$first_item = $items->first();
42+
// Assert that the first item in the list is a StringItem
43+
assertType('Drupal\Core\Field\Plugin\Field\FieldType\StringItem|null', $first_item);
44+
return [];
45+
}
46+
47+
public static function isApplicable(FieldDefinitionInterface $field_definition) {
48+
return true;
49+
}
50+
51+
public function getSettings() {
52+
return [];
53+
}
54+
55+
public function getSetting($key) {
56+
return null;
57+
}
58+
59+
public function setSetting($key, $value) {
60+
return $this;
61+
}
62+
63+
public function setSettings(array $settings) {
64+
return $this;
65+
}
66+
67+
public function getDefaultSettings() {
68+
return [];
69+
}
70+
71+
public function getPluginId() {
72+
return 'string_formatter';
73+
}
74+
75+
public function getPluginDefinition() {
76+
return [];
77+
}
78+
79+
public function getThirdPartySettings($provider = NULL) {
80+
return [];
81+
}
82+
83+
public function getThirdPartySetting($provider, $key, $default = NULL) {
84+
return $default;
85+
}
86+
87+
public function setThirdPartySetting($provider, $key, $value) {
88+
return $this;
89+
}
90+
91+
public function unsetThirdPartySetting($provider, $key) {
92+
return $this;
93+
}
94+
95+
public function getThirdPartyProviders() {
96+
return [];
97+
}
98+
}
99+
100+
/**
101+
* @template T of \Drupal\Core\Field\FieldItemInterface
102+
* @extends \Drupal\Core\Field\FormatterBase<T>
103+
*/
104+
class GenericExtendsFormatterBase extends FormatterBase {
105+
/**
106+
* @param \Drupal\Core\Field\FieldItemListInterface<T> $items
107+
*/
108+
public function viewElements(FieldItemListInterface $items, $langcode) {
109+
$first = $items->first();
110+
assertType('T of Drupal\Core\Field\FieldItemInterface (class DrupalFormatterInterface\GenericExtendsFormatterBase, argument)|null', $first);
111+
return [];
112+
}
113+
114+
public static function isApplicable(FieldDefinitionInterface $field_definition) {
115+
return true;
116+
}
117+
}
118+
119+
/**
120+
* @template T of \Drupal\Core\Field\FieldItemInterface
121+
*/
122+
class GenericFormatter implements FormatterInterface {
123+
public function settingsForm(array $form, FormStateInterface $form_state) {
124+
return [];
125+
}
126+
127+
public function settingsSummary() {
128+
return [];
129+
}
130+
131+
public function prepareView(array $entities_items) {}
132+
133+
/**
134+
* @param \Drupal\Core\Field\FieldItemListInterface<T> $items
135+
*/
136+
public function view(FieldItemListInterface $items, $langcode = NULL) {
137+
$first = $items->first();
138+
assertType('T of Drupal\Core\Field\FieldItemInterface (class DrupalFormatterInterface\GenericFormatter, argument)|null', $first);
139+
return [];
140+
}
141+
142+
/**
143+
* @param \Drupal\Core\Field\FieldItemListInterface<T> $items
144+
*/
145+
public function viewElements(FieldItemListInterface $items, $langcode) {
146+
$first = $items->first();
147+
assertType('T of Drupal\Core\Field\FieldItemInterface (class DrupalFormatterInterface\GenericFormatter, argument)|null', $first);
148+
return [];
149+
}
150+
151+
public static function isApplicable(FieldDefinitionInterface $field_definition) {
152+
return true;
153+
}
154+
155+
public function getSettings() {
156+
return [];
157+
}
158+
159+
public function getSetting($key) {
160+
return null;
161+
}
162+
163+
public function setSetting($key, $value) {
164+
return $this;
165+
}
166+
167+
public function setSettings(array $settings) {
168+
return $this;
169+
}
170+
171+
public function getDefaultSettings() {
172+
return [];
173+
}
174+
175+
public function getPluginId() {
176+
return 'generic_formatter';
177+
}
178+
179+
public function getPluginDefinition() {
180+
return [];
181+
}
182+
183+
public function getThirdPartySettings($provider = NULL) {
184+
return [];
185+
}
186+
187+
public function getThirdPartySetting($provider, $key, $default = NULL) {
188+
return $default;
189+
}
190+
191+
public function setThirdPartySetting($provider, $key, $value) {
192+
return $this;
193+
}
194+
195+
public function unsetThirdPartySetting($provider, $key) {
196+
return $this;
197+
}
198+
199+
public function getThirdPartyProviders() {
200+
return [];
201+
}
202+
}
203+
204+
/**
205+
* Test usage of the formatter through a function.
206+
*
207+
* @param \Drupal\Core\Field\FormatterInterface<\Drupal\Core\Field\Plugin\Field\FieldType\StringItem> $formatter
208+
* @param \Drupal\Core\Field\FieldItemListInterface<\Drupal\Core\Field\Plugin\Field\FieldType\StringItem> $items
209+
*/
210+
function testFormatterWithStringItems(FormatterInterface $formatter, FieldItemListInterface $items): void {
211+
$elements = $formatter->viewElements($items, 'en');
212+
assertType('array<int, array<int|string, mixed>>', $elements);
213+
214+
// Test with implementation
215+
$stringFormatter = new StringFormatter();
216+
$elements = $stringFormatter->viewElements($items, 'en');
217+
assertType('array<int, array<int|string, mixed>>', $elements);
218+
219+
$item = $items->first();
220+
// The generic type from FormatterInterface should flow to $item
221+
assertType('Drupal\Core\Field\Plugin\Field\FieldType\StringItem|null', $item);
222+
}
223+
224+
/**
225+
* Test that FormatterBase correctly implements FormatterInterface with generics.
226+
*
227+
* @param \Drupal\Core\Field\FormatterBase<\Drupal\Core\Field\Plugin\Field\FieldType\StringItem> $formatter
228+
* @param \Drupal\Core\Field\FieldItemListInterface<\Drupal\Core\Field\Plugin\Field\FieldType\StringItem> $items
229+
*/
230+
function testFormatterBaseWithStringItems(FormatterBase $formatter, FieldItemListInterface $items): void {
231+
$output = $formatter->view($items, 'en');
232+
assertType('array<int|string, mixed>', $output);
233+
234+
$item = $items->first();
235+
// The generic type from FormatterBase should flow to $item
236+
assertType('Drupal\Core\Field\Plugin\Field\FieldType\StringItem|null', $item);
237+
}

0 commit comments

Comments
 (0)