Extensions
Extensions provide a clean and easy way to extend Meteor functionality at runtime.
Meteor uses the Symfony Dependency Injection component to standardize the way objects are constructed in the application. An extension can declare it's own services or extend services with the dependency injection container. All core functionality within Meteor are written like extensions to ensure third-party extensions are given first-class treatmeant.
Creating a basic extension
The DemoExtension
class must implement Meteor\ServiceContainer\ExtensionInterface
.
<?php
namespace Jadu\MeteorDemo\ServiceContainer;
use Meteor\ServiceContainer\ExtensionInterface;
use Meteor\ServiceContainer\ExtensionManager;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class DemoExtension implements ExtensionInterface
{
public function getConfigKey()
{
return 'demo';
}
public function configure(ArrayNodeDefinition $builder)
{
}
public function initialize(ExtensionManager $extensionManager)
{
}
public function load(ContainerBuilder $container, array $config)
{
}
public function process(ContainerBuilder $container)
{
}
}
Configuration
The Symfony Config component is used to load and validate the meteor.json
config file.
The getConfigKey
method returns the config key for this extension to parse from the config.
The configure
method is where the extension can define the config structure for the demo
section (as used in this example).
public function getConfigKey()
{
return 'demo';
}
public function configure(ArrayNodeDefinition $builder)
{
$builder
->children()
->scalarNode('name')->end()
->end()
->end();
}
The above example means that the meteor.json
config expects a section named demo
with a name
node.
{
"demo": {
"name": "Tom Graham"
}
}
For more detailed examples and information about the Config component please see the Symfony docs on this subject.
Loading services
The load
method is where your extensions service should be defined.
public function load(ContainerBuilder $container, array $config)
{
$definition = new Definition('Jadu\MeteorDemo\TestService');
$container->setDefinition(self::SERVICE_TEST, $definition);
}
The config passed to this method will be the processed config for this extension. So continueing the example above, this would be:
array(
'name' => 'Tom Graham'
)
For more details examples and information about the ContainerBuilder
class please see the Symfony docs on this subject.
Note: It is recommended to use class constants for service names and parameter names to avoid duplication of hard-coded strings.
Extension points
Meteor provides some standard extension points into the application:
- CLI commands
- Event subscribers
- Patch strategies
- Patch task handlers
CLI commands
Meteor uses the Symfony Console component for the CLI.
Commands within Meteor should ideally extend the Meteor\Cli\Command\AbstractCommand
class, however it is not a requirement.
A simple example of a command class:
<?php
namespace Jadu\MeteorDemo\Cli\Command;
use Meteor\IO\IOInterface;
use Meteor\Patch\Cli\Command\AbstractCommand;
use Meteor\Platform\PlatformInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MyFirstCommand extends AbstractCommand
{
/**
* @param InputInterface $input
* @param OutputInterface $output
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// Do something
}
}
To add a new command to Meteor a service definition must be tagged with cli.command
.
Within your extensions load
method create a service defintion as shown below:
$definition = new Definition('Jadu\MeteorDemo\Cli\Command\MyFirstCommand', array(
'demo:my-first-command',
'%'.Application::PARAMETER_CONFIG.'%',
new Reference(IOExtension::SERVICE_IO),
new Reference(PlatformExtension::SERVICE_PLATFORM),
));
$definition->addTag(CliExtension::TAG_COMMAND);
$container->setDefinition(self::SERVICE_MY_FIRST_COMMAND, $definition);
Event subscribers
$definition = new Definition('Jadu\MeteorDemo\EventListener\MyEventListener');
$definition->addTag(EventDispatcherExtension::TAG_EVENT_SUBSCRIBER);
$container->setDefinition(self::SERVICE_MY_EVENT_LISTENER, $definition);
Patch strategies
Only load services for the strategy if the strategy is selected in the config. This can be achieved by checking the strategy parameter as shown in the example below:
public function load(ContainerBuilder $container, array $config)
{
if ($container->getParameter(PatchExtension::PARAMETER_STRATEGY) !== self::STRATEGY_NAME) {
return;
}
$definition = new Definition('Jadu\MeteorDemo\Patch\Strategy\DemoPatchStrategy');
$container->setDefinition(PatchExtension::SERVICE_STRATEGY_PREFIX.'.'.self::STRATEGY_NAME, $definition);
}
Ensure your strategy service name is in the format patch.strategy.[strategy name]
. This should be done within your extension using the class constants provided. For example:
PatchExtension::SERVICE_STRATEGY_PREFIX.'.'.self::STRATEGY_NAME
Meteor will set the active strategy using the service name and expects it to be in this format. If this pattern isn't followed then Meteor will not be able to find the custom strategy.
Patch task handlers
$definition = new Definition('Jadu\MeteorDemo\Patch\Task\MyTaskHandler');
$definition->addTag(PatchExtension::TAG_TASK_HANDLER, array(
'task' => 'Jadu\MeteorDemo\Patch\Task\MyTask',
));
$container->setDefinition(self::SERVICE_MY_TASK_HANDLER, $definition);