ContainerInterfaceProxyTest   A
last analyzed

Complexity

Total Complexity 10

Size/Duplication

Total Lines 349
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 197
dl 0
loc 349
c 0
b 0
f 0
rs 10
wmc 10

31 Methods

Rating   Name   Duplication   Size   Complexity  
A hp$1 ➔ has() 0 3 1
A testGetAndHasWithCallableServices() 0 25 1
A testGetWithoutConfig() 0 20 1
A tearDown() 0 4 1
A testGetContainerItself() 0 9 1
A testGetAndHasWithNotService() 0 9 1
A testGetAndHasWithWrongId() 0 15 1
A hp$0 ➔ get() 0 3 1
A testImmutability() 0 9 1
A testGetWithArrayConfigWithStringKeys() 0 29 1
A testGetAndHas() 0 7 1
createServiceCollector() 0 3 ?
A hp$4 ➔ createServiceCollector() 0 3 1
A hp$3 ➔ get() 0 3 1
A hp$4 ➔ createConfig() 0 15 1
A hp$4 ➔ test1() 0 17 1
createConfig() 0 15 ?
A hp$4 ➔ createContainer() 0 12 1
testBrokenProxyConstructor() 0 16 ?
A hp$4 ➔ testProxyIsNotNeeded() 0 16 1
test1() 0 17 ?
A hp$4 ➔ has() 0 3 1
A hp$4 ➔ testBrokenProxyConstructor() 0 16 1
testHasThrowsExceptionAndErrorInCollectorIsNotEmpty() 0 40 ?
test2() 0 17 ?
A hp$4 ➔ test2() 0 17 1
createContainer() 0 12 ?
testProxyIsNotNeeded() 0 16 ?
A hp$4 ➔ testHasThrowsExceptionAndErrorInCollectorIsNotEmpty() 0 40 2
A hp$1 ➔ testHasThrowsExceptionButErrorInCollectorIsAbsent() 0 42 2
testHasThrowsExceptionButErrorInCollectorIsAbsent() 0 42 ?
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Debug\Tests\Unit\Collector;
6
7
use Exception;
8
use PHPUnit\Framework\TestCase;
9
use Psr\Container\ContainerExceptionInterface;
10
use Psr\Container\ContainerInterface;
11
use Psr\EventDispatcher\EventDispatcherInterface;
12
use Psr\EventDispatcher\ListenerProviderInterface;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\NullLogger;
15
use stdClass;
16
use Throwable;
17
use Yiisoft\Di\CompositeContainer;
18
use Yiisoft\Di\Container;
19
use Yiisoft\Di\ContainerConfig;
20
use Yiisoft\EventDispatcher\Dispatcher\Dispatcher;
21
use Yiisoft\EventDispatcher\Provider\Provider;
22
use Yiisoft\Files\FileHelper;
23
use Yiisoft\Yii\Debug\Collector\CollectorInterface;
24
use Yiisoft\Yii\Debug\Collector\ContainerInterfaceProxy;
25
use Yiisoft\Yii\Debug\Collector\ContainerProxyConfig;
26
use Yiisoft\Yii\Debug\Collector\EventCollector;
27
use Yiisoft\Yii\Debug\Collector\EventDispatcherInterfaceProxy;
28
use Yiisoft\Yii\Debug\Collector\LogCollector;
29
use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy;
30
use Yiisoft\Yii\Debug\Collector\ServiceCollector;
31
use Yiisoft\Yii\Debug\Collector\TimelineCollector;
32
use Yiisoft\Yii\Debug\Tests\Support\Stub\BrokenProxyImplementation;
33
use Yiisoft\Yii\Debug\Tests\Support\Stub\Implementation1;
34
use Yiisoft\Yii\Debug\Tests\Support\Stub\Implementation2;
35
use Yiisoft\Yii\Debug\Tests\Support\Stub\Interface1;
36
use Yiisoft\Yii\Debug\Tests\Support\Stub\Interface2;
37
38
final class ContainerInterfaceProxyTest extends TestCase
39
{
40
    private string $path = 'tests/container-proxy';
41
42
    protected function tearDown(): void
43
    {
44
        parent::tearDown();
45
        FileHelper::removeDirectory($this->path);
46
    }
47
48
    public function testImmutability(): void
49
    {
50
        $containerProxy = new ContainerInterfaceProxy(new Container(ContainerConfig::create()), new ContainerProxyConfig());
51
52
        $this->assertNotSame(
53
            $containerProxy,
54
            $containerProxy->withDecoratedServices(
55
                [
56
                    LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class],
57
                ]
58
            )
59
        );
60
    }
