Completed
Push — master ( cc1f27...1888d1 )
by Alexander
01:57
created

CompositeContextContainer::attach()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 2
dl 0
loc 11
ccs 5
cts 6
cp 0.8333
crap 2.0185
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 1
    public function get($id)
25
    {
26 1
        foreach ($this->getContainers($this->currentContext) as $container) {
27
            try {
28 1
                return $container->get($id);
29
            } catch (NotFoundExceptionInterface $e) {
30
                // ignore
31
            }
32
        }
33
        throw new NotFoundException("No definition for $id");
34
    }
35
36
    /**
37
     * @param string $context
38
     * @return ContainerInterface[] All containers in the context specified
39
     */
40 1
    private function getContainers(string $context): iterable
41
    {
42 1
        foreach ($this->containers as $prefix => $containers) {
43 1
            if (strncmp($prefix, $context, strlen($prefix)) !== 0) {
44 1
                continue;
45
            }
46 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...
47
        }
48
    }
49
50
    public function has($id)
51
    {
52
        foreach ($this->getContainers($this->currentContext) as $container) {
53
            if ($container->has($id)) {
54
                return true;
55
            }
56
        }
57
        return false;
58
    }
59
60
    /**
61
     * Attaches a container to the composite container.
62
     * @param string $context The context for the new container.
63
     * @param ContainerInterface $container
64
     */
65 1
    public function attach(ContainerInterface $container, string $context = ''): void
66
    {
67 1
        if (isset($this->containers[$context])) {
68
            array_unshift($this->containers[$context], $container);
69
        } else {
70
            // If the context is new we reorder the containers array.
71 1
            $this->containers[$context] = [
72 1
                $container
73
            ];
74
            uksort($this->containers, static function ($a, $b) {
75 1
                return mb_strlen($b) <=> mb_strlen($a);
76 1
            });
77
        }
78
    }
79
80
    /**
81
     * Removes a container from the list of containers.
82
     * @param ContainerInterface $container
83
     */
84
    public function detach(ContainerInterface $container): void
85
    {
86
        foreach ($this->containers as $prefix => $containers) {
87
            foreach ($containers as $i => $c) {
88
                if ($container === $c) {
89
                    unset($this->containers[$prefix][$i]);
90
                }
91
            }
92
            if (empty($this->containers[$prefix])) {
93
                unset($this->containers[$prefix]);
94
            }
95
        }
96
    }
97
98
    /**
99
     * Gets a service from the container in the context.
100
     *
101
     * @param string $id Name of the service, not typehinted to remain compatible with PSR-11 `get()`
102
     * @param string $context
103
     * @return mixed
104
     * @throws NotFoundException
105
     */
106 1
    public function getFromContext($id, string $context)
107
    {
108 1
        foreach ($this->getContainers($context) as $container) {
109
            try {
110 1
                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 1
    public function hasInContext($id, string $context): bool
125
    {
126 1
        foreach ($this->getContainers($context) as $container) {
127 1
            if ($container->has($id)) {
128 1
                return true;
129
            }
130
        }
131 1
        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 1
    public function getContextContainer(string $context): ContainerInterface
140
    {
141 1
        return new ContextContainer($this, $context);
142
    }
143
}
144