At DrupalCamp Austin, several people from the WSCCI and CMI initiatives as well as the ctools team met to discuss the topic of Plugins. Much progress was made, and we were able to develop a plan for several still-TBD issues.
Attending
Larry Garfield - Crell
James Gilliland - neclimdul
Kris Vanderwater - EclipseGc
David Strauss - davidstrauss
Michael Favia - michaelfavia
Kurt Vanderwater - kvanderw (scribe)
Slogan
"The goal of the plugin system is to get everyone into the same chapter, not necessarily onto the same page." --David Strauss
Background
The "plugin question" has plagued Drupal for some time. We have hooks, which are a great way to respond to events within the system, and to add functionality to an existing system. They are a rather poor way to define "swappable components", however. Currently, core provides separate, inconsistent mechanisms for such components for every system that needs them: cache, session, text formats, blocks, password hashing, and various other core systems all have their own distinct one-off, inconsistent way of defining swappable stuff. Not only does that make it harder to learn, it means we can get no optimizations from common patterns or common code, because there are no common patterns or common code.
Various contributed modules have swappable systems, too. Most are similarly one-offs of limited capability. The most developed, and most widely used, is the ctools plugin system. Conceptually it is quite robust, although its implementation now carries around a lot of legacy baggage that make it more complex than we would want for core.
By providing a unified plugin API for core, drawing heavily on ctools plugins, we can simplify Drupal's APIs, improve learnability, and improve performance by having less code and a common way to lazy-load code on demand, and provide more power for module developers.
We previously investigated a few other PHP frameworks, and found that none of them had plugin systems as robust as we needed. However, the underlying concepts are seen in many other projects, including Symfony2, many Java programs, and the Cassandra document database.
Why does Web Services care?
So why are plugins relevant to the Web Services and Context Core Initiative? There are two primary reasons:
- Part of WSCCI is implementing a context-aware block-centric layout mechanism that allows individual blocks to be configured, placed, and rendered independently of the rest of the page. That is necessary for more robust caching and layered rendering. Doing that, however, creates another use case for swappable components. Rather than implement yet another one off, the WSCCI team feels strongly that it is better to build a common plugin system that we can leverage alongside the rest of Drupal.
- In order to return web service requests in a performant way, we need to greatly reduce Drupal's code weight. The cost of returning a simple request, such as an auto-complete callback, needs to be low. The easiest way to do that is to convert more code to object-based plugins that are able to lazy-load on demand. In any given request the vast majority of code in Drupal is not used, which means it should not even be loaded. Lazy-loaded plugins that reduce Drupal's code weight help with performance for all requests but especially for lightweight service-style non-HTML responses.
What does the architecture look like?
The architecture for the plugin system draws heavily from ctools conceptually, with some cleanup.
- Plugin Type
- A plugin type is the grouping for all plugins that satisfy a particular purpose. All plugins are the members of exactly one plugin type and should be interchangeable with other plugin of the same type. The canonical example here is caching. "Cache storage system" is a plugin type. "Database cache", "Memcache cache", etc. are plugins that conform to that type. A Plugin Type must define a PHP interface to which all Plugin Implementations must conform.
- Plugin Factory
- A Plugin Factory is responsible for discovering available Plugin Implementations and for instantiating a Plugin Instance. A Plugin Factory is a class. The vast majority of implementations, including all core implementations, will use configuration files as defined by the Configuration Management Initiative. Every Plugin Type will use one (1) Plugin Factory, but multiple Plugin Types may use the same Plugin Factory.
- Plugin Implementation
- A Plugin Implementation, or simply "Plugin", is a self-contained, encapsulated piece of code that satisfies the requirements of a Plugin Type. Some plugins may be used individually, such as cache plugins. Others may be joined together in series, such as text formats. Still others may be assembled into large, complex systems, such as Views plugins. In all cases, Plugin Implementations represent encapsulated logic, and should not be used for data storage. To a system that is using a plugin, its implementation should be (reasonably) opaque. All Plugin Implementations are implemented as a PHP class that implements the PHP interface for the corresponding Plugin Type.
- Plugin Instance
- A Plugin Instance is a particular configured plugin. A Plugin Instance consists of a machine name, a Plugin Implementation (specifically the class for that implementation), and some configuration. When loaded, a Plugin Instance is a PHP object of the class type of its Plugin Implementation.
- Plugin Mapper
- The Plugin Mapper is a front-facing object that is the main point of contact for the rest of the system. Module code will access the Plugin Manager and ask it to return a Plugin Instance for a given Plugin Type, with given selection criteria. The Factory will use the selection criteria to decide which Plugin Instance is appropriate to return. The format of the selection criteria is specific to a given Factory.
When loading a plugin, client code will call the mapper with the scope, type, and appropriate selection options (probably an array). The mapper will instantiate the factory for that plugin type, unless it has it already in which case it will be reused. The factory will then use the selection options plus whatever configuration is stored to locate the appropriate plugin object, instantiate it, and return it. In some cases the factory may keep a reference to the plugin and reuse it next time rather than creating a new one, such as for caching. The client code can then do whatever it was going to do with the plugin.
Reasoning
The Plugin Type, Plugin Implementation, and Plugin Instance are conceptually borrowed directly from ctools, and from common object-oriented patterns. It is a structure that is well-proven in the wild, both within Drupal and not.
Although many systems will have factory logic specific to their use case, there are many generic use cases that apply to many different Plugin Types. Forcing every Plugin Type to completely define its own factory logic from scratch would be wasteful. By making it a standard class and interface, however, we can allow different Plugin Types to share factories where it makes sense or subclass and customize them if appropriate. That gives the best balance between flexibility and consistency.
Discovery
One of the big challenges of the plugin system is identifying all available plugin types and plugin implementations. Traditionally, Drupal has used info hooks for that case. However, info hooks have a number of drawbacks:
- Like any other hook, info hooks are only available after the module system has been initialized, which in turn requires the database system, the cache system, etc. We want to use plugins for lower-level systems.
- Info hooks are PHP code, and therefore must be parsed into memory to be read. Once parsed, however, they cannot be taken out of memory for the remainder of the request. For plugin types that have a very large number of implementations (blocks, field types, access plugins, etc.), that can be very bad for memory. (We run into this issue now with, for example, hook_schema()).
- The information from info hooks must be stored somewhere to avoid reinvoking all hooks every time we need to know something about plugins. The normal "somewhere" is the cache system, but we want to use plugins for the cache system. So we'd need another somewhere.
For that reason, we decided that info hooks, while a known pattern, simply would not work in this case.
After some consideration, we concluded that the best place to put the Plugin Type and Plugin Implementation definitions is in the Configuration system being developed by the Configuration Management Initiative (CMI). For background, CMI is developing a system that stores complex configuration in one or more XML files that ship with a module. When a module is enabled, those configuration files are copied into site configuration directory. For more information see the writeup Greg Dunlap wrote after the BADCamp CMI sprint.
As a benefit, these XML files are already slated to become a major part of Drupal 8. That means developer are going to need to become familiar with them anyway for defining module configuration defaults, so it's only one new thing for module developers to learn. It also means that, although there is no alter hook, it is possible to customize a definition for a particular site if necessary through functionality CMI will already be providing.
There is some concern that plugin implementations, since they are not changed by a user or site administrator, do not qualify as "configuration" and therefore should not use the configuration system, even if it is a convenient optimization. An alternative storage mechanism for non-configuration "random stuff" is still in the hand-wavy stage, though, so until that's resolved it's probably best to move forward with using the configuration system.
Each Plugin Type is defined by a configuration file in the form plugin.type.$scope.$type.xml. That file includes the type's human-friendly name and description, the name of the PHP interface that defines the type's API, the name of the class that is the factory for that Plugin Type, and any configuration appropriate for the factory.
Each Plugin Implementation is defined by a configuration file in the form plugin.implementation.$scope.$type.$implementation.xml. That file includes the implementation's human-friendly name and description, the name of the class that is the implementation, and potentially other information as deemed necessary during implementation. That will likely also include default values for configuration options.
Each Plugin Instance is defined by a configuration file in the form plugin.instance.$scope.$type.$machine_name.xml. That file includes the name of the implementation that it is for and whatever instance-specific configuration is necessary.
The $scope part of the above definition is borrowed from ctools, and is intended to avoid namespace collision. For instance, core has a caching system (obviously). Views has a cache plugin system, the implementations of which are highly Views-specific and deal with cache clearing logic, not with data storage. Panels has cache plugins, too, dealing with pane caching, which again are totally different from either core's caching system or Views' caching logic plugins. ctools therefore namespaces plugin types by module name. For core plugins, we have generalized that to "scope", which is a machine-name-friendly string. That will usually correspond to a module name, but that is not required. Non-module plugin types (core caching, for instance), will have a scope of "core".
Another advantage of using the configuration files as above is that enumerating available plugin types, plugin implementations, and defined instances becomes very simple through functionality the Config system is already slated to support, and can be done without loading any additional PHP code into memory.
Factory types
Different plugin types will have different needs when it comes to their loading logic. Sometimes only a single instance of a plugin will be needed throughout the entire request; other times there will be a complex routine determining which plugin is needed. Sometimes, for instance with caching backends, the same exact object should be returned every time. In other cases, such as image processing, a new object will be wanted each time. Etc. For that reason, every Plugin Type may have its own factory, although reuse of factories and subclassing should be extremely common.
Because the definition file for a Plugin Type includes the factory class and its configuration, and configuration files will be copied from the providing module to a site-specific configuration directly, it will be possible to simply reuse the same configuration file to store mapping data. That will be up to the factory implementation.
Although hypothetically it will be possible for a factory to not use configuration files for its mapping data and plugin implementation and instance records, that is not recommended. At the sprint we agreed that not doing so constituted "maiming kittens".
The responsibility of a factory is to instantiate and return a configured plugin instance object. The factory may, at its discretion, save configuration for a particular plugin instance in the plugin.type.$scope.$type.xml file or break it up into plugin.instance.$scope.$type.$machine_name.xml files.
Common factories that core should provide include:
- Simple
- This factory is used when there is only a single instance appropriate at any given time, such as the password hashing system. It will store all configuration in the plugin.type.$scope.$type.xml file.
- Mapped
- This factory is used when there will be multiple instances appropriate mapped one to one to a particular key, such as the cache system and cache bins. It will store all configuration in the plugin.type.$scope.$type.xml file.
- Direct
- This factory is used when plugin instances will generally be loaded by machine name rather than by a configured map. The standard example here is blocks. It will not make any modifications to the plugin.type.$scope.$type.xml file, but will store every instance in its own plugin.instance.$scope.$type.$machine_name.xml file.
- Compound
- This factory is used when a given plugin represents multiple internal plugins, such as the text formatter system. We're not sure yet how it will store data.
More complex systems, such as Views, will likely implement their own factory extending the Compound factory.
Context and Configuration
One somewhat contentious question in the past has been whether or not plugins should require context objects, and whether all plugins should require a configuration object. There will be many plugin types, or at least implementations, that do not require one or both of those, but having different flavors of plugin that take different dependencies would make the system more complex. However, making all plugins require both of those means that plugins cannot be used before both of those systems have been initialized.
After some discussion, we reached the following conclusion:
- Plugins will be available really really early, but have to be available after the configuration system because everything else in them, such as the factory logic, requires configuration.
- Plugins that do not require configuration can easily be passed an empty configuration object, so no harm done.
- The Context API should be available very early as well. In fact, the only systems that might need plugins that happen before the Context API is initialized are Context itself and Session handling. However, the Context API is already mostly implemented using a different approach to handler-mapping. We also have been looking to implement lazy session initialization via the Context API, and rather than use our own plugin system for it rely on the session storage classes provided by the HttpFoundation library, which is already in core.
- An empty Context object may created at any time, quite easily, and there is already an established way to handle "no data here" for a context value. That means, like configuration, if we hit an edge case (such as maintenance mode) we can simply pass in an empty context object, so no harm done.
We therefore concluded that it was safe to make plugins depend on both context and configuration, as both systems should be available in any normal cirumstances and in exceptional circumstances (unit testing, maintenance mode, etc.) we can easily mock them to keep code working without having to scatter if (defined(MAINTENANCE_MODE)) calls all around the place.
Implementations
While every plugin implementation requires a class, it does not require a unique class. That is, two plugin implementations may use the same class with different configuration parameters to provide different functionality.
Taken to its logical conclusion, it should be possible for all plugins of a given type to use the same class and not even have to define it in each plugin implementation. Take, for example, layout plugins (as currently used in ctools/planels, but will also become necessary for core). A basic implementation of those could be (note, this is not even close to final syntax; just look at the concept):
plugin.type.core.layout.xml:
<config>
<name>layout</name>
<defaults>
<value name="class">Drupal\Plugin\Core\Layout\DefaultLayout</value>
</defaults>
<factory name="Drupal\Plugin\Core\Layout\Factory" />
</config>plugin.implementation.core.layout.mylayout.xml:
<config>
<name>mylayout</name>
<options>
<val name="css">mylayout.css</val>
<val name="css">mylayout-rtl.css</val>
<val name="template">mylayout.tpl.php</val>
<val name="javascript">mylayout.js</val>
</options>
</config>/core/includes/Drupal/Plugin/Core/Layout/Factory.php:
<?php
namespace Drupal\Plugin\Core\Layout;
class Factory extends MappedFactory {
public function getInstance($machine_name, $context) {
// load plugin.implementation.core.layout.mylayout.xml into a config object; CMI does this.
$config->options += $this->definition->defaults; // Sets the class property.
return new {$config->options['class']}($config, $context);
}
}
?>That is, any layout plugin that does not specify a class simply inherits the same common class, and that class knows exactly what to do with CSS files, template files, etc. Most people implementing a layout plugin would never see the word "class", unless they're documenting their CSS.
Similarly, subclassing off of another plugin's class and making only slight changes is also perfectly legal and appropriate. That should greatly simplify the implementation of, for instance, Field API Fields, Widgets, and Formatters.
Namespaces
How plugin classes are namespaced is dependent on how we end up structuring namespaces for Drupal in general, which is not yet decided pending performance investigation. However, for the time being we decided to use:
\Drupal\Plugin[core parts of the system]
\Drupal\Plugin\$Scope\$Type[stuff relating to that plugin type]That is subject to change as core figures out what it's going to do.
Saving configuration
How one would go about writing to plugin configuration, say to change what subsystem was configured or to change block layout, we're still not sure. In a large part that still depends heavily on the outcome of the Configuration Management Initiative, so for the time being our answer is to punt on that until we know what that API will look like in more detail.
Edge cases
There are, of course, a lot of systems that don't quite fit this model directly. They may have only a single instance active at any given time; they may not have any separate instance configuration, etc. One of the benefits of this approach is that such degenerate cases can still be supported by the same system.
For example, we spoke with Dave Reid, one of the maintainers of the Media module, at DrupalCamp Austin. The Media module needs plugins for different media browsers (local files, YouTube files, etc.) However, there is really no reason he could see for them to have multiple instances, since Media would only ever iterate over them and throw them into separate panes in a dialog. In that case, the factory would silently create an instance in the background for each media browser plugin implementation, and then load and return it. Everything else about the API is exactly the same, with all of the consistency, improved DX, and "less work for Dave" benefits that come with that.
We also spoke with Commerce maintainer Ryan Szrama (who was also present), since Commerce is heavy user of the info hook pattern and a likely candidate to leverage core plugins heavily in Drupal 8. From a limited examination it appeared to work for his use cases as well, and the ability to easily subclass another plugin should help reduce code duplication. The switch from info hooks to configuration files did not seem problematic.
Conclusion
This is a long writeup, as we wanted to be thorough. (We probably still forgot something.) However, the underlying concepts are not actually that complicated. A Plugin Type is simply a PHP interface; a Plugin Implementation is simply a PHP class; a Plugin Instance is simply an Implementation plus configuration; a Plugin factory is just that, a standard factory object, common to a myriad of systems in a variety of languages.
While there are some potential DX pitfalls, most notably the separation of the config file and the implementing class, we believe that the unification that this approach provides us will be well worth it, and once more widely implemented will become the expected approach. "Config file + Class" is a common model in many PHP frameworks, including Symfony2, so it's not invented out of whole cloth.
The runtime cost should also be fairly cheap, assuming the Configuration API is not too expensive. (And if it is, we will have larger problems anyway.)
EclipseGc and neclimdul have already started putting together a prototype implementation in the WSCCI sandbox, in the wscci-plugins branch. Have a look in /core/includes/Drupal/Plugin if you want to see what the code could look like.
Thanks again to everyone who attended the sprint. Thank you also to Four Kitchens for letting us take over their conference room all day to hash this stuff out. It was one of the most productive meetings I've had in a long time. And thank you to Pantheon for buying everyone a proper Texas BBQ for lunch.

