Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 15 additions & 33 deletions docs/building-extensions/plugins/basic-content-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ Ensure that your plugin language files are named correctly. You must include in

## Service Provider file

In this example we implement the plugin class as a [lazy object](./methods-and-arguments.md#lazy-objects).

```php title="plg_shortcodes/services/provider.php"
<?php

Expand All @@ -136,7 +138,6 @@ use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use My\Plugin\Content\Shortcodes\Extension\Shortcode;

return new class() implements ServiceProviderInterface
Expand All @@ -145,17 +146,16 @@ use My\Plugin\Content\Shortcodes\Extension\Shortcode;
{
$container->set(
PluginInterface::class,
function (Container $container) {

$config = (array) PluginHelper::getPlugin('content', 'shortcodes');
$subject = $container->get(DispatcherInterface::class);
$app = Factory::getApplication();
$container->lazy(Shortcode::class,
function (Container $container) {
$config = (array) PluginHelper::getPlugin('content', 'shortcodes');
$app = Factory::getApplication();
$plugin = new Shortcode($config);
$plugin->setApplication($app);

$plugin = new Shortcode($subject, $config);
$plugin->setApplication($app);

return $plugin;
}
return $plugin;
}
);
);
}
};
Expand All @@ -176,32 +176,14 @@ $config = (array) PluginHelper::getPlugin('content', 'shortcodes');
Ensure that this contains your plugin type and element, matching the manifest file.

```php
$plugin = new Shortcode($subject, $config);
$plugin = new Shortcode($config);
```

Ensure that this matches your class in your `src/Extension` directory.

:::tip
Since Joomla 6.1 it is possible to use [PHP 8.4 Lazy Objects](https://www.php.net/manual/en/language.oop5.lazy-objects.php) for Plugins. This helps to improve overall CMS performance.

To do so, update service provider to use `$container->lazy(<PluginClassName>, <Service callback>)` as in following example:

```php
return new class() implements ServiceProviderInterface
{
public function register(Container $container)
{
$container->set(
PluginInterface::class,
$container->lazy(Shortcode::class, function (Container $container) {
// ... existing code
})
);
}
};
```

:::
The plugin uses [lazy instantiation](../methods-and-arguments/#lazy-objects),
which means that the plugin's Extension class will be instantiated
only if one of the events to which it subscribes is triggered.

### Extension Class
This is the main code of the plugin. Hopefully the comments in the code explain what is going on.
Expand Down
6 changes: 3 additions & 3 deletions docs/building-extensions/plugins/how-plugins-work.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ For example, after Joomla is initialised the system plugins are imported (ie tho

Importing a plugin type involves:
- finding from the database all the plugins associated with a particular type - the type matches the associated subfolder of the `/plugins` folder in the file system. In the diagram these plugins are shown as `plugin1`, `plugin2` and `plugin3`,
- instantiating each of these plugins,
- instantiating each of these plugins (in specific terms, executing the services/provider.php file of each plugin)
- determining which events each plugin wants to subscribe to.
- writing the subscriptions to a data store represented by the `Listeners` box in the diagram.

Expand All @@ -60,7 +60,7 @@ Once a plugin has been imported and its subscriptions logged in the `Listeners`

The system plugins imported in the first step can subscribe to the `onContentPrepare` event ok, even though it's associated with content plugins, and will receive the `onContentPrepare` event if they have subscribed to it, without having to be imported again.

You should also be aware that unlike components and modules, there aren't different site plugins and administrator plugins. The plugins which you install are run for all the different contexts - site, administrator and API applications - so it's a good idea to in your plugins to check the application context.
You should also be aware that, unlike components and modules, there aren't different site plugins and administrator plugins. The plugins which you install are run for all the different contexts - site, administrator and API applications - so it's a good idea to in your plugins to check the application context.

## Sequence Diagram

Expand All @@ -74,7 +74,7 @@ sequenceDiagram
Note over PluginHelper, Plugins Table: Only once at runtime, and <br>result is cached in memory
Plugins Table-->>PluginHelper: Return all active
PluginHelper-->>Dispatcher: Import only <br>group of $type <br>(once at runtime)
PluginHelper->>Plugin: Instantiate plugin
PluginHelper->>Plugin: Execute each plugin's service/provider.php
Dispatcher->>Plugin: Call $plugin->getSubscribedEvents() <br>for each plugin in group
Plugin->>Dispatcher: Return list of listeners
Code->>Dispatcher: dispatch('onFooBar', <br>$eventObject)
Expand Down
143 changes: 142 additions & 1 deletion docs/building-extensions/plugins/methods-and-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ There are 2 functions which you must implement within your plugin:

1. The `getSubscribedEvents()` method. You tell Joomla
- which events you want to subscribe to
- the code to run when one of those events is triggered
- the code (handler function) to run when one of those events is triggered

2. The handler function which Joomla should call when one of those events is triggered.

Expand Down Expand Up @@ -218,6 +218,147 @@ public function onInstallerBeforeInstaller(\Joomla\CMS\Event\Installer\BeforeIns
}
```

## Instantiating your Plugin Class

You should instantiate your plugin in your services/provider.php file.
Here is an example of such a file which would instantiate the shortcodes plugin in the [plugin tutorial](./basic-content-plugin.md)

```php title="plg_shortcodes/services/provider.php"
<?php

use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use My\Plugin\Content\Shortcodes\Extension\Shortcode;

return new class() implements ServiceProviderInterface
{
public function register(Container $container)
{
$container->set(
PluginInterface::class,
function (Container $container) {

$config = (array) PluginHelper::getPlugin('content', 'shortcodes');
$plugin = new Shortcode($config);

return $plugin;
}
);
}
};
```

This `register()` function is called whenever Joomla [imports the plugin](./how-plugins-work.md#importing-a-plugin).

The code creates an entry in the [Dependency Injection container](../../general-concepts/dependency-injection/DIC.md),
specifically in this plugin's [child container](../../general-concepts/dependency-injection/extension-child-containers.md).

The entry is then read from the DIC, and this results in running the `function (Container $container) { ... }`,
which instantiates the plugin.

### Lazy Objects

Since version 8.4 PHP supports the concept of [lazy objects](https://www.php.net/manual/en/language.oop5.lazy-objects.php),
where the object is instantiated only whenever one of its properties is accessed.

This is particularly useful for plugins because only a subset of plugins are actually triggered in handling an HTTP request.
If plugins are implemented as lazy objects then this avoids many of them being instantiated unnecessarily,
because they're instantiated only if one of their handler functions is called.

(Note that calling the plugin's `getSubscribedEvents()` function does not cause the lazy object to be instantiated because it's a static function).

To define as plugin as a lazy object you can use the Dependency Injection Container's `lazy()` function:

```php
$callable = $container->lazy(Shortcode::class,
function (Container $container) {
$config = (array) PluginHelper::getPlugin('content', 'shortcodes');
$plugin = new Shortcode($config);
return $plugin;
});
```

You pass 2 parameters:

1. Shortcode::class - really just a shorthand for the string of the class's FQN: "My\Plugin\Content\Shortcodes\Extension\Shortcode"

2. The initializer of the class - the function which instantiates the plugin

The returned `$callable` is what you then set in the DIC:

```php
$container->set(PluginInterface::class, $callable);
```

When the entry is retrieved from the DIC (which happens when the plugin is imported) then the `$callable` is run,
which is the `lazy()` function above, and this creates the lazy proxy.

When a property of the Shortcode::class object is accessed (ie when one of the plugin handler functions is called),
then PHP runs the initializer function (2nd parameter of the `lazy()` function),
and this instantiates the plugin object.

### Performance Considerations

If you use lazy plugins and your plugin is not triggered
then the time that you save is the time which would have been taken to instantiate your plugin
(specifically the plugin Extension class instance):

- allocation of memory for your plugin and its properties

- executing of your plugin's constructor (by default, the constructor in CMSPlugin).

Your plugin code is still read and processed by PHP, whenever Joomla imports plugins of that type.

However the use of lazy objects does have some overhead - PHP needs to instantiate a ReflectionClass,
but as this is a standard PHP class its instantiation will be optimised.

So the key question to answer is this:

**Once my plugin's type is imported, how likely is it that one of my plugin's events will be triggered?**

If it's highly likely then don't use lazy instantiation, otherwise do use it.

For example, if your plugin is just listening for onContentPrepareForm
then you should definitely use lazy objects because content plugins are imported for any page displaying an article, contact, etc,
but most pages will not display a form.

On the other hand, the system onAfterInitialise event is dispatched pretty much for every HTTP request,
so if your plugin is a system plugin listening for this event then you should definitely not use a lazy object.
If you use a lazy object here then not only will your plugin be instantiated anyway,
but also you will have the overhead of instantiating ReflectionClass as well, so you will actually slow your site down.

Console plugins are imported only if Joomla is running a console job,
and in this case the \Joomla\Application\ApplicationEvents::BEFORE_EXECUTE event is always dispatched,
so for best performance don't use lazy plugins for console jobs.

[Ajax plugins](./plugin-examples/ajax-plugin.md) are imported when there is an Ajax request to com_ajax,
but if your plugin is likely to be handling only one of several com_ajax requests,
it makes sense to use lazy instantiation for your Ajax plugins.

If in doubt, you're probably best to use lazy plugins by default.

If your plugin is, for example, only appropriate to the administrator functionality,
then you can also check the application context in the static `getSubscribedEvents` function,
and register for events only if the context is 'administrator':

```php
public static function getSubscribedEvents(): array
{
if (\Joomla\CMS\Factory::getApplication()->isClient('administrator')) {
return [
'onModuleRender' => 'checkAdminModule',
];
} else {
return [];
}
}
```

This enhances the performance of your site,
because your plugin won't be instantiated for HTTP requests to your "site" application.

## Methods available to Plugins

Inside your plugin you have access to several methods.
Expand Down
Binary file not shown.
20 changes: 9 additions & 11 deletions docs/building-extensions/plugins/plugin-examples/ajax-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ A standard manifest file for a plugin:
```

### Service Provider file
A standard service provider file for instantiating a plugin via the dependency injection container:
A standard service provider file for instantiating a [lazy plugin](../methods-and-arguments.md#lazy-objects) via the dependency injection container:

```php title="plg_ajax_jobs/services/provider.php"
<?php
Expand All @@ -123,17 +123,15 @@ use My\Plugin\Ajax\AjaxJobs\Extension\Jobs;
{
$container->set(
PluginInterface::class,
function (Container $container) {

$config = (array) PluginHelper::getPlugin('ajax', 'jobs');
$subject = $container->get(DispatcherInterface::class);
$app = Factory::getApplication();
$container->lazy(Jobs::class,
function (Container $container) {

$plugin = new Jobs($subject, $config);
$plugin->setApplication($app);

return $plugin;
}
$config = (array) PluginHelper::getPlugin('ajax', 'jobs');
$plugin = new Jobs($config);

return $plugin;
}
)
);
}
};
Expand Down
20 changes: 19 additions & 1 deletion docs/general-concepts/dependency-injection/DIC.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,22 @@ You get things out of the container by calling `get()` passing the key of the re
- if the resource is shared, it will store the class instance, so that on subsequent invocations of `get()` it can just return the instance
- return the class instance to you

You can also define aliases for each key in the container, which means that you call `get()` passing either the key or an alias of the key.
You can also define aliases for each key in the container, which means that you call `get()` passing either the key or an alias of the key.

## Lazy Objects

Since version 8.4 PHP supports the concept of [lazy objects](https://www.php.net/manual/en/language.oop5.lazy-objects.php),
and you can store entries in the DIC to create lazy objects.

The container [`lazy()`](framework-api://classes/Joomla-DI-Container.html#method_lazy) function
provides a mechanism for storing in the DIC an entry which creates a PHP lazy proxy object.

You pass the name of your class and the initializer function to instantiate your class (as described in the PHP manual).

It returns the callable which you then `set` in the DIC.

This is particularly useful for plugins, because in handling a particular HTTP request many of them will not get triggered.
If they are implemented as lazy objects then this avoids the expense of instantiating them,
hence improving the site performance.

See [Instantiating your Plugin Class - Lazy Objects](../../building-extensions/plugins/methods-and-arguments.md#lazy-objects) for more details.