Skip to content

Commit 8755505

Browse files
author
hleithner
committed
Merge remote-tracking branch 'origin/main' into 5.0
# Conflicts: # migrations/44-50/removed-backward-incompatibility.md
2 parents 4203f57 + 64543db commit 8755505

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+3228
-1584
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"githubPullRequests.ignoredPullRequestBranches": [
3+
"main"
4+
]
5+
}

docs/general-concept/dependencies.md

Lines changed: 0 additions & 182 deletions
This file was deleted.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
title: The Dependency Injection Container
3+
sidebar_position: 2
4+
---
5+
# The Dependency Injection Container
6+
The Joomla Dependency Injection Container, DIC for short, is basically a repository of key-value pairs where:
7+
- the key is a string which is (usually) the fully qualified name of a class or interface
8+
- the value is an instance of the relevant class, or a function that returns an instance of that class.
9+
10+
It doesn't *really* matter what the key is set to - as long as it's intelligible and you use the same key for putting entries in and taking entries out. Of course, it has to be unique.
11+
12+
![DIC](_assets/dic.jpg "DIC")
13+
14+
You put things into the container using `set()` passing:
15+
- the key = the class name or interface name
16+
- the value = a function which returns an instance of the class (or the value can be just an instance of the class, without the enveloping function)
17+
- shared - a boolean defining whether the class instance may be shared or not (ie if there is a second request to the DIC to supply that instance, does it return the same instance or a new one)
18+
- protected - a boolean defining whether this entry into the DI container is protected or not (an error will be raised if you try to overwrite a protected entry by calling `set()` again using the same key).
19+
20+
The function `share()` is basically the same as `set()` with the shared boolean set to true.
21+
22+
You get things out of the container by calling `get()` passing the key of the resource you want. The DI functionality will
23+
- find the key in the container
24+
- if the value isn't already a class instance, then it will run the associated function to generate an instance of the class
25+
- if the resource is shared, it will store the class instance, so that on subsequent invocations of `get()` it can just return the instance
26+
- return the class instance to you
27+
28+
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.
33.8 KB
Loading
7.61 KB
Loading
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
title: Basic Concept
3+
sidebar_position: 1
4+
---
5+
# Basic Concept
6+
The basic idea behind Joomla dependency injection is that developers should no longer get access to key Joomla class instances by calling
7+
```php
8+
$obj = new JoomlaClass();
9+
or
10+
$obj = JoomlaClass::getInstance();
11+
```
12+
Instead they should make a request to the Dependency Injection Container (DIC):
13+
```php
14+
$obj = $container( – please give me an instance of JoomlaClass – );
15+
```
16+
or get an instance via the Application class or a Factory class which has been obtained from the DIC.
17+
18+
The main reason for this is to enable better testing by making it easier to mock classes. If the code has multiple calls to `JoomlaClass::getInstance()` then it's hard to mock that class for testing.
19+
20+
If instead the code always uses the DIC to get an instance of the class, then to mock it all we have to do is put the mocked class into the DIC instead of the real one.
21+
To make it less obvious that a specific class is being requested we can ask instead for a class which meets a particular interface:
22+
```php
23+
$obj = $container( – please give me an instance of a class that implements JoomlaInterface – );
24+
```
25+
The DIC will then return either the real class or the mocked class, depending upon how it has been configured. However, using a class name versus an interface name has no bearing on the functionality; as we shall see, the key used for putting things into and getting things out of the DIC is just a string, so we just have to ensure that we use the same key string for both operations.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
title: Dependency Injection Issues
3+
sidebar_position: 7
4+
---
5+
# Dependency Injection Issues
6+
Dependency Injection was introduced in Joomla 4, with the intention of removing direct access to most key classes via `getInstance()` calls, including via `Factory::` calls, and many of these API calls are deprecated in Joomla 4. Note that not all `getInstance()` calls are deprecated; examples which are not deprecated include `Uri::getInstance()` and `Filter\InputFilter::getInstance()`.
7+
8+
However, some issues do remain, which you should be aware of. Early versions of Joomla 5 still have a lot of these `getInstance` calls!
9+
10+
## Toolbar::getInstance()
11+
The `Toolbar` class is used for managing the buttons on the forms within the Joomla administrator back-end. The [Toolbar API](https://api.joomla.org/cms-4/classes/Joomla-CMS-Toolbar-Toolbar.html) documentation seems to suggest that we should replace
12+
```php
13+
$bar = Toolbar::getInstance('toolbar');
14+
```
15+
with
16+
```php
17+
$bar = Factory::getContainer()->get(ToolbarFactoryInterface::class)->createToolbar('toolbar');
18+
```
19+
However, this currently won't work. The problem is that all the other Joomla code uses `getInstance('toolbar')` to access the Toolbar class, including the code in administrator/modules/mod_toolbar/mod_toolbar.php which renders the toolbar. The `getInstance` method keeps track of the Toolbar instances in a local static variable `$instances`, and returns the same Toolbar instance on repeated invocations.
20+
21+
So if you use the second approach above you will get a Toolbar instance, but it won't be the same Toolbar instance as is stored in the `$instances` variable. It won't then be picked up by the Toolbar module, and so won't be rendered on the form.
22+
23+
## Table::getInstance()
24+
Usually a Table instance is created by the MVCFactory class by calling `getTable()` in your Model. However, there are other cases where you may want one:
25+
- in your Table class code you may want another instance of your own component table to verify if an `alias` field entered by a user already exists
26+
- you may wish to access the Table class of another component - eg of com_categories.
27+
28+
Neither of these is possible via your component's MVCFactory class as
29+
- it creates Table classes for your component alone, and,
30+
- in your Table code by default you've no pointer back to MVCFactory class instance
31+
32+
However, you can obtain instances by a more convoluted route, eg for com_content
33+
```php
34+
Factory::getApplication()->bootComponent('content')->getMVCFactory()->createTable($name, $prefix, $config);
35+
```
36+
You can also use this method to boot your own component, to get another instance of your own Table.
37+
38+
## Categories::getInstance()
39+
If you use Categories in your component then you may want to use the Categories API, for example in a custom router, or in a site CategoryModel. The old method of calling
40+
```php
41+
$categories = Categories::getInstance(...);
42+
```
43+
is deprecated.
44+
45+
You can get a CategoryFactory from your child DIC when you instantiate your Extension, but unfortunately it's not passed down via the MVCFactory class to your Model. One possible solution is to store the CategoryFactory reference as a static variable of your Extension class, as described in [Implementing Categories in your Component](../../using-core-functions/categories/implementing-categories-in-components.md).
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
title: Extensions and Child Containers
3+
sidebar_position: 4
4+
---
5+
# Extensions and Child Containers
6+
Whenever Joomla boots an extension it creates a child DIC, solely for use of that extension. It's shown diagrammatically below.
7+
8+
![Child DIC](_assets/child-dic.jpg "Child DIC")
9+
10+
The child DIC has a `parent` pointer to the main DIC, and acts similarly to the main DIC, but not exactly:
11+
- Whenever `set()` is called on this child DIC the key-value pair is inserted into the child container.
12+
- Whenever `get()` is called on it, the resource is obtained from the child container, but if it's not found there then the parent container is searched also.
13+
14+
From Joomla version 4 the Joomla developers are requesting that extension developers use dependency injection for their extensions by defining a services/provider.php file. The loading of an extension is then a two-step process, handled within the services/provider.php file::
15+
1. The extension class is entered into child DIC
16+
2. The extension class is retrieved from the child DIC, creating an instance of the class
17+
18+
Let's look at a minimal example of this for com_example, with namespace Mycompany\Component\Example.
19+
```php
20+
use Joomla\CMS\Extension\ComponentInterface;
21+
use Joomla\DI\Container;
22+
use Joomla\DI\ServiceProviderInterface;
23+
use Mycompany\Component\Example\Administrator\Extension\ExampleComponent;
24+
25+
return new class implements ServiceProviderInterface {
26+
public function register(Container $container): void
27+
{
28+
$container->set(
29+
ComponentInterface::class,
30+
function (Container $container) {
31+
$component = new ExampleComponent();
32+
return $component;
33+
}
34+
);
35+
}
36+
};
37+
```
38+
We can see that when Joomla does a `require` of this php file, then it will return a class instance:
39+
```php
40+
$provider = require $path; // $path points to the relevant services/provider.php file
41+
```
42+
The variable `$provider` then points to an object which is an instance of this anonymous class. Also the class implements `Joomla\DI\ServiceProviderInterface`, which basically means that it has a method called `register` with the above signature.
43+
44+
When next Joomla does
45+
```php
46+
if ($provider instanceof ServiceProviderInterface) {
47+
$provider->register($container);
48+
```
49+
the `register` function will be called, which will put into the child DIC an entry with
50+
- key = ComponentInterface::class – this is just a PHP shorthand way of specifying the string 'Joomla\CMS\Extension\ComponentInterface'
51+
- value = a function which returns a new instance of ExampleComponent, which is the [Extension class](../extension-and-dispatcher/extension-component.md) of `com_example`
52+
53+
Then when Joomla executes the code:
54+
```php
55+
$extension = $container->get($type);
56+
```
57+
the function above will run and will return a new instance of `ExampleComponent`.
58+
59+
You can follow through the Joomla code yourself in libraries/src/Extension/ExtensionManagerTrait.php, and confirm that the above pattern also applies to modules and plugins.
60+
61+
Notice also the following line in that file:
62+
```php
63+
if ($extension instanceof BootableExtensionInterface) {
64+
$extension->boot($container);
65+
}
66+
```
67+
So if the Extension class implements `BootableExtensionInterface` then Joomla will immediately call the `boot` function of the Extension instance, as described in the [Extension documentation](../extension-and-dispatcher/extension-component.md).
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Dependency Injection
3+
sidebar_position: 3
4+
---
5+
Dependency Injection was introduced in Joomla 4 to try to enable better testing of the Joomla code. If you're not familiar with the concepts of dependency injection then you may find the following videos useful: [Dependency Injection in PHP](https://www.youtube.com/watch?v=igx3bIl1T_c) and [DI Container](https://www.youtube.com/watch?v=78Vpg97rQwE). The videos are not related to Joomla, but do present good background information.
6+
7+
There are also a couple of Joomla-related videos, covering [Joomla 4 Dependency Injection](https://youtu.be/Y2oe73C1Q54) and [services/provider.php](https://youtu.be/n2fdf2szI_A).

0 commit comments

Comments
 (0)