Comments
Yay+++++
Yay+++++
CMI dependency
After a brief chat with catch in IRC, I want to highlight one thing I think I glossed over in the writeup.
We're not looking to hit CMI's configuration files directly. We're looking to leverage whatever CMI's API is, which as of last report included XML files with that sort of name-based nesting. In most cases, reading configuration will not be straight from disk but from an "active" version in a database (MySQL by default, probably MongoDB or Redis for high-performance sites).
That, of course, does raise an interesting problem for very-low-level configuration, particularly caching, since loading up the right cache system has to be pretty damned fast for page caching to not suck. Also, some such configuration may not be able to live in the database in "active" form as it would result in a chicken-and-egg problem. That will likely necessitate settings.php living on in some form, for instance.
Both of these issues are known problems that the CMI initiative is working to resolve. For plugins, we're essentially punting that problem to them to figure out. Making early configuration a fast read is a significant problem, but is not specific to plugins. At this point, we'd rather gamble that CMI will manage to solve that problem and we can build off of what they do than gamble that they won't and implement something else, then get stuck with an ugly one-off configuration system.
Awesome writeup for what
Awesome writeup for what looked like an awesome sprint, folks !
I'm still wrapping my head around this, and trying see how this will map to Field API.
A couple questions for now :
- "Plugins should not be used for data storage"
Can you expand a bit on why ?
Does that mean that field storage backends, while currently based on a "fake hook"-based API like the rest of Field API "pseudo-plugins" (field types, widgets...), are not valid candidates to move over to the Plugins system ?
- Widgets and formatters are obvious contenders for the Plugins system. However, all widgets are not swappable. A given widget (= a given Implementation of the "Field widget" Plugin Type) is only applicable to some field types (= to some implementations of the "Field type" Plugin Type) - same goes for formatters.
I guess that's not relevant as far as the plugins system is concerned, and is only an internal matter for Field API ? (probably for the factory for the "Field widget" Plugin Type ?)
- The part about "config" files for "Plugin Instances" confuses me.
Currently, for Field API, the corresponding data is stored in the $field (field type + settings) or $instance (widget and formatter + settings) definitions - that is, in D7, in the {field_config} and {field_config_instance} tables respectively.
When discussing the move to CMI back in BADcamp, we were planning to store those in :
field.<field_name>.xml(field)<entity_type>.bundle.<bundle_name>.instance.<field_name>.xml(instance)(i.e. deploying a node type == deploying everything under
node.bundle.<node_type>.*.xmland you're done)So if the Plugins system expects
plugin.instance.$scope.$type.$machine_name.xmlfiles for the Plugin Instances :do we get a
plugin.instance.field.field_type.<field_name>.xmlthat duplicates / replacesfield.<field_name>.xml?do we get some
plugin.instance.field.widget_type.<entity_type>__<bundle_name>__<field_name>.xmlandplugin.instance.field.formatter_type.<entity_type>__<bundle_name>__<view_mode>__<field_name>.xmlthat duplicate / replace<entity_type>.bundle.<bundle_name>.instance.<field_name>.xml?Or is up to the Factory class to chose whether to use plugin.instance.*.xml files or not (and instead store Plugin Instance config where it sees fit - e.g in "regular" CMI config files) ?
Plugins are not data storage
Plugins are not data storage in the sense that Plugins are not Entities, and Entities are not Plugins. Entities should represent Data. Plugins represent Logic.
Fields... Hm. Are a sort of annoying gray area, I guess. I was hoping that we could use Plugins for Fields, but I could see an argument that they're too "data-ish". Not sure there.
Widgets and Formatters are definitely plugin material, yes. For the config files... I'm not sure. You probably could do different naming in your factory. At that point I think it's more of a UX/DX question than architectural, although that perhaps indicates that we should make the base factory class factored such that it's easy to override the "logic to figure out the right file" with a single method for that reason.
What formatters are legitimate in what context (small c) sounds like something EclipseGc could speak to better than I, since he has the same problem to deal with for blocks. :-) Kris?
Line in the sand ?
You make it sound like it's more about having a line in the sand to provide conceptual guidelines wrt Entity-land, rather than a hard technical impossibility ?
Also, my question was about "Field storage" plugins (responsible for 'values storage' : core's field_sql_storage, mongodb_field_storage...), but you seem to expand the gray area to "Field Types" as well (not directly storage-related, but they define the shape of the values for the field type - i.e. the 'schema', which field_sql_storage directly treats as snippet of an actual table schema -, and they also perform some pre-save and post-load value massaging)
Anyway - the "Widget types as Plugins" part is fairly straightforward (I'll try to catch you folks on IRC for a couple remarks I raised along the way). I'll try to move forward on "Field types as Plugin", which will be a bit more challenging, and have a greater impact on the overall Field API.
Yes
Yes, Plugins != Entity is more of a design choice than a technical requirement. Because they represent different concepts. It's more trying to dissuade anyone from trying to port the Entity system to Plugins, which we don't think is a good idea.
Field API has too many nouns. :-) Field Storage objects I definitely think are Plugins, since they're responsible for the logic of saving rather than being themselves data. Field Type... honestly I'm not 100% clear on where the line is now between field type and field instance and field instance object. I'd say that anything that is not "the data structure that is the actual data of the field" is a valid use case for plugins. That data structure, I'm not sure, but as you noted that's more of a semantic distinction than technical one. I leave that to the implementation to sort out.
Yup, the "field value" itself
Yup, the "field value" itself (the thing that is found at $entity->field_foo) might possibly be interesting to have as a classed object with methods, but that's most probably unrelated to the Plugins system.
Widgets as Plugins were just warm up, and Field storage and Formatters shouldn't be much harder - at least as direct ports, not considering potential improvements people might want to come up with in D8.
But "Field types as Plugins vs $field & $instance" will definitely be the meaty part, with the most potential impact on the overall API - and possible race conditions with CMI shaping up. That's why I want to start exploring asap.
Sorry I couldn't reply
Sorry I couldn't reply earlier. A few clarifications:
1.) Plugins themselves are not data... that doesn't mean they can't contain the scaffolding for setting up something that would contain data (schema should be fair game in a plugin imo) so I don't have a problem with fields ultimately moving to plugins. I would like to see it happen, I will work to help make it happen.
2.) To Crell's statements about "entities" this is true, but what is also true is that entity_types (i.e. node, user, comment, etc) should actually be definable AS plugins. In fact much of what many different people have asked for in a more robust entity system is already in the plugin system, it would just need to be converted. I'd love to see this happen, but entity has had a lot of work go into it in D8 already and I've honestly not kept up. Shame on me. Still this point remains.
I've not looked at yched's custom branch that does field widget as plugins yet, but the factory is ultimately in control of a given plugin instance's configuration retrieval. In this way you don't actually even have to leverage the configuration system if you don't want to. As mentioned in the above write up, that's probably maiming kittens, but choosing to use a completely different config() naming schema seems like a really obvious and probably common alteration, so I have 0 issues with that. Hopefully this is feeling fairly solid and useful. :-D
Eclipse
False?
field = field type + global type settings + cardinality
instance = widget + some overloaded type settings applied to entity type
view mode = instance + formatter
Seems more like it, am I wrong?
(sorry a bit off-topic).
Pierre.
Yup, spitting the various
Yup, spitting the various display settings out of the $instance and into a separate 'animal' has been in my mind for some time (http://drupal.org/node/367498#comment-1232096, though quite outdated now), and the idea is still up - see the last paragraph of the "Field API : Config files layout" section of the latest CMI sprint summary.
Major Field API refactorings lie ahead with Plugins + CMI anyway. Maybe we'll be able to "naturally" slip this change in on that occasion, maybe it'll deserve its own discussion afterwards (UI impact...). At any rate, we punt on that until CMI starts to materialize.
Sandbox branch for Field API
For those interested, I pushed the 'wscci-plugins-fieldapi' branch to the WSCCI sandbox (http://drupalcode.org/sandbox/Crell/1260830.git)
It is branched off the current state of the 'wscci-plugins' branch (which does not fully reflect the architecture exposed in the OP above yet, but has functionning code already).
The code in there is an initial attempt at moving the "Field widgets" API to an OO API based on the Plugins system.
It uses a dedicated factory (WidgetFactory class) to route to the right implementation class, based on the $field and $instance definitions.
The API on the Field side (WidgetInterface and the actual implementation classes below) is more or less a direct port of the D7 "pseudo-hooks" for now, but the
code is working just great. (only widgets for the 'text' field types have been ported, so this should be tested on an entity type with only text fields - e.g. a 'page' node)
Feedback from the WSCCI/Plugins team much welcome - notably regarding compliance with the intended Plugins system "philosophy"
Minor note, you should type
Minor note, you should type hint all function parameters when you can (even with "array") it will help you to detect logic error really quickly because you'll experience WSOD and explicit PHP errors instead of (almost) silent PHP notices.
Aside of that, it's a good news you started working on this, I have nothing else to say on the code, it seems nice, hope you will progress as fast as you want to!
Pierre.
Type hinting : yes, I need to
Type hinting : yes, I need to check what hinting verbosity level you guys are aiming in the Context patch and get on par with that.
Thks for the review :-)
Coding standards
1) If a parameter is an array or a classed object, specify a type hint.
2) Always hint on an Interface. Never hint on a class.
3) If a parameter may be more than one type, specify the common parent interface. If it's not that simple (could be a string or a class, etc.), there's a 75% chance you have a design flaw. If it's not a design flaw, do not hint.
2) Always hint on an
In the case of Widgets, I added an abstract
class WidgetBase implements WidgetInterface, with some fit-all or fit-most method implementations, that in practice should be what implementors look at rather than the interface itself. No type hinting there either ?(When we come to "Field types as Plugins", the FieldTypeBase might be much more crucial - containing some base code that has to run for the field type to actually work)
Also : should it be
class SomeActualWidget extends WidgetBase {or
class SomeActualWidget extends WidgetBase implements WidgetInterface {?
Like this
<?php
interface WidgetInterface {}
class WidgetBase implements WidgetInterface {}
class RealWidgetA extends WidgetBase {}
// This may be rare, but must always be possible.
class RealWidgetB implements WidgetInterface {}
do_stuff_with_widget(WidgetInterface $widget) {}
?>
In practice the only time you'd fail that type check is if you pass a FALSE or string or array or some such, assuming you're not being completely dumb in your classes. Of course, if you pass FALSE or a string or array or some such there then you definitely have a bug, so it should blow up on you right there and tell you what you did that was dumb. That's the idea. :-)
My remarks on the API
After that first real scale implementation, I posted some remarks/questions on the current state of the API in http://groups.drupal.org/node/195618
Dependency Injection
Just a quick question about the plugin runtime resolution. Why you not choosen to use Dependency Injection ? I see so far that in the Symphony components you imported, the DI package seems used.
Althought I understand the needing for factories, DI offers the same functionalities such as configuration injection for dependencies and so on... Maybe its a question of performances or flexibility ? Or simply you didn't wanted to use it ?
DI is a useful system for runtime resolution
DI is a useful system for runtime resolution and that's definitely a possible and hopefully common usecase. Its even possible with plugins as they're currently written and to some extent is already being tested and will be in the next patch posted for review.
DI is not however the only way things resolve and some systems work great without DI. It does not provide any sort dynamic logic such as say, failing your cache to a different mechanism or resolving something at calltime which is what the MapperInterface is for. It does not provide the ability for creating individual instances to handle small tasks or ecapsulating the logic on instantiating objects which is what the factory is for. And most importantly it does not provide any sort of discoverability of implementations that can be used for admin interfaces(reporting, config, etc) which is really the key to the plugins we're building.
DI is very complimentary though. You can embed the PluginType so you have the service for say fetching cache backends embed in your DI container. You could also fetch instances from the plugin system, or use symfony's very flexible and awesome register system to have it instantiated and built by the plugin system on request.
So, yes DI is great and complimentary tool that is important to our future but the plugin system designed here solves a different set of problems.
Finally, this discussion is pretty much closed. If it wasn't for notifications I wouldn't have seen this. Better places are the sandbox issue queue and the core issue.
Thank you I was just looking
Thank you I was just looking for complementary information, which you gave me with nice explainations.