Read this guide if
Plugin developers can take advantage of constructor injection in most API classes in Matomo. This works for example for controllers, APIs, widgets, menus, tasks, commands etc. Matomo will automatically create the needed instances and pass it to your constructor.
For example if you want an instance of a logger and translator, simply define them in the constructor. It automatically also works for your own classes. For example:
use Piwik\Translation\Translator;
use Piwik\Log\LoggerInterface;
use Piwik\Plugins\MyPlugin\Dao\MyEntityDao;
class API
{
/**
* @var Translator
*/
private $translator;
/**
* @var LoggerInterface
*/
private $logger;
/**
* @var MyEntityDao
*/
private $myEntityDao;
public function __construct(Translator $translator, LoggerInterface $logger, MyEntityDao $myEntityDao)
{
$this->translator = $translator;
$this->logger = $logger;
$this->myEntityDao = $myEntityDao;
}
public function doSomething() {
$this->myEntityDao->storeSomething();
$text = $this->translator->translate('MyPlugin_TranslationKey');
$this->logger->info($text);
}
}
When you inject your own classes, Matomo will also automatically resolve the dependencies for these classes using the
constructor. Say for the above MyEntityDao
example you can take advantage of having dependencies automatically resolved like this
namespace Piwik\Plugins\MyPlugin\Dao;
use Piwik\Translation\Translator;
use Piwik\Log\LoggerInterface;
class MyEntityDao
{
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function storeSomething() {
$this->logger->info('store something in DB');
}
}
If you want to inject a configuration value (e.g. an int or a string) then you will have to create a DI configuration.
The container includes the following configuration files in the order listed:
config/global.php
: main configuration fileplugins/*/config/config.php
: the main configuration for each individual plugin (if any exists)plugins/*/config/tracker.php
: only loaded in tracker mode when a tracking request is being processedconfig/environment/$environment.php
: the "environment" configuration file (eg "dev", "test")
For each Matomo entry point (ie, cli, tracker) there is a different environment file that will be loaded. This allows plugins and developers to configure Matomo differently based on the way Matomo (formerly Piwik) is running. Currently, the following environments are recognized: cli
, tracker
.
plugins/*/config/$environment.php
: the environment configuration for each individual plugin (if any exists)config/environment/dev.php
: a special environment config file included when running in development modeplugins/*/config/dev.php
: the dev
configuration for each individual plugin (if any exists)config/environment/test.php
: a special environment config file included when running PHPUnit tests or UI testsplugins/*/config/test.php
: the test
configuration for each individual plugin (if any exists)config/config.php
: optional user configuration file (not versioned in git)When developing a plugin, you can supply DI config with your plugin in one of the files listed above to either configure your plugin or customize Matomo.
The syntax used in those files is described in PHP-DI's documentation. Below are examples of the most common use cases.
return array(
'Piwik\Translation\Loader\LoaderInterface' => Piwik\DI::autowire('Piwik\Translation\Loader\LoaderCache')
);
This will automatically create an instance of the LoaderCache
whenever the LoaderInterface
is requested.
return array(
'log.format' => '%level% %tag%[%datetime%] %message%'
);
This can come in handy if you want to pass a value instead of an object to a constructor. For more details see the next example.
Given that you have the following class:
class LineMessageFormatter
{
public function __construct($logFormat)
{
// ...
}
}
We configure to inject the log.format
entry in the constructor:
return array(
'Piwik\Log\Formatter\LineMessageFormatter' => Piwik\DI::autowire()
->constructor(Piwik\DI::link('log.format')),
);
or
return array(
'Piwik\Log\Formatter\LineMessageFormatter' => Piwik\DI::autowire()
->constructorParameter('logFormat', Piwik\DI::link('log.format')),
);
return array(
'foo.bar' => Piwik\DI::factory(function (\Piwik\Container\Container $c) {
$bar = // ...
return Foo::createSomething($bar);
}),
);
It's also possible to add additional event listeners for any Matomo event using Dependency Injection. As most events are
using references to make manipulation possible it's required to wrap the event listener functions into Piwik\DI::value
.
return [
'observers.global' => [
['AssetManager.getStylesheetFiles', Piwik\DI::value(function (&$stylesheets) {
$stylesheets[] = 'my\custom.css';
})],
],
];
When writing integration or system tests you can inject your own classes (such as mocks) into the Matomo environment one of two ways:
Add configuration to your plugin's config/test.php
file. For example:
<?php
return array(
'Piwik\Plugins\MyPlugin\MyRESTClient' => Piwik\DI::autowire('Piwik\Plugins\MyPlugin\Test\MockRESTClient'),
);
This configuration is applied in every test, however, so it may not be desirable.
Override the provideContainerConfig()
method in either your test case class or your Fixture
class and return the DI config. For example:
use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
class APITest extends IntegrationTestCase
{
// ... test cases ...
public function provideContainerConfig()
{
return array(
'Piwik\Plugins\MyPlugin\Dao\MyEntityDao' => Piwik\DI::autowire('Piwik\Plugins\MyPlugin\Test\Mock\MockMyEntityDao')
->constructorParameter('tmpPath', '/my/test/tmp/path'),
);
}
}
Both of these types of container configuration affect child processes as well. So, for example, tracker requests sent in Fixtures will use this overridden configuration.
For UI tests which are written in JavaScript, this is a bit trickier. You can either use the first approach above, or override the DI config in a Fixture class and use it in your tests.
For example, in your Fixture:
use Piwik\Tests\Framework\Fixture;
class MyFixture extends Fixture
{
// ...
public function provideContainerConfig()
{
return array( /** container config */);
}
}
then in your UI test:
describe("MyUiTest", function () {
this.fixture = "Piwik\\Plugins\\MyPlugin\\Test\\Fixtures\\MyFixture";
// ... tests ...
})