Completed
Push — master ( ecd3d2...180725 )
by Oscar
01:50
created

App::get()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.439
c 0
b 0
f 0
cc 6
eloc 15
nc 6
nop 1
1
<?php
2
3
namespace Fol;
4
5
use Fol\NotFoundException;
6
use Fol\ContainerException;
7
use Interop\Container\ContainerInterface;
8
use Interop\Container\ServiceProvider;
9
use Psr\Http\Message\UriInterface;
10
use Throwable;
11
12
/**
13
 * Manages an app.
14
 */
15
class App implements ContainerInterface
16
{
17
    private $containers = [];
18
    private $services = [];
19
    private $items = [];
20
    private $path;
21
    private $uri;
22
23
    /**
24
     * Constructor. Set the base path and uri
25
     *
26
     * @param string $path
27
     * @param UriInterface $uri
28
     */
29
    public function __construct(string $path, UriInterface $uri)
30
    {
31
        $this->path = rtrim($path, '/') ?: '/';
32
        $this->uri = $uri;
33
    }
34
35
    /**
36
     * Add new containers.
37
     *
38
     * @param ContainerInterface $container
39
     *
40
     * @return self
41
     */
42
    public function addContainer(ContainerInterface $container): self
43
    {
44
        $this->containers[] = $container;
45
46
        return $this;
47
    }
48
49
    /**
50
     * Add a new service provider.
51
     *
52
     * @param ServiceProvider $provider
53
     *
54
     * @return self
55
     */
56
    public function addServiceProvider(ServiceProvider $provider): self
57
    {
58
        foreach ($provider->getServices() as $id => $service) {
59
            $this->addService($id, $service);
60
        }
61
62
        return $this;
63
    }
64
65
    /**
66
     * Add a new service.
67
     *
68
     * @param string|int $id
69
     * @param callable   $service
70
     *
71
     * @return self
72
     */
73
    public function addService($id, callable $service): self
74
    {
75
        if (empty($this->services[$id])) {
76
            $this->services[$id] = [$service];
77
        } else {
78
            $this->services[$id][] = $service;
79
        }
80
81
        return $this;
82
    }
83
84
    /**
85
     * @see ContainerInterface
86
     *
87
     * {@inheritdoc}
88
     */
89
    public function has($id)
90
    {
91
        if (isset($this->items[$id])) {
92
            return true;
93
        }
94
95
        if (isset($this->services[$id])) {
96
            return true;
97
        }
98
99
        foreach ($this->containers as $container) {
100
            if ($container->has($id)) {
101
                return true;
102
            }
103
        }
104
105
        return false;
106
    }
107
108
    /**
109
     * @see ContainerInterface
110
     *
111
     * {@inheritdoc}
112
     */
113
    public function get($id)
114
    {
115
        if (isset($this->items[$id])) {
116
            return $this->items[$id];
117
        }
118
119
        if (isset($this->services[$id])) {
120
            $callback = array_reduce($this->services[$id], function ($callback, $service) use ($id) {
121
                return function () use ($callback, $service) {
122
                    return $service($this, $callback);
123
                };
124
            });
125
126
            try {
127
                return $this->items[$id] = $callback();
128
            } catch (Throwable $exception) {
129
                throw new ContainerException("Error retrieving {$id}: {$exception->getMessage()}");
130
            }
131
        }
132
133
        foreach ($this->containers as $container) {
134
            if ($container->has($id)) {
135
                return $container->get($id);
136
            }
137
        }
138
139
        throw new NotFoundException("Identifier {$id} is not defined");
140
    }
141
142
    /**
143
     * Set a variable.
144
     *
145
     * @param string|int $id
146
     * @param mixed  $value
147
     *
148
     * @return self
149
     */
150
    public function set($id, $value): self
151
    {
152
        $this->items[$id] = $value;
153
154
        return $this;
155
    }
156
157
    /**
158
     * Returns the absolute path of the app.
159
     *
160
     * @param string ...$dirs
161
     *
162
     * @return string
163
     */
164
    public function getPath(string ...$dirs): string
165
    {
166
        if (empty($dirs)) {
167
            return $this->path;
168
        }
169
170
        return self::canonicalize($this->path, $dirs);
171
    }
172
173
    /*
174
     * Returns the base uri of the app.
175
     *
176
     * @param string ...$dirs
177
     *
178
     * @return UriInterface
179
     */
180
    public function getUri(string ...$dirs): UriInterface
181
    {
182
        if (empty($dirs)) {
183
            return $this->uri;
184
        }
185
186
        return $this->uri->withPath(self::canonicalize($this->uri->getPath(), $dirs));
187
    }
188
189
    /**
190
     * helper function to fix paths '//' or '/./' or '/foo/../' in a path.
191
     *
192
     * @param string   $base
193
     * @param string[] $dirs
194
     *
195
     * @return string
196
     */
197
    private static function canonicalize(string $base, array $dirs): string
198
    {
199
        $path = $base.'/'.implode('/', $dirs);
200
        $replace = ['#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'];
201
202
        do {
203
            $path = preg_replace($replace, '/', $path, -1, $n);
204
        } while ($n > 0);
205
206
        return $path;
207
    }
208
}
209