Completed
Push — master ( fadeb4...d2fa33 )
by Alexander
02:05
created

CompositeContextContainer::getContainers()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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