Problem/Motivation

BlockPluginInterface::build() is documented as always returning an array.
When implementations do not return an array, the resulting bugs range from subtle errors to fatal whitescreens

Proposed resolution

Add return types to core block ::build() methods, add a deprecation warning to ::build() methods discovered without a return type, enforce the return type in Drupal 10.

Remaining tasks

Postponed on #3050720: [Meta] Implement strict typing in existing code

User interface changes

N/A

API changes

BlockPluginInterface::build() will require an array return type from Drupal 10.

Data model changes

N/A

Release notes snippet

Custom block plugins should now add the "array" return type to the ::build method.

Issue fork drupal-3164389

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

tim.plunkett created an issue. See original summary.

longwave’s picture

Status: Active » Needs review
StatusFileSize
new870 bytes

Inspired by BreadcrumbManager can we just do something like this? There is a null check further down, but should we even allow NULL returns?

tim.plunkett’s picture

The above patch is for block.module (though as you point out, it still allows for NULL defeating the purpose), and this issue was spun out of #3162699: Improve debugability of block plugins returning NULL in Layout Builder which will help Layout Builder. But this doesn't help Page Manager or any other contrib module using blocks.

Idk how best to fix it globally.

longwave’s picture

StatusFileSize
new913 bytes

We can deprecate ::build() without a return type and add it to the interface in Drupal 10.

This means we have to add return types across all of core, but not sure what to do about the possibility of contrib/custom code extending core blocks and overriding ::build()?

tim.plunkett’s picture

Issue tags: +Needs tests
StatusFileSize
new31.96 KB
new32.33 KB

$definition is not always an array, fixed that. As well as the existing classes.

This needs a deprecation test still.

not sure what to do about the possibility of contrib/custom code extending core blocks and overriding ::build

Plugin implementations are not part of the public API; other code should not be extending from them.
https://www.drupal.org/core/d8-bc-policy#plugins

The last submitted patch, 4: 3164389-4.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

longwave’s picture

