I've been going over the plugin_type_example in order to write some tutorials about the Drupal plugin system. In doing so I've found a few things in the module that I think could be improved upon. I've just been keeping this list as I go, so in no particular order here are some of my thoughts on how this example could be improved.

  • We should define a custom annotation class, and then use that to document the @foobar and @calories properties in the annotation, otherwise these are not documented anywhere. Technically it works as is, but would be best-practice to document your annotations and provide a specific class. A quick grep of core says @Plugin is only used by a few random tests at this point. Core was changed to use type specific annotation classes. Change record is here https://www.drupal.org/node/2484461
  • Not sure what exactly the policy is, but the plugin_type_example.services.yml file seems overly verbose. This module is intended to document how the plugin system works, not how to register a service. I believe this could be cleaned up a bit and just link to proper documentation on registering services and only highlighting the parts specific to this example.
  • Could use some general comment formatting/cleanup. For example, there are a lot of places that use double-spaces, " ", instead of single spaces, " ", between sentences which makes it kind of in-consistent.
  • The example could be improved by adding a SandwichBase class, that the other two can then extend. This is probably a good best practice to demonstrate. We could remove the "foobar" property from the annotation, and instead add a description property, the ::description() method on the base class could just read that and return it. Similar for @calories property. Maybe add a ::calories() method to the interface and the base class, and then in on of the two example plugins override that method to do something special with the calories calculation?

Thoughts? I would be happy to work on making these changes if people think it's the right direction to go.

Comments

eojthebrave created an issue. See original summary.

eojthebrave’s picture

Status: Active » Needs review
StatusFileSize
new29.17 KB

Here's a first pass at updating this example. This contains the following:

  1. Updates to comments to fix a couple of types, remove the double space used in some places but not others, and generally improve consistency. I've also removed a few chunks of comment where I didn't feel like they added to the topic of plugin type creation in a substantive way. And other hopefully helpful cleanup to existing text.
  2. Replace @foober property with @description, which is more akin to a real world use case
  3. Provide a plugin type specific annotation class
  4. Provide a base class for plugins to extend, illustrating a common use-case seen in many places throughout core, and teaching a useful pattern
  5. Update tests to match changes
blakehall’s picture

Status: Needs review » Reviewed & tested by the community

I think these changes make a lot of sense. This patch gets a hearty +1 from me.

eojthebrave’s picture

StatusFileSize
new29.54 KB
new3.26 KB

I just used this code as the basis for some tutorials for Drupalize.Me and made a few more tweaks to this in the process. Cleaning up some coding standards violations and, and adding a little more clarity to a few comments. Here's the updated patch.

mile23’s picture

Status: Reviewed & tested by the community » Needs work

A lot of good changes. Some changes that add to complexity, but I think it's probably worth it.

Thanks!