61
62
    public function testGetAndHas(): void
63
    {
64
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $this->createConfig());
65
66
        $this->assertTrue($containerProxy->isActive());
67
        $this->assertTrue($containerProxy->has(LoggerInterface::class));
68
        $this->assertInstanceOf(LoggerInterfaceProxy::class, $containerProxy->get(LoggerInterface::class));
69
    }
70
71
    public function testGetAndHasWithCallableServices(): void
72
    {
73
        $dispatcherMock = $this->createMock(EventDispatcherInterface::class);
74
        $config = new ContainerProxyConfig(
75
            true,
76
            [
77
                LoggerInterface::class => fn (ContainerInterface $container) => $container->get(LoggerInterfaceProxy::class),
78
                EventDispatcherInterface::class => [
79
                    EventDispatcherInterfaceProxy::class,
80
                    EventCollector::class,
81
                ],
82
            ],
83
            $dispatcherMock,
84
            $this->createServiceCollector(),
85
            $this->path,
86
            ContainerInterfaceProxy::LOG_ARGUMENTS
87
        );
88
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $config);
89
90
        $this->assertTrue($containerProxy->isActive());
91
        $this->assertTrue($containerProxy->has(LoggerInterface::class));
92
93
        $containerProxy->get(LogCollector::class)->startup();
94
        $containerProxy->get(LoggerInterface::class)->log('test', 'test message');
95
        $this->assertNotEmpty($containerProxy->get(LogCollector::class)->getCollected());
96
    }
97
98
    public function testGetWithArrayConfigWithStringKeys(): void
99
    {
100
        $dispatcherMock = $this->createMock(EventDispatcherInterface::class);
101
        $serviceCollector = $this->createServiceCollector();
102
        $serviceCollector->startup(); // activate collector
103
104
        $config = new ContainerProxyConfig(
105
            true,
106
            [
107
                LoggerInterface::class => ['logger' => LoggerInterfaceProxy::class],
108
                EventDispatcherInterface::class => [
109
                    EventDispatcherInterfaceProxy::class,
110
                    EventCollector::class,
111
                ],
112
            ],
113
            $dispatcherMock,
114
            $serviceCollector,
115
            $this->path,
116
            ContainerInterfaceProxy::LOG_ARGUMENTS
117
        );
118
        $container = $this->createContainer();
119
        $containerProxy = new ContainerInterfaceProxy($container, $config);
120
121
        $this->assertTrue($containerProxy->isActive());
122
        $this->assertInstanceOf(LoggerInterface::class, $containerProxy->get(LoggerInterface::class));
123
124
        $containerProxy->get(LoggerInterface::class)->log('test', 'test message');
125
        $this->assertInstanceOf(LoggerInterface::class, $containerProxy->get(LoggerInterface::class));
126
        $this->assertNotEmpty($config->getCollector()->getCollected());
127
    }
128
129
    public function testGetWithoutConfig(): void
130
    {
131
        $dispatcherMock = $this->createMock(EventDispatcherInterface::class);
132
        $config = new ContainerProxyConfig(
133
            true,
134
            [
135
                LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class],
136
                EventDispatcherInterface::class,
137
            ],
138
            $dispatcherMock,
139
            $this->createServiceCollector(),
140
            $this->path,
141
            ContainerInterfaceProxy::LOG_ARGUMENTS
142
        );
143
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $config);
144
145
        $this->assertInstanceOf(EventDispatcherInterface::class, $containerProxy->get(EventDispatcherInterface::class));
146
        $this->assertInstanceOf(
147
            stdClass::class,
148
            $containerProxy->get(EventDispatcherInterface::class)->dispatch(new stdClass())
149
        );
150
    }
151
152
    public function testGetAndHasWithWrongId(): void
