New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue #2817751 by bojanz: Create an API for bundle plugins #49
Conversation
entity.module
Outdated
function entity_entity_type_build(array &$entity_types) { | ||
foreach ($entity_types as $entity_type) { | ||
if ($entity_type->get('bundle_plugin_type')) { | ||
$entity_type->setHandlerClass('bundle_plugin', 'Drupal\entity\BundlePlugin\BundlePluginHandler'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should use BundlePluginHandler::class instead of the string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, are we fine with the fact that doing so will cause BundlePluginHandler to be autoloaded on every request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
autoloaded just because you use ::class???? why would you need to load the class for that? Please dear PHP god no ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Phew, I just tested this and of course the ::class syntax does not invoke the autoloader. You almost got me there!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! I learned something new. I assumed the use statement would be enough to trigger it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I remember this discussion. We used to use FQDN in install files instead of use statements before 8.0.x release. Then we cleared the idea that a use statement is not equal to include/include_once.
So this is for the case where the you directly set the plugin on the content entity and that defines the bundle, correct? So like payment.module for example handles the payment type. And not like media_entity.module, which has bundles and then the bundle has a plugin, but you could have multiple bundles using the same plugin? |
The plugin is the bundle. $entity->type stores the plugin ID. Instead of saying "this entity type uses config bundles, discover them and use them" we say "this entity type uses a plugin bundle, discover them and use them". So code instead of config driven. And you can always have an $entity->getBundlePlugin() method that gives you the plugin on which you can call other methods. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this. Awesome work @bojanz
public function installBundles(EntityTypeInterface $entity_type, array $modules) { | ||
$bundle_handler = $this->entityTypeManager->getHandler($entity_type->id(), 'bundle_plugin'); | ||
$bundles = array_filter($bundle_handler->getBundleInfo(), function ($bundle_info) use ($modules) { | ||
return in_array($bundle_info['provider'], $modules); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, should pass TRUE as third param
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we started enforcing this in core? I'm fine with adding it, just don't see it usually (the array is full of strings here, and strict handling is usually important only for numbers)
public function uninstallBundles(EntityTypeInterface $entity_type, array $modules) { | ||
$bundle_handler = $this->entityTypeManager->getHandler($entity_type->id(), 'bundle_plugin'); | ||
$bundles = array_filter($bundle_handler->getBundleInfo(), function ($bundle_info) use ($modules) { | ||
return in_array($bundle_info['provider'], $modules); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
|
||
use Drupal\entity\BundlePlugin\BundlePluginInterface; | ||
|
||
interface BundlePluginTestInterface extends BundlePluginInterface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: missing docblock. Do we need this or could the manager just look for the parent interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added it as an example (cause you'd usually have other methods in there), but I agree that it contributes nothing in this particular case, I'll remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
D'oh, Plugin API actually requires an interface to exist.
public function testPluginBundles() { | ||
$bundled_entity_types = entity_get_bundle_plugin_entity_types(); | ||
/** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */ | ||
$entity_type = reset($bundled_entity_types); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this lead to a fragile test? The results are keyed by ID, we could access it direct, without the reset()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, there's no reason for that reset.
I assume when combined with the plugin module, this gives you the ability to have a field type of 'plugin' that refers back to the instance of the bundle. So you could put additional logic in your interface/on the bundle. |
@larowlan public function getType() {
$payment_type_manager = \Drupal::service('plugin.manager.commerce_payment_type');
return $payment_type_manager->createInstance($this->bundle());
} Then $payment->getType()->doSomething(). |
src/BundleFieldDefinition.php
Outdated
* example uses BaseFieldDefinition, which is wrong. Tracked in #2346347. | ||
* | ||
* Note that this class implements both FieldStorageDefinitionInterface and | ||
* FieldStorageDefinitionInterface. This is a simplification for DX reasons, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it implement the same FieldStorageDefinitionInterface twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
use Drupal\Core\Entity\EntityTypeInterface; | ||
use Symfony\Component\DependencyInjection\ContainerInterface; | ||
|
||
class BundlePluginHandler implements BundlePluginHandlerInterface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Class comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't planning on adding one. I don't see a point in a comment that doesn't add any new information, such as "Default implementation of the BundlePluginHandlerInterface."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
$bundle_info = $entity_type_bundle_info->getBundleInfo('entity_test_bundle_plugin'); | ||
$this->assertEquals(2, count($bundle_info)); | ||
$this->assertTrue(isset($bundle_info['first'])); | ||
$this->assertTrue(isset($bundle_info['second'])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could use ->assertArrayHasKey() for better readability and verbosity should the test ever fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, addressed.
To summarize this we can say, a content entity can have different class BundleFieldDefinition extends BaseFieldDefinition {
/**
* {@inheritdoc}
*/
public function isBaseField() {
return FALSE;
}
} |
return $entity_type->hasHandlerClass('bundle_plugin'); | ||
}); | ||
|
||
return $entity_types; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should static cache this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? It's used when rebuilding the cached bundle or field data, it's not in any hot path. Plus, the function is not doing any I/O, it's just filtering an array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was just and uber nit pick. I thought hook_modules_installed
and hook_entity_bundle_info
can be called on same request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, let's skip that then.
/** | ||
* Tests the bundle plugins. | ||
*/ | ||
public function testPluginBundles() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add some EFQ asserts here as well? Just to make sure everything is dandy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea.
$bundles = []; | ||
foreach ($this->pluginManager->getDefinitions() as $plugin_id => $definition) { | ||
$bundles[$plugin_id] = [ | ||
'label' => $definition['label'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be feasible to add something like 'description' => $definition['description'],
here? This would enable us to add a bundle description & make the add-page considerably nicer looking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds like a nice idea!
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { | ||
return new static( | ||
$entity_type, | ||
$container->get('plugin.manager.' . $entity_type->get('bundle_plugin_type')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we catch the exception here and maybe provide a helpful error message "you need to implement a plugin manager for your bundle type called '....'"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is overengineering. The backtrace will show what is going on
$bundles = []; | ||
foreach ($this->pluginManager->getDefinitions() as $plugin_id => $definition) { | ||
$bundles[$plugin_id] = [ | ||
'label' => $definition['label'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds like a nice idea!
public function getBundleInfo(); | ||
|
||
/** | ||
* Gets the field storage definitions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Let's add documentation about the return type.
I think we are good with this PR now. 🎆 |
No description provided.