Some things I found:

  1. +++ b/plugin_type_example/plugin_type_example.module
    @@ -11,45 +11,60 @@
    + * our Drupal\plugin_type_example\SandwichInterface, and INSERT ANNOTATION CLASSNAME HERE
    

    Clearly we need to insert annotation classname here. -)

  2. +++ b/plugin_type_example/src/Controller/PluginTypeExampleController.php
    @@ -75,18 +78,19 @@ class PluginTypeExampleController extends ControllerBase {
    +    // annotation on the plugin class: see \Drupal\plugin_type_example\Plugin\Sandwich\ExampleHamSandwich.
    

    The class name should be on the next line if it doesn't fit in 80 chars.

  3. +++ b/plugin_type_example/src/Controller/PluginTypeExampleController.php
    @@ -75,18 +78,19 @@ class PluginTypeExampleController extends ControllerBase {
    +    //$ham_sandwich_plugin_definition = $this->sandwichManager->getDefinition('meatball_sandwich');
    

    Either this is commented code, which is bad, or it's example code which should be properly indented and have a @code/@endcode tag.

  4. +++ b/plugin_type_example/src/Controller/PluginTypeExampleController.php
    @@ -103,21 +107,14 @@ class PluginTypeExampleController extends ControllerBase {
    +   * For more about how dependency injection works read https://www.drupal.org/node/2133171
    

    Wrap the URL, add a @see annotation as well.

  5. +++ b/plugin_type_example/src/Plugin/Sandwich/ExampleMeatballSandwich.php
    @@ -2,16 +2,15 @@
    + * Plugin\Sandwich namespace, and provide a Sandwich annotation to be declared
    + *  as a plugin. This is defined in
      *
      * The following is the plugin annotation. This is parsed by Doctrine to make
    

    Indentation and an incomplete sentence.

  6. +++ b/plugin_type_example/src/SandwichBase.php
    @@ -0,0 +1,49 @@
    + * We intentionally declare our base class as abstract, and skip the order()
    + * method required by \Drupal\plugin_type_example\SandwichInterface. This way
    + * even if they are using our base class, developers will always be required to
    + * define an order() method for their custom sandwich type.
    

    To require that the method be implemented by subclasses, we'd declare it abstract on the abstract base class.

    The way this is set up, it doesn't actually implement the interface it says it does, which breaks for the wrong reason.

    So add abstract public function order() with {@inheritdoc} in the docblock.

  7. +++ b/plugin_type_example/src/SandwichInterface.php
    @@ -1,5 +1,24 @@
    + * @file
    + * Provides \Drupal\plugin_type_example\SandwichInterface.
    + *
    + * When defining a new plugin type you need to define an interface that all
    + * plugins of the new type will implement. This ensures that consumers of the
    

    We don't do @file for 'provides' any more. This docblock should comment on the interface declaration, not the file.

  8. +++ b/plugin_type_example/src/SandwichInterface.php
    @@ -15,4 +34,19 @@ interface SandwichInterface {
    +  /**
    +   * @param array $extras
    +   *   An array of extra ingredients to include with this sandwich.
    +   *
    +   * @return mixed
    +   */
    +  public function order(array $extras);
    

    Document what this method does.

  9. +++ b/plugin_type_example/src/SandwichPluginManager.php
    @@ -30,17 +36,19 @@ class SandwichPluginManager extends DefaultPluginManager {
         $plugin_interface = 'Drupal\plugin_type_example\SandwichInterface';
    ...
    +    $plugin_definition_annotation_name = 'Drupal\plugin_type_example\Annotation\Sandwich';
    

    Use ::class for these class names, rather than string literals.

eojthebrave’s picture

Thanks for the review. I can make all those changes no problem except the last one. I guess just don't understand what you're asking for. Could you explain what you mean with this?

+++ b/plugin_type_example/src/SandwichPluginManager.php
@@ -30,17 +36,19 @@ class SandwichPluginManager extends DefaultPluginManager {
     $plugin_interface = 'Drupal\plugin_type_example\SandwichInterface';
...
+    $plugin_definition_annotation_name = 'Drupal\plugin_type_example\Annotation\Sandwich';

Use ::class for these class names, rather than string literals.

mile23’s picture

In PHP 5.5+ you can do the following, and the output will be the fully-qualified class name:

<?php

use Drupal\plugin_type_example\SandwichInterface;

echo SandwichInterface::class;

This is better because it means you have a more useful error if the interface is removed or gets moved around. It also helps IDEs.

Thanks.

eojthebrave’s picture

Status: Needs work » Needs review
StatusFileSize
new93.68 KB
new5.95 KB

Thanks for the explanation. I had no idea you could do that. I've updated the patch to hopefully help address all of your feedback.

eojthebrave’s picture

StatusFileSize
new93.68 KB
new6.56 KB

Oops. Forgot one things. This now gets rid of the @file docblock in SandwichInterface.php as well.

mile23’s picture

Status: Needs review » Needs work
+++ b/STANDARDS.md
@@ -23,22 +20,19 @@ Contributors can also patch the `phpcs.xml.dist` file itself, in order to fix
-Use Composer to add Drupal's Coder project to your root Drupal project. Coder
-contains the PHP_CodeSniffer rules we need, and also requires `phpcs`.
+Generally: Use Composer to add Drupal's Coder project to your root
+Drupal project. Coder contains the PHP_CodeSniffer rules we need, and
+also requires `phpcs`.

Seems like out of scope changes... You might need to pull the repo again.

eojthebrave’s picture

Bah. Thanks for catching that. Here's an update. Same patch as above, just without all the extra changes to things outside the plugin_type_example/ directory.

eojthebrave’s picture

Status: Needs work » Needs review
mile23’s picture

StatusFileSize
new32.1 KB
new10.18 KB

Here's a patch with some more cleanup for CS.

Also, I just noticed that meatball sandwiches are different on Sundays, so I added a test, which breaks. :-) There's no t() on the plugin class, so either we have to demonstrate injecting services to the plugin, or we have to not translate the string. I'm not sure if the plugin system will do the translation on the annotation values, the way we have it set up.

Status: Needs review » Needs work

The last submitted patch, 13: 2810675_13.patch, failed testing.

eojthebrave’s picture

Status: Needs work » Needs review
StatusFileSize
new32.33 KB
new901 bytes

Thanks for helping with the cleanup. I feel like I'm getting to the point with this code where I'm missing things because I've been over it so many times.

This patch adds StringTranslationTrait to the ExampleHamSandwich plugin. Which fixes the use of $this->t() in the order() method. And the tests, including your fancy new Sunday test, are passing for me.

I'm not sure if the plugin system will do the translation on the annotation values, the way we have it set up.

If you're referring to the use of @Translation("string") in the plugin annotations, then yes the plugin system will do the translation of whatever the value is inside of the @Translation() block in the annotation. See https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Annotatio... for documentation, and https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Annotatio... for implementation.

Should we maybe add an @see plugin_translatable somewhere if this is a potential source of confusion?

mile23’s picture

StatusFileSize
new33.32 KB
new3.61 KB
+++ b/plugin_type_example/src/Plugin/Sandwich/ExampleMeatballSandwich.php
@@ -29,6 +30,9 @@ use Drupal\plugin_type_example\SandwichBase;
+  use StringTranslationTrait;

That works because \Drupal works, but it's a bit of a pet peeve of mine... #2733703: [plan] Service traits should require IoC injection

However, it turns out we can use a proper IoC dependency injection pattern with plugin objects: https://www.drupal.org/node/2012118

So here's a version with IoC injection.

eojthebrave’s picture

That works for me. I actually like that as a result this also ends up demonstrating how you can inject not just translation but also any service you might need. Which is probably something that's likely to come up for a lot of people.

  • Mile23 committed cd6017c on 8.x-1.x authored by eojthebrave
    Issue #2810675 by eojthebrave, Mile23, blakehall: Updates to...
mile23’s picture

Status: Needs review » Fixed

OK, since we're basically RTBCing each other.... I'll just commit and mark fixed. :-)

Add a new issue for follow-ups.

Thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.