Service manager as plugin broker #1550

merged 36 commits into from Jun 25, 2012


None yet

4 participants


In this PR, I've refactored all components that use a plugin broker (with the exception of AMF, which is currently unstable anyways) to instead compose component-specific service managers.

This introduces Zend\ServiceManager\AbstractPluginManager, as well as a few additions and fixes to the ServiceManager implementation itself. AbstractPluginManager accomplishes the following:

  • Allows passing class names directly (a feature of the old plugin broker solution)
  • Allows passing options to get(): get($name, $options). Options are by default passed directly to the constructor.
  • Allows marking the container to either share or not share instances by default, instead of requiring passing the $shared flag when calling one of the set/add methods. (This is actually a new feature of the ServiceManager, but only currently used in plugin manager implementations.)
  • Registers an initializer, validatePlugin(), at the top of the initializer stack. This allows validating that a plugin satisfies context-specific criteria.
  • Ability to peer on another service manager; if plugins cannot be retrieved from the plugin manager, but a peer is set, it will attempt to retrieve via the peer. By default, I've specified that any plugin manager managed by the MVC service manager configuration will peer off the application service manager. This makes adding plugins a trivial matter -- simply define them as services. (This functionality is technically part of the ServiceManager implementation.

Benefits of this include:

  • Keeping all plugin information in one place, instead of across multiple classes. The plugin broker solution required both the broker as well as a loader class for each plugin type. Now both are combined in a single solution.
  • Overall, 50% less code.
  • More flexibility in attaching plugins: attach instances, factories, abstract factories, etc. Or attach them to the application SM instance, and the plugin manager will fetch from there.
  • One less API to learn. Technically, we can remove the Plugin Broker and PluginClassLocator solutions at this time.

All code has been tested, though I have not completely tested with the skeleton application at this time. When possible, I have retained configuration keys for the MVC services in order to retain compatibility with the previous betas.

Build status:!/weierophinney/zf2/builds/1679619

weierophinney added some commits Jun 18, 2012
@weierophinney weierophinney Add ability to peer an SM instance off another
- Added addPeeringServiceManager() method to allow peering the current
  SM instance off another in either a child or parent relationship
@weierophinney weierophinney Added ability to pull from peering managers first
- Basically, allows overriding of items configured in the SM via a
  peering manager
@weierophinney weierophinney Move creational code to methods
- Will allow overriding later in subclasses
@weierophinney weierophinney Refactored FilterChain to use ServiceManager
- Updates base ServiceManager to allow arbitrary callables as initializers
- Created context-specific ServiceManager
  - Sets up initial list of invokables, aliases, and shared status
  - Toggles sharing flag in various setters to be off by default
  - Modifies get() to allow passing options as second argument
  - Overrides createFromInvokable to pass options to constructor, if present
  - Adds initializer to ensure we have a valid filter

Basically, serves as a POC for a SM-based plugin broker.
@weierophinney weierophinney Share by default
- In looking at original plugin broker implementation, sharing is the
  default. This helps reduce a lot of boilerplate.
@weierophinney weierophinney Added docblocks 56a7080
@weierophinney weierophinney Moved common functionality to AbstractPluginManager
- AbstractPluginManager defines:
  - allowOverride as on by default
  - validatePlugin() as abstract, and __construct() adds it as an
  - get/createFromInvokable() allow passing options for requested
- Filter\ServiceManager now extends AbstractPluginManager, and only
  needs to define invokables, aliases, and validatePlugin()
@weierophinney weierophinney Renamed Filter\ServiceManager to Filter\FilterPluginManager 826e26f
@weierophinney weierophinney Fix several tests and change filter aliases
- Fixed a few tests that were failing due to usage of broker
- Realized that all previously created aliases were unnecessary, as
  canonicalization strips out all special characters and lowercases.
  Removed, and then added aliases for canonicalized classnames instead
  (which allows pulling filters by class name).
@weierophinney weierophinney Refactor Filter\Inflector to use FilterPluginManager 0d5290c
@weierophinney weierophinney Removed Zend\Filter\Input
- Obsoleted by Zend_Filter_Input
@weierophinney weierophinney Refactored StaticFilter to use FilterPluginManager
- Identified another feature for PluginManager approach -- ability to unregister
  an instance after the fact and/or mark as not shared after registered.
@weierophinney weierophinney Added way to retrieve by class name
- If the plugin service does not exist, and it's a class name, add it as
  an invokable