153
    {
154
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $this->createConfig());
155
156
        $this->assertFalse($containerProxy->has(CollectorInterface::class));
157
158
        $this->expectException(ContainerExceptionInterface::class);
159
        $this->expectExceptionMessage(
160
            sprintf(
161
                'No definition or class found or resolvable for "%s" while building "%s".',
162
                CollectorInterface::class,
163
                CollectorInterface::class
164
            )
165
        );
166
        $containerProxy->get(CollectorInterface::class);
167
    }
168
169
    public function testGetContainerItself(): void
170
    {
171
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $this->createConfig());
172
173
        $this->assertTrue($containerProxy->has(ContainerInterface::class));
174
175
        $container = $containerProxy->get(ContainerInterface::class);
176
        $this->assertNotNull($container);
177
        $this->assertInstanceOf(ContainerInterface::class, $container);
178
    }
179
180
    public function testGetAndHasWithNotService(): void
181
    {
182
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $this->createConfig());
183
184
        $this->assertTrue($containerProxy->has(ListenerProviderInterface::class));
185
        $this->assertNotNull($containerProxy->get(ListenerProviderInterface::class));
186
        $this->assertInstanceOf(
187
            ListenerProviderInterface::class,
188
            $containerProxy->get(ListenerProviderInterface::class)
189
        );
190
    }
191
192
    public function testHasThrowsExceptionButErrorInCollectorIsAbsent(): void
193
    {
194
        $container = new CompositeContainer();
195
        $container->attach(
196
            container: new class () implements ContainerInterface {
197
                public function get($id)
198
                {
199
                    throw new class () extends Exception implements ContainerExceptionInterface {
200
                    };
201
                }
202
203
                public function has($id): bool
204
                {
205
                    throw new class () extends Exception implements ContainerExceptionInterface {
206
                    };
207
                }
208
            }
209
        );
210
        $container->attach($container);
211
212
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_NOTHING);
213
        $serviceCollector = $config->getCollector();
214
        $serviceCollector->startup();
215
        $containerProxy = new ContainerInterfaceProxy($container, $config);
216
217
        $thrown = null;
218
        try {
219
            $containerProxy->has('123');
220
        } catch (Throwable $e) {
221
            $thrown = $e;
222
        }
223
224
        $this->assertNotNull($thrown);
225
        $this->assertNotNull($containerProxy->getCurrentError());
226
227
        $data = $serviceCollector->getCollected();
228
        $this->assertCount(1, $data);
229
        $this->assertSame(ContainerInterface::class, $data[0]['service']);
230
        $this->assertSame(CompositeContainer::class, $data[0]['class']);
231
        $this->assertSame('has', $data[0]['method']);
232
        $this->assertSame('failed', $data[0]['status']);
233
        $this->assertNull($data[0]['error']);
234
    }
235
236
    public function testHasThrowsExceptionAndErrorInCollectorIsNotEmpty(): void
237
    {
238
        $container = new CompositeContainer();
239
        $container->attach(new class () implements ContainerInterface {
240
            public function get($id)
241
            {
242
                throw new class () extends Exception implements ContainerExceptionInterface {
243
                };
244
            }
245
246
            public function has($id): bool
247
            {
248
                throw new class () extends Exception implements ContainerExceptionInterface {
249
                };
250
            }
251
        });
252
        $container->attach($container);
253
254
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_ERROR);
255
        $serviceCollector = $config->getCollector();
256
        $serviceCollector->startup();
257
        $containerProxy = new ContainerInterfaceProxy($container, $config);
258
259
        $thrown = null;
260
        try {
261
            $containerProxy->has('123');
262
        } catch (Throwable $e) {
263
            $thrown = $e;
264
        }
265
266
        $this->assertNotNull($thrown);
267
        $this->assertNotNull($containerProxy->getCurrentError());
268
269
        $data = $serviceCollector->getCollected();
270
        $this->assertCount(1, $data);
271
        $this->assertSame(ContainerInterface::class, $data[0]['service']);
272
        $this->assertSame(CompositeContainer::class, $data[0]['class']);
273
        $this->assertSame('has', $data[0]['method']);
274
        $this->assertSame('failed', $data[0]['status']);