+++ b/core/lib/Drupal/Core/Block/BlockManager.php
@@ -65,6 +66,20 @@ protected function getType() {
+    if ($class) {

Apart from the unit tests is there a valid case where $class can still be NULL by the time we get here?

tim.plunkett’s picture

Assigned: Unassigned » tim.plunkett

#7 no, but I copied the if/else from the parent ::processDefinition().
Maybe \Drupal\Component\Plugin\Factory\DefaultFactory::getPluginClass() should be used instead? Hmm.

Working on fixing test failues

Status: Needs review » Needs work

The last submitted patch, 5: 3164389-typehint-5.patch, failed testing. View results

tim.plunkett’s picture

Assigned: tim.plunkett » Unassigned
Status: Needs work » Needs review
Issue tags: -Needs tests +Blocks-Layouts
StatusFileSize
new36.5 KB
new7.64 KB

Wrote a draft CR, added test coverage, fixed bugs this surfaced.
https://www.drupal.org/node/3164649

longwave’s picture

+++ b/core/tests/Drupal/Tests/Core/Block/BlockManagerTest.php
@@ -107,4 +123,47 @@ public function testHandlePluginNotFound() {
+    $expected = [];
+    $definitions = $this->blockManager->getDefinitions();
+    $this->assertSame($expected, $definitions);

I did a double take at this as I thought $expected should contain block1, then I realised it's because of the dummy provider, is this worth changing?

Otherwise the patch and the CR both look good, unsure if I can RTBC though.

longwave’s picture

Issue summary: View changes

Updated IS.

eclipsegc’s picture

Status: Needs review » Reviewed & tested by the community

I love this. Typed returns++

Any reason this shouldn't be RTBC?

tim.plunkett’s picture

StatusFileSize
new36.42 KB
new1.92 KB

Addressing #11 via docs, as well as restoring a couple lines of docs I incorrectly moved.
Leaving at RTBC.

longwave’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/tests/Drupal/Tests/Core/Block/BlockManagerTest.php
@@ -107,4 +124,49 @@ public function testHandlePluginNotFound() {
+    // Overwrite the definitions from ::setUp() to have a block thagt does not

Nit: thagt -> that

(spellchecker in core won't let this be committed now!)

tim.plunkett’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new36.42 KB
new924 bytes

Why doesn't that fail patches? 🤔

longwave’s picture

eclipsegc’s picture

Just ++ing the rtbc. No functional changes.

jungle’s picture

StatusFileSize
new516 bytes
new36.35 KB
+++ b/core/lib/Drupal/Core/Block/BlockManager.php
@@ -2,6 +2,8 @@
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;

Unused use statement removed. Stay RTBC.

tim.plunkett’s picture

Thanks for the link in #17!

catch’s picture

Status: Reviewed & tested by the community » Needs work

There are no changes to BlockPluginInterface here. Even if we can't add the return type hint in 9.1.x, we should document there that it will be added in 10.0.x I think?

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new37.1 KB
new1.33 KB

Yep, good call. Added an explicit @todo with a follow-up for D10: #3167432: Add array return type to ::build() on BlockPluginInterface

eclipsegc’s picture

Status: Needs review » Reviewed & tested by the community

Think we're back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +Needs release manager review

So this will break any contrib or custom code that extends block and overrides a build method - see https://3v4l.org/rbmaP

I have argued in the past that we should mark all blocks as final - see #3019332: Use final to define classes that are NOT extension points - unfortunately this has proved contentious but if we don't do that making changing like this is always going to be hard. Given this is the first issue in runtime code about return typehints (we have PHPUnit requiring them in the test system) I think we need release manager input on the way forward. I think we should mark all core blocks as final because then adding return typehints to other part of of the interface in a similar fashion will be simpler and there is then only one "break".

tim.plunkett’s picture

Grepping contrib, there are 5 modules that extend core blocks. All as new plugins, none as a plugin alter.

SystemBrandingBlock:
- amp

SystemMenuBlock:
- domain_menu_access
- domain_menus
- drulma_companion
- menu_item_fields

Looking at SystemMenuBlock, there is a LOT of useful stuff in there and I don't blame them for overriding it. If that were to be marked final, it'd be considerate of us to provide a service or a trait to encapsulate that code.

tim.plunkett’s picture

StatusFileSize
new44.88 KB
new15.72 KB

This doesn't address the problems of SystemMenuBlock (or SystemBrandingBlock), but here are all the blocks marked final.

Status: Needs review » Needs work

The last submitted patch, 26: 3164389-typehint-26.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tim.plunkett’s picture

There's a workaround in \Drupal\layout_builder_fieldblock_test\Plugin\Block\FieldBlock that will no longer work.

And then there's

Drupal\Tests\views\Unit\Plugin\views\display\BlockTest::testBuildNoOverride
Class "Drupal\views\Plugin\Block\ViewsBlock" is declared "final" and cannot be mocked.

Not pushing on these until further discussion happens around final

longwave’s picture

Status: Needs work » Needs review
StatusFileSize
new38.15 KB

Alternatively do we need to warn contrib with another deprecation?

longwave’s picture

StatusFileSize
new2.82 KB
tim.plunkett’s picture

My initial thoughts are that at least the typehint break is a one-line automatic change, and could be paired with a wakeup call change record about not extending blocks.

Making all of the core blocks final is going to make things rough to work around.

I wrote the above before #29, and now I'm even more convinced that #29 is a good step.

+++ b/core/lib/Drupal/Core/Block/BlockManager.php
@@ -70,6 +69,10 @@ public function processDefinition(&$definition, $plugin_id) {
+    if ($plugin_class->getParentClass() !== BlockBase::class) {
+      @trigger_error('Extending ' . $class . ' from classes other than ' . BlockBase::class . ' is deprecated in drupal:9.1.0 and will be disallowed before drupal:10.0.0. See https://www.drupal.org/node/xxxxxxxx.', E_USER_DEPRECATED);

Except that we have abstract block classes which are totally okay to extend from. My reflection is a bit rusty, can you instead check that the parent is abstract?

catch’s picture

My initial thoughts are that at least the typehint break is a one-line automatic change, and could be paired with a wakeup call change record about not extending blocks.

Making all of the core blocks final is going to make things rough to work around.

Yes this is my feeling as well. If you extend a block, you might or might not be OK, and you might or might not need to make a one line change. If we made the blocks final in a minor, then every extending class immediately gets a hard break - it's something we should only do in a major release IMO (apart from the further argument about whether to do it or not in general).

Also adding the return type hint works fine in the other direction - so a module that updates for 9.1.x will be compatible with 9.0.x without having to branch or raise minimum requirements etc.

Status: Needs review » Needs work

The last submitted patch, 29: 3164389-29.patch, failed testing. View results

longwave’s picture

Status: Needs work » Needs review
StatusFileSize
new38.19 KB
new2.32 KB

> Except that we have abstract block classes which are totally okay to extend from. My reflection is a bit rusty, can you instead check that the parent is abstract?

Yep, we can, let's see how this fares.

longwave’s picture

I even wonder if we should move this up a layer to DefaultPluginManager - ie. we should extend this to all plugins, not just blocks.

Status: Needs review » Needs work

The last submitted patch, 34: 3164389-34.patch, failed testing. View results

longwave’s picture

Status: Needs work » Needs review
StatusFileSize
new39 KB
new830 bytes

Fixing test failures.

joseph.olstad’s picture

Wow, one very large patch!

FWIW: I just did a dry run on 9.0.x, the patch applies cleanly so I triggered tests on 9.0.x also looks like it doesn't apply to 9.0.x after all, for some reason.

eclipsegc’s picture

Ok, so a few points here:

  1. I hate the idea of making any plugin core ships final. Maybe one or two here and there for a particularly good reason, but the whole idea was always that we wouldn't have to replicate the logic in hooks because OO allowed inheritance. Functionally, declaring plugin classes final makes them no better for down-stream developers than having D7-style hooks would. If they want similar functionality, they'll have to copy and maintain all the same methods/code. Yuck.
  2. Adding the typed return breaks downstream subclasses. I love this patch, however I hadn't considered this aspect. I think we (Drupal) may need to have a special BC consideration for introducing typed returns since there's no good way to prevent this. Initially I'd hoped that not putting it on the interface (yet) would be enough, but subclasses will break the run time. All of that to say, have we discussed this previously wrt our BC policy? Can someone point me at that if we have?
  3. There's been talk for a while that we could/should be producing rector rules for these sorts of changes. Is there any potential appetite for doing that at a point release instead of a major release? The fix for this is so easy from a code perspective and affects so few contrib modules. It might be an interesting pilot for this problem space.
  4. Finally, given my second point, I'm not sure I see the point of the trigger_error for blocks that are subclassing others. As I said in my first point, we definitely envisioned people subclassing core plugins to make minor modifications. Whatever the case, the mere presence of the subclass in the php run time will break it. It'd never make it this far.

Thoughts?

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

tim.plunkett’s picture

StatusFileSize
new39.19 KB

Rerolled. Still needs to address #39

Status: Needs review » Needs work

The last submitted patch, 41: 3164389-typehint-41.patch, failed testing. View results

anmolgoyal74’s picture

Status: Needs work » Needs review
StatusFileSize
new39.2 KB
new1.83 KB

Updated deprecated @expectedDeprecation annotation.

nwom’s picture

I couldn't get any of the previous patches to apply on D9.07:

error: core/modules/node/tests/modules/node_block_test/src/Plugin/Block/NodeContextTestBlock.php: No such file or directory

For those looking for a quick solution the following patch works: #3121395: Fatal error after add Main page content block layout builder

chandan.drupalchamp’s picture

StatusFileSize
new38.43 KB

Rerolled patch for 9.0.

nwom’s picture

#45 fixes the problem where the Main Content block cannot be added to the Layout Builder variant for Panels Everywhere, as discussed here: #3143487: Not compatible with "Layout Builder" variant as site template (Workaround found).

However, as mentioned in #4, it does indeed cause problems with certain contrib modules. Superfish has the following error message after applying the patch (See: https://www.drupal.org/project/superfish/issues/2951400#comment-13919952):

PHP Fatal error: Declaration of Drupal\superfish\Plugin\Block\SuperfishBlock::build() must be compatible with Drupal\system\Plugin\Block\SystemMenuBlock::build(): array in /modules/contrib/superfish/src/Plugin/Block/SuperfishBlock.php on line 24

andypost’s picture

Status: Needs review » Needs work

Needs to update patch deprecation for 9.2.0

Pooja Ganjage’s picture

StatusFileSize
new38.37 KB

Hi,

Creating a patch as suggested in #47 comment.

Please review the patch.

Thanks.

Pooja Ganjage’s picture

Status: Needs work » Needs review
Pooja Ganjage’s picture

StatusFileSize
new38.37 KB

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

lobodakyrylo’s picture

StatusFileSize
new38.33 KB

#50 patch is not applied to 9.2.0 version. I've created a new one.

tim.plunkett’s picture

Rerolled for 9.3.x and switched to a merge request instead of patches.

tim.plunkett’s picture

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

This seems straightforward to me.

phenaproxima’s picture

Status: Reviewed & tested by the community » Needs review

Ah, sorry -- I found one more thing that might be a condition to defend against.

xjm’s picture

I'm definitely concerned about the part where we add typehints to existing core classes. Plugin implementations are one of those technically internal, but nonetheless tempting to subclass, internal APIs. I also agree with:

Looking at SystemMenuBlock, there is a LOT of useful stuff in there and I don't blame them for overriding it. If that were to be marked final, it'd be considerate of us to provide a service or a trait to encapsulate that code.

@Berdir expresses related perspective in #3019332-3: Use final to define classes that are NOT extension points.

Furthermore, we have an expectation of providing deprecations rather than BC breaks even for internal APIs. Adding a return typehint to a parent class definitely qualifies. There may only be five usages in contrib, but there could be many more in custom code, and plugins are the kind of thing that's likely to get reused and tweaked there.

Both release managers have already indicated they are not presently comfortable adding final to things like plugins and controllers (in the referenced issue).

I think we should split this issue up, which @tim.plunkett also suggested in chat.

  1. Stuff to keep or add:
    • Raising deprecations about an incoming or outgoing data type that doesn't match our expectation is absolutely something we can commit without hesitation.
    • We can also add the typehints directly in D9 for the test fixtures.
    • We can even raise the same deprecation as in the patch for any direct implementations, and even sub-subclasses of core's current classes. This will both prevent us from introducing new implementations in core without the typehint (see #3154762: [policy, no patch] All new Drupal 10 code should use appropriate type hints wherever possible) and also ready contrib for us to add the typehint to the core implementations and the parent class in D10.
  2. The discussion and deprecation around making it impossible to subclass block plugins should also be moved to a separate related issue; it's not in scope and not necessarily even desirable. (We can discuss.)
  3. I like the idea of providing a trait or such for the useful bits in the existing system blocks; let's file an issue for that as well?
  4. "Require a typehint in D10 and deprecate it for child implementations" is going to be something we do over and over. It's probably worth providing as a generic core API. Also its own issue.
xjm’s picture

Issue tags: +Needs followup

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

sseto’s picture

Hi! Is there a patch for 9.3?

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

nwom’s picture

StatusFileSize
new42.11 KB

The patch no longer applied to 9.4.8. It was only failing on core/tests/Drupal/Tests/Core/Block/BlockManagerTest.php.

Here is a temporary patch for those that need it without the test. I'm sure it would be better updated as a merge request anyways.

catch’s picture

Status: Needs work » Postponed

Postponing this on #3050720: [Meta] Implement strict typing in existing code where (or in a sub-issue) we'll add deprecation support for adding return type hints.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

quietone’s picture

Issue summary: View changes

Adding postponed item to the list of remaining tasks per the issue summary field documentation, remaining task.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.