Passed
Push — dependabot/composer/phpunit/ph... ( 535ce1 )
by
unknown
08:21
created

CompositeContextContainer::attach()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0116

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 2
dl 0
loc 11
ccs 6
cts 7
cp 0.8571
crap 2.0116
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Yiisoft\Di;
4
5
use Psr\Container\ContainerInterface;
6
use Psr\Container\NotFoundExceptionInterface;
7
use Yiisoft\Factory\Exceptions\NotFoundException;
8
9
/**
10
 * This class implements a composite container with support for context switching.
11
 * The intended use is to allow for custom configurations of (nested) modules.
12
 */
13
class CompositeContextContainer implements ContainerInterface
14
{
15
    /**
16
     * Containers to look into.
17
     * The first level of this array is sorted by the length of the key, from long to short.
18
     * Longer key means container is more specific and is checked first.
19
     * @var array The lists of containers indexed by contexts
20
     */
21
    private $containers = [];
22
23
    private $currentContext = '';
24
25 1
    public function get($id)
26
    {
27 1
        foreach ($this->getContainers($this->currentContext) as $container) {
28
            try {
29 1
                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 1
    private function getContainers(string $context): iterable
42
    {
43 1
        foreach ($this->containers as $prefix => $containers) {
44 1
            if (strncmp($prefix, $context, strlen($prefix)) !== 0) {
45 1
                continue;
46
            }
47 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...
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 1
    public function attach(ContainerInterface $container, string $context = ''): void
67
    {
68 1
        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 1
            $this->containers[$context] = [
73 1
                $container
74
            ];
75 1
            uksort($this->containers, static function ($a, $b) {
76 1
                return mb_strlen($b) <=> mb_strlen($a);
77 1
            });
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
     * @return mixed
105
     * @throws NotFoundException
106
     */
107 1
    public function getFromContext($id, string $context)
108
    {
109 1
        foreach ($this->getContainers($context) as $container) {
110
            try {
111 1
                return $container->get($id);
112
            } catch (NotFoundExceptionInterface $e) {
113
                // ignore
114
            }
115
        }
116
        throw new NotFoundException("No definition for $id");
117
    }
118
119
    /**
120
     * Checks if we have a definition for a service in the given context
121
     * @param string $id Name of the service, not typehinted to remain compatible with PSR-11 `has()`
122
     * @param string $context The context to use
123
     * @return bool
124
     */
125 1
    public function hasInContext($id, string $context): bool
126
    {
127 1
        foreach ($this->getContainers($context) as $container) {
128 1
            if ($container->has($id)) {
129 1
                return true;
130
            }
131
        }
132 1
        return false;
133
    }
134
135
    /**
136
     * This will return a container that only resolves services from a specific context.
137
     * @param string $context
138
     * @return ContainerInterface
139
     */
140 1
    public function getContextContainer(string $context): ContainerInterface
141
    {
142 1
        return new ContextContainer($this, $context);
143
    }
144
}
145