275
        $this->assertNotNull($data[0]['error']);
276
    }
277
278
    public function testProxyIsNotNeeded(): void
279
    {
280
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_ERROR);
281
        $config = $config->withDecoratedServices([
282
            Implementation1::class => Implementation1::class,
283
        ]);
284
        $serviceCollector = $config->getCollector();
285
        $serviceCollector->startup();
286
        $container = $this->createContainer([
287
            Implementation1::class => Implementation1::class,
288
        ]);
289
        $containerProxy = new ContainerInterfaceProxy($container, $config);
290
291
        $implementation = $containerProxy->get(Implementation1::class);
292
        $this->assertNotNull($implementation);
293
        $this->assertInstanceOf(Implementation1::class, $implementation);
294
    }
295
296
    public function testBrokenProxyConstructor(): void
297
    {
298
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_ERROR);
299
        $config = $config->withDecoratedServices([
300
            Interface1::class => [BrokenProxyImplementation::class, stdClass::class],
301
        ]);
302
        $serviceCollector = $config->getCollector();
303
        $serviceCollector->startup();
304
        $container = $this->createContainer([
305
            Interface1::class => Implementation1::class,
306
        ]);
307
        $containerProxy = new ContainerInterfaceProxy($container, $config);
308
309
        $implementation = $containerProxy->get(Interface1::class);
310
        $this->assertNotNull($implementation);
311
        $this->assertInstanceOf(Implementation1::class, $implementation);
312
    }
313
314
    public function test1(): void
315
    {
316
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_ERROR);
317
        $config = $config->withDecoratedServices([
318
            Interface2::class => ['getName' => fn() => 'from tests'],
319
        ]);
320
        $serviceCollector = $config->getCollector();
321
        $serviceCollector->startup();
322
        $container = $this->createContainer([
323
            Interface2::class => Implementation2::class,
324
        ]);
325
        $containerProxy = new ContainerInterfaceProxy($container, $config);
326
327
        $implementation = $containerProxy->get(Interface2::class);
328
        $this->assertNotNull($implementation);
329
        $this->assertInstanceOf(Interface2::class, $implementation);
330
        $this->assertSame('from tests', $implementation->getName());
331
    }
332
333
    public function test2(): void
334
    {
335
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_ERROR);
336
        $config = $config->withDecoratedServices([
337
            'test-interface' => ['getName' => fn() => 'from tests'],
338
        ]);
339
        $serviceCollector = $config->getCollector();
340
        $serviceCollector->startup();
341
        $container = $this->createContainer([
342
            'test-interface' => Implementation2::class,
343
        ]);
344
        $containerProxy = new ContainerInterfaceProxy($container, $config);
345
346
        $implementation = $containerProxy->get('test-interface');
347
        $this->assertNotNull($implementation);
348
        $this->assertInstanceOf(Interface2::class, $implementation);
349
        $this->assertSame('from tests', $implementation->getName());
350
    }
351
352
    private function createConfig(int $logLevel = ContainerInterfaceProxy::LOG_ARGUMENTS): ContainerProxyConfig
353
    {
354
        return new ContainerProxyConfig(
355
            true,
356
            [
357
                LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class],
358
                EventDispatcherInterface::class => [
359
                    EventDispatcherInterfaceProxy::class,
360
                    EventCollector::class,
361
                ],
362
            ],
363
            $this->createMock(EventDispatcherInterface::class),
364
            $this->createServiceCollector(),
365
            $this->path,
366
            $logLevel
367
        );
368
    }
369
370
    private function createContainer(array $definitions = []): Container
371
    {
372
        $config = ContainerConfig::create()
373
            ->withDefinitions([
374
                EventDispatcherInterface::class => Dispatcher::class,
375
                ListenerProviderInterface::class => Provider::class,
376
                LoggerInterface::class => NullLogger::class,
377
                LogCollector::class => LogCollector::class,
378
                EventCollector::class => EventCollector::class,
379
                ...$definitions,
380
            ]);
381
        return new Container($config);
382
    }
383
384
    protected function createServiceCollector(): ServiceCollector
385
    {
386
        return new ServiceCollector(new TimelineCollector());
387
    }
388
}
389