- Allows us to remove any aliases for class names!
@weierophinney weierophinney Ensured all StaticFilter functionality works with FilterPluginManager
- setService($name, null) effectively removes a service by that name
- Added test to ensure above works as expected
@weierophinney weierophinney Refactored Validator to use AbstractPluginManager
- Removed ValidatorLoader and ValidatorBroker
- Added ValidatorPluginManager
- Ensured all tests continue to pass
@weierophinney weierophinney Refactored Log to use AbstractPluginManager
- Removed WriterBroker and WriterLoader
- Added WriterPluginManager
- Modified Logger to use WriterPluginManager
- Overrode setService() in AbstractPluginManager in order to validate
  the service before registering it.
@weierophinney weierophinney Only validate a service if not null
- in order to allow unsetting a service instance
@weierophinney weierophinney Refactored Barcode to use AbstractPluginManager
- Removed ObjectBroker, ObjectLoader, RendererBroker, and RendererLoader
- Added ObjectPluginManager and RendererPluginManager
- Refactored Barcode to use above plugin managers
@weierophinney weierophinney Fix options in Filter\Inflector
- Were still referencing pluginBroker instead of pluginManager
- Also removed import for broker
@weierophinney weierophinney Refactored Crypt to use AbstractPluginManager
- Removed SymmectricLoader, SymmectricBroker, PaddingLoader, and
- Added SymmetricPluginManager and PaddingPluginManager
- Refactored Symmetric\Mcrypt to use PaddingPluginManager
- Refactored BlockCipher to use SymmetricPluginManager
- Ensured all tests pass
@weierophinney weierophinney Refactored Mail to use AbstractPluginManager
- Removed Protocol\SmtpBroker and Protocol\SmtpLoader
- Added Protocol\SmtpPluginManager
- Refactored Transport\Smtp to use Protocol\SmtpPluginManager
- Ensured all tests pass
@weierophinney weierophinney Refactored Math\BigInteger to use AbstractPluginManager
- Removed AdapterLoader and AdapterBroker
- Added AdapterPluginManager
- Refactored BigInteger to use AdapterPluginManager
- Ensured all tests pass
@weierophinney weierophinney Refactored Serializer to use AbstractPluginManager
- Removed AdapterLoader and AdapterBroker
- Added AdapterPluginManager
- Refactored Serializer to use AdapterPluginManager
- Ensured all tests pass
@weierophinney weierophinney Refactored Tag\Cloud to use AbstractPluginManager
- Removed Cloud\DecoratorBroker and Cloud\DecoratorLoader
- Added Cloud\DecoratorPluginManager
- Refactored Cloud to use Cloud\DecoratorPluginManager
- Ensured all tests pass
@weierophinney weierophinney Refactored Markup to use AbstractPluginManager
- Removed ParserLoader, ParserBroker, RendererLoader, and RendererBroker
- Added ParserPluginManager and RendererPluginManager
- Refactored Markup to use ParserPluginManager and RendererPluginManager
- Ensured all tests pass
@weierophinney weierophinney Refactored Paginator to use AbstractPluginManager
- Removed AdapterLoader, AdapterBroker, ScrollingStyleLoader, and
- Added AdapterPluginManager and ScrollingStylePluginManager
- Refactored Paginator to use AdapterPluginManager and
- Modified array adapter to make constructor parameter optional
- Fixed trailing ';' issue in null adapter
- Ensured all tests pass
@weierophinney weierophinney Refactored Cache to use AbstractPluginManager
- Removed PatternBroker, PatternLoader, AdapterBroker, AdapterLoader,
  PluginBroker and PluginManager
- Added PatternPluginManager, AdapterPluginManager, and PluginManager
- Refactored PatternFactory to use PatternPluginManager
- Refactored StorageFactory to use AdapterPluginManager and PluginManager
- Renamed TagableInterface to TaggableInterface (grammar correction), and
  corrected all adapters that use it
- Various CS fixes as they were encountered
- Ensured all tests pass
@weierophinney weierophinney Refactored View to use AbstractPluginManager
- Helper management
  - Removed HelperLoader and HelperBroker
  - Created HelperPluginManager
  - Refactored PhpRenderer to use HelperPluginManager
- Helpers
  - Removed Navigation\HelperLoader, and replaced with Navigatin\PluginManager
  - Refactored Navigation to use a PluginManager instead of HelperLoader +
    internal registry
  - Fixed HeadLink and HeadMeta helpers; don't look for Pluggable interface, but
    instead duck type on plugin() method
- Forms
  - changed Form HelperLoader to a SM configuration object
  - duck type on plugin() instead of type check on Pluggable
- Refactored Mvc\View\ViewManager to remove references to HelperLoader and
  rename HelperBroker to HelperManager; kept original alias for ViewHelperBroker
  for migration. Moved all plugin mapping to helper manager configuration.
