Passed
Push — github-actions ( e63dce )
by Alexander
03:10
created

CompositeContextContainer   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 130
Duplicated Lines 0 %

Test Coverage

Coverage 59.09%

Importance

Changes 0
Metric Value
eloc 39
dl 0
loc 130
ccs 26
cts 44
cp 0.5909
rs 10
c 0
b 0
f 0
wmc 23

8 Methods

Rating   Name   Duplication   Size   Complexity  
A hasInContext() 0 8 3
A getFromContext() 0 10 3
A has() 0 8 3
A attach() 0 11 2
A getContextContainer() 0 3 1
A getContainers() 0 7 3
A detach() 0 10 5
A get() 0 10 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Di;
6
7
use Psr\Container\ContainerInterface;
8
use Psr\Container\NotFoundExceptionInterface;
9
use Yiisoft\Factory\Exceptions\NotFoundException;
10
11
/**
12
 * This class implements a composite container with support for context switching.
13
 * The intended use is to allow for custom configurations of (nested) modules.
14
 */
15
final class CompositeContextContainer implements ContainerInterface
16
{
17
    /**
18
     * Containers to look into.
19
     * The first level of this array is sorted by the length of the key, from long to short.
20
     * Longer key means container is more specific and is checked first.
21
     * @var array The lists of containers indexed by contexts
22
     */
23
    private array $containers = [];
24
25
    private string $currentContext = '';
26
27 1
    public function get($id)
28
    {
29 1
        foreach ($this->getContainers($this->currentContext) as $container) {
30
            try {
31 1
                return $container->get($id);
32
            } catch (NotFoundExceptionInterface $e) {
33
                // ignore
34
            }
35
        }
36
        throw new NotFoundException("No definition for $id");
37
    }
38
39
    /**
40
     * @param string $context
41
     * @return ContainerInterface[] All containers in the context specified
42
     */
43 1
    private function getContainers(string $context): iterable
44
    {
45 1
        foreach ($this->containers as $prefix => $containers) {
46 1
            if (strncmp($prefix, $context, strlen($prefix)) !== 0) {
47 1
                continue;
48
            }
49 1
            yield from $containers;
0 ignored issues
show
Bug Best Practice introduced by
The expression YieldFromNode returns the type Generator which is incompatible with the documented return type Psr\Container\ContainerInterface[].
Loading history...
50
        }
51 1
    }
52
53
    public function has($id): bool
54
    {
55
        foreach ($this->getContainers($this->currentContext) as $container) {
56
            if ($container->has($id)) {
57
                return true;
58
            }
59
        }
60
        return false;
61
    }
62
63
    /**
64
     * Attaches a container to the composite container.
65
     * @param string $context The context for the new container.
66
     * @param ContainerInterface $container
67
     */
68 1
    public function attach(ContainerInterface $container, string $context = ''): void
69
    {
70 1
        if (isset($this->containers[$context])) {
71
            array_unshift($this->containers[$context], $container);
72
        } else {
73
            // If the context is new we reorder the containers array.
74 1
            $this->containers[$context] = [
75 1
                $container
76
            ];
77
            uksort($this->containers, static function ($a, $b) {
78 1
                return mb_strlen($b) <=> mb_strlen($a);
79 1
            });
80
        }
81 1
    }
82
83
    /**
84
     * Removes a container from the list of containers.
85
     * @param ContainerInterface $container
86
     */
87
    public function detach(ContainerInterface $container): void
88
    {
89
        foreach ($this->containers as $prefix => $containers) {
90
            foreach ($containers as $i => $c) {
91
                if ($container === $c) {
92
                    unset($this->containers[$prefix][$i]);
93
                }
94
            }
95
            if (empty($this->containers[$prefix])) {
96
                unset($this->containers[$prefix]);
97
            }
98
        }
99
    }
100
101
    /**
102
     * Gets a service from the container in the context.
103
     *
104
     * @param string $id Name of the service, not type-hinted to remain compatible with PSR-11 `get()`
105
     * @param string $context
106
     * @return mixed
107
     * @throws NotFoundException
108
     */
109 1
    public function getFromContext($id, string $context)
110
    {
111 1
        foreach ($this->getContainers($context) as $container) {
112
            try {
113 1
                return $container->get($id);
114
            } catch (NotFoundExceptionInterface $e) {
115
                // ignore
116
            }
117
        }
118
        throw new NotFoundException("No definition for $id");
119
    }
120
121
    /**
122
     * Checks if we have a definition for a service in the given context
123
     * @param string $id Name of the service, not type-hinted to remain compatible with PSR-11 `has()`
124
     * @param string $context The context to use
125
     * @return bool
126
     */
127 1
    public function hasInContext($id, string $context): bool
128
    {
129 1
        foreach ($this->getContainers($context) as $container) {
130 1
            if ($container->has($id)) {
131 1
                return true;
132
            }
133
        }
134 1
        return false;
135
    }
136
137
    /**
138
     * This will return a container that only resolves services from a specific context.
139
     * @param string $context
140
     * @return ContainerInterface
141
     */
142 1
    public function getContextContainer(string $context): ContainerInterface
143
    {
144 1
        return new ContextContainer($this, $context);
145
    }
146
}
147