Test Failed
Push — master ( 91a335...e8c3d6 )
by Dmitriy
10:59
created

SymfonyContainerWrapper::creatDefinition()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 38
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 31
c 1
b 0
f 0
dl 0
loc 38
rs 8.4906
cc 7
nc 6
nop 2
1
<?php
2
declare(strict_types=1);
3
4
namespace App;
5
6
use Opis\Closure\SerializableClosure;
7
use Psr\Container\ContainerInterface;
8
use ReflectionObject;
9
use RuntimeException;
10
use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
11
use Symfony\Component\Config\ConfigCache;
12
use Symfony\Component\Config\FileLocator;
13
use Symfony\Component\DependencyInjection\ContainerBuilder;
14
use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface;
15
use Symfony\Component\DependencyInjection\Definition;
16
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
17
use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator;
18
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
19
use Symfony\Component\DependencyInjection\Reference;
20
use Symfony\Component\ExpressionLanguage\Expression;
21
use Throwable;
22
use Yiisoft\Config\Config;
23
use Yiisoft\Di\Container;
24
use Yiisoft\Di\Contracts\ServiceProviderInterface;
25
use Yiisoft\Factory\Definition\ArrayDefinition;
26
use Yiisoft\Factory\Definition\DefinitionInterface;
27
use Yiisoft\Factory\Definition\DynamicReference;
28
29
class SymfonyContainerWrapper
30
{
31
    public static $staticArguments = [];
32
    private ContainerInterface $container;
33
34
    public function __construct(ContainerInterface $container)
35
    {
36
        $this->container = $container;
37
    }
38
39
    public function wrap(array $yiiDefinitions, array $yiiProviders): ContainerInterface
40
    {
41
        $instanceof = [];
42
        $containerBuilder = new MyContainerBuilder($this->container);
43
//        $containerBuilder->merge();
44
        $containerBuilder->setProxyInstantiator(new CallableInitiator(new RuntimeInstantiator()));
45
        $containerBuilder->addExpressionLanguageProvider(new CallableExpressionProvider());
46
        $containerBuilder->addExpressionLanguageProvider(new ObjectExpressionProvider());
47
48
        $loader = new PhpFileLoader($containerBuilder, new FileLocator(__DIR__));
49
50
        $serviceConfigurator = new ServicesConfigurator($containerBuilder, $loader, $instanceof);
51
        $serviceConfigurator
52
            ->defaults()
53
            ->public(true)
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Component\Depend...sConfigurator::public() has too many arguments starting with true. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

53
            ->/** @scrutinizer ignore-call */ public(true)

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
54
            ->autowire(true)
55
            ->autoconfigure(true);
56
57
58
        $yiiDefinitions2 = [
0 ignored issues
show
Unused Code introduced by
The assignment to $yiiDefinitions2 is dead and can be removed.
Loading history...
59
            Stub::class => [
60
                'class' => Stub::class,
61
                '__construct()' => [
62
                    12345,
63
//                    \Yiisoft\Factory\Definition\Reference::to(Stub2::class),
64
                    new Stub2(5555555),
65
                    DynamicReference::to([
66
                        'class' => Stub2::class,
67
                        '__construct()' => [
68
                            12345,
69
                        ],
70
                    ]),
71
                    new \Yiisoft\Factory\Definition\CallableDefinition(fn() => 123),
72
                ],
73
            ],
74
            Stub1::class => new Stub1(),
75
            Stub2::class => [
76
                'class' => Stub2::class,
77
                '__construct()' => [
78
                    12345,
79
                ],
80
            ],
81
        ];
82
        $this->loadThirdPartyServices($serviceConfigurator);
83
        $this->ignoreNonServices($serviceConfigurator);
84
85
86
       $proxy = $this->wrapInternal($containerBuilder, $yiiDefinitions, $yiiProviders);
87
88
        $isDebug = false;
89
        $file = __DIR__ .'/../cache/container.php';
90
//        dd($file);
91
        $containerConfigCache = new ConfigCache($file, $isDebug);
92
93
        if (!$containerConfigCache->isFresh()) {
94
            $dumper = new PhpDumper($containerBuilder);
95
            $containerConfigCache->write(
96
                $dumper->dump(['class' => 'CachedContainer']),
0 ignored issues
show
Bug introduced by
It seems like $dumper->dump(array('cla... => 'CachedContainer')) can also be of type array; however, parameter $content of Symfony\Component\Config...kerConfigCache::write() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
                /** @scrutinizer ignore-type */ $dumper->dump(['class' => 'CachedContainer']),
Loading history...
97
                $containerBuilder->getResources()
98
            );
99
        }
100
        $proxy->injectServices();
101
        foreach (self::$staticArguments as $id => $service) {
102
            $containerBuilder->set($id, $service);
103
//            dd($argument,$class);
104
        }
105
        return $containerBuilder;
106
    }
107
108
    private function wrapInternal(ContainerBuilder $containerBuilder, array $yiiDefinitions, array $yiiProviders)
109
    {
110
//        $containerBuilder->set(Stub1::class, new Stub1());
111
112
//        $v = $containerBuilder->get(Stub1::class);
113
//        dd((class_implements($v)));
114
115
        foreach ($yiiDefinitions as $class => $yiiDefinition) {
116
            if ($class === 'Yiisoft\\Cache\\File\\FileCache') {
117
                $var = true;
118
            }
119
            if ($class === 'Yiisoft\\DataResponse\\DataResponseFactoryInterface') {
120
                $var = true;
121
            }
122
            if ($class === 'App\\Blog\\PostRepository') {
123
                $var = true;
124
            }
125
            if ($class === 'App\\Blog\\BlogService') {
126
                $var = true;
127
            }
128
            $definition = $this->creatDefinition($class, $yiiDefinition);
129
            if ($definition instanceof Definition) {
130
                $containerBuilder->setDefinition($class, $definition);
131
            } elseif ($definition instanceof Reference) {
132
                $containerBuilder->set($class, $definition);
133
            } else {
134
                $containerBuilder->setDefinition($class, $definition);
135
                $containerBuilder->set($class, $definition);
136
            }
137
        }
138
        $proxy = new ContainerConfigProxy($containerBuilder);
139
        foreach ($yiiProviders as $yiiProvider) {
140
            /* @var ServiceProviderInterface $provider */
141
            $provider = new $yiiProvider;
142
            $provider->register($proxy);
143
        }
144
//        var_dump($symfonyDefenitions);
145
        $containerBuilder->compile();
146
//        dd($containerBuilder->getDefinitions());
147
//        $loader = new PhpFileLoader($containerBuilder, new FileLocator(__DIR__ . '/../config'));
148
//        $loader->load('services.php');
149
//        $s = $containerBuilder->get(RouteCollectionInterface::class);
150
//        $s = $containerBuilder->get(Stub1::class);
151
//        dd($s);
152
153
        return $proxy;
154
    }
155
156
    private function creatDefinition(string $class, $yiiDefinition)
157
    {
158
        $definition = new Definition($class);
159
        if (is_array($yiiDefinition)) {
160
            if (isset($yiiDefinition['definition'])) {
161
                $definition = new CallableDefinition();
162
                $definition->setLazy(true);
163
                $definition->setClosure($yiiDefinition['definition']);
164
                return $definition;
165
            }
166
            $arguments = $yiiDefinition['__construct()'] ?? [];
167
            $arguments = $this->processArguments($arguments);
168
            $class = $yiiDefinition['class'] ?? $class;
169
170
            $definition->setClass($class);
171
            $definition->setArguments($arguments);
172
        } else if (is_callable($yiiDefinition)) {
173
            $definition = new CallableDefinition();
174
            $definition->setLazy(true);
175
            $definition->setClosure($yiiDefinition);
176
            return $definition;
177
        } elseif (is_object($yiiDefinition)) {
178
            $definition = new InlineDefinition();
179
            $definition->setClass($class);
180
            $definition->setLazy(true);
181
            $definition->setObject($yiiDefinition);
182
            return $definition;
183
        } else if (is_string($yiiDefinition) && class_exists($yiiDefinition)) {
184
            $definition->setClass($yiiDefinition);
185
            return $definition;
186
187
            return $definition1;
0 ignored issues
show
Unused Code introduced by
return $definition1 is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
188
        }
189
        $definition->setPublic(true);
190
//        $definition->setShared(true);
191
        $definition->setAutowired(true);
192
        $definition->setAutoconfigured(true);
193
        return $definition;
194
    }
195
196
    private function processArguments(array $arguments)
197
    {
198
        $result = [];
199
        foreach ($arguments as $key => $argument) {
200
            if ($key === 'App\\Blog\\PostRepository') {
201
                $var = true;
0 ignored issues
show
Unused Code introduced by
The assignment to $var is dead and can be removed.
Loading history...
202
            }
203
            if (!is_numeric($key)) {
204
                $result['$' . $key] = $this->processArgument($argument);
205
            } else {
206
                $result[] = $this->processArgument($argument);
207
            }
208
        }
209
        return $result;
210
    }
211
212
    private function processArgument(mixed $argument)
213
    {
214
        if ($argument instanceof Closure) {
0 ignored issues
show
Bug introduced by
The type App\Closure was not found. Did you mean Closure? If so, make sure to prefix the type with \.
Loading history...
215
            $definition = new CallableDefinition();
216
            $definition->setLazy(true);
217
            $definition->setClosure($argument);
218
//            dd($argument);
219
            return $argument;
220
        }
221
//        if (is_string($argument)) {
222
//            return new Reference($argument);
223
//        }
224
        if ($argument instanceof \Yiisoft\Factory\Definition\Reference) {
225
            $id = $argument->getId();
226
            return new Reference($id);
227
        }
228
229
        if ($argument instanceof DynamicReference) {
230
            $ref = new ReflectionObject($argument);
231
            $def = $ref->getProperty('definition');
232
            $def->setAccessible(true);
233
            /* @var DefinitionInterface $val */
234
            $val = $def->getValue($argument);
235
            return $this->processArgument($val);
236
        }
237
238
        if ($argument instanceof ArrayDefinition) {
239
//            dd($argument);
240
            return new Definition(
241
                $argument->getClass(),
242
                $this->processArguments($argument->getConstructorArguments())
243
            );
244
        }
245
246
        if ($argument instanceof \Yiisoft\Factory\Definition\CallableDefinition) {
247
            $ref = new ReflectionObject($argument);
248
            $def = $ref->getProperty('method');
249
            $def->setAccessible(true);
250
            /* @var callable $val */
251
            $val = $def->getValue($argument);
252
            $definition = new Expression(sprintf(
253
                'closure("%s")',
254
                preg_quote(serialize(new SerializableClosure($val)), '"')
255
            ));
256
//            $definition->setClass('qq');
257
//            $definition->setClosure($this->processArgument($val));
258
            return $definition;
259
        }
260
261
        if (is_object($argument)) {
262
            $definition = new CallableDefinition(get_class($argument));
263
            $definition->setLazy(true);
264
            $definition->setClosure($argument);
265
            $serviceId = get_class($argument) . spl_object_id($argument);
0 ignored issues
show
Unused Code introduced by
The assignment to $serviceId is dead and can be removed.
Loading history...
266
//            dd($argument);
267
//            $inline = new InlineServiceConfigurator($definition);
268
//            $inline->args([444]);
269
            $definition = new Expression(sprintf(
270
                'object("%s")',
271
                preg_quote(serialize($argument), '"')
272
            ));
273
//            self::$staticArguments[$serviceId] = $definition;
274
275
            return $definition;
276
        }
277
278
        return $argument;
279
    }
280
281
    private function loadThirdPartyServices(ServicesConfigurator $serviceConfigurator): void
282
    {
283
        $configs = [
284
            [
285
                'namespace' => 'App\\',
286
                'path' => 'src/',
287
            ],
288
            [
289
                'namespace' => 'Yiisoft\\Access\\',
290
                'path' => 'vendor/yiisoft/access/src/',
291
            ],
292
            [
293
                'namespace' => 'Yiisoft\\Csrf\\',
294
                'path' => 'vendor/yiisoft/csrf/src/',
295
            ],
296
            [
297
                'namespace' => 'Yiisoft\\DataResponse\\',
298
                'path' => 'vendor/yiisoft/data-response/src/',
299
            ],
300
            [
301
                'namespace' => 'Yiisoft\\User\\',
302
                'path' => 'vendor/yiisoft/user/src/',
303
            ],
304
//            [
305
//                'namespace' => 'Cycle\\ORM\\',
306
//                'path' => 'vendor/cycle/orm/src/',
307
//            ],
308
        ];
309
        foreach ($configs as $config) {
310
            $serviceConfigurator
311
                ->load($config['namespace'], sprintf('../%s/*', $config['path']))
312
                ->autoconfigure(true)
313
                ->autowire(true);
314
        }
315
    }
316
317
    private function ignoreNonServices(ServicesConfigurator $serviceConfigurator)
318
    {
319
        $configs = [
320
            'App\Blog\PostStatus',
321
            'App\CallableInitiator',
322
            'App\User\User',
323
            'App\InlineDefinition',
324
            'Yiisoft\DataResponse\DataResponse',
325
            'Yiisoft\User\Event\AfterLogin',
326
            'Yiisoft\User\Event\BeforeLogin',
327
            'Yiisoft\User\Event\AfterLogout',
328
            'Yiisoft\User\Event\BeforeLogout',
329
            'Yiisoft\User\Login\Cookie\CookieLogin',
330
            'Yiisoft\User\Login\Cookie\CookieLoginMiddleware',
331
        ];
332
        foreach ($configs as $config) {
333
            $serviceConfigurator
334
                ->remove($config);
335
        }
336
    }
337
}
338
339
340
class Stub
341
{
342
    private $value;
343
    private Stub2 $obj;
344
    private Stub2 $obj2;
345
    private int $int;
346
347
    public function __construct($value, Stub2 $obj, Stub2 $obj2, $int)
348
    {
349
//        dd(class_implements($int));
350
        $this->value = $value;
351
        $this->obj = $obj;
352
        $this->obj2 = $obj2;
353
        $this->int = $int;
354
    }
355
}
356
357
interface StubInterface
358
{
359
    public function doSmth();
360
}
361
362
class Stub1 implements StubInterface
363
{
364
    public function doSmth()
365
    {
366
        return 1234;
367
    }
368
}
369
370
class Stub2
371
{
372
    private $value;
373
374
    public function __construct($value)
375
    {
376
        $this->value = $value;
377
    }
378
}
379
380
class MyContainerBuilder extends ContainerBuilder
381
{
382
    private ContainerInterface $container;
383
384
    public function __construct(ContainerInterface $container)
385
    {
386
        $this->container = $container;
387
        parent::__construct();
388
    }
389
390
    public function get(string $id, int $invalidBehavior = SymfonyContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
391
    {
392
        try {
393
            return parent::get($id, $invalidBehavior);
394
        } catch (Throwable $e) {
395
            return $this->container->get($id);
396
        }
397
    }
398
}
399
400
class ContainerConfigProxy extends Container
401
{
402
    private ContainerBuilder $containerBuilder;
403
    private $services = [];
404
405
    public function __construct(ContainerBuilder $containerBuilder)
406
    {
407
        $this->containerBuilder = $containerBuilder;
408
    }
409
410
    public function get($id)
411
    {
412
        return $this->containerBuilder->get($id);
413
    }
414
415
    public function has($id): bool
416
    {
417
        return $this->containerBuilder->has($id);
418
    }
419
420
    public function set(string $id, $service): void
421
    {
422
        if ($id === 'Psr\\Container\\ContainerInterface') {
423
            return;
424
        }
425
        if (is_object($service)) {
426
            $this->services[$id] = $service;
427
            $definition = new SyntenthicDefinition($id);
428
            $definition->setSynthetic(true);
429
            $v = $this->containerBuilder->hasDefinition($id);
0 ignored issues
show
Unused Code introduced by
The assignment to $v is dead and can be removed.
Loading history...
430
            $this->containerBuilder->setDefinition($id, $definition);
431
            return;
432
        }
433
434
        throw new RuntimeException('Not object config ' . $id);
435
    }
436
437
    public function injectServices(): void
438
    {
439
        foreach ($this->services as $id => $service) {
440
            $this->containerBuilder->set($id, $service);
441
        }
442
    }
443
444
    /**
445
     * @return ContainerBuilder
446
     */
447
    public function getContainerBuilder(): ContainerBuilder
448
    {
449
        return $this->containerBuilder;
450
    }
451
}
452