- Ensured all tests related to views, MVC, and forms pass
@weierophinney weierophinney Refactored Mvc\Controller to use AbstractPluginManager
- Removed PluginBroker and PluginLoader
- Added PluginManager
- Refactored ActionController and RestfulController to use PluginManager
- Removed ControllerPluginLoaderFactory and ControllerPluginBrokerFactory
- Added ControllerPluginManagerFactory (and aliased it to ControllerPluginBroker
  for migration)
- Ensured all MVC and ModuleManager tests pass
@weierophinney weierophinney Better capabilities surrounding sharing
- Added flag "sharedByDefault" to ServiceManager implementation, set to
  true by default
- Disabling the flag disables storing created instances for re-use
- Used with a variety of components: Cache, Crypt, Paginator, and the
  static variants of Filter and Validator
- Cannot re-set the flag if allowOverride is false
@weierophinney weierophinney Refactored Mvc\Router to use AbstractPluginManager
- Removed RouteBroker
- Created RoutePluginManager; does not share by default, and overrides
  createFromInvokable() to create using route class' factory methods.
- Refactored SimpleRouteStack, TreeRouteStack, and Part route to use
  RoutePluginManager, and to have config key "route_plugins" instead of
- Ensured all tests pass
@weierophinney weierophinney Cleanup
- Ensured all file and class level docblocks are correct
- Removed all extraneous import statements
- Ensured @throws and property annotations are correct

Review in progress.

@DASPRiD DASPRiD referenced this pull request Jun 24, 2012

Feature/i18n #1585

weierophinney and others added some commits Jun 25, 2012
@weierophinney weierophinney [zen-56][#1550] pull from peering managers first
- Necessary to allow overriding a plugin; reported by Evan Coury
@weierophinney weierophinney [zen-56][#1550] Test for setPluginManager instead of Pluggable
- Altered ControllerLoaderFactory's initializers to test for
  method_exists($instance, 'setPluginManager') instead of the Pluggable
  interface, and then to call setPluginManager() instead of setBroker()
@EvanDotPro EvanDotPro Use HelperManager in ViewManager
- Also set aliases for the short names so overrides work
- Cleaned up some trailing whitespace
- Side note: I think we might be going overboard with the Manager
  suffix... just sayin'.
@weierophinney weierophinney [zen-56][#1550] Do not clone controller plugin manager
- Leads to odd issues, due to duplicate registries
@EvanDotPro EvanDotPro merged commit 6f87c33 into zendframework:master Jun 25, 2012

Is there a reason why $this->creationOptions is set two lines before and then reset in this line? Doesn't this make the whole $options parameter useless as it's not used in this function?


Yes, there's a very good reason for it.

$creationOptions is specific to the AbstractPluginManager, and the $options does not exist in the parent ServiceManager definition. What we're doing here is setting the state so that when one of the various creation methods (createFromInvokable, createFromFactory, etc.) is invoked, they are present; those methods then use them. Once we've finished creating our instance, however, we need to reset the state so that if a later call to get() is made without an $options argument, the plugin manager will not use them.


Matthew, I have always been wondering why the creation options argument was removed in the main service manager? Sometimes there are cases when i want to pass arguments via constructor without extending the abstractpluginmanager.

That very same issue also made me think about adding a new functionality for the Servicemanager called 'constructibles' that will have the same functionality as having to have arguments injected to objects via constructors. Although one may argue that this may be anti IOC but hey if we are implementing constructor injection in AbstractPluginManager, why not just have it for the main SM?

If you are interested i created a prototype for (*sorry if this should be another thread)


@franz-deleon The creation options were never part of the main service manager. Essentially, if you want to have service-specific options, you should create a new service factory that pulls options from configuration or defines those options in the factory itself.

When it comes to plugins, however, we often want (a) multiple instances, and (b) different behavior per-instance -- which is why we added creation options to the AbstractPluginManager definition.


you should create a new service factory that pulls options from configuration or defines those options in the factory itself.
I guess the beauty of it is it forces the programmer to abide by the standards or what is considered right.

Although sometimes i am just lazy to create extra classes for something like below and still have the SM handle it:

new Car('red')

$car = new Car();

Thanks for the info and it enlightens me and makes much more sense now why things are designed the way it is.

@weierophinney weierophinney added a commit to zendframework/zend-servicemanager that referenced this pull request May 15, 2015
@weierophinney weierophinney [zen-56][zendframework/zendframework#1550] pull from peering managers…
… first

- Necessary to allow overriding a plugin; reported by Evan Coury
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment